Excel RTD Servers: Minimal C++ Implementation (Part 1)
Continuing the discussion of Excel RTD servers, this time I’m going to show you how to write a minimal RTD server in native C++. There’s quite a bit more to discuss so I broke this entry into two parts.
Last time I showed you a minimal RTD server written in C# and it sure is easy to bang it together. There isn’t a whole lot of code to write thanks to the mountains of abstractions provided by the CLR and the .NET Framework. Those abstractions can however get in the way. If nothing else they get in the way of our understanding of what is actually going on under the covers and that in turn can lead to unexplained bugs. If you’ve learnt anything from reading my blog I hope it is the value of understanding how things work, not just how to get them to work. Did you catch the difference?
Although C++ affords you many more options for implementing and deploying an RTD server, here I’m focusing on the simplest implementation of an in-process apartment model COM server. Subsequent entries will explore additional scenarios that go beyond this starting point.
It’s a Visual C++ Project
Every good project starts with an empty Visual C++ project. :)
Create a new project in Visual Studio based on the Visual C++ “Win32 Project” template. Select the Application Settings tab in the Win32 Application Wizard. Select the “DLL” application type and also check the “Empty project” option.
Add a precompiled header to the project and include the following:
#pragma once
#include <atlbase.h>
#include <atlcom.h>
#define ASSERT ATLASSERT
#define VERIFY ATLVERIFY
#define TRACE ATLTRACE
#define HR(expr) { HRESULT expr_hr = expr; if (FAILED(expr_hr)) return expr_hr; }
atlbase.h is the main ATL header that includes the main Windows SDK headers and the most common ATL functions and classes used by virtually all native C++ applications (that use ATL).
atlcom.h provides most of the code for generating all the boilerplate code required to implement a COM server.
The macros are optional but help to simplify the code and should be self-explanatory.
It’s a DLL
Since we’re limiting this implementation to an in-process COM server it will be packaged as a dynamic-link library. DLLs built with the C run-time library use an entry point function called DllMain. This should be used as an indicator of when the operating system is loading and unloading your DLL and not much else.
STDAPI_(BOOL) DllMain(HINSTANCE /*module*/,
DWORD /*reason*/,
void* /*reserved*/)
{
return TRUE;
}
DLLs typically communicate with the outside world by providing a set of exported functions. One way to define these exports is via a module definition file. Add a file to the project called Exports.def and include the following text:
LIBRARY "RtdServer"
EXPORTS
The name in quotes must match the name of the DLL produced by the linker. Any functions you wish to export are listed under the EXPORTS header. You need to also tell the linker about your module definition file. You can set this in the linker’s “Input” properties from within the project’s property pages.
Although DllMain isn’t strictly an exported function, I tend to stick it along with any other exported functions in a C++ file called Exports.cpp just so that it’s obvious where all the code paths within the DLL originate from.
It’s a COM Server
A COM server must provide a way for a client to get the class object for a particular CLSID. COM servers hosted in a DLL achieve this by exporting a function called DllGetClassObject. The DllRegisterServer and DllUnregisterServer functions are also typically exported and called by installers or simply by the built-in regsvr32.exe tool to register and unregister the server. Finally, the DllCanUnloadNow function should also be exported to allow the COM runtime to unload your DLL promptly.
These functions are well defined in the COM specification and I won’t go into their implementation details too much. The best thing you can do is rely on ATL to provide the implementation. Start by defining an ATL module class for your DLL as follows.
class Module : public CAtlDllModuleT<Module>
{
};
Amazingly you can now implement the DLL’s entry point and all of the necessary exported functions simply by delegating to the ATL implementation as follows:
Module s_module;
STDAPI_(BOOL) DllMain(HINSTANCE, DWORD reason, LPVOID reserved)
{
return s_module.DllMain(reason, reserved);
}
STDAPI DllRegisterServer()
{
return s_module.DllRegisterServer(FALSE);
}
STDAPI DllUnregisterServer()
{
return s_module.DllUnregisterServer(FALSE);
}
STDAPI DllGetClassObject(REFCLSID clsid, REFIID iid, void** object)
{
return s_module.DllGetClassObject(clsid, iid, object);
}
STDAPI DllCanUnloadNow()
{
return s_module.DllCanUnloadNow();
}
Now that the exported functions are implemented you can add them to the list of exports in the module definition file:
EXPORTS
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE
DllGetClassObject PRIVATE
DllCanUnloadNow PRIVATE
The PRIVATE keyword prevents other binaries from directly linking to these functions. You can of course still call them via LoadLibrary/GetProcAddress which is exactly what COM and tools like regsvr32.exe would do.
That’s it for today. Next time we’ll conclude the minimal C++ implementation with:
- It’s a COM Class
- It’s an Automation Server
- It’s an RTD Server
Stay tuned.
If you’re looking for one of my previous articles here is a complete list of them for you to browse through.
Produce the highest quality screenshots with the least amount of effort! Use Window Clippings.