This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.


MIND

Extreme C++
mindplus@microsoft.com        Download the code (34KB)
Steve Zimmerman
A DCOM Whiteboard
L
ast year, I wrote a two-article series called "Designing ActiveX Components" (MIND, April and May 1997), in which I described how to create a multiuser virtual whiteboard application. The multiuser Scribble was based on the popular Visual C++® Scribble sample using MFC and WinInet. This month, I'll briefly discuss the advantages and disadvantages of that approach and then show you how to implement an improved version of the whiteboard application using DCOM.
Figure 1: Original Virtual Whiteboard Architecture
Figure 1: Original Virtual Whiteboard Architecture


      The system I presented last year is composed of one or more MFC-based ActiveX® control clients using WinInet to exchange data stored by an ISAPI DLL server application (see Figure 1). Because the client-server communication uses HTTP, any client with Internet access can post data to the whiteboard, even across a firewall boundary. In addition, the client-side ActiveX controls can be installed and configured automatically simply by placing them on a Web page hosted by Microsoft Internet Explorer (see Figure 2).

Figure 2: The Whiteboard in Action
Figure 2: The Whiteboard in Action


      Although the system is functional, it has several drawbacks. Since the HTTP connection between client and server is non-persistent and unidirectional, there is no way for the server to notify each client when someone draws on the whiteboard. As a result, each client must repeatedly poll the server for new data in a background thread. When the worker thread receives a new whiteboard stroke, it posts a message to the ActiveX control window so that the user interface thread can update its view. This approach works sufficiently well, but it requires a good bit of synchronization code to handle the multithreading. More significantly, it wastes bandwidth and server resources because the clients continuously send HTTP requests even when no one is drawing on the whiteboard.
      Since the WinInet API has no built-in security features, it is difficult to restrict certain groups of users from drawing on the whiteboard. If you wanted to implement a scheme where everyone was allowed to view the whiteboard but only senior developers were allowed to draw on it, you'd have a lot of administrative code to write.
      Although the WinInet and Internet Server APIs are effective, they are not at all object-oriented. Instead of calling numerous global functions, it would be nice to find a way to encapsulate the client-server communication so that the client could just create an instance of a server object and make direct calls to its methods. Ideally, the client would be able to pass the server object a pointer to itself so that the server could simply call a method on the client whenever it had new doodles to display on the whiteboard.

Enter DCOM
      If you haven't already guessed, many of the disadvantages of my original whiteboard system could have been avoided had I used DCOM instead of the Internet APIs. That's not to say that DCOM is always preferable to other means of client-server communication, but in a controlled system, such as a corporate intranet whiteboard, DCOM seems to be an ideal choice. It encapsulates the communication protocol, security enforcement, and server callback mechanisms, essentially allowing you to treat the client and server as objects in the same process space even though they might reside on different machines.
      In light of these advantages, I redesigned the whiteboard application using DCOM. I started by defining the IScribble-Client and IScribbleServer interfaces that describe the interaction between the whiteboard server and its clients (see Figure 3). Rather than reinvent the proverbial wheel, I used essentially the same ActiveX client framework code—with its associated template, document, and view classes—that I presented last year. I'll leave a line-by-line comparison to the curious reader, but the most notable difference is that the improved Scribble control simply creates an instance of the Scribble server object rather than attempting to connect to a Web server. Instead of using the repetitive background polling approach (also known as "The Nag Pattern") that is necessary to get updates from an HTTP server, the DCOM Scribble control simply exposes an outgoing interface, which it passes to the server via the RegisterClient method (see Figure 4).

A Pointless Connection
      Inside the RegisterClient method, the server calls AddRef on the IScribbleClient interface pointer it receives from the client control. Behind the scenes, COM magically marshals the interface pointer on the client side (creating a stub) and unmarshals the interface pointer on the server side (creating a proxy). As a result, the client and server effectively establish a form of bidirectional asynchronous communication—the client can call server methods whenever it wants to and the server can call client methods whenever it wants to, so everyone is happy.
      This architecture is simpler than the standard COM connection point approach to establishing two-way communication between objects—all you have to do is pass a pointer and call AddRef—but it does have a subtle drawback. In the whiteboard system, I use the HTML <OBJECT> tag to host the ActiveX controls in the browser (see Figure 2), so Internet Explorer essentially determines the lifetime of the client. However, because the server increments the reference count of the client object in the call to RegisterClient, that count won't drop to zero automatically when the user attempts to close the browser window. If the client object is coded so that it waits until its reference count drops to zero before releasing the server, both objects will end up stuck in limbo because the client and server contain a circular reference. To overcome this problem, I had to introduce an Unregister- Client method that the client calls when the browser invokes IOleControl::Close.
      Incidentally, the circular reference problem is adequately solved by the COM connection point architecture, but at the expense of simplicity. Even with the help of MFC or ATL, the four interfaces you must implement (IConnectionPoint, IConnectionPointContainer, IEnumConnections, and IEnumConnectionPoints) are a bit tricky. It's up to you to decide which callback approach is best for your applications. Either approach works fine as long as you are careful about reference counts.

Marshaling the Data
      When a client and a server residing on different machines want to call each other's methods, the parameter data must somehow be sent across the wire from one object to another. The most common approach (and the one that COM uses) is to copy the parameters from the stack into a flat memory buffer that is transmitted over the network using remote procedure calls (RPCs). When the memory stream arrives at its destination, it is converted back into a stack and passed to the remote method. This stack-to-memory conversion (marshaling) is simple when you're dealing with primitive data types such as integers, floats, and chars. But when the parameter passed to a remote method is a pointer (for example, an LPSTR) the task becomes significantly more difficult. It doesn't make any sense to simply copy the pointer from the stack to the transfer buffer because that pointer won't be valid on the other machine. Instead, the marshaler must copy the chunk of data that the pointer references.
      To handle an LPSTR accurately, the marshaler could use strlen to determine how much data to copy, but there is no way to programmatically determine how much data to copy given a pointer to an array of integers or to an arbitrary struct. Thus, the marshaler must have intimate knowledge of the data types it copies into the memory buffer, which means that COM cannot automatically supply the marshaling code for all data types. You must either restrict the method parameters in your object interfaces to the data types recognized by COM's built-in Automation marshaler (BSTR, long, VARIANT, Boolean, DATE, IDispatch, and so on), or supply a proxy/stub DLL that marshals your custom data types.
      In the whiteboard application, I chose to make the IScribbleClient and IScribbleServer interfaces Automation-compliant by restricting their method parameters to the standard OLE data types. The benefits of this approach include simplified object deployment (no proxy/stub DLL to distribute and register), and VBScript compatibility should I ever decide to use these interfaces in conjunction with Active Server Pages. The downside is that I had to write code that the client control uses to convert the whiteboard stroke data into a VARIANT array before passing it to the server. What I came up with are two helper functions, MakeObjectFrom-Variant and MakeVariantFromObject, which can convert any serializable CObject-derived class into a VARIANT-based SAFEARRAY and back (see Figure 5).

The Whiteboard Server
      While I developed the client control using MFC to take advantage of that framework's user interface support, I wrote the server application using ATL because of its support for high-performance EXE servers. Because the whiteboard server may potentially be connected to many simultaneous clients and needs to maintain an in-memory collection of whiteboard strokes, I developed it as a Windows NT® service with help from the ATL COM AppWizard. This approach limits server-side resource usage to a single process—by means of the REGCLS_MULTIPLEUSE class objects flag—and keeps that process running even if the server's reference count drops to zero. As a result, I didn't have to write code to persist the strokes when nobody is using the whiteboard.
      As you can see in Figure 6, the server has no knowledge of the format of the strokes. It simply stores them as an array of VARIANTs using the Standard Template Library (STL) vector class. This is advantageous for two reasons. First, the server stays lean and mean because it offloads the VARIANT-to-CStroke conversion to its clients. But more importantly, because the whiteboard server contains no Scribble-specific code, I could use it as a general-purpose engine for such applications as a chat server or multiuser dungeon game without making significant changes to the code.
      One of the best things about DCOM is that the objects you create don't need to know whether they are being accessed remotely. You can test them first on your desktop PC and then use REGSVR32 and DCOMCNFG to install and configure them on one or more remote machines. There are some tricks to using DCOMCNFG; namely, you must make sure that both the client and server machines are configured to allow security access to one another (refer to MSDN™ for details). I should also mention that even though I've set things up so you can use the Win32® Debug build configuration to test the whiteboard server locally under Windows® 95, the release version will only work correctly under Windows NT because it runs as a service.

Conclusion
      With the WinInet whiteboard I presented last year and the DCOM version I've just introduced, you now have two fully functional examples of near-real-time multiuser Internet applications. Although the approach you decide to take in your own systems will depend on a combination of technical requirements and your own preference, I hope I've given you some ideas and sample code that will help get you started. Happy coding!

From the July 1998 issue of Microsoft Interactive Developer.