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

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.

 

Copyright © -