Figure 2. Interface casting detail (or why this_ is sometimes that_). The first thing to notice from this figure is that there are two possible values of this_, one for each interface, and remember, it's not possible to call them "this_" and “that_". One little known property of casting is that the actual numeric value returned from a cast may not be equal to what was cast. This is another way of saying a cast may do more then just adjusting for different data formats. When QueryInterface is invoked to get another interface inside an object, the returned pointer changes. this_ actually changes value. It has to, it must. This is completely normal. The only requirement COM imposes on the returned pointer is that a QueryInterface on two separate interfaces for IUnknown return the same value, so object identity may be determined. This is important to us in the following way: we need a method to pass back this_, a cast interface pointer, cast to any arbitrary interface we support, and at the same time, use this_ to unwind our entire object structure. We need to get to all the information of the object, since all our code initially knows about it's instance is literally this_, and if this_ can change it means we cannot simply add an offset to this_ to get to other elements in the object structure. Interface casting is performed like so: In our object, each vtable pointer is kept in a ObjectEntry structure, which has two elements, the vtable pointer immediately followed by the base address of our object. The base address is the starting address of the object, it's the address of the ObjectData structure. Also, the ObjectData structure contains the address of the first ObjectEntry structure. In this way, given this_ we can walk about our object and get to any structure from any this_ cast via code such as follows: SomeInterface_SomeFunct PROC this_:DWORD, param1:DWORD, param2:DWORD mov ecx, this_ mov eax, [ecx + 4] ; ObjectData pointer in eax Simple and direct. In actual use, we cast ecx at an ObjectEntry pointer so we don't have to remember the binary layout. Thus the actual library code to cast this_ to the custom data area is like so: pObjectBase MACRO pObject:REQ, reg:REQ, _offset:VARARG .code ; cast object to the base ClassItem interface mov reg, pObject ; get object mov reg, (ObjectEntry PTR[reg]).m_pBase ; walk to base data IFNB <_offset> add reg, _offset ENDIF ENDM Once we have these references to the object's internal structures, we can access any and all parts of it. Casting for an interface is performed in only one place: the IUnknown::QueryInterface implementation. Back when the object was first created, the ClassMap was used to get reference to the InterfaceMap. The InterfaceItem held the interface ID iid, and a pointer to that vtable. These pointer values were then copied in order over into our object. When QueryInterface is given an interface to cast, it compares the given iid parameter with this InterfaceMap, looking for a match. If interface iid N matches the parameter, then the object is cast to it by returning the address of ObjectEntryN, thus making this_ point to the requested interface. AGGREGATION When an object is aggregated, it uses the IUnknown implementation of the containing object. If not aggregated, it uses it's own code for these functions. Aggregation is a runtime choice, so this change must be done on the fly. CoLib uses the same method as found in "Inside COM," Chapter 8, "Component Reuse: Containment and Aggregation." Briefly, this entails having two separate IUnknown implementations, a delegation and non-delegating IUnknown. The non-delegating version does what we've come to expect from IUnknown, reference count and interface cast. The delegating IUnknown looks for another IUnknown implementation to pass it's work off to. At object creation time, pUnkOuter is checked for NULL. If not NULL, it is stored in the object and used by the delegating IUnknown to do the work. If it is NULL, a reference to the very first ObjectEntry is saved instead. The first ObjectEntry is always automatically assigned to the non-delegating IUnknown. If none of this made sense, you really need not worry, the library does this all for you. If you're still worrying, go check out "Inside COM." IMPORTING THE LIBRARY The coserver files need to be added to your masm32 area. For consistency sake, it is recommended that you place the library .inc files in \masm32\com\include, and the library itself in \masm32\com\colib. To use the library, you will first need to include the library itself (and it's own dependencies): include \masm32\include\masm32.inc include \masm32\com\include\oaidl.inc include \masm32\com\include\CoLib.inc includelib \masm32\lib\masm32.lib includelib \masm32\com\colib\CoLib.lib The main project, if a dll server, should also export a ClassMap for each coclass: PUBLIC ClassMap CoLib.Lib depends on the linker to add those components a project requires, making it simple to override CoLib methods if you choose to implement anything differently. The CoLib.inc just defines some of the basic structures, it does not make .code or .data. USING OTHER FEATURES OF THE LIBRARY The following macros of CoLib.inc expand to code (not procedures) (they act as inline functions) and may be used anywhere in your code: pObjectBase pObject, reg, _offset Used to cast this_ to the base address of the object so ObjectData may be accessed. Rarely used, except inside to the library itself pObject the this_ to cast reg register to place result _offset optional parameter, additional offset to add to cast pObjectData pObject, reg, _offset Used to cast this_ to the base address of the custom data structure so the custom data may be accessed. pObject the this_ to cast reg register to place result _offset optional parameter, additional offset to add to cast DeclareGuid gName, sIID Used to instance a GUID textequate. Will assign a GUID textequate to a guid name, and also define a pointer to this guid. gName Label name for this guid. Will also assign "p" & "gName" to create a reference to the guid. sIID optional parameter, used to define the textequate for the guid if this does not exist elsewhere. DeclareGuid is handy to reduce typing (and hopefully improve clarity). For example: DeclareGuid IID_IUnknown, sIID_IUnknown will generate the following symbols: IID_Unknown GUID {000000000H, 00000H, 00000H, {0C0H, 000H, \ 000H, 000H, 000H, 000H, 000H, 046H}} pIID_Unknown OFFSET IID_Unknown The second parameter is optional. If sIID_IUnknown is defined elsewhere and is of the form "s" & "guid name" then the macro will supply the equate. sIID is included for the occasion one needs to define a new guid number. A DeclareVARIANT macro is provided to correctly initialize a VARIANT structure. These can be messy on your own. For example: DeclareVARIANT MyVariant, VT_I4, 10 will generate the following symbols: MyVariant VARIANT {VT_I4, , , , {10}} OTHER REQUIRED PROJECT FILES A project will also require the following files to completely build and define a COM server dll. See example project for samples and further explanation. Interface Definition file .idl Defines the object to produce a type library Type Library .tlb Produced by the .idl file, a black box of object information registry script .rgs Standard script for dll registration or unregistration. See "Creating Registrar Scripts" in MSDN for .rgs syntax and usage. The library follows the same format. rcrs.rc .rc Standard masm32 resource file. A starter template is provided, this should be edited to your own needs as to your particular typelib and registrar script filenames. Project.def .def DLL export definitions. RELATED FILES
CoLib.inc structures, constants, and macro functions needed by the CoLib library, include with your server project.
component.inc perhaps a continual work in progress, defining many constants, structures, and interfaces (54 and counting) for .ocx controls.
L.inc macro to define Unicode string constants
language.inc Equates and macros to support localization (help to interpret lcid parameters)
mini_win.inc Striped to the bone windows.inc file to speed the CoLib.Lib building process
oaidl.inc Basic definitions for any ASM COM project
olectl.inc Basic definitions for an .ocx project
colibl.rc Common resource definitions
APPENDIX A EXPORTED LIBRARY METHODS Standard dll Exports DllMain PROTO :DWORD, :DWORD, :DWORD DllRegisterServer PROTO DllUnregisterServer PROTO DllCanUnloadNow PROTO DllGetClassObject PROTO :DWORD, :DWORD, :DWORD DllRegister/Unregister Internal Functions Register PROTO :DWORD SetSubKey PROTO :DWORD, :DWORD, :DWORD GetNextToken PROTO AddReplacements PROTO :DWORD GuardedDeleteKey PROTO :DWORD, :DWORD Standard Interface Implementations IUnknown Interface Implementations QueryInterface PROTO :DWORD, :DWORD, :DWORD AddRef PROTO :DWORD Release PROTO :DWORD NDQueryInterface PROTO :DWORD, :DWORD, :DWORD NDAddRef PROTO :DWORD NDRelease PROTO :DWORD IDispatch Interface Implementations GetTypeInfoCount PROTO :DWORD, :DWORD GetTypeInfo PROTO :DWORD, :DWORD, :DWORD, :DWORD GetIDsOfNames PROTO :DWORD, :DWORD, :DWORD, :DWORD, :DWORD, :DWORD Invoke_ PROTO :DWORD, :DWORD, :DWORD, :DWORD, :WORD, :DWORD, :DWORD, :DWORD, :DWORD IDispatch Interface Helper Function GetCachedTypeInfo PROTO :DWORD, :DWORD, :DWORD Class Factory Interface Implementations CreateInstance PROTO :DWORD, :DWORD, :DWORD, :DWORD LockServer PROTO :DWORD, :DWORD Class Factory Internal Functions AllocObject PROTO :DWORD, :DWORD, :DWORD, :DWORD DeallocObject PROTO :DWORD External Helper Functions ComPtrAssign PROTO :DWORD, :DWORD ComQIPtrAssign PROTO :DWORD, :DWORD, :DWORD APPENDIX B LIBRARY DATA STRUCTURES ClassItem ClassItem STRUCT m_clsid DWORD 0 ; CLSID; GUID of this class m_Flags DWORD 0 ; various flags m_pTypeLibInfo DWORD 0 ; type info map m_pIMap DWORD 0 ; InterfaceMap for this object m_pConstructor DWORD 0 ; custom object constructor function m_pDestructor DWORD 0 ; custom object destructor function m_pData DWORD 0 ; custom object data reference m_ObjectSize DWORD 0 ; byte count of the ; private data structure ClassItem ENDS Contains parameters for the ClassMap array elements to define a class supported by the project dll. m_clsid Class ID (clsid) for this class. m_Flags Various flags controlling the object. This member can include zero or a combination of the following values: AGGREGATABLE Object will allow itself to be aggregated
DISPINTERFACE Object will support IDispatch
SUPPORT_ERROR_INFO Object will supply IErrorInfo for early bound clients. Currently not implemented.
SUPPLY_TYPE_INFO Object will supply ITypeInfo through it's IDispatch. Do not include in objects that hide their interface.
m_pTypeLibInfo Reference to the TypeLibInfo structure for this class. m_pIMap Reference to the InterfaceMap for this class. m_pConstructor Function pointer for the class creator function. Used to create any class-specific resources. May be NULL if no constructor is supplied. m_pDestructor Function pointer for the class deletion function. Used to clean up any class-specific resources. May be NULL if no destructor is supplied. m_pData Structure pointer for custom class information. It is entirely up to the specific class to determine how this parameter is used. May be NULL if no data is required. m_ObjectSize SIZEOF the specific class object structure. See ProjectObject. InterfaceItem InterfaceItem STRUCT m_refiid DWORD 0 m_pVtbl DWORD 0 InterfaceItem ENDS Contains parameters for an InterfaceMap item. Each interface of a class needs this definition. m_refiid refid for the GUID of this interface. m_pVtbl pointer to the implementation table for this interface. ObjectData ObjectData STRUCT m_RefCount DWORD m_pUnkOuter DWORD m_lcid DWORD m_pti DWORD m_pAggList DWORD m_pClassItem DWORD m_pEntry0 DWORD ObjectData Contains basic instance information for an object. Each instance of a class needs this definition. m_RefCount reference count of the object m_pUnkOuter aggregating object's IUnknown m_lcid current LCID of this object m_pti object type library info pointer m_pAggList pointer to first aggregated object m_pClassItem reference to the ClassItem of this object m_pEntry0 reference to first EntryItem entry (to index past the custom data area) ObjectEntry ObjectEntry STRUCT m_pVtbl DWORD m_pBase DWORD ObjectEntry ENDS Contains parameters for the TypeLibInfo structure to define a type library for a ClassItem class definition structure. m_pVtbl reference to the virtual table of functions for this interface m_pBase reference to base address of this object TypeLibInfo TypeLibInfo STRUCT m_pIID_TYPELIB DWORD 0 m_MajorVer DWORD 0 m_MinorVer DWORD 0 TypeLibInfo ENDS Contains parameters for the TypeLibInfo structure to define a type library for a ClassItem class definition structure. m_pIID_TYPELIB refid for this type lib. m_MajorVer Major Version of type lib. m_MinorVer Minor Version of type lib. APPENDIX C DATA STRUCTURES IMPLEMENTED BY THE PROJECT ClassMap ClassMap: ClassItem1 ClassItem { } ClassItem2 ClassItem { } ... ClassItemN ClassItem { } END_CLASS_MAP A ClassMap structure to define all classes supported by a particular dll. This array is particular to a given dll, and must be defined by the project employing the library. A ClassItem is used to define the ClassMap members, where the individual ClassItem structures are specified. ClassItemN An ClassItem structure for a particular class. Each class needs its ClassItem to be defined. InterfaceMap InterfaceMap EQU InterfaceItem1 InterfaceItem1 InterfaceItem { } InterfaceItem2 InterfaceItem { } ... InterfaceItemN InterfaceItem { } END_OF_INTERFACEMAP A InterfaceMap structure to define all interfaces supported by a particular class. This array is particular to a given class, and each class must have an InterfaceMap defined by the project. An InterfaceItem is used to define the InterfaceMap elements, where the individual InterfaceItem structure is specified. InterfaceMap The InterfaceMap is a reference to the first element in the map array. InterfaceItem A InterfaceItem structure for a particular interface. Each interface in a class needs its InterfaceItem to be defined. END_OF_INTERFACEMAP A macro to define a NULL InterfaceItem element to signify the end of the InterfaceMap. ProjectData ProjectData STRUCT m_DataItem1 DWORD ? m_DataItem2 DWORD ? m_DataItem3 DWORD ? m_DataItem4 DWORD ? ProjectData ENDS ProjectData is given as an example only. Each class must have an object data structure to hold its custom members. ProjectData is an example of the layout of this structure m_DataItemN This structure, and the data defined are arbitrary, for example only. Each particular class needs to define its own custom data structure in the project file. ProjectObject ProjectObject STRUCT ObjectData1 ObjectData { } ProjectData0 ProjectData { } ObjectEntry0 ObjectEntry { } ObjectEntry1 ObjectEntry { } ObjectEntry2 ObjectEntry { } ObjectEntry3 ObjectEntry { } ProjectObject ENDS ProjectObject is given as an example only. Each instanced class must have an object structure to hold its internal state. ProjectObject is an example of the layout of this structure. ProjectObject itself is never defined in a structure definition. ObjectData1 An instance of the ObjectData structure ProjectData0 ProjectData is a holder for a particular class member data. It is defined in the project file. ObjectEntryN An ObjectEntry structure. Each class needs N+1 ObjectEntry structures in sequence to support N interfaces. APPENDIX D GLOBAL VARIABLES g_ObjectCount g_ObjectCount DWORD Contains the count of all objects currently held by the dll. g_hModule g_hModule DWORD A handle to the DLL. The value is the base address of the DLL. The HINSTANCE of a DLL is the same as the HMODULE, so g_hModule can be used in subsequent calls to the GetModuleFileName function and other functions that require a module handle. g_hHeap g_hHeap DWORD Contains the handle to the process heap returned by GetProcessHeap in DllMain. APPENDIX E MACRO FUNCTIONS DeclareGuid DeclareGuid MACRO gName, IID Macro data function to define a guid from a textequate. Defines guid name label as the OFFSET of the guid structure. Also defined a named reference to the guid Parameters gName Textequate. Name to use for the GUID. Will also define "p" & gName with a reference to gName.
reg Optional parameter. Textequate. Text representation of the guid value. May be left blank if sgName is defined elsewhere in the project. DeclareVARIANT DeclareVARIANT MACRO varName, VarType, VarValue Macro data function to define a variant data structure. Defines Also defined a named reference to the guid Parameters varName Variable name of the VARIANT being defined. VarType Variable type of the VARIANT being defined. VarValue Variable value of the VARIANT being defined.
pObjectBase DWORD pObjectBase MACRO pObject, reg, _offset Macro code function to re-cast this_ to a reference to the ObjectBase data area. Returns cast value in reg. Macro code function are always inline. Parameters pObject Object reference, the this_ parameter passed to an class method. reg Name of the register to return the cast. _offsetl Optional parameter. Specifies an additional offset to be added to the cast.
pObjectData DWORD pObjectBase MACRO pObject, reg, _offset Macro code function to re-cast this_ to a reference to the ObjectData data area. Returns cast value in reg. Macro code function are always inline. Parameters pObject Object reference, the this_ parameter passed to an class method. reg Name of the register to return the cast. _offsetl Optional parameter. Specifies an additional offset to be added to the cast.
APPENDIX F LIBRARY HELPER PROCEDURES ComPtrAssign The ComPtrAssign function assigns one COM pointer to another, performing AddRef and Release as required. ComPtrAssign PROC pp:DWORD, // reference of variable to receive a pointer lp:DWORD // reference to existing COM pointer Parameters pp reference of variable to receive a pointer. lp reference to existing COM pointer. May be NULL to release pp Return Value None. See Also ComQIPtrAssign The ComQIPtrAssign function performs a QueryInterface on one COM pointer, and assigns the resulting pointer, performing AddRef and Release as required. ComQIPtrAssign PROC pp:DWORD, // reference of variable to receive a pointer lp:DWORD // reference to existing COM pointer riid:DWORD // reference to existing COM pointer Parameters pp reference of variable to receive a pointer. lp reference to existing COM pointer. Must be valid. riid reference to an interface identifier Return Value None. See Also APPENDIX G INITIALIZING LARGE DATA STRUCTURES IN MASM When working with large structures in MASM, you will run up against a very hard limit: the character count of any given line may not exceed. Logical lines cannot exceed a total of 512 characters. Period. Dang. So, what are you to do when like myself, you wish to implement a vtable consisting of 10 interfaces with 82 function pointers? That is 82 DWORDs alone, and as the string "DWORD " is 7 characters long, 82 * 7 = 574, so we can't even fit all the type casts inside that structure. What to do? Easy, you cheat. Finagle. Lie boldface to the compiler. CoLib structures are arrays of other structures. Small items grouped together to make larger structures. Usually, you can break a large structure into small pieces, and put the pieces into the full structure. That lets you define the structure. Now to initialize an instance of the structure.... cheat! Structures define how an area of memory is arranged. It is really a compiler directive, not runtime code. So... just make the instance initialization a series of WORDS, BYTES, and DWORDS as you need to duplicate the big structure. Put one nice big label at the top of this data structure. Say you call the "real" structure "sCOLLUSUS," and this data structure "COLLUSUS." Then you may access its members with: mov eax, sCOLLUSUS PTR COLLUSUS.MemberDataItem Yeah, I know, not very clean. But it does work. APPENDIX H Creating Registrar Scripts A registrar script provides data-driven, rather than API-driven, access to the system registry. Data-driven access is typically more efficient since it takes only one or two lines in a script to add a key to the registry. You will need to generate a registrar script for your COM server. You then make this .rgs script a resource to your project. The CoLib DllRegisterServer and DllUnregisterServer exports processes your registrar script at run time. Registrar scripts use the following string literals. Script symbols are case sensitive, The following table describes the string literals used in an ATL Registrar script. String Literal
Description
ForceRemove
Completely remove the following key (if it exists) and then recreate it.
NoRemove
Do not remove the following key during Unregister.
val
The following <Key Name> is actually a named value.
Delete
Delete the following key during Register.
s
The following value is a string.
d
The following value is a DWORD.
HKCR
HKEY_CLASSES_ROOT
HKCU
HKEY_CURRENT_USER
HKLM
HKEY_LOCAL_MACHINE
HKCC
HKEY_CURRENT_CONFIG
HKDD
HKEY_DYN_DATA
HKU
HKEY_USERS
In a registrar script you define one or more parse trees in your script. A parse tree can add multiple keys and subkeys to the <root key>. In doing so, it keeps a subkeys handle open until the parser has completed parsing all its subkeys. This approach is more efficient than operating on a single key at a time, as seen in the following parse tree example: HKCR { 'MyVeryOwnKey' { 'HasASubKey' { 'PrettyCool?' } } } Here, the Registrar initially opens (creates) HKEY_CLASSES_ROOT\MyVeryOwnKey. It then sees that MyVeryOwnKey has a subkey. Rather than close the key to MyVeryOwnKey, the Registrar retains the handle and opens (creates) HasASubKey using this parent handle. (The system registry can be slower when no parent handle is open.) Thus, opening HKEY_CLASSES_ROOT\MyVeryOwnKey and then opening HasASubKey with MyVeryOwnKey as the parent is faster than opening MyVeryOwnKey, closing MyVeryOwnKey, and then opening MyVeryOwnKey\HasASubKey. Using %MODULE% It is very often required that a component register the filenamepath where it is stored. This is accomplished with the %MODULE% parameter. CoLib uses this replaceable parameter for the actual location of your server's DLL or EXE. The following example shows how to use the replacement: 'MyGoofyKey' = s '%MODULE%, 1' will be registered as if it was: 'MyGoofyKey' = s '\myfolder\mydll.dll, 1' Where "myfolder" is the filepath of the dll, and "mydll.dll" is the file name. APPENDIX H KNOWN ISSUES Plans exist to include support for objects that aggregate other objects, though currently this is unsupported. Plans exist to include support for the ISupportErrorInfo interface is still in process. The self-registration methods fail catastrophically in NT
APPENDIX I The complete CoLib object map
This map of all the CoLib objects and their interconnections was saved for last, as not to scare away the faint of heart. However, since a picture is still worth about a buck fifty, I felt forced to include it.