IT-Consultant: Frederick J. Harris > Fred's COM (Component Object Model) Tutorials

COM Tutorial #4: Using Command Line Compiling To Build Dual Interface Component

(1/1)

Frederick J. Harris:
In this tutorial we’ll use C++ to build a dual interface COM component which can be used to calculate the board foot volume of a sawtimber tree as might be found in one of the forests of the Eastern United States.  As such, it’s a pretty simple COM component that essentially just wraps a volume equation.  The volume equation is rather complex however, and the function which wraps it will need to pass in a species code, the diameter of the tree in inches, the sawtimber height of the tree in numbers of sixteen foot logs, and finally the Form Class of the tree, which is a relative measure of the tree’s taper.  Common numbers for this latter parameter are in the vicinity of 77 to 84.  For our example we’ll use 78.  Here is a C++ implementation of the function returning an unsigned int…

unsigned int BFVolume(short sp, double dbh, double ht, short form_class, double* vol)
{
*vol=
(
(1.52968*pow(ht,2.0)+9.58615*ht-13.35212)+
(1.79620-0.27465*pow(ht,2.0)-2.59995*ht)*dbh+
(0.04482-0.00961*pow(ht,2.0)+0.45997*ht)*pow(dbh,2.0))*((form_class-78)*0.03+1
);

return S_OK;
}

As mentioned, its rather complex, but here we’re just interested in creating a dual interface COM Dll which wraps it, so lets just treat it as a ‘black box’ from this point on.  I could have just used the volume of a box, but I thought this would be less trivial!

Once we’ve created and registered the Dll we’ll use both PowerBASIC and C++ to connect to the object through late binding, early binding, and through its direct Vtable interface.

I’m going to use Microsoft Visual C++ 6 for the compilation here and I’m only going to do command line compiling with no IDEs.  The example should work with Microsoft’s newest compilers though such as VC9 and VC10.   If I recall correctly, my registry code might need to be tweaked a bit with these later Microsoft compilers which complain somewhat about security issues with some of my string functions.  If time permits I’ll see if I can get it working using GNU g++.  I’ve tested this code on Win 2000 sp4 and Win XP sp3 laptops.

For those who just want to study the code and not compile it I’ll attach release and debug builds of the component.

To start a command line compiling project I always create a directory for my project and put a batch file in it to open up a command prompt window for myself.  I named this project ForUtils – short for ‘ForestryUtilities’.  I made this directory…

C:\Code\VStudio\VC++6\ForUtils

Into that directory I put this batch file and named it ForUtils.bat…

--- Code: ---CD\
cd C:\Code\VStudio\VC++6\ForUtils
cmd

--- End code ---

My PATH environment variable contains the paths to the necessary compilation binaries in the C/C++ compiler tool chain of VC++6, so when I issue commands to the compiler, the system will find what it needs.

The first step in making a component with C++ is to create an idl file (Interface Definition Language) for the midl.exe compiler.  This file lists the COM components, interfaces, and type library information for the component.  Here is ForUtils.idl…

--- Code: ---//ForUtils.idl
import "unknwn.idl";

[object,uuid(20000006-0000-0000-0000-000000000002),dual]
interface IBFVolume : IDispatch
{
[id(7)] HRESULT BFVolume([in] short int sp, [in] double dbh, [in] double ht, [in] short int form_class, [out, retval] double* vol);
};

[uuid(20000006-0000-0000-0000-000000000001),helpstring("Forestry Utilities Library 1"),version(1.1)]
library ForUtilsLib
{
importlib("stdole32.tlb");
interface IBFVolume;

[uuid(20000006-0000-0000-0000-000000000000)]
coclass ForUtils
{
interface IBFVolume;
};
};

--- End code ---

You can see our BFVolume method above which has been assigned a dispatch id of 7.  The last parameter is described in idl notation as [out, retval] double* vol.  That means that the result of the function call will be returned to the caller through the last parameter, which is a pointer parameter passed in from the client.  Using our batch file to run the midl compiler on this file, followed immediately by a ‘dir’ directory listing of the ForUtils directory will reveal this output…

--- Code: ---C:\Code\VStudio\VC++6\ForUtils>midl ForUtils.idl
Microsoft (R) MIDL Compiler Version 5.01.0164
Processing .\ForUtils.idl
ForUtils.idl
Processing C:\Program Files\Microsoft Visual Studio\VC98\include\unknwn.idl
unknwn.idl
Processing C:\Program Files\Microsoft Visual Studio\VC98\include\wtypes.idl
wtypes.idl
Processing C:\Program Files\Microsoft Visual Studio\VC98\include\oaidl.idl
oaidl.idl
Processing C:\Program Files\Microsoft Visual Studio\VC98\include\objidl.idl
objidl.idl

C:\Code\VStudio\VC++6\ForUtils>dir
Volume in drive C is Main
Volume Serial Number is 0C18-9CF6

Directory of C:\Code\VStudio\VC++6\ForUtils

02/22/2011  03:22 PM    <DIR>          .
02/22/2011  03:22 PM    <DIR>          ..
02/22/2011  03:22 PM               837 dlldata.c
02/20/2011  01:07 PM                43 ForUtils.bat
02/22/2011  03:22 PM             6,722 ForUtils.h
02/22/2011  03:22 PM               559 ForUtils.idl
02/20/2011  01:05 PM                44 ForUtils.rc
02/22/2011  03:22 PM             1,752 ForUtils.tlb
02/22/2011  03:22 PM             1,115 ForUtils_i.c
02/22/2011  03:22 PM             9,529 ForUtils_p.c
8 File(s)         20,601 bytes
2 Dir(s)  36,314,562,560 bytes free

C:\Code\VStudio\VC++6\ForUtils>

--- End code ---

The top line above shows the midl call on ForUtils.idl.  About midway down you see the >dir command which lists the files now in my project directory, and the most important file there for me is ForUtils.tlb which is my type library.  This is a binary file containing type information.

We’re going to want to compile this type library file into our COM Dll and for that we’ll want to use the resource compiler a little bit.  You can see a file above named ForUtils.rc.  Here is the contents of that resource script file…

--- Code: ---// ForUtils.rc
1 TYPELIB "ForUtils.tlb"

--- End code ---

Our next step is to use the various resource compilers to convert this to an *.obj file for eventual linking into our COM Dll.  There are two steps.  First rc.exe is used to create *.res file; then cvtres.exe is used to convert the *.res file to a *.obj file for linking.  A little wrinkle I’m throwing in here is that while my input file to the resource compiler rc.exe is going to be ForUtils.rc, I’m going to ask the compiler to output the *.res file with a different file name other than ForUtils.res.  This is because my C++ source code file is named ForUtils.cpp, and compiling that will create ForUtils.obj, which would overwrite my ForUtils.obj created from the resource file compilation stage.  This renaming can be done through command line parameters of the resource compilers.  To see all the compilation parameters just invoke the compiler with no file name but just a ‘/h’ for help, i.e.,

--- Code: ---C:\Code\VStudio\VC++6\ForUtils>rc.exe /h

Microsoft (R) Windows 32 Resource Compiler, Version 4.00 - Build 1367

Usage:  rc [options] .RC input file
Switches:
/r    Emit .RES file (optional)
/v    Verbose (print progress messages)
/d    Define a symbol
/u    Undefine a symbol
/fo   Rename .RES file
/l    Default language ID in hex
/i    Add a path for INCLUDE searches
/x    Ignore INCLUDE environment variable
/c    Define a code page used by NLS conversion
Flags may be either upper or lower case

C:\Code\VStudio\VC++6\ForUtils>

--- End code ---

You can see the /fo switch – think ‘file out’ above; which we’ll use.  Here’s our invocation with rc.exe…

--- Code: ---C:\Code\VStudio\VC++6\ForUtils>rc.exe /v /foForUtilsRes.res ForUtils.rc
Microsoft (R) Windows 32 Resource Compiler, Version 4.00 - Build 1367

Using codepage 1252 as default
Creating ForUtilsRes.res
RC: RCPP
-CP 1252
-f C:\Code\VStudio\VC++6\ForUtils\RCa03308
-g C:\Code\VStudio\VC++6\ForUtils\RDa03308
-DRC_INVOKED
-D_WIN32
-pc\:/
-E
-I.
-I .
-I C:\Program Files\Microsoft Visual Studio\VC98\atl\include
-I C:\Program Files\Microsoft Visual Studio\VC98\mfc\include
-I C:\Program Files\Microsoft Visual Studio\VC98\include

FORUTILS.RC.
Writing TYPELIB:1,      lang:0x409,     size 1752

C:\Code\VStudio\VC++6\ForUtils>dir
Volume in drive C is Main
Volume Serial Number is 0C18-9CF6

Directory of C:\Code\VStudio\VC++6\ForUtils

02/22/2011  03:50 PM    <DIR>          .
02/22/2011  03:50 PM    <DIR>          ..
02/22/2011  03:22 PM               837 dlldata.c
02/20/2011  01:07 PM                43 ForUtils.bat
02/22/2011  03:22 PM             6,722 ForUtils.h
02/22/2011  03:22 PM               559 ForUtils.idl
02/20/2011  01:05 PM                44 ForUtils.rc
02/22/2011  03:22 PM             1,752 ForUtils.tlb
02/22/2011  03:50 PM             1,828 ForUtilsRes.res
02/22/2011  03:22 PM             1,115 ForUtils_i.c
02/22/2011  03:22 PM             9,529 ForUtils_p.c
9 File(s)         22,429 bytes
2 Dir(s)  36,314,521,600 bytes free

C:\Code\VStudio\VC++6\ForUtils>

--- End code ---

In my dir listing after the compilation you see we now have a ForUtilsRes.res.  We now need to use cvtres.exe on this to create our *.obj file.  First, here are the command line switches for cvtres.exe…

--- Code: ---C:\Code\VStudio\VC++6\ForUtils>cvtres /h
Microsoft (R) Windows Resource To Object Converter Version 5.00.1736.1

usage: CVTRES [options] ResFile

options:

/MACHINE:{IX86|ALPHA|ARM|AXP64|IA64|MIPS|MIPS16|MIPSR41XX|PPC|SH3|SH4}
/NOLOGO
/OUT:filename
/VERBOSE
/WINDOWSCE[:{CONVERT|EMULATION}]

C:\Code\VStudio\VC++6\ForUtils>

--- End code ---

And here’s our compilation…

--- Code: ---C:\Code\VStudio\VC++6\ForUtils>cvtres.exe /MACHINE:IX86 /v ForUtilsRes.res
Microsoft (R) Windows Resource To Object Converter Version 5.00.1736.1

adding resource -- type:TYPELIB, name:1, language:409, flags:30, size:1752

C:\Code\VStudio\VC++6\ForUtils>dir
Volume in drive C is Main
Volume Serial Number is 0C18-9CF6

Directory of C:\Code\VStudio\VC++6\ForUtils

02/22/2011  03:58 PM    <DIR>          .
02/22/2011  03:58 PM    <DIR>          ..
02/22/2011  03:22 PM               837 dlldata.c
02/20/2011  01:07 PM                43 ForUtils.bat
02/22/2011  03:22 PM             6,722 ForUtils.h
02/22/2011  03:22 PM               559 ForUtils.idl
02/20/2011  01:05 PM                44 ForUtils.rc
02/22/2011  03:22 PM             1,752 ForUtils.tlb
02/22/2011  03:58 PM             2,220 ForUtilsRes.obj
02/22/2011  03:50 PM             1,828 ForUtilsRes.res
02/22/2011  03:22 PM             1,115 ForUtils_i.c
02/22/2011  03:22 PM             9,529 ForUtils_p.c
10 File(s)         24,649 bytes
2 Dir(s)  36,314,480,640 bytes free

C:\Code\VStudio\VC++6\ForUtils>

--- End code ---

The only file added to the above list would have been ForUtilsRes.obj, which we’ll need in a bit.  At this point I’d like to encourage you to take a quick look (or longer if you want) at the midl auto-generated file ForUtils.h.  This header file contains quite a bit of ‘gunk’ we don’t really need here, but amid all the gunk is something we do need, which in simplified form looks like this…

--- Code: ---#include "objbase.h"

interface IBFVolume : public IDispatch
{
virtual HRESULT __stdcall BFVolume(short sp, double dbh, double ht, short form_class, double* vol) = 0;
};

--- End code ---

Get rid of all that gunk in ForUtils.h when you are done looking at it, and just replace it with the above six lines.  I’m something of a ‘minimalist’, you know!  That above is a C++ definition of the IBFVolume interface.  In C++ the interface keyword is just a redefinition of the struct keyword, which is about the same thing as a PowerBASIC Type.  The important point about the above code is that this struct/interface contains a method named BFVolume(), and that it inherits from something named IDispatch, which in itself is a rather complicated interface which contains the three methods from IUnknown, and four of its own appended to that.  So in total it has seven methods; the three from IUnknown, and four of its own.

The next step in our journey to create a dual interface COM Dll out of this is to write the C++ code to do it.  That is involved, because implementing the four methods of Idispatch isn’t all that trieval.  Here is the C++ code for ForUtils.cpp which will make our dual interface Dll…

--- Code: ---//cl ForUtils.cpp registry.c UUID.lib Advapi32.lib Ole32.lib oleaut32.lib ForUtilsRes.obj ForUtils.def /LD
#include       <stdio.h>
#include       <math.h>
#include       "ForUtils.h"
#include       "registry.h"
const CLSID    CLSID_ForUtils      = {0x20000006,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}};
const IID      LIBID_ForUtils      = {0x20000006,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01}};
const IID      IID_IBFVolume       = {0x20000006,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02}};
const char     g_szFriendlyName[]  = "Forestry Utilities";  //Store friendly name of component
const char     g_szVerIndProgID[]  = "ForUtils.Volumes";    //Store Version Independent ProgID
const char     g_szProgID[]        = "ForUtils.Volumes.1";  //Store Versioned Program ID.
static HMODULE g_hModule           = NULL ;                 // DLL module handle
long           g_cComponents       = 0;
long           g_cServerLocks      = 0;

class ForUtils : public IBFVolume
{
public:
ForUtils() : m_cRef(1) { g_cComponents++; }  //Inline Constructor with Initialization List For class ForUtils
~ForUtils() { }                              //Destructor for class ForUtils
bool Init(void);                             //Forward declaration of Init() member of class

//IUnknown
ULONG   __stdcall Release();
HRESULT __stdcall QueryInterface(REFIID riid, void** ppv);

//IDispatch
HRESULT __stdcall GetTypeInfoCount(UINT* pCountTypeInfo);
HRESULT __stdcall GetTypeInfo(UINT iTypeInfo, LCID lcid, ITypeInfo** ppITypeInfo);
HRESULT __stdcall GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId);
HRESULT __stdcall Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr);

//IBFVolume
HRESULT __stdcall BFVolume(short sp, double dbh, double ht, short form_class, double* vol);

private:
ULONG      m_cRef;
ITypeInfo* m_pTypeInfo;
};

bool ForUtils::Init(void)
{
ITypeLib* pTypeLib;

return false;
HRESULT hr = pTypeLib->GetTypeInfoOfGuid(IID_IBFVolume, &m_pTypeInfo);
pTypeLib->Release();
if(FAILED(hr))
return false;

return true;
}

HRESULT ForUtils::GetTypeInfoCount(UINT* pCountTypeInfo)
{
*pCountTypeInfo = 1;
return S_OK;
}

HRESULT ForUtils::GetTypeInfo(UINT iTypeInfo, LCID lcid, ITypeInfo** ppITypeInfo)
{
*ppITypeInfo = NULL;
if(iTypeInfo != 0)
*ppITypeInfo = m_pTypeInfo;

return S_OK;
}

HRESULT ForUtils::GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId)
{
if(riid != IID_NULL)
return DISP_E_UNKNOWNINTERFACE;

return DispGetIDsOfNames(m_pTypeInfo, rgszNames, cNames, rgDispId);
}

HRESULT ForUtils::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr)
{
if(riid != IID_NULL)
return DISP_E_UNKNOWNINTERFACE;

return DispInvoke(this, m_pTypeInfo, dispIdMember, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
}

{
return ++m_cRef;
}

ULONG ForUtils::Release()
{
if(--m_cRef != 0)
return m_cRef;
delete this;

return 0;
}

HRESULT ForUtils::QueryInterface(REFIID riid, void** ppv)
{
if(riid == IID_IUnknown)
*ppv = (IUnknown*)this;
else if(riid == IID_IBFVolume)
*ppv = (IBFVolume*)this;
else if(riid == IID_IDispatch)
*ppv = (IDispatch*)this;
else
{
*ppv = NULL;
return E_NOINTERFACE;
}

return S_OK;
}

HRESULT ForUtils::BFVolume(short sp, double dbh, double ht, short form_class, double* vol)
{
*vol=
(
(1.52968*pow(ht,2.0)+9.58615*ht-13.35212)+
(1.79620-0.27465*pow(ht,2.0)-2.59995*ht)*dbh+
(0.04482-0.00961*pow(ht,2.0)+0.45997*ht)*pow(dbh,2.0))*((form_class-78)*0.03+1
);

return S_OK;
}

class CFactory : public IClassFactory
{
public:
CFactory() : m_cRef(1) { }
~CFactory() { }

HRESULT __stdcall QueryInterface(REFIID riid, void** ppv);
ULONG __stdcall Release();
HRESULT __stdcall CreateInstance(IUnknown *pUnknownOuter, REFIID riid, void** ppv);
HRESULT __stdcall LockServer(BOOL bLock);

private:
ULONG m_cRef;
};

{
return ++m_cRef;
}

ULONG CFactory::Release()
{
if(--m_cRef!=0)
return m_cRef;
delete this;

return 0;
}

HRESULT CFactory::QueryInterface(REFIID riid, void** ppv)
{
if(riid == IID_IUnknown || riid == IID_IClassFactory)
*ppv = (IClassFactory *)this;
else
{
*ppv = NULL;
return E_NOINTERFACE;
}

return S_OK;
}

HRESULT CFactory::CreateInstance(IUnknown *pUnknownOuter, REFIID riid, void** ppv)
{
if(pUnknownOuter != NULL)
return CLASS_E_NOAGGREGATION;
ForUtils* pForUtils = new ForUtils;
if(pForUtils == NULL)
return E_OUTOFMEMORY;
pForUtils->Init();
HRESULT hr = pForUtils->QueryInterface(riid, ppv);
pForUtils->Release();

return hr;
}

HRESULT CFactory::LockServer(BOOL bLock)
{
if(bLock)
g_cServerLocks++;
else
g_cServerLocks--;

return S_OK;
}

{
if(g_cServerLocks == 0 && g_cComponents == 0)
return S_OK;
else
return S_FALSE;
}

HRESULT __stdcall DllGetClassObject(REFCLSID clsid, REFIID riid, void** ppv)
{
if(clsid != CLSID_ForUtils)
return CLASS_E_CLASSNOTAVAILABLE;
CFactory* pFactory = new CFactory;
if(pFactory == NULL)
return E_OUTOFMEMORY;
HRESULT hr = pFactory->QueryInterface(riid, ppv);
pFactory->Release();

return hr;
}

HRESULT __stdcall DllRegisterServer()
{
ITypeLib* pTypeLib=NULL;

if(SUCCEEDED(hr))
{
pTypeLib->Release();
hr=RegisterServer(g_hModule, &CLSID_ForUtils, g_szFriendlyName, g_szVerIndProgID, g_szProgID);
if(SUCCEEDED(hr))
return hr;
}

return hr;
}

HRESULT __stdcall DllUnregisterServer()
{
HRESULT hr=UnRegisterTypeLib(LIBID_ForUtils, 1, 1, LANG_NEUTRAL, SYS_WIN32);
if(FAILED(hr))
return hr;

return UnregisterServer(&CLSID_ForUtils,"ForUtils.Volumes","ForUtils.Volumes.1");
}

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)
{
if(dwReason==DLL_PROCESS_ATTACH)
g_hModule=(HINSTANCE)hModule ;

return TRUE;
}

--- End code ---

At the top of the listing is the command line compilation string for the Microsoft compiler, which starts with ‘cl’ for compile – link.  Under that you can see several necessary globals such as the program id which in this case is ‘ForUtils.Volumes.1’.  The GUIDs are also listed there.  The program also needs registry code for registering the component, and you can see that include plus a listing for it in the compilation string.  Here are those files which you’ll need…

--- Code: ---//Registry.h
extern "C" HRESULT RegisterServer(HMODULE hModule, const CLSID* clsid, const char* szFriendlyName, const char* szVerIndProgID, const char* szProgID);
extern "C" HRESULT UnregisterServer(const CLSID* clsid, const char* szVerIndProgID, const char* szProgID);

--- End code ---

--- Code: ---//Registry.c  If compiling with C++ 9 you’ll need to modify this a bit.
#include <objbase.h>
#define CLSID_STRING_SIZE  39

BOOL SetKeyAndValue(const char* szKey, const char* szSubkey, const char* szValue)
{
char szKeyBuf[1024];
long lResult;
HKEY hKey;

strcpy(szKeyBuf,szKey);   //Copy keyname into buffer.
if(szSubkey!=NULL)        // Add subkey name to buffer.
{
strcat(szKeyBuf, "\\") ;
strcat(szKeyBuf, szSubkey ) ;
}
//Create and open key and subkey.
lResult=RegCreateKeyEx(HKEY_CLASSES_ROOT,szKeyBuf,0,NULL,REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&hKey,NULL);
if(lResult!=ERROR_SUCCESS)
return FALSE;
if(szValue!=NULL)         //Set the Value.
RegSetValueEx(hKey,NULL,0,REG_SZ,(BYTE*)szValue,strlen(szValue)+1);
RegCloseKey(hKey);

return TRUE ;
}

void CLSIDtochar(const CLSID* clsid, char* szCLSID, int length)    // Convert a CLSID to a char string.
{
LPOLESTR wszCLSID=NULL;
HRESULT hr;

hr=StringFromCLSID(clsid,&wszCLSID);    // Get CLSID
if(SUCCEEDED(hr))
{
wcstombs(szCLSID, wszCLSID,length);  // Covert from wide characters to non-wide.
}
}

LONG RecursiveDeleteKey(HKEY hKeyParent, const char* lpszKeyChild)       // Key to delete
{
char szBuffer[256];
DWORD dwSize=256 ;
HKEY hKeyChild;
FILETIME time;
LONG lRes;

lRes=RegOpenKeyEx(hKeyParent,lpszKeyChild,0,KEY_ALL_ACCESS,&hKeyChild); //Open the child.
if(lRes!=ERROR_SUCCESS)
return lRes;
while(RegEnumKeyEx(hKeyChild,0,szBuffer,&dwSize,NULL,NULL,NULL,&time)==S_OK) //Enumerate all of the decendents of this child.
{
lRes=RecursiveDeleteKey(hKeyChild,szBuffer);  //Delete the decendents of this child.
if(lRes!=ERROR_SUCCESS)
{
RegCloseKey(hKeyChild);  //Cleanup before exiting.
return lRes;
}
dwSize=256;
}
RegCloseKey(hKeyChild);      // Close the child.

return RegDeleteKey(hKeyParent,lpszKeyChild);  //Delete this child.
}

HRESULT RegisterServer(HMODULE hModule, const CLSID* clsid, const char* szFriendlyName, const char* szVerIndProgID, const char* szProgID)
{
char szCLSID[CLSID_STRING_SIZE];
char szModule[512];
char szKey[64];

if(GetModuleFileName(hModule,szModule,sizeof(szModule)/sizeof(char)))
{
CLSIDtochar(clsid, szCLSID,sizeof(szCLSID));                     //Get server location &Convert the CLSID into a char.
strcpy(szKey, "CLSID\\");                                        //Build the key CLSID\\{...}
strcat(szKey,szCLSID);
SetKeyAndValue(szKey,NULL,szFriendlyName);                       //Add the CLSID to the registry.
SetKeyAndValue(szKey, "InprocServer32", szModule);               //Add the server filename subkey under the CLSID key.
SetKeyAndValue(szKey, "ProgID", szProgID);                       //Add the ProgID subkey under the CLSID key.
SetKeyAndValue(szKey,"VersionIndependentProgID",szVerIndProgID); //Add the version-independent ProgID subkey under CLSID key.
SetKeyAndValue(szVerIndProgID, NULL, szFriendlyName);            //Add the version-independent ProgID subkey under HKEY_CLASSES_ROOT.
SetKeyAndValue(szVerIndProgID, "CLSID", szCLSID);
SetKeyAndValue(szVerIndProgID, "CurVer", szProgID);
SetKeyAndValue(szProgID, NULL, szFriendlyName) ;                 //Add the versioned ProgID subkey under HKEY_CLASSES_ROOT.
SetKeyAndValue(szProgID, "CLSID", szCLSID) ;
}
else
return E_FAIL;

return S_OK ;
}

HRESULT UnregisterServer(const CLSID* clsid, const char* szVerIndProgID, const char* szProgID)
{
char szCLSID[CLSID_STRING_SIZE];
char szKey[64];
LONG lResult;

CLSIDtochar(clsid, szCLSID, sizeof(szCLSID));                     //Convert the CLSID into a char.
strcpy(szKey, "CLSID\\");                                         //Build the key CLSID\\{...}
strcat(szKey, szCLSID) ;
lResult=RecursiveDeleteKey(HKEY_CLASSES_ROOT, szKey);             //Delete the CLSID Key - CLSID\{...}
lResult=RecursiveDeleteKey(HKEY_CLASSES_ROOT, szVerIndProgID);    //Delete the version-independent ProgID Key.
lResult=RecursiveDeleteKey(HKEY_CLASSES_ROOT, szProgID) ;         //Delete the ProgID key.

return S_OK ;
}

--- End code ---

Here is another little but necessary file.  It lists the exports for the Dll, which includes DllGetClassObject() and the files to register and unregister the component…

--- Code: ---; ForUtils.def
LIBRARY              "ForUtils.dll"
DESCRIPTION          '(c)2007 Fred Harris'

EXPORTS
DllGetClassObject    PRIVATE
DllRegisterServer    PRIVATE
DllUnregisterServer  PRIVATE

--- End code ---

Finally, using our commamd line compilation string from at top produces (hopefully) this…

--- Code: ---C:\Code\VStudio\VC++6\ForUtils>cl ForUtils.cpp registry.c UUID.lib Advapi32.lib Ole32.lib oleaut32.lib ForUtilsRes.obj ForUtils.def /LD
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86

ForUtils.cpp
Generating Code...
Compiling...
registry.c
Generating Code...
Microsoft (R) Incremental Linker Version 6.00.8447

/out:ForUtils.dll
/dll
/implib:ForUtils.lib
/def:ForUtils.def
ForUtils.obj
registry.obj
UUID.lib
Ole32.lib
oleaut32.lib
ForUtilsRes.obj
Creating library ForUtils.lib and object ForUtils.exp

C:\Code\VStudio\VC++6\ForUtils>

--- End code ---

Success!  Here is a directory listing now…

--- Code: ---C:\Code\VStudio\VC++6\ForUtils>dir
Volume in drive C is Main
Volume Serial Number is 0C18-9CF6

Directory of C:\Code\VStudio\VC++6\ForUtils

02/22/2011  04:42 PM    <DIR>          .
02/22/2011  04:42 PM    <DIR>          ..
02/20/2011  01:07 PM                43 ForUtils.bat
02/22/2011  02:30 PM             6,717 ForUtils.cpp
02/21/2011  12:23 PM               226 ForUtils.def
02/22/2011  04:42 PM            69,632 ForUtils.dll
02/22/2011  04:42 PM             1,022 ForUtils.exp
02/21/2011  10:26 AM               177 ForUtils.h
02/22/2011  03:22 PM               559 ForUtils.idl
02/22/2011  04:42 PM             1,768 ForUtils.lib
02/22/2011  04:42 PM             8,852 ForUtils.obj
02/20/2011  01:05 PM                44 ForUtils.rc
02/22/2011  03:22 PM             1,752 ForUtils.tlb
02/22/2011  03:58 PM             2,220 ForUtilsRes.obj
02/22/2011  03:50 PM             1,828 ForUtilsRes.res
02/21/2011  11:09 AM             4,243 Registry.c
02/21/2011  11:07 AM               259 Registry.h
02/22/2011  04:42 PM             2,652 registry.obj
16 File(s)        101,994 bytes
2 Dir(s)  36,314,034,176 bytes free

C:\Code\VStudio\VC++6\ForUtils>

--- End code ---

As you can see above, we now have our ForUtils.dll.  Let’s register it with RegSvr32.exe.  Here is what the command line would look like…

C:\Code\VStudio\VC++6\ForUtils>Regsvr32.exe C:\Code\VStudio\VC++6\ForUtils\ForUtils.dll

RegSvr32 is a GUI rather than a command line program so I then got a message box something to the effect that…

“DllRegisterServer() in C:\Code\VStudio\VC++6\ForUtils\ForUtils.dll Succeeded!”

At this point I can try various C/C++ or PowerBASIC clients.  Lets start with the easiest C++ client possible which simply exercises Vtable direct access of the dual interface.  Here is Client1.cpp with the command line compilation string at top…

--- Code: ---#include    <stdio.h>        // cl Client1.cpp UUID.lib Advapi32.lib Ole32.lib oleaut32.lib
#include    <objbase.h>
const CLSID CLSID_ForUtils = {0x20000006,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}};
const IID   IID_IBFVolume  = {0x20000006,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02}};

interface IBFVolume : public IDispatch
{
virtual HRESULT __stdcall BFVolume(short sp, double dbh, double ht, short form_class, double* vol) = 0;
};

int main(void)
{
IBFVolume* pVol=NULL;
double dblVolume=0;

OleInitialize(NULL);
CoCreateInstance(CLSID_ForUtils,NULL,CLSCTX_INPROC_SERVER,IID_IBFVolume,(void**)&pVol);
pVol->BFVolume(30,16.0,2.0,78,&dblVolume);
printf("dblVolume = %4.2f\n",dblVolume);
pVol->Release();
OleUninitialize();
getchar();

return 0;
}

--- End code ---

Here is my console screen after compiling/linking and a test run…

--- Code: ---C:\Code\VStudio\VC++6\ForUtils>cl Client1.cpp UUID.lib Advapi32.lib Ole32.lib oleaut32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86

Client1.cpp
Microsoft (R) Incremental Linker Version 6.00.8447

/out:Client1.exe
Client1.obj
UUID.lib
Ole32.lib
oleaut32.lib

C:\Code\VStudio\VC++6\ForUtils>Client1.exe
dblVolume = 177.04

C:\Code\VStudio\VC++6\ForUtils>

--- End code ---

Next let’s try using IDispatch…

--- Code: ---#include <stdio.h>       //cl Client2.cpp UUID.lib Advapi32.lib Ole32.lib oleaut32.lib
#include <objbase.h>

int main(void)
{
IDispatch* pIDispatch=NULL;
OLECHAR* name=L"BFVolume";
DISPID dispid;
CLSID clsid;
HRESULT hr;

hr=OleInitialize(NULL);
wchar_t progid[]=L"ForUtils.Volumes";
CLSIDFromProgID(progid,&clsid);
hr=CoCreateInstance(clsid,NULL,CLSCTX_INPROC_SERVER,IID_IDispatch,(void**)&pIDispatch);
if(SUCCEEDED(hr))
{
pIDispatch->GetIDsOfNames  //We 1st need to get the dispatch ID of IBFVolume's only
(                          //member function
IID_NULL,                 //not implemented
&name,                    //pointer to L"BFVolume" of type OLECHAR* (wide char pointer)
1,                        //Only looking up one name
GetUserDefaultLCID(),     //who cares?
&dispid                   //this is your output parameter we need!
);
printf("dispid = %u\n",dispid);

//Create 4 VARIANT structs because BFVolume() has 4 parameters.  Note parameters are
//loaded in VolArgs[] last to first.  The last argument is Form Class, so that goes
VARIANTARG VolArgs[4];     //in first at VolArgs[0].

//form_class
VariantInit(&VolArgs[0]);  //All this function does is set VARIANT::vt field to VT_EMPTY
VolArgs[0].vt=VT_I2;       //The fourth parameter of BFVolume is a short int which here
VolArgs[0].iVal=78;        //we are setting to 78.  This is a relative measure of tree taper

//height                   //This is the 3rd parameter of BFVolume, i.e., height.  The 2.0
VariantInit(&VolArgs[1]);  //number represents two sixteen foot logs.
VolArgs[1].vt=VT_R8;
VolArgs[1].dblVal=2.0;

//dbh                      //This is the 2nd parameter of IBFVolume::BFVolume().  It is
VariantInit(&VolArgs[2]);  //the tree's diameter at breast height (dbh), i.e., 4.5 feet
VolArgs[2].vt=VT_R8;       //above ground.  Here it is sixteen inches.
VolArgs[2].dblVal=16.0;

//species                  //This is the 1st parameter of the IBFVolume::BFVolume() call.
VariantInit(&VolArgs[3]);  //It is a species code.  Thirty (30) is our code for Red oak.
VolArgs[3].vt=VT_I2;       //The volume equation doesn't actually use it.
VolArgs[3].iVal=30;

//DISPPARAMS VolParams={VolArgs,NULL,4,0};  //Less wordy to set them this way but maybe a
DISPPARAMS VolParams;                       //bit more mysterious!
VolParams.rgvarg            = VolArgs;
VolParams.rgdispidNamedArgs = NULL;
VolParams.cArgs             = 4;
VolParams.cNamedArgs        = 0;

VARIANT vResult;            //Need to use VARIANT for result!  Note we'll get it out of a
VariantInit(&vResult);      //VARIANT in a somewhat more awkward manner than with PowerBASIC!
pIDispatch->Invoke(dispid,IID_NULL,GetUserDefaultLCID(),DISPATCH_METHOD,&VolParams,&vResult,NULL,NULL);
printf("vResult.dblVal = %f\n",vResult.dblVal);
pIDispatch->Release();
}
OleUninitialize();
getchar();

return 0;
}

--- End code ---

Outout Screen

--- Code: ---C:\Code\VStudio\VC++6\ForUtils>cl Client2.cpp UUID.lib Advapi32.lib Ole32.lib oleaut32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86

Client2.cpp
Microsoft (R) Incremental Linker Version 6.00.8447

/out:Client2.exe
Client2.obj
UUID.lib
Ole32.lib
oleaut32.lib

C:\Code\VStudio\VC++6\ForUtils>Client2.exe
dispid = 7
vResult.dblVal = 177.040020

C:\Code\VStudio\VC++6\ForUtils>

--- End code ---

That all seems to be working.  Let’s try direct access with PowerBASIC Console Compiler 5…

--- Code: ---#Compile Exe
#Dim All

Interface IBFVolume GUID\$("{20000006-0000-0000-0000-000000000002}") : Inherit IDispatch
Method BFVolume<7>(Byval sp As Integer, Byval dbh As Double, Byval ht As Double, Byval form_class As Integer) As Double
End Interface

Function PBMain() As Long
Local dblDbh,dblHt As Double
Local pIVols As IBFVolume
Local dblReturn As Double
Local iSp,iFC As Integer

pIVols=NewCom("ForUtils.Volumes")
If IsObject(pIVols) Then
Print "pIVols Is An Object!"
iSp=30 : dblDbh=16.0 : dblHt=2.0 : iFC=78
dblReturn = pIVols.BFVolume(iSp,dblDbh,dblHt,iFC)
Print "dblReturn = " dblReturn
Set pIVols=Nothing
End If
Waitkey\$

PBMain=0
End Function

'pIVols Is An Object!
'dblReturn =  177.04002

--- End code ---

And here would be the same with PowerBASIC but this time using Idispatch…

--- Code: ---#Compile Exe "BFVol2.bas"
#Dim All

Function PBMain() As Long
Local vSp, vDbh, vHt, vFC, vRet As Variant
Local pIVol As Dispatch

pIVol=NewCom("ForUtils.Volumes")
vSp=30 : vDbh=16.0 : vHt=2.0 : vFC=78
Object Call pIVol.BFVolume(vSp,vDbh,vHt,vFC) To vRet
Print "Variant#(vRet) = " Variant#(vRet)
Waitkey\$

PBMain=0
End Function

'Variant#(vRet) =  177.04002

--- End code ---