Standard for COM in MASM32 ABSTRACT The basis and rational for Component Object Programming 'include' files as defined in these files is explained. INTRODUCTION Attempting to perform COM operations without any include files can be a frustrating proposition. Several people have made some attempts, with some success, but each are working to their own "standard" as to COM information. I have seen some of this work when published, but found it lacking in some respects. Many months of efforts, and several sometimes major revision has gone into these files. Public comments were sought and included where applicable. The goal of this document is to get new and existing COM in ASM players to 'play by the same rules,' such that any new file may be added as is to this hopefully growing library of include files and definitions. Include File Features Any .inc file should have the following features: 1) The MASM32 convention of loose typedef will be continued. This means parameters will be defined as their base type, typically a DWORD. 2) The information should not create any code lines at all. It will merely define information for the mail application. Should a file need to make functions, these should be in the form of macros so they are not expanded to code unless explicitly instructed to. 3) Structures shall be defined to align with their "C" prototypes. Structure curly brackets { } are preferred over angle brackets < >. 4) The GUID structure itself is defined in windows.inc. This definition is correct. GUID values shall be defined through a textequ so they do not directly generate any code. See the following sections on GUIDS. 5) Interfaces are defined in a two-step process where first a general macro is declared to create a general Interface structure, then the structure itself is defined with the Interface name as name decoration on the method name. This both makes methods immune to namespace duplication (necessary for method polymorphism) and allows Interface structure definitions to be 'inherited' in succeeding Interfaces. See the following section on Interfaces. 6) COM Interface method calls will be accomplished with the coinvoke Macro. This macro uses the Interface definition as given to de-reference the object pointer to the function table pointer, and then generate the offset into the function table pointer list and call that function. Simple parameter count and size checking is also performed at compile time. See the following section on the coinvoke Macro. GUIDS GUID values shall be defined through a textequ so they do not directly generate any code. This equate shall have a label consisting of the interface name with a "s" (for string) prefix. EXAMPLE: sIID_IUnknown TEXTEQU <{000000000H, 00000H, 00000H, \ {0C0H, 000H, 000H, 000H, 000H, 000H, 000H, 046H}} This can be employed as such: IID_IUnknown GUID sIID_IUnknown The DeclareGuid Macro is provided as an aid to defining GUIDs It has the form: DeclareGuid GuidName, [<guid value>]
INTERFACES Due to the MASM32 prototype convention of loose type checking in invokes, all that is really checked at compile time is a simple count of parameters. This fact can extremely simplify our definitions of an interface, as the same small set of function prototypes can be used over and over. Interface structure members (methods) will be defined with the interface name plus an underscore as name decoration. With the sole exception of IUnknown, all interfaces must inherit from another interface. It would be convenient to have some sort of macro provide us this function. Thus, the manor interfaces are created from a macro will allow that macro to be re-used (thus "inherited) in further definitions. The following definitions are first established for all interfaces to use: comethod1Proto typedef proto :DWORD comethod2Proto typedef proto :DWORD, :DWORD comethod3Proto typedef proto :DWORD, :DWORD, :DWORD comethod4Proto typedef proto :DWORD, :DWORD, :DWORD, :DWORD comethod4Proto typedef proto :DWORD, :DWORD, :DWORD, :DWORD, :DWORD comethod1 typedef ptr comethod1Proto comethod2 typedef ptr comethod2Proto comethod3 typedef ptr comethod3Proto comethod4 typedef ptr comethod4Proto comethod5 typedef ptr comethod4Proto Actually, these definitions are carried out for 15 parameters. I arbitrarily chose this number, should a function come along with more then 15 parameters, of course this table should be extended. This may seem to be strongly in disagreement with my previous writings. However, the same coinvoke macro will handle this. All I have done was eliminate a ton of proto names that never got used by the compiler. There are a few methods that do not take DWORD size parameters (most notable, the IDispatch Interface). These methods are handled on a case-by-case basis, at the place in the include file where that differing method is declared. Let us look at some familiar interfaces generated with these methods:
EXAMPLE 1 The IUnknown Interface: IUnknown is the basic interface, all others inherit from this. Function prototypes are defined as above. The form of the vtable structure is defined in a macro, then this macro is used to create the structure itself. _vtIUnknown MACRO CastName:REQ ; IUnknown methods &CastName&_QueryInterface comethod3 ? &CastName&_AddRef comethod1 ? &CastName&_Release comethod1 ? ENDM IUnknown STRUCT _vtIUnknown IUnknown IUnknown ENDS This will expand to the following: IUnknown STRUCT IUnknown_QueryInterface comethod3 ? IUnknown_AddRef comethod1 ? IUnknown_Release comethod1 ? IUnknown ENDS The macro is defined with a leading underscore, since the "vt" prefix is handy to leave undefined when it comes time to instance a vtable. Thus, one may wish to provide an instance of the vtable as such: .data vtIUnknown _vtIUnknown {MyQueryInterface, MyAddRef, MyRelease} Method names are defined with the interface name added on as name decoration. Different Interfaces may have methods with the same name (that is simple polymorphism), but without this name decoration the MASM compile cannot tell them apart. (Note that it is legal for two different interfaces may have the same name, same method names, but differing parameters. That is valid (only the IID GUID's need be different), but bad coding practice, so we'll ignore that condition with just this warning here. This standard cannot tell those interfaces apart, and some workaround must be sought.) EXAMPLE 2 The IClassFactory Interface IClassFactory inherits from IUnknown. It begins with the IUnknown methods, then adds it's own 2 methods. _vtIClassFactory MACRO CastName:REQ ; IUnknown methods _vtIUnknown CastName ; IClassFactory methods &CastName&_CreateInstance comethod4 ? &CastName&_LockServer comethod2 ? ENDM
IClassFactory STRUCT _vtIClassFactory IClassFactory IClassFactory ENDS This will expand to the following: IClassFactory STRUCT IClassFactory_QueryInterface comethod3 ? IClassFactory_AddRef comethod1 ? IClassFactory_Release comethod1 ? IClassFactory_CreateInstance comethod4 ? IClassFactory_LockServer comethod2 ? IClassFactory ENDS coinvoke, a MACRO to perform COM Interface Methods We can simplify a COM invoke further with a macro: ;--------------------------------------------------------------------- ; coinvoke MACRO ; ; invokes an abritrary COM interface ; ; revised 12/29/00 to check for edx as a param and force compilation ; error(thanks to Andy Car for a how-to suggestion) ; revised 7/18/00 to pass pointer in edx not eax to avoid confusion ; with parmas passed with ADDR (Jeremy Collake's excellent suggestion) ; revised 5/4/00 for member function name decoration ; ; pInterface pointer to a specific interface instance ; Interface the Interface's struct typedef ; Function which function or method of the interface to perform ; args all required arguments ; (type, kind and count determined by the function) ; coinvoke MACRO pInterface:REQ, Interface:REQ, Function:REQ, args:VARARG LOCAL istatement, arg FOR arg, <args> ;; run thru args to see if edx is lurking in there IFIDNI <&arg>, <edx> .ERR <edx is not allowed as a coinvoke parameter> ENDIF ENDM istatement CATSTR <invoke (Interface PTR[edx]).&Interface>,<_>,<&Function, pInterface> IFNB <args> ;; add the list of parameter arguments if any istatement CATSTR istatement, <, >, <&args> ENDIF mov edx, pInterface mov edx, [edx] istatement ENDM ;--------------------------------------------------------------------- Thus, the same QueryInterface method as before can be invoked in a single line: coinvoke ppv ,IUnknown, QueryInterface, ADDR IID_SomeOtherInterface, ADDR ppnew Note that now the name decoration is done for us by the macro. Testing HRESULTS The return parameter for every COM call is an hResult, a 4 byte return value in eax. It is used to signal success or failure. Since the most significant digit is used to indicate failure, you can test the result with a simple: .IF !SIGN? ; function passed .ELSE ; function failed .ENDIF Again, this can be simplified with some more simple macros: .IF SUCCEEDED ; TRUE if SIGN bit not set.IF FAILED ; TRUE is SIGN bit set And, for the truly code-frugal person who doesn't with to explicitly test eax, these macros will be useful: .IF_SUCCEEDED ; TRUE if eax >= 0 .IF_FAILED ; TRUE if eax < 0 These macros also perform the test for you. Conclusion That's about all you need to fully invoke and use interfaces from COM objects from assembly. These techniques work with any COM or activeX object.