Our final sample for this chapter illustrates custom marshaling, in which an object can tell COM to bypass all its standard marshaling support and use the object's implementation of the IMarshal interface. The EKoala5 server (CHAP06\EKOALA5) contains the same Koala object as EKoala4, with the same IAnimal and IKoala interfaces, but it also has an implementation of IMarshal to specify custom marshaling. IMarshal::GetUnmarshalClass returns CLSID_KoalaProxy (0002114c-0000-0000-c000-000000000046), which is then implemented in KOALAPRX.DLL (CHAP06\KOALAPRX). This DLL also provides an implementation of the same Koala object with the same public interfaces to show the client, except that most functions are implemented by making custom-marshaled calls to the object in EKoala5.
The sample client to use with EKoala5 and its proxy is ObjectUser3, the same one that we used to demonstrate custom interface marshalers in the previous section. In other words, a change in the marshaling technique for any given object class does not affect clients at all. ObjectUser3 was oblivious to the existence of marshalers for a custom interface in the previous section; here it's oblivious to the presence of custom marshaling.
Earlier in this chapter, we discussed the many ways to perform custom marshaling: shared memory, private RPC connections, and so on. KOALAPRX and EKoala5 in this sample communicate through Windows messages, specifically through WM_COMMAND messages that the proxy sends or posts to a window connected to the object within the server process. This works well on any single machine and also works between 16-bit and 32-bit processes because window handles (HWNDs) are global within a system. While this technique will work in the absence of shared memory, it will not work across a network.
The proxy implemented here doesn't blindly forward every call into IAnimal and IKoala, however. The proxy can handle a lot of implementation itself because it has intimate knowledge about the local object. For example, the proxy can fully implement IAnimal::WhatKindOfAnimal because COM will load that proxy only when the local Koala object specifies it through IMarshal. The proxy, therefore, knows the object is a Koala object and that it can hard code the IID returned from WhatKindOfAnimal. In addition, the proxy knows that the local object doesn't do anything with IKoala::ClimbEucalyptusTree or with IKoala::PouchOpensDown. Thus, the proxy doesn't bother to send these on to the local object because performing a useless context switch would simply be a waste of time.
This knowledge shared between the proxy and the local object is precisely what makes custom marshaling so useful—you can make any optimizations you want to cut down on cross-process or cross-machine calls and thus improve overall performance. In this specific example, the proxy passes only IAnimal::Eat, IAnimal::Sleep, IAnimal::Procreate, and IKoala::SleepAfterEating calls to the local object along with the final call to Release (but no other AddRef or Release calls because the proxy knows that the object is alive as long as there's a reference count of 1). The proxy makes these calls by sending or posting a WM_COMMAND message to the object's window with one of the following command IDs:
#define MSG_RELEASE 1000
#define MSG_EAT 1001
#define MSG_SLEEP 1002
#define MSG_PROCREATE 1003
#define MSG_SLEEPAFTEREATING 1004
Let's see how EKoala5 prepares to receive these messages and how the proxy obtains the window handle to which it sends the messages.