Click Here to Install Silverlight*
United StatesChange|All Microsoft Sites
MSDN
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ > August 1999
August 1999

Microsoft Systems Journal Homepage

Wicked Code

Code for this article: Aug99WickedCode.exe (270KB)
Jeff Prosise is the author of Programming Windows 95 with MFC (Microsoft Press, 1996). He also teaches Visual C++, MFC and COM programming seminars. For more information, visit http://www.solsem.com.

One of the most eagerly anticipated enhancements that Windows® 2000 will bring to COM is support for asynchronous method calls. Asynchronous calls permit COM clients to make method calls without blocking until the calls are completed. For certain types of COM methods, particularly those that require unusual amounts of processing time on the receiving end, this ability is a godsend. Rather than do nothing while it waits for the method call to return, the client can carry out other processing tasks and return to the task that precipitated the call once the information generated by the call is available.
COM programmers may be surprised to learn what's involved in making asynchronous method calls in Windows 2000. It won't be a simple matter of slapping an [asynchronous] tag on an IDL method definition and magically being notified when processing is completed. (For details, see Don Box's House of COM column in the May 1999 issue of MSJ.) Instead, you'll tag an interface with an [async_uuid] attribute and supply a GUID, which MIDL will use to generate an async version of the interface containing Begin and Finish versions of the interface's methods. You'll create a call object and call the Begin method that corresponds to the method that you want to call asynchronously. Next, you'll either poll the call object to find out when the method call has completed or implement ISynchronize and allow COM to tell you when the call has completed. Finally, you'll call the Finish method to retrieve the method's [out] parameters.
Making asynchronous method calls won't be trivial, but it won't be overly difficult, either. And Windows 2000 will support asynchronous method execution on the server side as well as on the client side. But what if you want to make nonblocking calls on a COM object today? What if you can't wait for Windows 2000, or what if you're developing for platforms outfitted with older versions of Windows? Is it possible to issue asynchronous method calls on a COM object today, and if so, how?
The answer to the first question is yes, of course it's possible. As for the how, I'll demonstrate by presenting an MFC COM client that makes nonblocking method calls on a COM object implemented with ATL. Keep in mind that this is only one way to kill the beast; there are other ways to achieve asynchronicity in method calls, but this technique can be adapted for use with just about any kind of object and any kind of client.

Asynchronous Method Calls

One strategy for permitting a client to make nonblocking calls on a COM object can be summarized as follows.

  1. Allow the client to pass a pointer to a callback interface in the method call.
  2. In the method implementation, spin up a new thread to process the method call and marshal the caller's interface pointer to the newly created thread.
  3. Return from the method call as soon as the new thread is created.
  4. When the new thread has completed processing the method call, pass the results back to the caller using the callback interface.
This approach is embodied in the source code that appears in Figure 1. The source code is that of an ATL object named Sieve whose home is an out-of-proc COM server named AsyncServer.exe. Sieve implements a custom interface named ISieve, which has just one method. That method, CountPrimes, starts a thread that computes the number of prime numbers between 2 and a ceiling specified by the caller. The computation can take quite a long time to execute if the input value, nMax, is large enough.
CountPrimes begins by marshaling the client's callback interface—a custom interface named ISieveCallback—into a stream using the COM API function CoMarshalInterThreadInterfaceInStream. Because the IStream pointer initialized by CoMarshalInterThreadInterfaceInStream will be used by another thread, CountPrimes creates the variable that holds the pointer on the heap to ensure that the variable will still exist when it is retrieved by the other thread (a stack variable could go out of scope—and therefore out of existence—before the other thread gets around to accessing it).

IStream** ppStream = new IStream*;
HRESULT hr =
    CoMarshalInterThreadInterfaceInStream
    (IID_ISieveCallback, pCallback, ppStream);
After starting a new thread and passing it the address of the IStream interface pointer and the nMax value specified by the caller, CountPrimes closes the thread handle returned by CreateThread and returns. To the caller, the method call returns immediately, even though the work of counting primes has just begun.
The counting is performed by the thread function ThreadFunc. After retrieving both the IStream pointer and nMax from the heap, ThreadFunc unmarshals the ISieveCallback interface pointer into its own apartment.

ISieveCallback* pCallback;
  CoGetInterfaceAndReleaseStream (*ppStream,  
      IID_ISieveCallback, (void**) &pCallback);
After computing the number of prime numbers between 2 and nMax, ThreadFunc passes the result to the client by calling ISieveCallback::CountPrimes_Result:

pCallback->CountPrimes_Result (nCount);
Thus, the circle is closed. The client calls ISieve::CountPrimes to request a count of prime numbers; ISieve::CountPrimes launches a thread to do the counting; and the thread transmits the result to the client via ISieveCallback::CountPrimes_Result. Between the time that CountPrimes returns and CountPrimes_ Result is called, the client is free to do what it wants.
Figure 2 AsyncClient
Figure 2 AsyncClient
Sieve.h and Sieve.cpp are part of an ATL project named AsyncServer that you can download from the link at the top of this article. Be sure to also download AsyncClient, which contains the MFC test client shown in Figure 2. The code that creates the Sieve object, calls ISieve::CountPrimes, and implements ISieveCallback::CountPrimes_Result is found in AsyncClientDlg.cpp, which is shown in Figure 3 along with its header file, AsyncClientDlg.h. Be sure to build AsyncServer before you run AsyncClient; building AsyncServer registers the Sieve object and also builds and registers the proxy/stub DLL that contains the marshaling code for ISieve and ISieveCallback.
To test AsyncServer, run AsyncClient and click the Go button to instigate a call to ISieve::CountPrimes. After a few seconds, the number of prime numbers between 2 and 10,000,000 will appear in the window. AsyncClient simply continues to process messages while waiting for the callback to occur, but you can prove that CountPrimes returns immediately by dragging the window while you wait for the count to appear. If CountPrimes were a blocking method, you wouldn't be able to move the window (or do much anything else with it, for that matter) because its message pump would be blocked waiting for CountPrimes to return.
Since AsyncClient is an MFC application, it uses MFC to implement the callback interface. If this is the first time you've seen a COM interface implemented in MFC, the code bears further explanation. Here's the scoop. In CAsyncClientDlg's class declaration, the statements

BEGIN_INTERFACE_PART (Callback, ISieveCallback)
    virtual HRESULT __stdcall CountPrimes_Result (int nCount);
END_INTERFACE_PART (Callback)

declare a nested class named XCallback ("nested" because its declaration appears inside CAsyncClientDlg's class declaration) that implements the COM interface ISieveCallback. These same statements also declare an instance of XCallback named m_xCallback. The implementation of ISieveCallback::CountPrimes_Result appears in the CPP file in the form of an XCallback member function:

HRESULT __stdcall
CAsyncClientDlg::XCallback::CountPrimes_Result (int nCount)
{
    METHOD_PROLOGUE (CAsyncClientDlg, Callback)
    pThis->DisplayResult (nCount);
    return S_OK;
}
METHOD_PROLOGUE is an MFC macro that declares a pointer to the containing class instance—in this case, to the CAsyncClientDlg object of which the XCallback object is a member. The pointer is named pThis. XCallback::CountPrimes_Result uses pThis to call CAsyncClientDlg::DisplayResult, which displays the result returned by the Sieve object. The remaining XCallback member functions implement ISieveCallback's IUnknown methods by delegating to the implementations of AddRef, Release, and QueryInterface that CAsyncClientDlg inherits from CCmdTarget.
To ensure a somewhat orderly shutdown if the user closes AsyncClient while a callback is pending, CAsyncClientDlg's WM_DESTROY handler calls CoDisconnectObject to forcibly close down the stub manager and disconnect any clients holding ISieveCallback pointers. Ideally, you'd also like to shut down the threads that hold the interface pointers, but that's easier said than done because COM provides no reasonable way for a client to detect that CoDisconnectObject has been called, short of attempting a method call and checking the HRESULT.
A logical question to ask at this point is, why bother? Why go to the trouble of spinning off threads in a COM object when a synchronous design is so much easier to implement? Assuming circumstances are such that asynchronous processing would benefit the client, shouldering the burden of launching threads and marshaling interface pointers in the object makes client code easier to write. As the developer of objects, you want to make it as painless as possible for developers to use the code that you write. What better way to do that than by hiding the details of asynchronous method execution behind COM interfaces rather than force clients to achieve asynchronicity by creating threads and marshaling interface pointers themselves?

Marshaling Interface Pointers Between Processes

One of the fundamental COM programming principles embodied in AsyncServer is how to marshal an interface pointer from one thread to another. While I'm on the subject of marshaling interface pointers, let me take the opportunity to address one of the most commonly asked questions in all of COM: namely, how do I launch one process (process B) from another (process A) and attach process B to an object instance created by process A by passing an interface pointer from A to B? Marshaling an interface pointer from one process to another isn't difficult, but it's more work than marshaling to another thread in the same process. As usual, the devil is in the details. I'll exorcise that devil by examining the source code for an application that launches a second instance of itself and marshals an interface pointer to the new process.
The secret to passing interface pointers between processes is to marshal them so that COM can create the requisite proxies and stubs. CoMarshalInterThreadInterfaceInStream and CoGetInterfaceAndReleaseStream are actually simplified versions of a pair of COM API functions named CoMarshalInterface and CoUnmarshalInterface, which may be used to marshal interface pointers between processes as well as between threads. To marshal an interface pointer from process A to process B, you call CoMarshalInterface in process A to create a marshaled interface pointer, and you call CoUnmarshalInterface in process B to unmarshal the pointer. Another COM API function, CoGetMarshalSizeMax, also plays an important role because it tells you how big a buffer to allocate to hold the marshaled interface pointer. If you pass the marshaled pointer in memory, you also need CreateStreamOnHGlobal to layer an IStream interface on top of a memory block.

Figure 4 StringClient
Figure 4 StringClient

The StringClient application shown in Figure 4 uses this technique to marshal interface pointers to other instances of itself. When the first instance of StringClient is launched, it instantiates a string cache object and caches an IStringCache interface pointer. Selecting New Process from StringClient's Options menu prompts StringClient to use the Win32® CreateProcess function to launch a second instance of StringClient. When the second instance sees the /NoCreate parameter that the first instance placed on its command line, it doesn't create an object instance of its own; instead, it connects to the string cache object by unmarshaling the IStringCache interface pointer obtained from the first instance.
Pertinent portions of StringClient's source code are shown in Figure 5. The code responsible for marshaling the interface pointer is found in CStringClientDlg::OnOptionsNewProcess, which handles the New Process command. OnOptionsNewProcess first calls CoGetMarshalSizeMax to determine the size of the buffer required to hold the marshaled interface pointer. The MSHCTX_LOCAL flag indicates that the pointer is being marshaled to another client on the same machine, as opposed to a client running on a remote machine:

ULONG ulSize;
HRESULT hr = ::CoGetMarshalSizeMax (&ulSize,
    IID_IStringCache, m_pStringCache, MSHCTX_LOCAL,
    NULL, MSHLFLAGS_NORMAL);
StringClient then allocates a block of memory large enough to hold the marshaled pointer:

HGLOBAL hGlobal = ::GlobalAlloc (GMEM_MOVEABLE, 
                                 (DWORD) ulSize);
Next, it uses CreateStreamOnHGlobal to effectively convert the memory block into a stream object—that is, a storage medium that can be read and written using IStream methods:

hr = ::CreateStreamOnHGlobal (hGlobal, TRUE, &pStream);
Finally, it passes the IStream pointer returned by CreateStreamOnHGlobal and the IStringCache pointer that it obtained when it created the object instance to CoMarshalInterface to marshal the interface pointer into the memory block:

  hr = ::CoMarshalInterface (pStream, IID_IStringCache,
                             m_pStringCache,
                             MSHCTX_LOCAL, NULL, 
                             MSHLFLAGS_NORMAL);
StringClient's next task is to launch a second instance of itself and somehow pass it the data written to the memory block by CoMarshalInterface. Getting the data across is as simple as sending a WM_COPYDATA message. The hard part is obtaining a window handle (the handle of the new process's main window) to use in the call to SendMessage. StringClient solves this problem by calling the WaitForInputIdle API function to give the new process time to create a window and empty its message queue, and using EnumWindows to enumerate all the top-level windows in the system and GetWindowThreadProcessId to obtain the process ID of the process that created each window. Next, it compares each process ID returned by GetWindowThreadProcessId to the process ID returned by CreateProcess. When it finds matching IDs, StringClient has the window handle it needs. Transmitting the marshaled interface pointer to the other process then requires just three lines of code:

COPYDATASTRUCT cds = { 0, (DWORD) ulSize, ::GlobalLock 
    (hGlobal) };
::SendMessage (hWnd, WM_COPYDATA, (WPARAM) m_hWnd, 
               (LPARAM) &cds);
::GlobalUnlock (hGlobal);
The process on the receiving end of the WM_COPYDATA message responds by first making a local copy of the data encapsulated in the message. It then uses CreateStreamOnHGlobal to glue an IStream interface to the memory block containing the copy, and unmarshals the interface pointer with CoUnmarshalInterface:

IStream* pStream;
HRESULT hr = CreateStreamOnHGlobal (hGlobal, TRUE, &pStream);
 .
 .
 .
IStringCache* pStringCache;
hr = CoUnmarshalInterface (pStream, IID_IStringCache,
                           (void**) &pStringCache);
The new instance of StringClient now holds an interface pointer to the string cache object created by the first instance. Both instances can talk to the object by placing method calls through their respective interface pointers.
You can try StringClient and StringServer (the COM server in which the string cache object is implemented) for yourself by downloading them from the link at the top of this article and building StringServer and StringClient, in that order. Start one instance of StringClient and use its New Process command to launch a second instance. Then prove that they're connected to the same string cache object by typing something into the edit control in one instance of StringClient, selecting Set String from the Options menu to pass the string to the server, and selecting Get String in the other instance of StringClient to read the cached string. Because both clients are connected to the same object, you should see in instance B what you typed into instance A.

Drop Me a Line

Are there tough Win32, MFC, COM, or MTS programming questions you'd like answered? If so, drop me a note at jeffpro@msn.com. Please include "Wicked Code" in the message title. I regret that time doesn't permit me to respond to individual questions, but rest assured that each and every suggestion will be considered for inclusion in a future column.

Have a tricky issue dealing with Windows? Send your questions via email to Jeff Prosise: JeffPro@msn.com

From the August 1999 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe.

© 1998 Microsoft Corporation. All rights reserved.
Terms of Use
.

© 2017 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft