When you ran the EX25A, EX25B, and EX25C components from Excel VBA, you used something called late binding. Normally, each time VBA accesses a property or a method, it calls IDispatch::GetIDsOfNames to look up the dispatch ID from the symbolic name. Not only is this inefficient, VBA can't do type-checking until it actually accesses a property or a method. Suppose, for example, that a VBA program tried to get a property value that it assumed was a number, but the component provided a string instead. VBA would give you a runtime error when it executed the Property Get statement.
With early binding, VBA can preprocess the Visual Basic code, converting property and method symbols to DISPIDs before it runs the component program. In so doing, it can check property types, method return types, and method parameters, giving you compile-time error messages. Where can
VBA get the advance information it needs? From the component's type library, of course. It can use that same type library to allow the VBA programmer to browse the component's properties and methods. VBA reads the type library before it even loads the component program.
Registering a Type Library
You've already seen that Visual C++ generates a TLB file for each component. For VBA to locate that type library, its location must be specified in the Windows Registry. The simplest way of doing this is to write a text REG file that the Windows Regedit program can import. Here's the ex25b.reg file for the EX25B DLL component:
REGEDIT4 [HKEY_CLASSES_ROOT\TypeLib\{A9515ACA-5B85-11D0-848F-00400526305B}] [HKEY_CLASSES_ROOT\TypeLib\{A9515ACA-5B85-11D0-848F-00400526305B}\1.0] @="Ex25b" [HKEY_CLASSES_ROOT\TypeLib\{A9515ACA-5B85-11D0-848F-00400526305B}\1.0\0] [HKEY_CLASSES_ROOT\TypeLib\{A9515ACA-5B85-11D0-848F-00400526305B}\1.0\0\win32] @="C:\\vcpp32\\ex25b\\Debug\\ex25b.tlb" [HKEY_CLASSES_ROOT\TypeLib\{A9515ACA-5B85-11D0-848F-00400526305B}\1.0\FLAGS] @="0" [HKEY_CLASSES_ROOT\TypeLib\{A9515ACA-5B85-11D0-848F-00400526305B}\1.0\HELPDIR] @="C:\\vcpp32\\ex25b\\Debug" [HKEY_CLASSES_ROOT\Interface\{A9515AD7-5B85-11D0-848F-00400526305B}] @="IEx25bAuto" [HKEY_CLASSES_ROOT\Interface\{A9515AD7-5B85-11D0-848F-00400526305B}\ProxyStubClsid] @="{00020420-0000-0000-C000-000000000046}" [HKEY_CLASSES_ROOT\Interface\{A9515AD7-5B85-11D0-848F-00400526305B}\ProxyStubClsid32] @="{00020420-0000-0000-C000-000000000046}" [HKEY_CLASSES_ROOT\Interface\{A9515AD7-5B85-11D0-848F-00400526305B}\TypeLib] @="{A9515ACA-5B85-11D0-848F-00400526305B}" "Version"="1.0"
Notice that this file generates subtrees under the Registry's TypeLib and Interface keys. The third entry specifies the path for the version 1.0 TLB file. The 0 subkey stands for "neutral language." If you had a multilingual application, you would have separate entries for English, French, and so forth. Browsers use the TypeLib entries, and the Interface entries are used for runtime
type-checking and, for an EXE component, marshaling the dispinterface.
How a Component Can Register Its Own Type Library
When an EXE component is run stand-alone, it can call the MFC AfxRegisterTypeLib function to make the necessary Registry entries, as shown here:
VERIFY(AfxOleRegisterTypeLib(AfxGetInstanceHandle(), theTypeLibGUID, "ex25b.tlb"));
Shown here is theTypeLibGUID, a static variable of type GUID:
// {A9515ACA-5B85-11D0-848F-00400526305B} static const GUID theTypeLibGUID = { 0xa9515aca, 0x5b85, 0x11d0, { 0x84, 0x8f, 0x00, 0x40, 0x05, 0x26, 0x30, 0x5b } };
The AfxRegisterTypeLib function is declared in the afxwin.h header, which requires _AFXDLL to be defined. That means you can't use it in a regular DLL unless you copy the code from the MFC source files.
The ODL File
Now is a good time to look at the ODL file for the same project.
// ex25b.odl : type library source for ex25b.dll // This file will be processed by the MIDL compiler to produce the // type library (ex25b.tlb) [ uuid(A9515ACA-5B85-11D0-848F-00400526305B), version(1.0) ] // GUID for the type librarymatches TypeLib Registry key and // AfxOleRegisterTypeLib parameter library Ex25b { // library name for Excel's object borrower importlib("stdole32.tlb"); // primary dispatch interface for CEx25bAuto [ uuid(A9515AD7-5B85-11D0-848F-00400526305B) ] // GUID from component's interface mapmatches Registry Interface // entry dispinterface IEx25bAuto { // name used in VBA Dim statement and Object list properties: // NOTE - ClassWizard will maintain property // information here. // Use extreme caution when editing this section. //{{AFX_ODL_PROP(CEx25bAuto) [id(1)] long LongData; [id(2)] VARIANT TextData; //}}AFX_ODL_PROP methods: // NOTE - ClassWizard will maintain method // information here. // Use extreme caution when editing this section. //{{AFX_ODL_METHOD(CEx25bAuto) [id(3)] boolean DisplayDialog(); //}}AFX_ODL_METHOD }; [ uuid(A9515AD8-5B85-11D0-848F-00400526305B) ] // component's CLSID coclass Ex25bAuto { [default] dispinterface IEx25bAuto; }; //{{AFX_APPEND_ODL}} };
As you can see, numerous connections exist among the Registry, the type library, the component, and the VBA client.
A useful Visual C++ utility, OLEVIEW, lets you examine registered components and their type libraries.
Let's examine the sequence of steps Excel uses to utilize your type library:
When the workbook is saved, this reference information is saved with it.
Dim DllComp as Object
with
Dim DllComp as IEx25bAuto
The VBA program will exit immediately if it can't find IEx25bAuto in its list of references.
You might think that early binding would make your Automation component run faster. You probably won't notice any speed increase, though, because the IDispatch::Invoke calls are the limiting factor. A typical MFC Invoke call from a compiled C++ client to a compiled C++ component requires about 0.5 millisecond, which is pretty gross.
The browse capability that the type library provides is probably more valuable than the compiled linkage. If you are writing a C++ controller, for example, you can load the type library through various COM functions, including LoadTypeLib, and then you can access it through the
ITypeLib and ITypeInfo interfaces. Plan to spend some time on that project, however, because the type library interfaces are tricky.
Faster Client-Component Connections
Microsoft has recognized the limitations of the IDispatch interface. It's naturally slow because all data must be funneled through VARIANTs and possibly converted on both ends. There's a new variation called a dual interface. (A discussion of dual interfaces is beyond the scope of this book. See Kraig Brockschmidt's Inside OLE, 2d ed. [Microsoft Press, 1995], for more information.) In a dual interface, you define your own custom interface, derived from IDispatch. The Invoke and GetIDsOfNames functions are included, but so are other functions. If the client is smart enough, it can bypass the inefficient Invoke calls and use the specialized functions instead. Dual interfaces can support only standard Automation types, or they can support arbitrary types.
There is no direct MFC support for dual interfaces in Visual C++ 6.0, but the ACDUAL Visual C++ sample should get you started.