The Custom Marshaling Object: EKoala5

The EKoala4 sample that worked with custom interfaces and standard marshaling actually implemented all of the entry points for each interface member function. In EKoala5, with custom marshaling, these entry points are no longer necessary: calls from the client process come into a window procedure because that's the communication channel between the processes. The Koala object (KOALA.CPP) in this sample implements only one interface: IMarshal (which includes IUnknown). In addition, it creates a window for itself inside its CKoala::Init function (called from EKoala5's IClassFactory::CreateInstance). The Koala object eventually passes the handle to this window to the proxy in a marshaling packet:


BOOL CKoala::Init(HINSTANCE hInst, HWND hWndParent)
{
m_hWnd=CreateWindow(TEXT("KoalaObject"), TEXT("KoalaObject")
, WS_CHILD, 35, 35, 35, 25, hWndParent, NULL
, hInst, this);

if (NULL==m_hWnd)
return FALSE;

return TRUE;
}

By the time IClassFactory::CreateInstance returns an interface pointer to this Koala object, the object will have created a window for itself. (EKoala5's initialization code, CApp::Init, registers the KoalaObject window class.)

Now COM's IClassFactory stublet knows that it has to create a new stub for this object unless that object supports custom marshaling. The stublet calls CoMarshalInterface to do the honors, which in turn queries for IMarshal and, finding it, calls IMarshal::GetMarshalSizeMax. To make a connection with its proxy, the Koala object knows that it needs to send the proxy only its window handle, which thus forms the entire contents of the marshaling packet for which the Koala object defines this structure:


typedef struct
{
HWND hWnd; //Message window
} KOALAMARSHAL, *PKOALAMARSHAL;

The implementation of GetMarshalSizeMax merely returns the size of this structure:


STDMETHODIMP CKoala::GetMarshalSizeMax(REFIID riid, LPVOID pv
, DWORD dwDestCtx, LPVOID pvDestCtx, DWORD dwFlags
, LPDWORD pdwSize)
{
if (dwDestCtx & MSHCTX_DIFFERENTMACHINE)
return ResultFromScode(E_FAIL);

*pdwSize=sizeof(KOALAMARSHAL);
return NOERROR;
}

Notice how we check the marshaling context in use here for MSHCTX_DIFFERENTMACHINE. Because we know that we use a window handle for communication, a machine boundary disallows our custom marshaling, so we fail this call. This tells CoMarshalInterface to fail, so CreateInstance will also fail: marshaling simply isn't available in such a case.

Assuming that we're on the same machine, GetMarshalSizeMax then returns the size of the KOALAMARSHAL structure. CoMarshalInterface creates the marshaling packet stream and calls our IMarshal::GetUnmarshalClass, where we return the proxy CLSID:


STDMETHODIMP CKoala::GetUnmarshalClass(REFIID riid
, LPVOID pv, DWORD dwCtx, LPVOID pvCtx, DWORD dwFlags
, LPCLSID pClsID)
{
/*
* If context is on different machine, we cannot use
* our custom marshaling based on SendMessage.
*/
if (dwCtx & MSHCTX_DIFFERENTMACHINE)
return ResultFromScode(E_FAIL);

//Same proxy for all interfaces
*pClsID=CLSID_KoalaProxy;
return NOERROR;
}

Then CoMarshalInterface calls our IMarshal::MarshalInterface function, in which we create our marshaling packet structure and write it into the stream:


STDMETHODIMP CKoala::MarshalInterface(LPSTREAM pstm
, REFIID riid, LPVOID pv, DWORD dwDestCtx, LPVOID pvDestCtx
, DWORD dwFlags)
{
KOALAMARSHAL km;

if (dwDestCtx & MSHCTX_DIFFERENTMACHINE)
return ResultFromScode(E_FAIL);

//Proxy needs to know only where to send messages.
km.hWnd=m_hWnd;

//This is for the client that will call Release when needed.
AddRef();

//Write marshaling packet to stream.
return pstm->Write((void *)&km, sizeof(KOALAMARSHAL), NULL);
}

Nothing fancy is going on here except the extra AddRef call, which is really quite necessary because there is no stub that actually holds a reference to the Koala object itself. Usually a stub will maintain a reference count for an object, but with custom marshaling there is no stub. Therefore, the Koala object holds a reference count on itself, which its proxy actually controls. In other words, the object and the proxy have an unwritten agreement that the proxy will not send a Release call until the client has released the proxy for the final time. We'll see shortly where Koala makes the Release call that reverses this AddRef.

Once we return from MarshalInterface, COM has all the information it needs to create the proxy in the client process, and all we do now is wait for WM_COMMAND messages, which are handled in CKoala::HandleCall, to show up in the object's window procedure:


DWORD CKoala::HandleCall(UINT iMsg, LPARAM lParam)
{
DWORD dw;
short iRet=0;

switch (iMsg)
{
case MSG_RELEASE: //Last IUnknown::Release
Release();
break;

case MSG_EAT:
m_fJustAte=TRUE;
break;

case MSG_SLEEP: //IAnimal::Sleep
//Client's in-parameter in LOWORD(lParam)
iRet=LOWORD(lParam)+m_cSleepAfterEat;
m_fJustAte=FALSE; //Probably want to eat again.
break;

case MSG_PROCREATE: //IAnimal::Procreate
dw=GetTickCount()/100;

iRet=((dw/10)*10==dw) ? 1 : 0;
break;

case MSG_SLEEPAFTEREATING: //IKoala::SleepAfterEating
m_cSleepAfterEat=LOWORD(lParam);
break;

default:
break;
}

return iRet;
}

The HandleCall function actually contains the entire implementation of the object's member functions that are not otherwise handled in the proxy. Most of the code you see in each message case is what the Koala object in EKoala4 had in its individual interface member functions. From this, you can thus see how custom marshaling allows you to put this code wherever is most convenient for your implementation.

Notice that the MSG_RELEASE case contains the Release call to match the AddRef call we made in IMarshal::MarshalInterface. This is how the proxy effectively calls Release, illustrating why we had to call AddRef on ourselves in the first place.

The final point to make about this custom marshaling Koala is that it doesn't do anything in the other IMarshal member functions. Specifically, Disconnect is never called because EKoala5 never calls CoDisconnectObject, and COM never calls UnmarshalInterface or ReleaseMarshalData because they have meaning only for the client-side proxy. To see the corresponding implementation of these functions, we examine the sources for KOALAPRX.DLL.