Archives
-
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: -
Windows with C++: X64 Debugging With Pseudo Variables and Format Specifiers
My latest Windows with C++ column in the December 2008 issue of MSDN Magazine is now online:
-
Visual Studio 2010 Mouse Wheel Zooming
I just noticed that Visual Studio 2010 has duplicated the mouse wheel zooming feature used by Windows Explorer and Word 2007. Simply hold down the Ctrl key and scroll the mouse wheel to zoom the text editor in and out.
-
Excel RTD Servers: Minimal C# Implementation
Continuing the discussion of Excel RTD servers, here is about the simplest RTD server I could come up with. I used C# for this example to allow us to focus on semantics. Next time I’ll show a minimal C++ implementation.
[
Guid("9AA100A8-E50E-4047-9C60-E4732391063E"),
ProgId("Kerr.Sample.RtdServer"),
]
public class RtdServer : IRtdServer
{
As you can see, the RtdServer class is attributed with a CLSID and ProgId that can be used by COM clients to locate the COM server. By default (if you don’t employ registration-free COM), COM clients will locate the COM server by looking up either the ProgId (to find the CLSID) or the CLSID directly in the registry. You can register your assembly using the RegAsm tool that ships with the .NET Framework as follows:
%SystemRoot%\Microsoft.Net\Framework\v2.0.50727\RegAsm.exe RtdServer.dll /codebase
Remember also to mark your assembly as being visible to COM as follows:
[assembly: ComVisible(true)]
Next we need to define a minimal set of member variables in order for the RTD server to function:
private IRTDUpdateEvent m_callback;
private Timer m_timer;
private int m_topicId;
m_callback is needed to hold onto a reference to the callback interface provided by Excel. This interface is primarily used to let Excel know that new data is available. m_timer is a Windows.Forms timer used to periodically notify Excel via the callback interface. This timer is obviously optional and you are free to implement update notifications any way you want. I did however use this technique for a reason which I’ll outline in a moment. Finally, m_topicId is used to identify the topic that Excel is “subscribing” to. Again, this is just one approach which as you’ll see in a moment is very naïve.
public int ServerStart(IRTDUpdateEvent callback)
{
m_callback = callback;
m_timer = new Timer();
m_timer.Tick += new EventHandler(TimerEventHandler);
m_timer.Interval = 2000;
return 1;
}
ServerStart is the first method called by Excel and is where we prepare the RTD server. In particular we set the callback member variable and prepare the timer. Notice that the timer is not yet enabled. Returning 1 indicates that everything is fine.
public void ServerTerminate()
{
if (null != m_timer)
{
m_timer.Dispose();
m_timer = null;
}
}
ServerTerminate is called when Excel is ready to unload the RTD server. Here we simply release the timer.
public object ConnectData(int topicId,
ref Array strings,
ref bool newValues)
{
m_topicId = topicId;
m_timer.Start();
return GetTime();
}
ConnectData is called for each “topic” that Excel wishes to “subscribe” to. It is called once for every unique subscription. As should be obvious, this implementation assumes there will only be a single topic. In a future post I’ll talk about handling multiple topics. ConnectData also starts the timer and returns an initial value that Excel can display.
public void DisconnectData(int topicId)
{
m_timer.Stop();
}
DisconnectData is called to tell the RTD server that Excel is no longer interested in data for the particular topic. In this case, we simply stop the timer to prevent the RTD server from notifying Excel of any further updates.
private void TimerEventHandler(object sender,
EventArgs args)
{
m_timer.Stop();
m_callback.UpdateNotify();
}
TimerEventHandler is the private method that is called when the timer Tick event is raised. It stops the timer and uses the callback interface to let Excel know that updates are available. Stopping the timer is important since we don’t want to call UpdateNotify repeatedly.
public Array RefreshData(ref int topicCount)
{
object[,] data = new object[2, 1];
data[0, 0] = m_topicId;
data[1, 0] = GetTime();
topicCount = 1;
m_timer.Start();
return data;
}
RefreshData is called when Excel is ready to retrieve any updated data for the topics that it has previously subscribed to via ConnectData. The implementation looks a bit strange. That’s mainly because Excel is expecting the data as a COM SAFEARRAY. Although it isn’t pretty, The CLR’s COM infrastructure does a commendable job of marshalling the data for you. All you need to do is populate the two-dimensional array with the topic Ids and values and set the topicCount parameter to the number of topics that are included in the update. Finally, the timer is restarted before returning the data.
public int Heartbeat()
{
return 1;
}
Heartbeat is called by Excel if it hasn’t received any updates recently in an attempt to determine whether your RTD server is still OK. Returning 1 indicates that everything is fine.
private string GetTime()
{
return DateTime.Now.ToString("hh:mm:ss:ff");
}
}
GetTime is a private method used to get a formatted time string that represents the data to display in Excel. As you can imagine, this RTD server simply updates the time in the cell roughly every two seconds.
Clearly this RTD server implementation leaves a lot to be desired but it does demonstrate enough functionality to give you an idea of how RTD servers work. To give it a try you can use the following function from within Excel:
=RTD("Kerr.Sample.RtdServer", , "topic")
The only thing left that’s worth mentioning about this implementation is the use of the Windows.Forms.Timer class and why this even works. If you look at the way the RegAsm tool (and internally the RegistrationServices class) registers the types within the assembly you may notice that it is registered with a threading model of “Both” which indicates that the COM class is able, from a threading perspective, to load into the apartment of the caller (no proxy). In the case of Excel, the apartment happens to be a single-threaded apartment which as part of its contract provides a message pump and the message pump is all the timer needs to function. Internally it creates a hidden window to handle the WM_TIMER messages and then raise the Tick event. So it happens to work because Excel creates the RTD server in a single threaded apartment and the RTD server is happy to run directly in that apartment. This is incidentally also how ActiveX controls work. -
Excel RTD Servers: C# Interfaces
So it turns out that there are in fact quite a few developers who care about writing RTD servers. I’ve received a few emails asking for more information so I may post a few more entries on the subject. Last time I simply showed what the RTD server interfaces look like in C++. Here’s the equivalent in C#:
[Guid("A43788C1-D91B-11D3-8F39-00C04F3651B8")]
interface IRTDUpdateEvent
{
void UpdateNotify();
int HeartbeatInterval { get; set; }
void Disconnect();
}
[Guid("EC0E6191-DB51-11D3-8F3E-00C04F3651B8")]
interface IRtdServer
{
int ServerStart(IRTDUpdateEvent callback);
object ConnectData(int topicId,
ref Array strings,
ref bool newValues);
Array RefreshData(ref int topicCount);
void DisconnectData(int topicId);
int Heartbeat();
void ServerTerminate();
}
Of course what these interfaces look like and how they are actually called by Excel are two very different things thanks to the horrors wonders of IDispatch.
I’m including C# in the discussion as its very tempting for .NET developers to implement an RTD server in managed code. Unfortunately it’s easy to forget that you still need to follow the rules outlined by the COM specification. If you don’t, it will come back to haunt you later on.