Imagine that you are building a real time control application, e.g., a factory robot, and that you want to control your robot from a .NET client. The client can be hosted on a Windows XP/2000 computer, or on the Windows CE powered Single Board Computer that controls the robot itself in real time. You wish to receive events back from the robot to signal certain states without polling the device. Well, this article will show you how you can achieve this.
A possible solution to this problem is implementing a Web Service on the Windows CE device, but the disadvantage of this technology is that you can’t signal asynchronous events directly from the device (server) back to the client (as long as WS eventing is not supported).
Another technology well known today is .NET remoting. However, this technology is currently not available in Windows CE 5.0 and .NET CF 2.0.
If you are familiar with COM, you could consider DCOM. Since COM interop is available in .NET CF 2.0, and DCOM is available under Windows CE 5.0, this could be a viable solution. Agreed, the server needs to be implemented as a COM server, but all clients – typically User Interface - can be written in .NET. Another fact to know is that when writing a real time application, .NET cannot be used because of its unpredictable garbage collector, which brings us back to native C++. If designed carefully, the COM server can execute the real time tasks and at the same time expose a COM interface to the outside world.
This said, the COM server will be written in C++, and the client(s) in C#. As a bonus, we will use the same C# source code for the remote client running under Windows XP and the .NET framework, as well as for the local client running under Windows CE and the .NET Compact Framework, showing the real power of running .NET on several OS platforms. Whoever used DCOM under Windows CE knows that COM rich error information is not propagated properly from the server back to the client. The sample program will also implement this missing functionality.
Preparing your Windows CE image for DCOM
Unfortunately, Pocket PC 2003 and 2005 do not support DCOM. Understanding this, we will need to build our own Windows CE image by means of Platform Builder 5.0, the tool for creating, configuring, and building your own Windows CE 5.0 image.
First, download from the Microsoft website the .NET CF 2.0 CEC installer for Platform Builder 5.0, and install it so it is available for selection from the catalog within Platform Builder. We need the Compact Framework 2.0, because it allows COM interop functionality that is not present in previous versions of the Compact Framework. Create a Windows CE image that has full DCOM support and .NET CF 2.0 added to it. How to create a Windows CE image is explained best in other places, so I will not go into the details here.
As we will use Visual Studio 2005 for creating and debugging our code, it is advisable to add the necessary executables and DLLs for debugging applications with Visual Studio 2005 as well, to your image. Check the online Help on how to do this.
Other useful applications to be added to your image are:
- DCOMCnfg.exe for Windows CE to configure your DCOM settings.
- RegsvrCE.exe for Windows CE to register your COM DLLs.
- NTLMUser.exe for Windows CE to create local NTLM accounts on the Windows CE device.
All these tools are available in the download package too in the source code.
Note: All Windows CE sample projects in this article will reference the DSM52 SDK. This is the name of the image I created and that you need to install prior to creating/using any smart device project in Visual Studio 2005. If you give your SDK another name, you will need to replace all “DSM52” references in the vcproj files with your SDK name in order to open and load the sample code projects.
Setting up the COM server
Windows CE (5.0 and before) does not support the Automation Marshaler, sometimes also referred to as the Type Library Marshaler. Therefore, we have to create our own proxy/stub marshaling code to accompany with our COM server and client, to make sure our COM calls are properly marshaled across process boundaries. However, if we stick to OLE automation compatible data types in our COM methods, we don’t need to provide the proxy/stub DLL on the Windows XP/2000 client side as the Automation Marshaler is always present there (as part of ole32.dll and oleaut32.dll). If you still need to use other data types, you are obliged to compile the proxy/stub code also for Windows XP/2000 and register it there too.
For the same reason, we have to modify the event sink’s dispinterface inside the
LIBRARY block in the IDL file (as generated by the Visual Studio ATL COM project Wizard for event notification) and change it to an interface section outside the
LIBRARY block. For all interfaces defined outside the
LIBRARY section of our IDL file, the MIDL compiler will create proxy/stub code that needs to be added to your proxy/stub DLL. Doing this, marshaling code for your events firing back to the client will also be created in the proxy/stub DLL. Note that this interface is marked as dual and derives from
IDispatch, simulating dispinterface functionality.
interface _IGateEvents : IDispatch
[id(1), helpstring("method Opened")] HRESULT
Opened([in] VARIANT Destination);
[id(1), helpstring("method Opened")]
HRESULT Opened([in] BSTR Destination);
[default, source] dispinterface _IGateEvents;
Now that we are talking about marshaling, I would like to address another problem in using COM under Windows CE. It is a common procedure to return an error code (
HRESULT) from your COM method call if a problem occurred during the method call. It is advisable to foresee a rich error description also to help the user to resolve the problem. Currently, Windows CE does not propagate this rich error information across process boundaries (through the
SetErrorInfo() APIs). Later on in this article, we will show how this missing functionality can be added to your code as well. As part of determining whether a COM’s server implements and foresees rich error information, the
ISupportErrorInfo interface needs to be marshaled too. The marshaling code for this interface is also not available in Windows CE. For that reason, proxy/stub marshaling code is manually generated and added to the proxy/stub DLL too. The
ISupportErrorInfo interface is copied from oaidl.idl.
Figure 1. DCOM marshalling
Now let us create the COM server
Note: Before we can use Visual Studio 2005 to create our smart device sample code, the SDK of our own created image (platform with DCOM support in it) needs to be installed so that it can be selected in Visual Studio 2005 for use with our own projects.
We use Visual Studio 2005 to create a C++ ATL smart device project. The server will be an ATL COM EXE server. The sample program I’ve created is inspired by the famous TV series “Stargate”, and allows you to dial in to other planets in our galaxy and even far beyond, provided you know how to deal with the bad guys. Ever wanted to meet Thor and visit Atlantis? Check it out. By means of the wizard, we can add a few methods to our interface and implement it in our COM object.
Mind you, we do add support for
ISupportErrorInfo and connection points, and we use the free-threading model. This is normal procedure when creating COM objects. One thing I’ve added on top of the wizard generated code is that the COM events are not fired to the client synchronously from the same thread as where the “incoming” interface methods are called, but they are fired from a separate worker thread to avoid potential dead locks with .NET. This code is implemented in the
ThrowCOMError() function is responsible for taking the rich error description from the embedded resource file and storing the error info in the current thread context where later on the
IChannelHook interface will pick it up and send it to the client’s process and thread context.
How does the rich error information get transported to the client? When an out of process COM method is executed, the COM library will send out of band (hidden) information along with the marshaled method parameter data, as explained in the article of Don Box in Microsoft Systems Journal. It uses the
IChannelHook interface, provided that you have installed your own hook with
CoRegisterChannelHook. I have chosen to install my own hook that implements the missing functionality to transport the rich error object from the server back to the client. I have added this code in the proxy/stub DLL so that whenever the interface is called, this hook gets installed auto-magically as well. Of course, if you have multiple interfaces to be marshaled in different proxy/stub DLLs, the channel hook is better implemented in a separate DLL and registered in an early stage of the Windows CE image boot process so that it is available when needed. The following figure explains what actually happens when a COM method call returns with an error condition set in the returned
Figure 2. Program flow of COM rich error info
This solution allows to transport rich error information between two out-of-process COM applications, either both client and server running on Windows CE, or even one of them running on Windows XP/2000 (through DCOM).
Creating the clients
As of .NET CF 2.0, Microsoft added COM interop support that was missing in CF 1.0. This great new feature brought me to the idea of creating a C# user interface client that could be compiled and used for both the .NET Compact Framework as well as the desktop .NET Framework. Of course, in the Compact Framework, not all functionality is available as in the desktop variant, but if you are aware of this limitation and if you write your Compact Framework version first, with a little bit of luck, your code compiles without problems on both frameworks just like that. The only thing you need to do is add a reference to the TLB file that was generated while compiling the COM EXE server. And now look, you can write your own C# or VB client and run them both on Windows XP/2000 and Windows CE connecting to the same COM server, that at the same time is executing real time C++ code on Windows CE. Isn’t that great? Imagine that one day this would be possible in Pocket PC as well… Controlling your house robot from your Pocket PC…
One small remark about rich error information, though. In the .NET framework, the
COMException object immediately gives you the error description in the
Message property, but not in the Compact Framework. Therefore, I created a static wrapper class - named
ComError - around the COM API
GetErrorInfo() function that retrieves this information for you when available. This code uses P/Invoke and manual marshaling to present the error object information to the application in a managed class.
Dial the Gate
When your Windows CE image is started properly on your hardware and when you can connect Visual Studio 2005 to this image, it is time to get the code started.
First, deploy both the COM EXE server as well as your proxy/stub DLL onto your device. Next, make sure the proxy/stub DLL is registered. You can run “RegSvrCE.exe /StarGatePS.dll” for this, or have Visual Studio do this for you automatically when deploying. The EXE server will not be registered automatically, so from the command shell in Windows CE, you need to register it yourself. Run “StarGate.exe /RegServer”. This will add the necessary entries in the registry. At this point, you can start your Windows CE C# user interface and test this part already. Open ControlRoomCE.csproj, build, deploy, and run.
If you press the “Enter Control room” button, the Gate object is created and you can call the “Dial gate” method on it. If you do this more than seven times, the server will fire an event mentioning the gate is opened. If you “Dial gate” more than eight times, an error message will be returned.
As soon as our COM EXE server is registered, we can fire up DCOMCnfg.exe on the Windows CE device. This is the place to setup authentication, access, and launch permission. Just for this test, we disable some security checks that makes life a bit easier for now.
Although we have disabled most of the default security checks, we still need to do one more thing. Prior to making the DCOM call from the Windows XP/2000 client to the Windows CE server, DCOM will try to authenticate the client on the CE device. It uses NTLM for that. There are two possibilities:
- Either your device is part of a domain and then the domain server will do the authentication for you, provided that you have added the [HKLM]\Comm\Redir\DefaultDomain = <your domain name> registry key to your Windows CE device.
- Or your device is not part of a domain, and then you have to create a local NTLM account. You can use NTLMUser.exe for that. This tool will use the
SetNTLMUserInfo() API function for that. Enter the same username and password as the (logged on) user that will run the client.
Make sure both solutions (the
DefaultDomain registry key or the local NTLM account) are not set simultaneously as this will not work. Consult the Windows CE Platform Builder help for more information.
It is also useful, but not required, to specify on the Windows CE device the same username and password in the Control Panel | Owner | Network ID tab. This information is merely used by Windows CE to authenticate you when accessing remote directories, for example, a shared drive on your XP/2000 system. Don’t confuse these credentials with the NTLM credentials. Both serve different purposes. The domain parameter is only required if you are really part of a domain.
As our interfaces only use automation compatible data types, we don’t have to register a proxy/stub DLL on the Windows XP/2000 side. Remember from our discussion earlier that this functionality is already provided by oleaut32.dll and ole32.dll. We rely on the built-in Type Library Marshaler to do the work. For ease of use, we register the type library so that it can be queried immediately in Visual Studio. Run the RegTlb.bat tool to do that.
How does the Windows XP/2000 C# client know about the whereabouts of our remote COM EXE server? The answer is the registry. If we enter the Gate class information in the registry, we can specify its location in the registry by means of the DCOMCnfg.exe tool on Windows XP/2000. Run RegRGS.bat first to enter this info in the registry. Next, run DCOMCnfg.exe on Windows XP/2000 and search for the Gate class entry. Right-click and set the location to the IP address of your Windows CE device.
Note that no COM server code whatsoever is present on the Windows XP/2000 side, only registry info and the Universal OLE Automation Marshaler.
One other point of interest with DCOM is that, when you have a firewall enabled somewhere, make sure ports 135, 137-139 are opened. The DCOM RPC SCM is listening on port 135, and NETBIOS name resolving is required to make DCOM working properly. Also, DCOM requires ports open above 1023.
To see the code in action, we will run the components in their respective Visual Studio debugger.
- Start Stargate.sln and run the COM EXE server on the CE device.
- Start ControlRoomCE.sln and run the CE client.
- Start ControlRoomXP.sln and run the XP client simultaneously.
To learn about how the
IChannelHook interface works, start the debugger from the proxy/stub DLL and set a breakpoint in
IChannelHook::ServerGetSize(). On each out-of-process method call returning inside the server, this hook is called allowing you to shuffle in some out-of-band information. On the receiving (client) side, the
IChannelHook::ClientNotify() is the place to extract this information again. The same mechanism is also available in the other direction, but not required for returning rich error information, But could easily be used for other DCOM debugging purposes.