COM in ASM By Bill T. ------------------------------------------------------------------------------ This article will discuss how to use COM interfaces in your assembly language programs. It will not discuss what COM is and how it is used, but rather how it can be used when programming in assembler. It will discuss only how to use existing interfaces, and not how to actually implement new ones; this will be shown in a future atricle. About COM ------------------------------------------------------------------------------ Here is a brief introduction to the basics behind COM. A COM object is one in which access to an object's data is achieved exclusively through one or more sets of related functions. These function sets are called interfaces, and the functions of an interface are called methods. COM requires that the only way to gain access to the methods of an interface is through a pointer to the interface. An interface is actually a contract that consists of a group of related function prototypes whose usage is defined but whose implementation is not. An interface definition specifies the interface's member functions, called methods, their return types, the number and types of their parameters, and what they must do. There is no implementation associated with an interface. An interface implementation is the code a programmer supplies to carry out the actions specified in an interface definition. An instance of an interface implementation is actually a pointer to an array of pointers to methods (a function table that refers to an implementation of all of the methods specified in the interface). Any code that has a pointer through which it can access the array can call the methods in that interface. Using a COM object assembly language ------------------------------------------------------------------------------- Access to a COM object occurs through a pointer. This pointer points to a table of function pointers in memory, called a virtual function table, or vtable in short. This vtable contains the addresses of each of the objects methods. To call a method, you indirectly call it through this pointer table. Here is an example of a C++ interface, and how its methods are called: interface IInterface { HRESULT QueryInterface( REFIID iid, void ** ppvObject ); ULONG AddRef(); ULONG Release(); Function1( INT param1, INT param2); Function2( INT param1 ); } // calling the Function1 method pObject->Function1( 0, 0); Now here is how the same functionality can be implemented using assembly language: ; defining the interface ; each of these values are offsets in the vtable QueryInterface equ 0h AddRef equ 4h Release equ 8h Function1 equ 0Ch Function2 equ 10h ; calling the Function1 method in asm ; the method is called by obtaining the address of the objects ; vtable and then calling the function addressed by the proper ; offset in the table push param2 push param1 mov eax, pObject push eax mov eax, [eax] call [eax + Function1] You can see this is somewhat different than calling a function normally. Here, pObject points to the Interface's vTable. At the Function1(0Ch) offset in this table is a pointer to the actual function we wish to call. Using HRESULT's ------------------------------------------------------------------------------- The return value of OLE APIs and methods is an HRESULT. This is not a handle to anything, but is merely a 32-bit value with several fields encoded in the value. The parts of an HRESULT are shown below. HRESULTs are 32 bit values layed out as follows: 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +-+-+-+-+-+---------------------+-------------------------------+ |S|R|C|N|r| Facility | Code | +-+-+-+-+-+---------------------+-------------------------------+ S - Severity Bit Used to indicate success or failure 0 - Success 1 - Fail By noting that this bit is actually the sign bit of the 32-bit value, checking success/failure is simply performed by checking its sign: call ComFunction ; call the function test eax,eax ; now check its return value js error ; jump if signed (meaning error returned) ; success, so continue R - reserved portion of the facility code, corresponds to NT's second severity bit. C - reserved portion of the facility code, corresponds to NT's C field. N - reserved portion of the facility code. Used to indicate a mapped NT status value. r - reserved portion of the facility code. Reserved for internal use. Used to indicate HRESULT values that are not status values, but are instead message ids for display strings. Facility - is the facility code FACILITY_WINDOWS = 8 FACILITY_STORAGE = 3 FACILITY_RPC = 1 FACILITY_WIN32 = 7 FACILITY_CONTROL = 10 FACILITY_NULL = 0 FACILITY_ITF = 4 FACILITY_DISPATCH = 2 To retreive the Facility, call ComFunction ; call the function shr eax, 16 ; shift the HRESULT to the right by 16 bits and eax, 1FFFh ; mask the bits, so only the facility remains ; eax now contains the HRESULT's Facility code Code - is the facility's status code To get the Facility's status code, call ComFunction ; call the function and eax, 0000FFFFh ; mask out the upper 16 bits ; eax now contains the HRESULT's Facility's status code Using COM with MASM ------------------------------------------------------------------------------ If you use MASM to assemble your programs, you can use some of its capabilities to make calling COM functions very easy. Using invoke, you can make COM calls look almost as clean as regular calls, plus you can add type checking to each function. Defining the interface: IInterface_Function1Proto typedef proto :DWORD IInterface_Function2Proto typedef proto :DWORD, :DWORD IInterface_Function1 typedef ptr IInterface_Function1Proto IInterface_Function2 typedef ptr IInterface_Function2Proto IInterface struct DWORD QueryInterface IUnknown_QueryInterface ? AddRef IUnknown_AddRef ? Release IUnknown_Release ? Function1 IInterface_Function1 ? Function2 Interface_Function2 ? IInterface ends Using the interface to call COM functions: mov eax, pObject mov eax, [eax] invoke (IInterface [eax]).Function1, 0, 0 As you can see, the syntax may seem a bit strange, but it allows for a simple method using the function name itself instead of offsets, as well as type checking. A Sample program written using COM ------------------------------------------------------------------------------ Here is some sample source code which uses COM written in straight assembly language, so it should be compatable with any assembler you prefer with only minor changes necessary. This program uses the Windows Shell Interfaces to show the contents of the Desktop folder in a window. The program is not complete, but shows how the COM library is initialized, de-initialized, and used. I also shows how the shell library is used to get folders and obcets, and how to perform actions on them. .386 .model flat, stdcall include windows.inc ; include the standard windows header include shlobj.inc ; this include file contains the shell namespace ; definitions and constants ;---------------------------------------------------------- .data wMsg MSG g_hInstance dd ? g_pShellMalloc dd ? pshf dd ? ; shell folder object peidl dd ? ; enum id list object lvi LV_ITEM iCount dd ? strret STRRET shfi SHFILEINFO ... ;---------------------------------------------------------- .code ; Entry Point start: push 0h call GetModuleHandle mov g_hInstance,eax call InitCommonControls ; initialize the Component Object Model(COM) library ; this function must be called before any COM functions are called push 0 call CoInitialize test eax,eax ; error when the MSB = 1 ; (MSB = the sign bit) js exit ; js = jump if signed ; Get the Shells IMalloc object pointer, and save it to a global variable push offset g_pShellMalloc call SHGetMalloc cmp eax, E_FAIL jz shutdown ; here we would set up the windows, list view, message loop, and so on.... ; we would also call the FillListView procedure... ; .... ; Cleanup ; Release IMalloc Object pointer mov eax, g_pShellMalloc push eax mov eax, [eax] call [eax + Release] ; g_pShellMalloc->Release(); shutdown: ; close the COM library call CoUninitialize exit: push wMsg.wParam call ExitProcess ; Program Terminates Here ;---------------------------------------------------------- FillListView proc ; get the desktop shell folder, saved to pshf push offset pshf call SHGetDesktopFolder ; get the objects of the desktop folder using the EnumObjects method of ; the desktop's shell folder object push offset peidl push SHCONTF_NONFOLDERS push 0 mov eax, pshf push eax mov eax, [eax] call [eax + EnumObjects] xor ebx, ebx ;use ebx for the counter ; now loop through the enum id list idlist_loop: ; Get next id list item push 0 push offset pidl push 1 mov eax, peidl push eax mov eax, [eax] call [eax + Next] test eax,eax jnz idlist_endloop mov lvi.imask, LVIF_TEXT or LVIF_IMAGE mov lvi.iItem, ebx ; Get the item's name by using the GetDisplayNameOf method push offset strret push SHGDN_NORMAL push offset pidl mov eax, pshf push eax mov eax, [eax] call [eax + GetDisplayNameOf] ; GetDisplayNameOf returns the name in 1 of 3 forms, so get the correct ; form and act accordingly cmp strret.uType, STRRET_CSTR je strret_cstr cmp strret.uType, STRRET_OFFSET je strret_offset strret_olestr: ; here you could use WideCharToMultiByte to get the string, ; I have left it out because I am lazy jmp strret_end strret_cstr: lea eax, strret.cStr jmp strret_end strret_offset: mov eax, pidl add eax, strret.uOffset strret_end: mov lvi.pszText, eax ; Get the items icon push SHGFI_PIDL or SHGFI_SYSICONINDEX or SHGFI_SMALLICON or SHGFI_ICON push sizeof SHFILEINFO push offset shfi push 0 push pidl call SHGetFileInfo mov eax, shfi.iIcon mov lvi.iImage, eax ; now add item to the list push offset lvi push 0 push LVM_INSERTITEM push hWndListView call SendMessage ; increment counter ebx and repeat the loop inc ebx, ebx jmp idlist_loop idlist_endloop: ; now free the enum id list ; Remember all allocated objects must be released... mov eax, peidl push eax mov eax,[eax] call [eax + Release] ; free the desktop shell folder object mov eax, pshf push eax mov eax,[eax] call [eax + Release] ret FillListView endp END start Conclusiom ------------------------------------------------------------------------------- Well, that is about it for using COM with assembly language. Hopefully, my next article will go into how to define your own interfaces. As you can see, using COM is not difficult at all, and with it you can add a very powerful capability to your assembly language programs. ----------------------------------------------------------------------------- Copyright (C) 1998 Bill T. (billasm@usa.net)