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.