Inside CoLib, a Component Object Library
Copyright © 2000 by Ernest Murphy ernie@surfree.com
For educational use only. All commercial use only by written license.
Revised 2/25/2001 for simplified class definition Version 1.1
ABSTRACT
"CoLib.Lib" is a library of functions to be used in the creation of COM servers. CoLib.Lib will make a dll server to support any number of COM objects with any number of COM interfaces. CoLib also provides a base to create COM objects in other environments, such as internal helper objects in either another COM object, or stand-alone. This document is an attempt to explain the logic behind this object file, and explain how to use it to create arbitrary COM objects.
CoLib.Lib is meant to be a reusable library of functions, to serve as the base of your object. It lets the programmer define what the object is, to create it and destroy it, supports the basic .DLL exports of a COM server, and the interfaces IUnknown, IClassFactory, and IDispatch. It optionally supports aggregation. CoLib will be an invaluable source of functionality for any in-process server.
CoLib is designed to work with the MASM32 assembly language package. This package is available free at http://www.pbq.com.au/home/hutch/masm.htm
Version 1.1 Notes
A few minor changes to CoLib were made for the first service revision. Most notably, the requirement to create an object image structure has been removed, you need just define your custom data structure. It was realized the ClassItem held enough information to infer the full object size given the custom data object, hence the ClassItem definition has been revised.
This should not break older code. However, classes defined per the original spec will create objects that will waste some memory space.
The registrar code was revised some, a string pointer deletion invoke was accidentally removed, though registering is still a problem in NT, though less so.
The lib files were further subdividing so it is more useful in creating objects on the client side, without linking to unused code. Previously, if you linked to AllocObject, you also got a DllGetClassObject implementation due to the way the files cross-linked. This has been corrected to give the smallest builds possible.
Several typographical errors (and some just plain bad stuff) were also corrected in this document.
INTRODUCTION
Do not try to draw any correlation between the coserver library structures and those used by the Active Type Library (ATL). These two libraries have similar goals, but completely different implementations. The CoLib library structures have different meaning then the ATL structures, and a comparison should not be made.
When describing how the CoLib works, the following definitions are used:
CoLib or library: functionality implemented by the CoLib.Lib library
Project: a full dll COM server built using the CoLib library. Also used to illustrate named particulars a project must supply
Client: A separate program that invokes the compiled project dll
this_: a reference to the COM object to be returned from the COM
server to a client
CoLib OBJECT CREATION
Under Windows, when a COM client needs a COM object, it will invoke the CoCreate-Instance API, passing in the Class Identifier (clsid) and Interface Identifier (iid) of the required object. The CoCreateInstance function will load the DLL, then invokes DLL exports DllMain followed by DllGetClassObject. DllGetClassObject needs to perform a check if this class clsid is supported.
Inside a CoLib dll, DllGetClassObject performs this check by walking a structure called a ClassMap. This Map is an array of ClassItem structures, each items describing a class the DLL supports. If a match between the requested class clsid and a supported clsid in the ClassMap is made, a Class Factory Object (CFO) is created.
This CFO contains a reference to the matching ClassItem, such that each CFO is associated with a particular class. Thus, a particular CFO is a factory for a single class only.
The CFO method IClassFactory::CreateInstance is then invoked by the CoCreateInstance API. The factory creates its referenced object, and casts it as IUnknown. The IUnknown::QueryInterface method of this unknown object is then invoked, requesting the interface iid parameter of the original CoCreateInstance invocation. If the interface is supported, a reference to it is passed back to the caller. If not, the object is deleted and a fail code is returned.
Each ClassItem contains a reference to an InterfaceMap, which is an array of InterfaceItem structures. Each item describes an interface supported by the object. This is how QueryInterface is able to determine what interfaces an object supports.
Briefly, that is how CoLib creates an object. The members of these class structures will be discussed in detail later on.
CLASS BASICS
Classes are defined in a structure called ClassItem. Each class defined in the project needs a ClassItem, which defines the requirements of the class. The fields of this structure are defined as so:
ClassItem STRUCT
m_clsid DWORD ; reference to the Class ID (clsid) or
; GUID of this class
m_Flags DWORD ; Control some basic object properties
; (See Object Flags)
m_pTypeLibInfo DWORD ; reference to a TypeLibInfo structure for
; this class
m_pIMap DWORD ; reference to an InterfaceMap for
; this class
m_pConstructor DWORD ; pointer to custom class constructor
; function, may be NULL
m_pDestructor DWORD ; pointer to custom class destructor
; function, may be NULL
m_pData DWORD ; pointer to custom class data
; structure, may be NULL
m_ObjectSize DWORD ; size of the custom ProjectData
; structure
ClassItem ENDS
This structure was redefined in CoLib version 1.1. Originally, m_ObjectSize defined the entire object size. This meant you had to custom define this object, not a simple task. It finally dawned on the author that only the custom data structure needs definition, as the other elements sizes may be determined either through SIZEOF at compile time, or counting at runtime.
Several interlocking structures are defined to provide all the information any class will need to define it's objects. All objects to be created through CoCreateInstance need have a ClassItem included in the ClassMap. Each project needs to define its custom ClassMap.
The ClassMap is an array of ClassItem structures as so:
ClassMap:
ClassItem1 ClassItem { }
ClassItem1 ClassItem { }
...
ClassItemN ClassItem { }
END_CLASS_MAP
The END_CLASS_MAP macro is provided to flag the end of the Map list.
Classes also may also be instanced without a call to CoCreateInstance. If your class does not need to be instanced through this procedure, you need not include it in the ClassMap. All an object needs to be instanced is its describing ClassItem, and this reference may be passed to the AllocObject method to create helper or internal COM objects.
A reference to a TypeLibInfo structure provided for in the ClassItem structure. TypeLibInfo defines the Type Library to be used for a class. Type Libraries are very important for this implementation, as the CoLib IDispatch interface implementation needs this type information to perform its tasks. This parameter may be left NULL for objects that do not support IDispatch. See MSDN regarding the ITypeInfo interface to discover how a type library can support IDispatch.
TypeLibInfo STRUCT
m_pIID_TYPELIB DWORD ; reference to the ID (refid) for
; this type lib
m_MajorVer DWORD ; Major Version of type lib
m_MinorVer DWORD ; Minor Version of type lib
TypeLibInfo ENDS
Each object that may be served from the project dll will need its particular TypeLibInfo.
An InterfaceMap is an array of InterfaceItem structures. This allows each object to define which interfaces it supports, and to be cast to any of these interfaces.
InterfaceMap:
InterfaceItem1 InterfaceItem { }
InterfaceItem2 InterfaceItem { }
...
InterfaceItemN InterfaceItem { }
END_INTERFACE_MAP
The END_INTERFACE_MAP macro is provided to flag the end of the Map list.
Each object that may be served from the dll will need its particular InterfaceMap. Each InterfaceItem will need to refer to it's iid and also hold a pointer to the table of member functions of the interface.
InterfaceItem STRUCT
m_refiid DWORD ; reference to the Interface ID (iid)
; the GUID of this interface
m_pVtbl DWORD ; pointer to implementation table ('virtual'
; function vtable , or just vtable) of this
; interface
InterfaceItem ENDS
OBJECT BASICS
As previously discussed elsewhere (see "Creating COM objects in ASM," Assembly Programming Journal, Issue 8: Mar-Aug 2000), an object should be thought of a run-time allocated area of memory. It needs a well defined structure so it's internal properties may be employed to give custom instance information and access to the methods of the object.
In the CoLib.Lib library, an object is defined as an array of structures, build up as so:
ProjectObject STRUCT
ObjectData1 { }
ProjectData { }
ObjectEntry0 { }
ObjectEntry1 { }
ObjectEntry2 { }
...
ObjectEntryN { }
ProjectObject ENDS
These internal structures are used as follows:
ObjectData Contains the internal state of a particular object, such as the reference count, a reference to class data and such.
ProjectData An arbitrary structure determined by the specific project, meant to be a catch-all for all object-specific data.
ObjectEntryX The ObjectEntry structure provides a uniform way to support multiple interfaces. The value of this_ at any time is in fact a pointer to a particular ObjectEntry. This structure is used to cast this_ internally to the base address of the object.
In CoLib version 1.1 and higher this structure need not be defined. Just be aware that this is how your object is created by the lib.
The ObjectData is a standard structure holds the basic instance information for the object.
ObjectData STRUCT
m_RefCount DWORD ; The object reference count
m_pUnkOuter DWORD ; aggregating object's IUnknown
m_lcid DWORD ; current LCID of this object
m_pti DWORD ; object ITypeInfo pointer
m_pAggList DWORD ; reference to aggregated object linked list
m_pClassItem DWORD ; ClassItem data pointer
m_pEntry0 DWORD ; first ObjectEntry (to skip custom data)
ObjectData
The ProjectData structure is custom to each class and must be defined by the project for each class. There are no limits on the size or makeup of this structure. Since both this data structure and the methods are created together in a project, it is simple to allocate any private data required.
The ProjectData structure is a runtime creation, thus you will never need to create a .data structure of type ProjectData. The structure definition is quite useful for defining and accessing the private data when writing the custom method code. The size of this structure (m_ObjectSize) is used in the ClassItem.
The ObjectEntry structure is used to either direct a method call to the implementation table, or to cast this_ to the base address of the object itself.
ObjectEntry STRUCT
m_pVtbl DWORD ; interface object
m_pBase DWORD ; pointer to base
ObjectEntry ENDS
When CoLib creates an object, it included (N+1) ObjectEntry structures to support N interfaces. The extra ObjectEntry is needed to support the two IUnknown interfaces for aggregation (see the discussion following on aggregation).
Note that the ProjectObject structure itself is only instanced at runtime, no instance of the project object structure is made. Nor is there any ProjectObject structure defined anywhere. It is just a convenient fiction, used only to see how the of memory is organized at runtime.
OBJECT CONSTRUCTION AND DESTRUCTON
To allow for custom object creation and destruction, you may define functions to handle these events. This is a convent way to allocate and deallocate resources.
These functions should be written to the following prototypes:
ConstructorProto PROC this_:DWORD, pCustomData:DWORD
DestructorProto PROC this_:DWORD
The Constructor proto includes a pointer to a data structure you may customize for this class. This pointer is included in the AllocObject method. When called from DllGetClassObject, there is no way to customize this pointer, but it may be defined as needs be in a sub-object creation. (This prototype and definition was corrected for CoLib 1.1)
Thus. classes are provided two separate ways to be customized. The constructor accepts a reference to custom data, and there is also a undefined data pointer in the ClassItem itself. Alternate methods of customization are provided since a class may be instanced in two ways, and may be useful with a general prototype class, one that just requires a little extra bit of custom information. An enumeration interface is one example of this.
CASTING AN OBJECT FOR AN INTERFACE
To "cast" an object in assembler does need some explanation. The term "cast" is borrowed from C++, where it does make some concrete sense, but here in the assembly end, the concepts of class and object are a bit fuzzier.
The COM interface contract allows an object to support multiple interfaces. This interface has a well-defined binary structure that we can easily duplicate in assembly, but this opens the question of interface casting.
A much better explanation of this may be found on page 44 of "Inside COM," "Multiple Inheritance and Casting."
Let us look at the interface for an imaginary object. An interface is a table of pointers to functions. Say Object this imaginary object contains Interfaces IX and IY. As this is a COM object, both IX and IY inherit from IUnknown. Let us give both IX and IY a single method each, IX.Foo and IY.Bar. Also remember that this_ holds a reference to a pointer to the virtual table of functions. The binary layout of this interface would look like so:
CLIENT

OBJECT

OBJECT


COM

INSTANCE

IMPLIMENTATION


POINTER










IX::QueryInterface
IX

this_

pvtable1

IX::AddRef




pvtable2

IX::Release






IX::Foo






IY::QueryInterface
IY





IY::AddRef






IY::Release






IY::Bar



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.