COM, ATL

Why type library marshaling?

디버그정 2008. 8. 14. 18:45
Why type library marshaling?

Type library marshaling is the most general type of marshaling. It’s a form of standard marshaling but doesn’t require that you supply your own proxy-stub components: instead, you use the IDispatch marshaler supplied by COM in oleaut32.dll. The IDispatch marshaler can be used by IDispatch, IDispatch-derivative interfaces, and even custom interfaces (derived from IUnknown or some other IUnknown-derivative interface) with two requirements:

  • You must use only IDispatch-compatible data types in such interfaces.
  • You must register your type library.

When you examine what happens when a type library is registered, you’ll understand these requirements.

Type libraries are registered via the RegisterTypeLib() API function:

HRESULT RegisterTypeLib(ITypeLib 
	FAR* ptlib, OLECHAR FAR* 
	szFullPath, OLECHAR FAR* 
	szHelpDir);

or via the LoadTypeLibEx() API function:

HRESULT LoadTypeLibEx(LPCOLESTR 
	szFile, REGKIND regkind,
	ITYPELIB pptlib );

When you invoke these functions to register a type library, they scan the interface descriptions in the latter looking for the oleautomation interface attribute, e.g.:

[
 object, oleautomation,
 uuid(C5258700-D3B4-11d1-817F-
   0060080269B0),
 helpstring("IName Interface"),
 pointer_default(unique)
]
interface IName : IUnknown
{
…
}

This attribute does two things: It tells COM that the interface only supports IDispatch-compatible (Auto-mation-compatible) data types in its method calls, and it causes the MIDL compiler to issue warnings if you try to compile the interface with non-compatible data types.

When RegisterTypeLib() or Load-TypeLibEx() find the oleautomation attribute on an interface description in a type library, they automatically create an interface registration that establishes the IDispatch marshaler as the proxy-stub for that interface (Figure 2).

Figure 2

For example, here you can see that the interface registration for the IName interface (found in the registry under HKEY_CLASSES_ROOT\In-terface) has a ProxyStubClsid32 key set to {00020424-0000-0000-C000-000000000046}. If you look up that Clsid in the registry (under HKEY_-CLASSES_ROOT\-Clsid), you will find that Clsid points to oleaut32.dll (Figure 3).

Figure 3

There are some downsides to this approach—not major ones, but you should be aware of them. First, type library marshaling performance is slightly slower than standard marshaling, where you provide the proxy-stub components. That’s because the type library has to be loaded at runtime by the proxy-stub (COM’s IDispatch marshaler) to know how to marshal an interface and its method parameters. Fortunately, once COM loads the type library, it doesn’t have to reload it so long as the server is in use by a client. The first instantiation of a COM object that implements type library marshaling will be a little slow; however, subsequent instantiations will be just as fast as with standard marshaling.

The upside is, you can use any IDispatch- or IUnknown-derivative interface, and you don’t have to create your own proxy-stub components: COM provides them for you and will even register them for you, provided you follow the requirements stipulated earlier.

So what about those requirements? Well, the first one isn’t so bad: You can’t use any custom data types, but you can use any and all of the Automation-compatible data types described in Table 2. You’re not required to implement IDispatch, just to stick to IDispatch-compatible types. And that’s a good thing: IDispatch-compatible types automatically in-clude all data types in Visual Basic—VB types are automatically compatible with Automation. If you’re writing servers for VB clients, there won’t be any issues about how VB will get the data returned from a method call. Things will work as expected.

Automation-compatible data type
Automation-Compatible Types Description
boolean Data item that can be true or false; size of unsigned char
unsigned char 8-bit unsigned data item
double 64-bit IEEE floating-point number
float 32-bit IEEE floating-point number
int Integer; size is system dependent (MIDL treats int as a 32-bit signed integer on 32-bit systems)
long 32-bit signed integer
short 16-bit signed integer
BSTR Length-prefixed string
CY 8-byte fixed-point number (formerly CURRENCY)
DATE 64-bit floating-point fractional number indicating number of days since December 31, 1899
SCODE Built-in error type that corresponds to HRESULT
enum Signed integer; size is system dependent
IDispatch* Pointer to IDispatch interface (VT_DISPATCH)
IUnknown* Pointer to an interface that is not derived from IDispatch (VT_UNKNOWN); any interface can be represented by its Iunknown interface)
SAFEARRAY (TypeName) Array of TypeName that can be any of the above types
TypeName* Pointer to TypeName that can be any of the above types

The second requirement isn’t so bad, either: You need to register your server’s type library when the server itself is registered. I’ll show you how to do this next.

Registering your TypeLib

While you can use Register-TypeLib() or LoadTypeLibEx() to register a type library, both MFC and ATL have helper functions to make the process relatively simple. Indeed, there’s nothing you have to do at all with ATL projects: the standard server registration code in ATL will call AtlModuleRegisterType-Lib(), which will pretty much take care of the dirty work. MFC provides a similar helper function as well: AfxOleRegister-TypeLib(). It’s invoked automatically in code generated by the MFC wizards when you create an ActiveX Control project, but you can add a call to it in your server registration code and get your type library registered in a snap. The formal function syntax looks like this:

#include <afxctl.h>
BOOL AfxOleRegisterTypeLib(
  HINSTANCE hInstance, REFGUID

  tlid, LPCTSTR pszFileName = 
  NULL, LPCTSTR pszHelpDir = 
  NULL);

You can call it by passing your module instance handle (obtained by calling AfxGetInstanceHandle()), and the type library ID (usually LIBID_moduleName, where module-Name is found in the header file output from the MIDL compiler). If your type library is in a separate .tlb file, you’ll need to pass the name of the file in as a third parameter. But if you added the type library to your project as a separate resource, you can just pass in the instance handle and the type library ID. The function will load the type library from your program EXE or DLL automatically.

For example,

AfxOleRegisterTypeLib(AfxGetInstanceHandle(),
	LIBID_MfcCustomInterfaceServerExe); 

will register the type library for a server, where the type library ID is found in LIBID_MfcCustomInter-faceServerExe. This code can be added to the DllRegisterServer() function in an inproc server, or to the registration code found in the Init-Instance() override of an EXE server. To bring in the type library ID, you should #include the interfaces.h file as I explained in the August issue. And because AfxOleRegisterTypeLib() is declared in afxctl.h, you’ll need to #include that header in the module that contains your registration code or in stdafx.h.

And since I’m mentioning the August issue, one last point: in that issue I explained how to embed your type library in your server’s resources and that Visual C++ only generally does this for you when you create ActiveX Control projects. That technique is still valid, but the other day, I noticed another case in some in-proc server projects, where Visual C++ will automatically embed the type library as a resource. So, before you add the type library, open the RC file as a text file and see if a reference to the type library is already there—you might save yourself some time and effort.

That’ll take care of type library registration. I’ve included an example program (both in EXE and inproc form) so you can see for yourself how it works. Again, the only real limitation is that you have to stick to IDispatch-compatible data types. To use fancier custom data types, you’ll have to drop down a level to standard marshaling and supply your own proxy-stub components. And that’s food for discussion in my next column.

Richard Hale Shaw regularly speaks and writes on Visual C++, COM, ActiveX, MFC, Visual J++, and Java. He can be reached at www.richardhaleshaw.com.