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.php
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 |
|
|
|
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. |
