In Chapter 14, we saw that a marshaled interface pointer can be as simple as the string "FileMap,StubEvent,ProxyEvent". When you use custom marshaling, it is up to you to decide what constitutes a marshaled interface pointer. Since the standard marshaling architecture is built on top of custom marshaling, the standard marshaler must decide what constitutes a marshaled interface pointer. It should come as no surprise that interface pointers marshaled by the standard marshaler are stored in a very different format from that in the custom marshaling example in Chapter 14. To obtain the marshaled form of an arbitrary interface pointer, regardless of whether it uses custom or standard marshaling, you need only create a stream object and then call the CoMarshalInterface function, as shown in the following code fragment:
IStream* pStream = 0; hr = CreateStreamOnHGlobal(0, TRUE, &pStream); hr = CoMarshalInterface(pStream, riid, pObject, MSHCTX_DIFFERENTMACHINE, 0, MSHLFLAGS_NORMAL); |
Now you can examine the marshaled interface pointer stored in the stream. Of course, the data is stored in a binary format that does not lend itself to casual inspection. One common technique for viewing binary data is to convert it to a string that can be displayed. You can use many algorithms to convert binary data to a string format; one of the simplest is to convert each byte of data into two hexadecimal characters. This means, for example, that a decimal value of 0 is converted to the characters 00, the decimal value 78 is converted to the characters 4E, and 255 becomes FF. To automate this conversion, we wrote a function that converts an arbitrary interface pointer to a hexadecimal string using this technique; the code for the function, named IPToHexString, is shown here:
HRESULT IPToHexString(REFIID riid, IUnknown* pObject, char** output) { HRESULT hr; IStream* pStream = 0; hr = CreateStreamOnHGlobal(0, TRUE, &pStream); hr = CoMarshalInterface(pStream, riid, pObject, MSHCTX_DIFFERENTMACHINE, 0, MSHLFLAGS_NORMAL); ULONG size; hr = CoGetMarshalSizeMax(&size, riid, pObject, MSHCTX_DIFFERENTMACHINE, 0, MSHLFLAGS_NORMAL); HGLOBAL hg; hr = GetHGlobalFromStream(pStream, &hg); unsigned char* buffer = (unsigned char*)GlobalLock(hg); *output = (char*)CoTaskMemAlloc((size * 2) + 1); char hex[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; for(ULONG count = 0; count < size; count++) { (*output)[count*2] = hex[buffer[count] / 16]; (*output)[(count*2)+1] = hex[buffer[count] _ (buffer[count] / 16) * 16]; } (*output)[((count-1)*2)+2] = 0; GlobalUnlock(hg); pStream->Release(); return hr; } |
This function reveals the fascinating world of standard marshaled interface pointers. After converting an interface pointer to a string, you can easily display it using a printf-style function. The other day we found a really good one, shown below. If you are wondering what this data means, don't despair—the exact contents of a standard marshaled interface pointer are covered in Chapter 19.
4d454f57010000000100001000000000000000000000000100000000000000004 00500004d3b9e044a0500004d3b9e0401000000e5b5f6ff31baf6ff0100000023 00140007003100390039002e00330034002e00350038002e00330030005b00310 0330035005d00000000000a00000044004f004d00410049004e005c0047004100 4c0049000000000000004c0100a0346434353466353730313030303030303031 |
You might be thinking, "Hey, that's pretty neat. But what can I do with a marshaled interface pointer in string form?" The great thing about having a string form of a marshaled interface pointer is that you can easily transfer it. For example, you can e-mail the interface pointer shown above to a friend. And what can your friend do with a marshaled interface pointer in string form? She can convert the string back to a binary marshaled interface pointer and then unmarshal it, yielding a valid interface pointer to your object. The code you need to convert a hexadecimal stream back to a binary block of memory is on the companion CD. Here is the code that unmarshals the interface pointer and provides an actual interface pointer, which is named pObject:
IStream* pStream = 0; hr = CreateStreamOnHGlobal(pointer, TRUE, &pStream); hr = CoUnmarshalInterface(pStream, riid, pObject); pStream->Release(); |
This technique for transmitting interface pointers works whether you transmit the interface pointer across the hall or across the continent. Of course, the unmarshaled interface pointer is valid only as long as the original object is running; once the object terminates, the interface pointer is useless. And of course the client machine must have a registered proxy installed that knows how to unmarshal the interface pointer.
In the first edition of this book, we suggested that an interesting project would be to build a custom moniker that uses the string form of a marshaled interface pointer as its display name, and we left this as an exercise for the reader. Since then, Microsoft has taken us up on the challenge and built the OBJREF moniker. As its display name, the OBJREF moniker uses the marshaled form of an interface pointer that has been converted to a string. But instead of converting the marshaled interface pointer to a hexadecimal string, Microsoft chose to do the conversion using a 64-bit encoding scheme. The result, although even less readable, is similar to the hexadecimal string shown earlier:
objref:TUVPVwEAAAAAAAAAAAAAAMAAAAAAAABGAQAAAAAAAAAPDgAAmijNCBkOAACaKM0 IAQAAACPj9v8D4PbAQAAACIAFAAHADEAOQA5AC4AMwA0AC4ANQA4AC4AMwA5AFsAMQAzAD UAXQAAAAAACgAAAEQATwBNAEEASQBOAFwARwBVAFkAAAAAAA==: |
Notice that the ProgID of the OBJREF moniker is objref. You are able to create an OBJREF moniker by calling the CreateObjrefMoniker function. CreateObjrefMoniker requires only the IUnknown interface pointer of the object you want the moniker to represent. The OBJREF moniker representing that interface pointer is returned as the second parameter of the CreateObjrefMoniker function. Using the OBJREF moniker, you can call the IMoniker::GetDisplayName method to obtain the string representation of the object reference, as shown previously.
The string representation of an OBJREF moniker can then be passed to a client by any available means. One popular way to transmit this type of data over the Internet is to use an Active Server Pages (ASP) file that instantiates a COM+ object using the CreateObject function. The server-side script calls a method of the object that returns its display name using the OBJREF moniker and embeds that display name in the HTML returned to the client's browser. VBScript code running in the browser can then gain access to the object on the server by calling the GetObject function with the OBJREF moniker's display name. As you know from Chapter 11, internally the VBScript GetObject function calls the MkParseDisplayName function to re-create the OBJREF moniker from the display name, followed by the IMoniker::BindToObject method to connect with the original object on the server. A client application written in C++ might call the CoGetObject convenience function in place of MkParseDisplayName and IMoniker::BindToObject.