In this post I'll present the progress I've made so far on converting my PowerBASIC ActiveX Grid Control to x64 C++. Basically, there isn't any grid yet; all I've accomplished so far is the creation of what I'd term the COM infrastructure code, plus an increase in my understanding of x64 specific issues and use of the x64 command line tool chain. I've just been doing command line compiling so far with Microsoft's x64 midl.exe, rc.exe, cvtres.exe, and cl.exe. I'm using Visual Studio 2008 Professional. By the way, I decided to use C++ instead of C because of the need for the Parse member of my String Class.
This code can be compiled to either a 32 bit dll or a 64 bit one. It can also be compiled ansi or wide character. Be careful to comment/uncomment #defines in all the necessary *.cpp files. Another #define I had to create was X64_GRID. You'll see that in Server.cpp. The issue there is that the exported function DllUnregisterServer() makes a call to UnRegisterTypeLib(), and the last parameter of that call is an enumeration with several members such as SYS_WIN32 and SYS_WIN64.
#if defined X64_GRID
hr=UnRegisterTypeLib(LIBID_FHGrid,1,0,LANG_NEUTRAL,SYS_WIN64);
#else
hr=UnRegisterTypeLib(LIBID_FHGrid,1,0,LANG_NEUTRAL,SYS_WIN32);
#endif
Also, there is a #define MYDEBUG that controls the output of voluminous file and console debug output. The files are as follows ...
Server.cpp //Some global data and the exported functions DllRegisterServer, DllUnregisterServer,
//DllGetClassObject, and DllCanUnloadNow().
Registry.h //Header for Registry functions
Registry.cpp //Registry routines
Interfaces.h //Guids and interface declarations
Grid.h //Header for grid code. Contains C++ Class headers
Grid.cpp //Actual grid code
Here is the Idl file on which everything is based. This is very similiar to what I used for my PowerBASIC FHGrid.idl file. I believe the only thing I changed is I used HWND parameters where needed instead of ints ...
// FHGrid.idl
import "unknwn.idl";
[object, uuid(30000000-0000-0000-0000-000000000001), oleautomation] interface IGrid : IUnknown
{
HRESULT CreateGrid
(
[in] HWND hParent,
[in] BSTR strSetup,
[in] int x,
[in] int y,
[in] int cx,
[in] int cy,
[in] int iRows,
[in] int iCols,
[in] int iRowHt,
[in] int iSelectionBackColor,
[in] int iSelectionTextColor,
[in] BSTR strFontName,
[in] int iFontSize,
[in] int iFontWeight
);
HRESULT SetRowCount([in] int iRowCount, [in] int blnForce);
HRESULT GetRowCount([out, retval] int* iRowCount);
HRESULT SetData([in] int iRow, [in] int iCol, [in] BSTR strData);
HRESULT GetData([in] int iRow, [in] int iCol, [out, retval] BSTR* strData);
HRESULT FlushData();
HRESULT Refresh();
HRESULT GetVisibleRows([out, retval] int* iVisibleRows);
HRESULT GethGrid([out, retval] HWND* hWnd);
HRESULT GethCell([in] int iRow, [in] int iCol, [out, retval] HWND* hCell);
HRESULT GethComboBox([in] int iCol, [out, retval] HWND* hCombo);
HRESULT SetCellAttributes([in] int iRow, [in] int iCol, [in] int iBackColor, [in] int iTextColor);
HRESULT DeleteRow([in] int iRow);
};
[object, uuid(30000000-0000-0000-0000-000000000002), oleautomation] interface IGridEvents : IUnknown
{
HRESULT Grid_OnKeyPress([in] int iKeyCode, [in] int iKeyData, [in] int iRow, [in] int iCol, [out] int* blnCancel);
HRESULT Grid_OnKeyDown([in] int KeyCode, [in] int iKeyData, [in] int iCellRow, [in] int iGridRow, [in] int iCol, [out] int* blnCancel);
HRESULT Grid_OnLButtonDown([in] int iCellRow, [in] int iGridRow, [in] int iCol);
HRESULT Grid_OnLButtonDblClk([in] int iCellRow, [in] int iGridRow, [in] int iCol);
HRESULT Grid_OnPaste([in] int iCellRow, [in] int iGridRow, [in] int iCol);
HRESULT Grid_OnRowSelection([in] int iRow, [in] int iAction);
HRESULT Grid_OnDelete([in] int iRow);
};
[uuid(30000000-0000-0000-0000-000000000003), helpstring("FHGrid TypeLib"), version(1.0)] library FHGridLibrary
{
importlib("stdole32.tlb");
interface IGrid;
interface IGridEvents;
[uuid(30000000-0000-0000-0000-000000000000)] coclass FHGrid
{
interface IGrid;
[source] interface IGridEvents;
}
};
Next is FHGrid.rc ...
1 TYPELIB "FHGrid.tlb"
Here's FHGrid.def ...
;//FHGrid.def
LIBRARY "FHGrid"
EXPORTS
DllGetClassObject PRIVATE
DllCanUnloadNow PRIVATE
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE
Now Server.cpp ...
// Server.cpp
// CD C:\Code\VStudio\VC++9\64_Bit\FHGrid
// cl Server.cpp Grid.cpp Registry.cpp FHGridRes.obj UUID.lib Advapi32.lib Ole32.lib OleAut32.lib FHGrid.def /O1 /Os /FeFHGrid.dll /LD
#define UNICODE
#define _UNICODE
#define X64_GRID // This code can be compiled to create either a 32 bit or 64 bit COM object. The X64_GRID define at
#define MYDEBUG // left is the only equate I've found which was made necessary by the 32/64 bit issue. The define is
#include <windows.h> // used in DllUnregisterServer() in the code that Unregisters the Type Library, i.e., UnRegisterTypeLib().
#include <tchar.h> // The 5th parameter of that function is an object of type SYSKIND, which is an enumeration with members
#include <initguid.h> // SYS_WIN32 and SYS_WIN64. The creation of my X64_GRID #define seemed like a reasonable way to handle
#include <ocidl.h> // this somewhat unfortunate situation.
#include <cstdio>
#include "Grid.h" // Note that if you have registered both a 32 bit version and a 64 bit version of this COM dll, and you
#include "Registry.h" // choose to Unregister one of those, only the win32 or win64 TypeLib subkey will be removed.
#if defined MYDEBUG
FILE* fp=NULL;
#endif
//Globals
HINSTANCE g_hModule = NULL;
const TCHAR g_szFriendlyName[] = _T("Fred Harris Grid Control v1");
const TCHAR g_szVerIndProgID[] = _T("FHGrid.Grid");
const TCHAR g_szProgID[] = _T("FHGrid.Grid.1");
long g_lObjs = 0;
long g_lLocks = 0;
long g_CtrlId = 1500;
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv)
{
GridClassFactory* pCF=NULL;
HRESULT hr;
#if defined MYDEBUG
printf(" Entering DllGetClassObject()\n");
#endif
if(rclsid!=CLSID_FHGrid)
return E_FAIL;
pCF=new GridClassFactory;
if(pCF==0)
return E_OUTOFMEMORY;
#if defined MYDEBUG
printf(" pCF = %d\n",pCF);
#endif
hr=pCF->QueryInterface(riid,ppv);
if(FAILED(hr))
delete pCF;
#if defined MYDEBUG
printf(" Leaving DllGetClassObject()\n");
#endif
return hr;
}
STDAPI DllCanUnloadNow()
{
#if defined MYDEBUG
printf("Entering DllCanUnloadNow()\n");
printf(" g_lObjs = %d\n",g_lObjs);
printf(" g_lLocks = %d\n",g_lLocks);
#endif
if(g_lObjs||g_lLocks)
{
#if defined MYDEBUG
printf(" Gonna Hang Around A Bit Yet Wheather You Like It Or Not!\n");
printf("Leaving DllCanUnloadNow()\n");
#endif
return S_FALSE;
}
else
{
#if defined MYDEBUG
printf(" I'm Outta Here!\n");
printf("Leaving DllCanUnloadNow()\n");
#endif
return S_OK;
}
}
STDAPI DllRegisterServer()
{
ITypeLib* pTypeLib=NULL;
TCHAR szBuffer[512];
wchar_t szWide[512];
HRESULT hr=E_FAIL;
#if defined MYDEBUG
GetCurrentDirectory(512,szBuffer);
_tcscat(szBuffer,_T("\\Output.txt"));
fp=_tfopen(szBuffer,_T("w"));
if(!fp)
return E_FAIL;
fprintf(fp,"Entering DLLRegisterServer\n");
_ftprintf(fp,_T(" szBuffer = %s\n"),szBuffer);
#endif
if(GetModuleFileName(g_hModule,szBuffer,512)>=512)
{
#if defined MYDEBUG
_ftprintf(fp,_T(" GetModuleFileName() Failed!\n"));
_ftprintf(fp,_T("Leaving DllRegisterServer()\n"));
fclose(fp);
#endif
return E_FAIL;
}
#if defined UNICODE
_tcscpy(szWide,szBuffer);
#else
mbstowcs(szWide,szBuffer,511);
#endif
#if defined MYDEBUG
_ftprintf(fp,_T(" szBuffer = %s\n"),szBuffer);
fwprintf(fp,L" szWide = %s\n",szWide);
#endif
hr=LoadTypeLibEx(szWide, REGKIND_REGISTER, &pTypeLib);
if(SUCCEEDED(hr))
{
#if defined MYDEBUG
fprintf(fp," LoadTypeLibEx() Succeeded!\n");
#endif
pTypeLib->Release();
}
else
{
#if defined MYDEBUG
_ftprintf(fp,_T(" LoadTypeLib() Failed!\n"));
_ftprintf(fp,_T("Leaving DllRegisterServer()\n"));
fclose(fp);
#endif
return E_FAIL;
}
hr=RegisterServer(szBuffer,CLSID_FHGrid,g_szFriendlyName,g_szVerIndProgID,g_szProgID);
if(SUCCEEDED(hr))
{
#if defined MYDEBUG
fprintf(fp," RegisterServer() Succeeded!\n");
#endif
}
else
{
#if defined MYDEBUG
fprintf(fp," RegisterServer() Failed!\n");
#endif
}
#if defined MYDEBUG
_ftprintf(fp,_T("Leaving DllRegisterServer()\n"));
fclose(fp);
#endif
return hr;
}
STDAPI DllUnregisterServer()
{
TCHAR szBuffer[512];
HRESULT hr=E_FAIL;
#if defined MYDEBUG
GetCurrentDirectory(512,szBuffer);
_tcscat(szBuffer,_T("\\Output.txt"));
fp=_tfopen(szBuffer,_T("w"));
if(!fp)
return E_FAIL;
_ftprintf(fp,_T("Entering DllUnregisterServer()\n"));
#endif
hr=UnregisterServer(CLSID_FHGrid,g_szVerIndProgID,g_szProgID);
#if defined MYDEBUG
_ftprintf(fp,_T(" hr = %d\n"),hr);
#endif
if(SUCCEEDED(hr))
{
#if defined MYDEBUG
_ftprintf(fp,_T(" UnregisterServer() Succeeded!\n"));
#endif
#if defined X64_GRID
hr=UnRegisterTypeLib(LIBID_FHGrid,1,0,LANG_NEUTRAL,SYS_WIN64);
#else
hr=UnRegisterTypeLib(LIBID_FHGrid,1,0,LANG_NEUTRAL,SYS_WIN32);
#endif
if(SUCCEEDED(hr))
{
#if defined MYDEBUG
_ftprintf(fp,_T(" UnRegisterTypeLib() Succeeded!\n"));
#endif
}
else
{
#if defined MYDEBUG
_ftprintf(fp,_T(" UnRegisterTypeLib() Failed!\n"));
#endif
}
}
#if defined MYDEBUG
_ftprintf(fp,_T("Leaving DllUnregisterServer()\n"));
fclose(fp);
#endif
return hr;
}
BOOL APIENTRY DllMain(HINSTANCE hInst, DWORD reason, LPVOID reserved)
{
switch (reason)
{
case DLL_PROCESS_ATTACH:
g_hModule=hInst;
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
continued ...
Here is Registry.h
BOOL SetKeyAndValue(const TCHAR* szKey, const TCHAR* szSubkey, const TCHAR* szValue);
void CLSIDToChar(const CLSID& clsid, TCHAR* szCLSID, int length);
LONG RecursiveDeleteKey(HKEY hKeyParent, const TCHAR* lpszKeyChild);
HRESULT RegisterServer(TCHAR* szModule, const CLSID& clsid, const TCHAR* szFriendlyName, const TCHAR* szVerIndProgID, const TCHAR* szProgID);
HRESULT UnregisterServer(const CLSID& clsid, const TCHAR* szVerIndProgID, const TCHAR* szProgID);
And Registry.cpp
// Registry.cpp
#define UNICODE
#define _UNICODE
#define MYDEBUG
#include <objbase.h>
#include <tchar.h>
#include <cstdio>
#include "Registry.h"
const int CLSID_STRING_BUFFER_LENGTH = 39; //A CLSID Converted Needs 38 chars, i.e., {30000000-0000-0000-0000-000000000000}. The 39 number is for the terminating NULL.
#if defined MYDEBUG
extern FILE* fp;
#endif
BOOL SetKeyAndValue(const TCHAR* szKey, const TCHAR* szSubkey, const TCHAR* szValue)
{
TCHAR szKeyBuf[1024];
long lResult;
HKEY hKey;
#if defined MYDEBUG
_ftprintf(fp,_T(" Entering SetKeyAndValue()\n"));
_ftprintf(fp,_T(" szKey = %s\n"),szKey);
_ftprintf(fp,_T(" szSubkey = %s\n"),szSubkey);
_ftprintf(fp,_T(" szValue = %s\n"),szValue);
#endif
_tcscpy(szKeyBuf,szKey);
if(szSubkey!=NULL) // Add subkey name to buffer.
{
_tcscat(szKeyBuf, _T("\\"));
_tcscat(szKeyBuf, szSubkey);
#if defined MYDEBUG
_ftprintf(fp,_T(" szKeyBuf = %s\n"),szKeyBuf);
#endif
}
//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,(_tcslen(szValue)*sizeof(TCHAR))+sizeof(TCHAR));
RegCloseKey(hKey);
#if defined MYDEBUG
_ftprintf(fp,_T(" Leaving SetKeyAndValue()\n"));
#endif
return TRUE;
}
void CLSIDToTChar(const CLSID& clsid, TCHAR* szCLSID, int iStrlen) // Convert a CLSID to a TCHAR string.
{
LPOLESTR wszCLSID=NULL;
size_t pRet=0;
HRESULT hr;
#if defined MYDEBUG
_ftprintf(fp,_T(" Entering CLSIDToTChar()\n"));
#endif
hr=StringFromCLSID(clsid,&wszCLSID); // Get CLSID
#if defined MYDEBUG
_ftprintf(fp,_T(" iStrLen = %d\n"),iStrlen);
fwprintf(fp, L" wszCLSID = %s\n",wszCLSID);
#endif
if(SUCCEEDED(hr))
{
#if defined UNICODE
_tcscpy_s(szCLSID,iStrlen,wszCLSID);
#if defined MYDEBUG
_ftprintf(fp,_T(" szCLSID = %s\n"),szCLSID);
#endif
#else
wcstombs_s(&pRet,szCLSID,iStrlen,wszCLSID,iStrlen); // Covert from wide characters to non-wide.
#if defined MYDEBUG
_ftprintf(fp, _T(" szCLSID = %s\n"),szCLSID);
_ftprintf(fp, _T(" pRet = %u\n"),pRet);
#endif
#endif
CoTaskMemFree(wszCLSID); // Free memory.
}
#if defined MYDEBUG
_ftprintf(fp,_T(" Leaving CLSIDToTChar()\n"));
#endif
}
LONG RecursiveDeleteKey(HKEY hKeyParent, const TCHAR* lpszKeyChild) // Key to delete
{
TCHAR 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(TCHAR* szModule, const CLSID& clsid, const TCHAR* szFriendlyName, const TCHAR* szVerIndProgID, const TCHAR* szProgID)
{
TCHAR szCLSID[CLSID_STRING_BUFFER_LENGTH]; // GetModuleFileName(hModule,szModule,sizeof(szModule)/sizeof(TCHAR))
TCHAR szKey[64];
#if defined MYDEBUG
_ftprintf(fp,_T(" Entering RegisterServer()\n"));
_ftprintf(fp,_T(" szFriendlyName = %s\n"),szFriendlyName);
_ftprintf(fp,_T(" szVerIndProgID = %s\n"),szVerIndProgID);
_ftprintf(fp,_T(" szProgID = %s\n"),szProgID);
_ftprintf(fp,_T(" szModule = %s\n"),szModule);
#endif
CLSIDToTChar(clsid, szCLSID, CLSID_STRING_BUFFER_LENGTH); //Get server location &Convert the CLSID into a char.
_tcscpy_s(szKey, _T("CLSID\\")); //Build the key CLSID\\{...}
_tcscat_s(szKey,szCLSID);
#if defined MYDEBUG
_ftprintf(fp,_T(" szCLSID = %s\n"),szCLSID);
_ftprintf(fp,_T(" szKey = %s\n"),szCLSID);
#endif
SetKeyAndValue(szKey,NULL,szFriendlyName); //Add the CLSID to the registry.
SetKeyAndValue(szKey, _T("InprocServer32"), szModule); //Add the server filename subkey under the CLSID key.
SetKeyAndValue(szKey, _T("ProgID"), szProgID); //Add the ProgID subkey under the CLSID key.
SetKeyAndValue(szKey,_T("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, _T("CLSID"), szCLSID);
SetKeyAndValue(szVerIndProgID, _T("CurVer"), szProgID);
SetKeyAndValue(szProgID, NULL, szFriendlyName); //Add the versioned ProgID subkey under HKEY_CLASSES_ROOT.
SetKeyAndValue(szProgID, _T("CLSID"), szCLSID);
#if defined MYDEBUG
_ftprintf(fp,_T(" Leaving RegisterServer()\n"));
#endif
return S_OK ;
}
HRESULT UnregisterServer(const CLSID& clsid, const TCHAR* szVerIndProgID, const TCHAR* szProgID)
{
TCHAR szCLSID[CLSID_STRING_BUFFER_LENGTH];
TCHAR szKey[64];
LONG lResult;
#if defined MYDEBUG
_ftprintf(fp, _T(" Entering UnregisterServer()\n"));
#endif
CLSIDToTChar(clsid, szCLSID,CLSID_STRING_BUFFER_LENGTH); //Convert the CLSID into a char.
_tcscpy_s(szKey, _T("CLSID\\")); //Build the key CLSID\\{...}
_tcscat_s(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.
#if defined MYDEBUG
_ftprintf(fp, _T(" Leaving UnregisterServer()\n"));
#endif
return S_OK ;
}
Now Interfaces.h, Grid.h, and Grid.cpp ...
//IFunctions.h
const CLSID CLSID_FHGrid = {0x30000000, 0x0000, 0x0000, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00} };
const IID IID_IFHGrid = {0x30000000, 0x0000, 0x0000, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01} };
const IID IID_IFHGrid_Events = {0x30000000, 0x0000, 0x0000, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02} };
const IID LIBID_FHGrid = {0x30000000, 0x0000, 0x0000, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03} };
interface IGrid : IUnknown
{
virtual HRESULT STDMETHODCALLTYPE CreateGrid (HWND, BSTR, int, int, int, int, int, int, int, int, int, BSTR, int, int ) = 0;
virtual HRESULT STDMETHODCALLTYPE SetRowCount (int, int ) = 0;
virtual HRESULT STDMETHODCALLTYPE GetRowCount (int* ) = 0;
virtual HRESULT STDMETHODCALLTYPE SetData (int, int, BSTR ) = 0;
virtual HRESULT STDMETHODCALLTYPE GetData (int, int, BSTR* ) = 0;
virtual HRESULT STDMETHODCALLTYPE FlushData (void ) = 0;
virtual HRESULT STDMETHODCALLTYPE Refresh (void ) = 0;
virtual HRESULT STDMETHODCALLTYPE GetVisibleRows (int *iVisibleRows ) = 0;
virtual HRESULT STDMETHODCALLTYPE GethGrid (HWND* ) = 0;
virtual HRESULT STDMETHODCALLTYPE GethCell (int, int, HWND* ) = 0;
virtual HRESULT STDMETHODCALLTYPE GethComboBox (int, HWND* ) = 0;
virtual HRESULT STDMETHODCALLTYPE SetCellAttributes (int, int, int, int ) = 0;
virtual HRESULT STDMETHODCALLTYPE DeleteRow (int ) = 0;
};
interface IGridEvents : IUnknown
{
virtual HRESULT STDMETHODCALLTYPE Grid_OnKeyPress (int iKeyCode, int iKeyData, int iRow, int iCol, int* blnCancel ) = 0;
virtual HRESULT STDMETHODCALLTYPE Grid_OnKeyDown (int KeyCode, int iKeyData, int iCellRow, int iGridRow, int iCol, int* blnCancel) = 0;
virtual HRESULT STDMETHODCALLTYPE Grid_OnLButtonDown (int iCellRow, int iGridRow, int iCol ) = 0;
virtual HRESULT STDMETHODCALLTYPE Grid_OnLButtonDblClk (int iCellRow, int iGridRow, int iCol ) = 0;
virtual HRESULT STDMETHODCALLTYPE Grid_OnPaste (int iCellRow, int iGridRow, int iCol ) = 0;
virtual HRESULT STDMETHODCALLTYPE Grid_OnRowSelection (int iRow, int iAction ) = 0;
virtual HRESULT STDMETHODCALLTYPE Grid_OnDelete (int iRow ) = 0;
};
//Grid.h
#include "Interfaces.h"
extern long g_lObjs;
extern long g_lLocks;
class FHGrid : public IGrid, public IConnectionPointContainer, public IConnectionPoint
{
public:
FHGrid(); //Constructor
virtual ~FHGrid(); //Destructor
//Iunknown Functions
virtual HRESULT STDMETHODCALLTYPE QueryInterface(const IID& iid, void** ppv);
virtual ULONG STDMETHODCALLTYPE AddRef();
virtual ULONG STDMETHODCALLTYPE Release();
//IGrid Interface Methods/Functions
virtual HRESULT STDMETHODCALLTYPE CreateGrid(HWND, BSTR, int, int, int, int, int, int, int, int, int, BSTR, int, int);
virtual HRESULT STDMETHODCALLTYPE SetRowCount(int, int);
virtual HRESULT STDMETHODCALLTYPE GetRowCount(int*);
virtual HRESULT STDMETHODCALLTYPE SetData(int, int, BSTR);
virtual HRESULT STDMETHODCALLTYPE GetData(int, int, BSTR*);
virtual HRESULT STDMETHODCALLTYPE FlushData(void);
virtual HRESULT STDMETHODCALLTYPE Refresh(void);
virtual HRESULT STDMETHODCALLTYPE GetVisibleRows(int*);
virtual HRESULT STDMETHODCALLTYPE GethGrid(HWND*);
virtual HRESULT STDMETHODCALLTYPE GethCell(int, int, HWND*);
virtual HRESULT STDMETHODCALLTYPE GethComboBox(int, HWND*);
virtual HRESULT STDMETHODCALLTYPE SetCellAttributes(int, int, int, int);
virtual HRESULT STDMETHODCALLTYPE DeleteRow(int);
//IConnectionPointContainer Interface Methods/Functions
virtual HRESULT STDMETHODCALLTYPE EnumConnectionPoints(IEnumConnectionPoints** ppEnum); //not implemented
virtual HRESULT STDMETHODCALLTYPE FindConnectionPoint(REFIID riid, IConnectionPoint** ppCP);
// IConnectionPoint
virtual HRESULT STDMETHODCALLTYPE GetConnectionInterface(IID* pIID); //not implemented
virtual HRESULT STDMETHODCALLTYPE GetConnectionPointContainer(IConnectionPointContainer** ppCPC); //not implemented
virtual HRESULT STDMETHODCALLTYPE Advise(IUnknown* pUnknown, DWORD* pdwCookie);
virtual HRESULT STDMETHODCALLTYPE Unadvise(DWORD dwCookie);
virtual HRESULT STDMETHODCALLTYPE EnumConnections(IEnumConnections** ppEnum); //not implemented
protected:
long m_lRef;
};
class GridClassFactory : public IClassFactory
{
public:
GridClassFactory();
virtual ~GridClassFactory();
public:
//IUnknown
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, void**);
virtual ULONG STDMETHODCALLTYPE AddRef();
virtual ULONG STDMETHODCALLTYPE Release();
//IclassFactory
virtual HRESULT STDMETHODCALLTYPE CreateInstance(LPUNKNOWN, REFIID, void**);
virtual HRESULT STDMETHODCALLTYPE LockServer(BOOL);
protected:
long m_lRef;
};
//End Grid.h
//Grid.cpp
#define MYDEBUG
#include <windows.h>
#include <objbase.h>
#include <ocidl.h>
#include <olectl.h>
#include <cstdio>
#include "Grid.h"
#if defined MYDEBUG
extern FILE* fp;
#endif
FHGrid::FHGrid() //C++ Constructor for class FHGrid
{
#if defined MYDEBUG
printf(" Entering FHGrid Constructor!\n");
#endif
m_lRef=0;
#if defined MYDEBUG
printf(" this->m_lRef = %d\n", this->m_lRef);
#endif
InterlockedIncrement(&g_lObjs);
#if defined MYDEBUG
printf(" g_lObjs = %d\n", g_lObjs);
printf(" sizeof(*this) = %d\n",sizeof(*this));
#endif
#if defined MYDEBUG
printf(" Leaving FHGrid Constructor!\n");
#endif
}
FHGrid::~FHGrid() //C++ Destructor for class FHGrid
{
#if defined MYDEBUG
printf(" Entering FHGrid Destructor!\n");
printf(" g_lObjs = %d\n", g_lObjs);
#endif
InterlockedDecrement(&g_lObjs);
#if defined MYDEBUG
printf(" g_lObjs = %d\n", g_lObjs);
printf(" Leaving FHGrid Destructor!\n");
#endif
}
HRESULT STDMETHODCALLTYPE FHGrid::QueryInterface(REFIID riid, void** ppv)
{
#if defined MYDEBUG
printf(" Entering FHGrid::QueryInterface()\n");
printf(" this = %d\n", this);
#endif
*ppv=0;
if(riid==IID_IUnknown)
*ppv=(IGrid*)this;
else if(riid==IID_IFHGrid)
*ppv=(IGrid*)this;
else if(riid==IID_IConnectionPointContainer)
*ppv=(IConnectionPointContainer*)this;
else if(riid==IID_IConnectionPoint)
*ppv=(IConnectionPoint*)this;
if(*ppv)
{
AddRef();
#if defined MYDEBUG
printf(" *ppv = %u\n",*ppv);
printf(" Leaving FHGrid::QueryInterface()\n");
#endif
return S_OK;
}
#if defined MYDEBUG
printf(" Leaving FHGrid::QueryInterface()\n");
#endif
return(E_NOINTERFACE);
}
ULONG STDMETHODCALLTYPE FHGrid::AddRef()
{
#if defined MYDEBUG
printf(" Entering FHGrid::AddRef()\n");
printf(" m_lRef = %d\n", m_lRef);
#endif
InterlockedIncrement(&m_lRef);
#if defined MYDEBUG
printf(" m_lRef = %d\n", m_lRef);
printf(" Leaving FHGrid::AddRef()\n");
#endif
return m_lRef;
}
ULONG STDMETHODCALLTYPE FHGrid::Release()
{
#if defined MYDEBUG
printf(" Entering FHGrid::Release()\n");
printf(" m_lRef = %d\n", m_lRef);
#endif
if(InterlockedDecrement(&m_lRef)==0)
{
#if defined MYDEBUG
printf(" m_lRef = %d\n", m_lRef);
printf(" Will Now Delete this ...\n");
printf(" Leaving FHGrid::Release()\n");
#endif
delete this;
return 0;
}
#if defined MYDEBUG
printf(" m_lRef = %d\n", m_lRef);
printf(" Leaving FHGrid::Release()\n");
#endif
return m_lRef;
}
HRESULT STDMETHODCALLTYPE FHGrid::CreateGrid
(
HWND hParent,
BSTR strSetup,
int x,
int y,
int cx,
int cy,
int iRows,
int iCols,
int iRowHt,
int iSelectionBackColor,
int iSelectionTextColor,
BSTR strFontName,
int iFontSize,
int iFontWeight
)
{
printf("Called FHGrid::CreateGrid()\n");
return S_OK;
}
HRESULT STDMETHODCALLTYPE FHGrid::SetRowCount(int iRowCount, int blnForce)
{
printf("Called FHGrid::SetRowCount()\n");
return S_OK;
}
HRESULT STDMETHODCALLTYPE FHGrid::GetRowCount(int* iRowCount)
{
printf("Called FHGrid::GetRowCount()\n");
return S_OK;
}
HRESULT STDMETHODCALLTYPE FHGrid::SetData(int iRow, int iCol, BSTR strData)
{
printf("Called FHGrid::SetData()\n");
return S_OK;
}
HRESULT STDMETHODCALLTYPE FHGrid::GetData(int iRow, int iCol, BSTR* strData)
{
printf("Called FHGrid::GetData()\n");
return S_OK;
}
HRESULT STDMETHODCALLTYPE FHGrid::FlushData()
{
printf("Called FHGrid::FlushData()\n");
return S_OK;
}
HRESULT STDMETHODCALLTYPE FHGrid::Refresh()
{
printf("Called FHGrid::Refresh()\n");
return S_OK;
}
HRESULT STDMETHODCALLTYPE FHGrid::GetVisibleRows(int* iVisibleRows)
{
printf("Called FHGrid::GetVisibleRows()\n");
return S_OK;
}
HRESULT STDMETHODCALLTYPE FHGrid::GethGrid(HWND* hWnd)
{
printf("Called FHGrid::GethGrid()\n");
return S_OK;
}
HRESULT STDMETHODCALLTYPE FHGrid::GethCell(int iRow, int iCol, HWND* hCell)
{
printf("Called FHGrid::GethCell()\n");
return S_OK;
}
HRESULT STDMETHODCALLTYPE FHGrid::GethComboBox(int iCol, HWND* hCombo)
{
printf("Called FHGrid::GethCell()\n");
return S_OK;
}
HRESULT STDMETHODCALLTYPE FHGrid::SetCellAttributes(int iRow, int iCol, int iBackColor, int iTextColor)
{
printf("Called FHGrid::SetCellAttributes()\n");
return S_OK;
}
HRESULT STDMETHODCALLTYPE FHGrid::DeleteRow(int iRow)
{
printf("Called FHGrid::DeleteRow()\n");
return S_OK;
}
HRESULT FHGrid::EnumConnectionPoints(IEnumConnectionPoints** ppEnum)
{
printf("Called FHGrid::EnumConnectionPoints()\n");
return E_NOTIMPL;
}
HRESULT FHGrid::FindConnectionPoint(REFIID riid, IConnectionPoint** ppCP)
{
printf("Called FHGrid::FindConnectionPoint()\n");
return E_NOINTERFACE;
}
HRESULT FHGrid::GetConnectionInterface(IID* pIID)
{
printf("Called FHGrid::GetConnectionInterface()\n");
return E_NOTIMPL;
}
HRESULT FHGrid::GetConnectionPointContainer(IConnectionPointContainer** ppCPC)
{
printf("Called FHGrid::GetConnectionPointContainer()\n");
return E_NOTIMPL;
}
HRESULT FHGrid::Advise(IUnknown* pUnknown, DWORD* pdwCookie)
{
printf("Called FHGrid::Advise()\n");
return E_FAIL;
}
HRESULT FHGrid::Unadvise(DWORD dwCookie)
{
printf("Called FHGrid::Unadvise()\n");
return NOERROR;
}
HRESULT FHGrid::EnumConnections(IEnumConnections** ppEnum)
{
printf("Called FHGrid::EnumConnections()\n");
return E_NOTIMPL;
}
// Grid Class Factory
GridClassFactory::GridClassFactory()
{
#if defined MYDEBUG
printf(" Entering GridClassFactory Constructor!\n");
#endif
m_lRef=0;
#if defined MYDEBUG
printf(" this->m_lRef = %d\n", this->m_lRef);
#endif
#if defined MYDEBUG
printf(" Leaving GridClassFactory Constructor!\n");
#endif
}
GridClassFactory::~GridClassFactory() //GridClassFactory Destructor
{
#if defined MYDEBUG
printf(" Entering GridClassFactory Destructor!\n");
#endif
#if defined MYDEBUG
printf(" this->m_lRef = %d\n", this->m_lRef);
#endif
#if defined MYDEBUG
printf(" Leaving GridClassFactory Destructor!\n");
#endif
}
HRESULT STDMETHODCALLTYPE GridClassFactory::QueryInterface(REFIID riid, void** ppv)
{
printf(" Entering GridClassFactory::QueryInterface()\n");
*ppv=0;
if(riid==IID_IUnknown || riid==IID_IClassFactory)
*ppv=this;
#if defined MYDEBUG
printf(" *ppv = %u\n", *ppv);
#endif
if(*ppv)
{
AddRef();
printf(" Leaving GridClassFactory::QueryInterface()\n");
return S_OK;
}
return E_NOINTERFACE;
}
ULONG STDMETHODCALLTYPE GridClassFactory::AddRef()
{
#if defined MYDEBUG
printf(" Entering GridClassFactory::AddRef()!\n");
printf(" this->m_lRef = %d\n", this->m_lRef);
#endif
InterlockedIncrement(&m_lRef);
#if defined MYDEBUG
printf(" this->m_lRef = %d\n", this->m_lRef);
printf(" Leaving GridClassFactory::AddRef()!\n");
#endif
return this->m_lRef;
}
ULONG STDMETHODCALLTYPE GridClassFactory::Release()
{
#if defined MYDEBUG
printf(" Entering GridClassFactory::Release()!\n");
printf(" this->m_lRef = %d\n", this->m_lRef);
#endif
InterlockedDecrement(&m_lRef);
if(this->m_lRef==0)
{
printf(" this->m_lRef = %d\n", this->m_lRef);
delete this;
#if defined MYDEBUG
printf(" GridClassFactory Has Been Destroyed!\n");
printf(" Leaving GridClassFactory::Release()!\n");
#endif
return 0;
}
#if defined MYDEBUG
printf(" this->m_lRef = %d\n", this->m_lRef);
printf(" Leaving GridClassFactory::Release()!\n");
#endif
return m_lRef;
}
HRESULT STDMETHODCALLTYPE GridClassFactory::CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, void** ppvObj)
{
FHGrid* pGrid=NULL;
HRESULT hr;
#if defined MYDEBUG
printf(" Entering GridClassFactory::CreateInstance()\n");
#endif
*ppvObj=0;
pGrid=new FHGrid;
#if defined MYDEBUG
printf(" pGrid = %u\n",pGrid);
printf(" sizeof(*pGrid) = %d\n",sizeof(*pGrid));
#endif
if(pGrid==NULL)
return E_OUTOFMEMORY;
hr=pGrid->QueryInterface(riid,ppvObj);
if(FAILED(hr))
delete pGrid;
#if defined MYDEBUG
printf(" Leaving GridClassFactory::CreateInstance()\n");
#endif
return hr;
}
HRESULT STDMETHODCALLTYPE GridClassFactory::LockServer(BOOL fLock)
{
if(fLock)
InterlockedIncrement(&g_lLocks);
else
InterlockedDecrement(&g_lLocks);
return S_OK;
}
//End Grid.cpp
continued ...
Visual Studio 2008 Pro puts a bunch of shortcuts on the Start Menu and one of those will start a command line session where paths and environment variables are set up to invoke either the 32 bit or 64 bit tool chain. So I made a directory for my 64 bit code at C:\Code\VStudio\VC++9\64_Bit\FHGrid and I navigate to there from where VStudio's command prompt opens up. I use midl to create the type library, then rc and cvtres to create the FHGridRes.obj file containing the resources. Then I use cl.exe to link all the pieces together and create the dll. Here is the complete 64 bit console output for building the dll from the above code ...
Setting environment for using Microsoft Visual Studio 2008 Beta2 x64 tools.
c:\Program Files (x86)\Microsoft Visual Studio 9.0\VC>CD\
c:\>CD C:\Code\VStudio\VC++9\64_Bit\FHGrid
C:\Code\VStudio\VC++9\64_Bit\FHGrid>midl FHGrid.idl
Microsoft (R) 32b/64b MIDL Compiler Version 7.00.0500
Copyright (c) Microsoft Corporation 1991-2006. All rights reserved.
64 bit Processing .\FHGrid.idl
FHGrid.idl
64 bit Processing C:\Program Files\\Microsoft SDKs\Windows\v6.0A\include\unknwn.idl
unknwn.idl
64 bit Processing C:\Program Files\\Microsoft SDKs\Windows\v6.0A\include\wtypes.idl
wtypes.idl
64 bit Processing C:\Program Files\\Microsoft SDKs\Windows\v6.0A\include\basetsd.h
basetsd.h
64 bit Processing C:\Program Files\\Microsoft SDKs\Windows\v6.0A\include\guiddef.h
guiddef.h
64 bit Processing C:\Program Files\\Microsoft SDKs\Windows\v6.0A\include\oaidl.idl
oaidl.idl
64 bit Processing C:\Program Files\\Microsoft SDKs\Windows\v6.0A\include\objidl.idl
objidl.idl
64 bit Processing C:\Program Files\\Microsoft SDKs\Windows\v6.0A\include\oaidl.acf
oaidl.acf
C:\Code\VStudio\VC++9\64_Bit\FHGrid>rc.exe /v /foFHGridRes.res FHGrid.rc
Microsoft (R) Windows (R) Resource Compiler Version 6.0.5724.0
Copyright (C) Microsoft Corporation. All rights reserved.
Using codepage 1252 as default
Creating FHGridRes.res
FHGrid.rc.
Writing TYPELIB:1, lang:0x409, size 5056
C:\Code\VStudio\VC++9\64_Bit\FHGrid>cvtres.exe /MACHINE:X64 /v FHGridRes.res
Microsoft (R) Windows Resource To Object Converter Version 9.00.21022.08
Copyright (C) Microsoft Corporation. All rights reserved.
adding resource. type:TYPELIB, name:1, language:0x0409, flags:0x30, size:5056
C:\Code\VStudio\VC++9\64_Bit\FHGrid>cl Server.cpp Grid.cpp Registry.cpp FHGridRes.obj UUID.lib Advapi32.lib Ole32.lib OleAut32.lib FHGrid.def /O1 /Os /FeFHGrid.dll /LD
Microsoft (R) C/C++ Optimizing Compiler Version 15.00.21022.08 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
Server.cpp
Grid.cpp
Registry.cpp
Generating Code...
Microsoft (R) Incremental Linker Version 9.00.21022.08
Copyright (C) Microsoft Corporation. All rights reserved.
/out:FHGrid.dll
/dll
/implib:FHGrid.lib
/def:FHGrid.def
Server.obj
Grid.obj
Registry.obj
FHGridRes.obj
UUID.lib
Advapi32.lib
Ole32.lib
OleAut32.lib
Creating library FHGrid.lib and object FHGrid.exp
C:\Code\VStudio\VC++9\64_Bit\FHGrid>
Next you'll want to Register the Server with RegSvr32.exe. You may need to run your command prompt window using the 'Run As Administrator' option to get that to work. Note I don't have a hard coded debug output file created in DllRegisterServer(), but rather I believe my code will work wherever you want to create this. Here is part of that routine that gets called by RegSvr32...
STDAPI DllRegisterServer()
{
ITypeLib* pTypeLib=NULL;
TCHAR szBuffer[512];
wchar_t szWide[512];
HRESULT hr=E_FAIL;
#if defined MYDEBUG
GetCurrentDirectory(512,szBuffer);
_tcscat(szBuffer,_T("\\Output.txt"));
fp=_tfopen(szBuffer,_T("w"));
if(!fp)
return E_FAIL;
fprintf(fp,"Entering DLLRegisterServer\n");
...
...
Here is the Output.txt file I got through the registration when I was testing with ansi...
Entering DLLRegisterServer
szBuffer = C:\Code\VStudio\VC++9\64_Bit\FHGrid\FHGrid.dll
iReturn = 46
szWide = C:\Code\VStudio\VC++9\64_Bit\FHGrid\FHGrid.dll
LoadTypeLibEx() Succeeded!
Entering RegisterServer()
szFriendlyName = Fred Harris Grid Control v1
szVerIndProgID = FHGrid.Grid
szProgID = FHGrid.Grid.1
szModule = C:\Code\VStudio\VC++9\64_Bit\FHGrid\FHGrid.dll
Entering CLSIDToTChar()
iStrLen = 39
wszCLSID = {30000000-0000-0000-0000-000000000000}
szCLSID = {30000000-0000-0000-0000-000000000000}
pRet = 39
Leaving CLSIDToTChar()
szCLSID = {30000000-0000-0000-0000-000000000000}
szKey = {30000000-0000-0000-0000-000000000000}
Entering SetKeyAndValue()
szKey = CLSID\{30000000-0000-0000-0000-000000000000}
szSubkey = (null)
szValue = Fred Harris Grid Control v1
Leaving SetKeyAndValue()
Entering SetKeyAndValue()
szKey = CLSID\{30000000-0000-0000-0000-000000000000}
szSubkey = InprocServer32
szValue = C:\Code\VStudio\VC++9\64_Bit\FHGrid\FHGrid.dll
szKeyBuf = CLSID\{30000000-0000-0000-0000-000000000000}\InprocServer32
Leaving SetKeyAndValue()
Entering SetKeyAndValue()
szKey = CLSID\{30000000-0000-0000-0000-000000000000}
szSubkey = ProgID
szValue = FHGrid.Grid.1
szKeyBuf = CLSID\{30000000-0000-0000-0000-000000000000}\ProgID
Leaving SetKeyAndValue()
Entering SetKeyAndValue()
szKey = CLSID\{30000000-0000-0000-0000-000000000000}
szSubkey = VersionIndependentProgID
szValue = FHGrid.Grid
szKeyBuf = CLSID\{30000000-0000-0000-0000-000000000000}\VersionIndependentProgID
Leaving SetKeyAndValue()
Entering SetKeyAndValue()
szKey = FHGrid.Grid
szSubkey = (null)
szValue = Fred Harris Grid Control v1
Leaving SetKeyAndValue()
Entering SetKeyAndValue()
szKey = FHGrid.Grid
szSubkey = CLSID
szValue = {30000000-0000-0000-0000-000000000000}
szKeyBuf = FHGrid.Grid\CLSID
Leaving SetKeyAndValue()
Entering SetKeyAndValue()
szKey = FHGrid.Grid
szSubkey = CurVer
szValue = FHGrid.Grid.1
szKeyBuf = FHGrid.Grid\CurVer
Leaving SetKeyAndValue()
Entering SetKeyAndValue()
szKey = FHGrid.Grid.1
szSubkey = (null)
szValue = Fred Harris Grid Control v1
Leaving SetKeyAndValue()
Entering SetKeyAndValue()
szKey = FHGrid.Grid.1
szSubkey = CLSID
szValue = {30000000-0000-0000-0000-000000000000}
szKeyBuf = FHGrid.Grid.1\CLSID
Leaving SetKeyAndValue()
Leaving RegisterServer()
RegisterServer() Succeeded!
Leaving DllRegisterServer()
I intend to discuss the above code in detail, but to do that one pretty much needs a client/host to work with. So here is Client.h and Client.cpp. This is a Windows GUI app that creates a little Window/Dialog/Form ...
//Client.h
#ifndef CLIENT_H_INCLUDED
#define CLIENT_H_INCLUDED
#define dim(x) (sizeof(x) / sizeof(x[0]))
#define IDC_STATIC -1
#define IDC_UNLOAD_GRID 1500
#define IDC_DUMP_COM_MEMORY 1505
struct WindowsEventArguments
{
HWND hWnd;
WPARAM wParam;
LPARAM lParam;
HINSTANCE hIns;
};
typedef WindowsEventArguments* lpWndEventArgs;
long fnWndProc_OnCreate (lpWndEventArgs Wea);
long fnWndProc_OnCommand (lpWndEventArgs Wea);
long fnWndProc_OnPaint (lpWndEventArgs Wea);
long fnWndProc_OnDestroy (lpWndEventArgs Wea);
struct EVENTHANDLER
{
unsigned int iMsg;
long (*fnPtr)(lpWndEventArgs);
};
const EVENTHANDLER EventHandler[]=
{
{WM_CREATE, fnWndProc_OnCreate},
{WM_COMMAND, fnWndProc_OnCommand},
{WM_PAINT, fnWndProc_OnPaint},
{WM_DESTROY, fnWndProc_OnDestroy}
};
#endif // CLIENT_H_INCLUDED
//Client.cpp
// cl Client.cpp Kernel32.lib User32.lib Gdi32.lib UUID.lib Ole32.lib OleAut32.lib /O1 /Os /MT /GA
// CD C:\Code\VStudio\VC++9\64_Bit\FHGrid
#define UNICODE
#define _UNICODE //At this point the grid doesn't exist yet, however, the COM infrastructure code in
#include <windows.h> //which it will eventually be created does. This client app exercises that COM
#include <objbase.h> //infrastructure code by instantiating an FHGrid object and iterating through its
#include <tchar.h> //various structures and virtual function tables (VTables) to elucidate its memory
#include <fcntl.h> //footprint. The app is a GUI Windows App and creates a small window with two buttons
#include <io.h> //on it. The button on the left outputs the memory footprint of the various interfaces
#include <stdio.h> //and COM structures to the console window which the program creates. The button on
#include <ocidl.h> //the right releases the COM object.
#include "Client.h"
const CLSID CLSID_FHGrid = {0x30000000, 0x0000, 0x0000, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00} };
const IID IID_IFHGrid = {0x30000000, 0x0000, 0x0000, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01} };
const IID IID_IFHGrid_Events = {0x30000000, 0x0000, 0x0000, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02} };
const IID LIBID_FHGrid = {0x30000000, 0x0000, 0x0000, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03} };
const IID IID_Junk = {0x12345678, 0x1234, 0x5678, {0x12,0x34,0x56,0x78,0x98,0x76,0x54,0x32} };
interface IGrid : IUnknown
{
virtual HRESULT STDMETHODCALLTYPE CreateGrid (HWND, BSTR, int, int, int, int, int, int, int, int, int, BSTR, int, int ) = 0;
virtual HRESULT STDMETHODCALLTYPE SetRowCount (int, int ) = 0;
virtual HRESULT STDMETHODCALLTYPE GetRowCount (int* ) = 0;
virtual HRESULT STDMETHODCALLTYPE SetData (int, int, BSTR ) = 0;
virtual HRESULT STDMETHODCALLTYPE GetData (int, int, BSTR* ) = 0;
virtual HRESULT STDMETHODCALLTYPE FlushData (void ) = 0;
virtual HRESULT STDMETHODCALLTYPE Refresh (void ) = 0;
virtual HRESULT STDMETHODCALLTYPE GetVisibleRows (int ) = 0;
virtual HRESULT STDMETHODCALLTYPE GethGrid (HWND* ) = 0;
virtual HRESULT STDMETHODCALLTYPE GethCell (int, int, HWND* ) = 0;
virtual HRESULT STDMETHODCALLTYPE GethComboBox (int, HWND* ) = 0;
virtual HRESULT STDMETHODCALLTYPE SetCellAttributes (int, int, int, int ) = 0;
virtual HRESULT STDMETHODCALLTYPE DeleteRow (int ) = 0;
};
long fnWndProc_OnCreate(lpWndEventArgs Wea) //This code can be compiled for either 32 bit or 64 bit. If compiled for 32 bit it will
{ //attempt to load a 32 bit grid. If compiled with a 64 bit C++ compiler it will attempt
IClassFactory* pClassFactory=NULL; //to load the 64 bit version. Of course, to succeed those versions have to be registered.
SMALL_RECT sm_rect;
IGrid* pGrid=NULL;
HANDLE hStdOut;
HWND hCtl=NULL;
HRESULT hr;
FILE* hf=0;
COORD pt;
int hCrt;
Wea->hIns=((LPCREATESTRUCT)Wea->lParam)->hInstance;
AllocConsole();
hStdOut=GetStdHandle(STD_OUTPUT_HANDLE); //This mess at left is some pretty obscure stuff related
hCrt=_open_osfhandle((long)hStdOut,_O_TEXT); //to the fact that when a GUI process is started by
hf=_fdopen( hCrt, "w" ); //Windows the standard output handles are zeroed out and
__iob_func()[1]=*hf; //printf needs them. This code re-initializes it so printf
pt.X=120, pt.Y=600; //works. I had to change it a little from what it used to be.
SetConsoleScreenBufferSize(hStdOut,pt);
sm_rect.Left=0, sm_rect.Top=0, sm_rect.Right=119, sm_rect.Bottom=39;
SetConsoleWindowInfo(hStdOut,TRUE,&sm_rect);
printf("Entering fnWndProc_OnCreate()\n");
printf(" sizeof(IGrid*) = %d\n",sizeof(IGrid*));
hr=CoInitialize(NULL);
if(SUCCEEDED(hr))
{
hCtl=CreateWindow(_T("button"),_T("Dump COM Memory"),WS_CHILD|WS_VISIBLE,30,120,150,25,Wea->hWnd,(HMENU)IDC_DUMP_COM_MEMORY,Wea->hIns,NULL);
hCtl=CreateWindow(_T("button"),_T("Destroy Grid"),WS_CHILD|WS_VISIBLE,225,120,150,25,Wea->hWnd,(HMENU)IDC_UNLOAD_GRID,Wea->hIns,NULL);
printf(" CoInitialize() Succeeded!\n");
hr=CoGetClassObject(CLSID_FHGrid,CLSCTX_INPROC_SERVER,NULL,IID_IClassFactory,(void**)&pClassFactory);
if(SUCCEEDED(hr))
{
printf(" CoGetClassObject() Succeeded!\n");
printf(" pClassFactory = %u\n",pClassFactory);
hr=pClassFactory->CreateInstance(NULL,IID_IFHGrid,(void**)&pGrid);
if(SUCCEEDED(hr))
{
printf(" IClassFactory->CreateInstance() Succeeded!\n");
printf(" pGrid = %u\n",pGrid);
SetWindowLongPtr(Wea->hWnd,0,(LONG_PTR)pGrid);
}
else
printf(" IClassFactory->CreateInstance() Failed!\n");
pClassFactory->Release();
}
else
printf(" CoGetClassObject() Failed!\n");
}
else
printf(" CoInitialize() Failed!\n");
printf("Leaving fnWndProc_OnCreate()\n\n");
return 0;
}
void DumpCOMMemory(HWND hHost)
{
IConnectionPointContainer* pCnPtCnt=NULL;
size_t* pVTbl=0;
size_t* pIUnk=0;
size_t* VTbl=0;
HRESULT hr;
printf(" Entering DumpCOMMemory()\n");
IGrid* pGrid=(IGrid*)GetWindowLongPtr(hHost,0);
printf(" pGrid = %u\n",pGrid);
if(pGrid)
{
pVTbl=(size_t*)pGrid;
printf(" pVTbl = %u\n",pVTbl);
printf(" &pVTbl[0] = %u\n", &pVTbl[0]);
printf(" &pVTbl[1] = %u\n", &pVTbl[1]);
printf(" &pVTbl[2] = %u\n", &pVTbl[2]);
VTbl=(size_t*)pVTbl[0];
printf(" VTbl = %u\n",VTbl);
printf(" VTbl[0] = %u\n\n", VTbl[0]);
//IGrid Function Pointers
HRESULT (STDMETHODCALLTYPE* pQueryInterface )(size_t* pThis, REFIID riid, void** ppv );
ULONG (STDMETHODCALLTYPE* pAddRef )(size_t* );
ULONG (STDMETHODCALLTYPE* pRelease )(size_t* );
HRESULT (STDMETHODCALLTYPE* pCreateGrid )(size_t*, HWND, BSTR, int, int, int, int, int, int, int, int, int, BSTR, int, int);
HRESULT (STDMETHODCALLTYPE* pSetRowCount )(size_t*, int, int );
HRESULT (STDMETHODCALLTYPE* pGetRowCount )(size_t*, int* );
HRESULT (STDMETHODCALLTYPE* pSetData )(size_t*, int, int, BSTR );
HRESULT (STDMETHODCALLTYPE* pGetData )(size_t*, int, int, BSTR* );
HRESULT (STDMETHODCALLTYPE* pFlushData )(size_t* );
HRESULT (STDMETHODCALLTYPE* pRefresh )(size_t* );
HRESULT (STDMETHODCALLTYPE* pGetVisibleRows )(size_t*, int* );
HRESULT (STDMETHODCALLTYPE* pGethGrid )(size_t*, HWND* );
HRESULT (STDMETHODCALLTYPE* pGethCell )(size_t*, int, int, HWND* );
HRESULT (STDMETHODCALLTYPE* pGethComboBox )(size_t*, int, HWND* );
HRESULT (STDMETHODCALLTYPE* pSetCellAttributes )(size_t*, int, int, int, int );
HRESULT (STDMETHODCALLTYPE* pDeleteRow )(size_t*, int );
//IConnectionPointContainer Function Pointers
HRESULT (STDMETHODCALLTYPE* pEnumConnectionPoints )(size_t* This, IEnumConnectionPoints** ppEnum );
HRESULT (STDMETHODCALLTYPE* pFindConnectionPoint )(size_t* This, REFIID riid, IConnectionPoint** ppCP );
//IConnectionPoint Function Pointers
HRESULT (STDMETHODCALLTYPE* pGetConnectionInterface )(size_t*, IID* pIID );
HRESULT (STDMETHODCALLTYPE* pGetConnectionPointContainer)(size_t*, IConnectionPointContainer** ppCPC );
HRESULT (STDMETHODCALLTYPE* pAdvise )(size_t*, IUnknown* pUnkSink, DWORD* pdwCookie );
HRESULT (STDMETHODCALLTYPE* pUnadvise )(size_t*, DWORD dwCookie );
HRESULT (STDMETHODCALLTYPE* pEnumConnections )(size_t*, IEnumConnections** ppEnum );
//IGrid::QueryInterface()
pQueryInterface=(HRESULT (STDMETHODCALLTYPE*)(size_t*, REFIID, void**))VTbl[0];
pQueryInterface(&pVTbl[0],IID_Junk,(void**)&pIUnk);
//IGrid::AddRef()
pAddRef=(ULONG (STDMETHODCALLTYPE*)(size_t*))VTbl[1];
pAddRef(&pVTbl[0]);
//IGrid::Release()
pRelease=(ULONG (STDMETHODCALLTYPE*)(size_t*))VTbl[2];
pRelease(&pVTbl[0]);
printf("\n");
printf(" &pVTbl[i] &VTbl[j] pFn=VTbl[j] pFn() Function Pointer Call\n");
printf(" ===========================================================================\n");
//IGrid::QueryInterface()
pQueryInterface=(HRESULT (STDMETHODCALLTYPE*)(size_t*, REFIID, void**))VTbl[0];
printf(" %u\t%u\t%u\n",&pVTbl[0],&VTbl[0],pQueryInterface);
//IGrid::AddRef()
printf(" %u\t%u\t%u\n",&pVTbl[0],&VTbl[1],pAddRef);
//IGrid::Release()
printf(" %u\t%u\t%u\n",&pVTbl[0],&VTbl[2],pRelease);
//IGrid::CreateGrid()
pCreateGrid=(HRESULT (STDMETHODCALLTYPE*)(size_t*,HWND,BSTR,int,int,int,int,int,int,int,int,int,BSTR,int,int))VTbl[3];
printf(" %u\t%u\t%u\t",&pVTbl[0],&VTbl[3],pCreateGrid);
HWND hWnd=NULL;
BSTR strNothing=NULL;
pCreateGrid(&pVTbl[0], hWnd, strNothing, 0, 0, 0, 0, 0, 0, 0, 0, 0, strNothing, 0, 0);
//IGrid::SetRowCount()
pSetRowCount=(HRESULT (STDMETHODCALLTYPE*)(size_t*, int, int))VTbl[4];
printf(" %u\t%u\t%u\t",&pVTbl[0],&VTbl[4],pSetRowCount);
pSetRowCount(&pVTbl[0],0,0);
//IGrid::GetRowCount()
pGetRowCount=(HRESULT (STDMETHODCALLTYPE*)(size_t*, int*))VTbl[5];
printf(" %u\t%u\t%u\t",&pVTbl[0],&VTbl[5],pSetRowCount);
pGetRowCount(&pVTbl[0],0);
//IGrid::SetData()
pSetData=(HRESULT (STDMETHODCALLTYPE*)(size_t*, int, int, BSTR))VTbl[6];
printf(" %u\t%u\t%u\t",&pVTbl[0],&VTbl[6],pSetData);
pSetData(&pVTbl[0],0,0,strNothing);
//IGrid::GetData()
pGetData=(HRESULT (STDMETHODCALLTYPE*)(size_t*, int, int, BSTR*))VTbl[7];
printf(" %u\t%u\t%u\t",&pVTbl[0],&VTbl[7],pGetData);
pGetData(&pVTbl[0],0,0,&strNothing);
//IGrid::FlushData()
pFlushData=(HRESULT (STDMETHODCALLTYPE*)(size_t*))VTbl[8];
printf(" %u\t%u\t%u\t",&pVTbl[0],&VTbl[8],pFlushData);
pFlushData(&pVTbl[0]);
//IGrid::Refresh()
pRefresh=(HRESULT (STDMETHODCALLTYPE*)(size_t*))VTbl[9];
printf(" %u\t%u\t%u\t",&pVTbl[0],&VTbl[9],pRefresh);
pRefresh(&pVTbl[0]);
//IGrid::GetVisibleRows()
pGetVisibleRows=(HRESULT (STDMETHODCALLTYPE*)(size_t*, int*))VTbl[10];
printf(" %u\t%u\t%u\t",&pVTbl[0],&VTbl[10],pGetVisibleRows);
pGetVisibleRows(&pVTbl[0],NULL);
//IGrid::GethGrid()
pGethGrid=(HRESULT (STDMETHODCALLTYPE*)(size_t*, HWND*))VTbl[11];
printf(" %u\t%u\t%u\t",&pVTbl[0],&VTbl[11],pGethGrid);
pGethGrid(&pVTbl[0],&hWnd);
//IGrid::GethCell()
pGethCell=(HRESULT (STDMETHODCALLTYPE*)(size_t*, int, int, HWND*))VTbl[12];
printf(" %u\t%u\t%u\t",&pVTbl[0],&VTbl[12],pGethCell);
pGethCell(&pVTbl[0],0,0,&hWnd);
//IGrid::GethComboBox()
pGethComboBox=(HRESULT (STDMETHODCALLTYPE*)(size_t*, int, HWND*))VTbl[13];
printf(" %u\t%u\t%u\t",&pVTbl[0],&VTbl[13],pGethComboBox);
pGethComboBox(&pVTbl[0],0,&hWnd);
//IGrid::SetCellAttributes()
pSetCellAttributes=(HRESULT (STDMETHODCALLTYPE*)(size_t*, int, int, int, int))VTbl[14];
printf(" %u\t%u\t%u\t",&pVTbl[0],&VTbl[14],pSetCellAttributes);
pSetCellAttributes(&pVTbl[0],0,0,0,0);
//IGrid::DeleteRow()
pDeleteRow=(HRESULT (STDMETHODCALLTYPE*)(size_t*, int))VTbl[15];
printf(" %u\t%u\t%u\t",&pVTbl[0],&VTbl[15],pDeleteRow);
pDeleteRow(&pVTbl[0],0);
printf("\n");
//End IGrid VTable
VTbl=(size_t*)pVTbl[1]; //Get Base Address Of IConnectionPointContainer VTable
//IConnectionPointContainer::EnumConnectionPoints()
pEnumConnectionPoints=(HRESULT (STDMETHODCALLTYPE*)(size_t*, IEnumConnectionPoints**))VTbl[3];
IEnumConnectionPoints* pEnumConnPts=NULL;
printf(" %u\t%u\t%u\t",&pVTbl[1],&VTbl[3],pEnumConnectionPoints);
pEnumConnectionPoints(&pVTbl[1],&pEnumConnPts);
//IConnectionPointContainer::FindConnectionPoint()
pFindConnectionPoint=(HRESULT (STDMETHODCALLTYPE*)(size_t*, REFIID, IConnectionPoint**))VTbl[4];
printf(" %u\t%u\t%u\t",&pVTbl[1],&VTbl[4],pFindConnectionPoint);
IConnectionPoint* pConnectionPoint=NULL;
pFindConnectionPoint(&pVTbl[1],IID_Junk,&pConnectionPoint);
printf("\n");
//End IConnectionPointContainer VTable
VTbl=(size_t*)pVTbl[2]; //Get Base Address Of IConnectionPoint VTable
//IConnectionPoint::GetConnectionInterface()
pGetConnectionInterface=(HRESULT (STDMETHODCALLTYPE*)(size_t*, IID*))VTbl[3];
printf(" %u\t%u\t%u\t",&pVTbl[2],&VTbl[3],pGetConnectionInterface);
IID pJunk;
pGetConnectionInterface(&pVTbl[2],&pJunk);
//IConnectionPoint::GetConnectionPointContainer
pGetConnectionPointContainer=(HRESULT (STDMETHODCALLTYPE*)(size_t*, IConnectionPointContainer**))VTbl[4];
printf(" %u\t%u\t%u\t",&pVTbl[2],&VTbl[4],pGetConnectionPointContainer);
pGetConnectionPointContainer(&pVTbl[2],&pCnPtCnt);
//IConnectionPoint::Advise()
pAdvise=(HRESULT (STDMETHODCALLTYPE*)(size_t*, IUnknown*, DWORD*))VTbl[5];
printf(" %u\t%u\t%u\t",&pVTbl[2],&VTbl[5],pAdvise);
DWORD dwCookie;
pAdvise(&pVTbl[2],(IUnknown*)pCnPtCnt,&dwCookie);
//IConnectionPoint::Unadvise()
pUnadvise=(HRESULT (STDMETHODCALLTYPE*)(size_t*, DWORD))VTbl[6];
printf(" %u\t%u\t%u\t",&pVTbl[2],&VTbl[6],pUnadvise);
pUnadvise(&pVTbl[2],dwCookie);
//IConnectionPoint::EnumConnections
pEnumConnections=(HRESULT (STDMETHODCALLTYPE*)(size_t*, IEnumConnections**))VTbl[7];
IEnumConnections* pIEnumConnections=NULL;
printf(" %u\t%u\t%u\t",&pVTbl[2],&VTbl[7],pEnumConnections);
pEnumConnections(&pVTbl[2],&pIEnumConnections);
printf("\n");
hr=pGrid->QueryInterface(IID_IConnectionPointContainer, (void**)&pCnPtCnt);
if(SUCCEEDED(hr))
{
printf(" pGrid->QueryInterface(IID_IConnectionPointContainer) Succeeded!\n");
printf(" pCnPtCnt = %u\n",pCnPtCnt);
pCnPtCnt->Release();
}
else
printf(" pGrid->QueryInterface(IID_IConnectionPointContainer) Failed!\n");
}
printf(" Leaving DumpCOMMemory()\n");
}
void DestroyGrid(HWND hHost)
{
printf(" Entering DestroyGrid()\n");
IGrid* pGrid=(IGrid*)GetWindowLongPtr(hHost,0);
printf(" pGrid = %u\n",pGrid);
if(pGrid)
{
pGrid->Release();
SetWindowLongPtr(hHost,0,NULL);
}
printf(" Leaving DestroyGrid()\n");
}
long fnWndProc_OnCommand(lpWndEventArgs Wea)
{
switch(LOWORD(Wea->wParam))
{
case IDC_UNLOAD_GRID:
{
printf("\nEntering fnWndProc_OnCommand()\n");
DestroyGrid(Wea->hWnd);
printf("Leaving fnWndProc_OnCommand()\n");
break;
}
case IDC_DUMP_COM_MEMORY:
{
printf("\nEntering fnWndProc_OnCommand()\n");
DumpCOMMemory(Wea->hWnd);
printf("Leaving fnWndProc_OnCommand()\n");
break;
}
}
return 0;
}
long fnWndProc_OnPaint(lpWndEventArgs Wea)
{
PAINTSTRUCT ps;
int iBkMode;
HDC hDC;
hDC=BeginPaint(Wea->hWnd,&ps);
iBkMode=SetBkMode(hDC,TRANSPARENT);
TextOut(hDC,40,45,_T("The ActiveX Grid Control Will Eventually Go Here!"),49);
SetBkMode(hDC,iBkMode);
EndPaint(Wea->hWnd,&ps);
return 0;
}
long fnWndProc_OnDestroy(lpWndEventArgs Wea)
{
DestroyGrid(Wea->hWnd);
PostQuitMessage(0);
return 0;
}
LRESULT CALLBACK WndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
WindowsEventArguments Wea;
for(unsigned int i=0; i<dim(EventHandler); i++)
{
if(EventHandler[i].iMsg==msg)
{
Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
return (*EventHandler[i].fnPtr)(&Wea);
}
}
return (DefWindowProc(hwnd, msg, wParam, lParam));
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
TCHAR szClass[]=_T("64 Bit Grid");
WNDCLASSEX wc;
HWND hWnd;
MSG Msg;
wc.lpszClassName=szClass, wc.lpfnWndProc=WndProc, wc.cbSize=sizeof(WNDCLASSEX);
wc.style=0, wc.cbClsExtra=0, wc.cbWndExtra=40;
wc.hInstance=hInstance, wc.hIcon=LoadIcon(NULL,IDI_APPLICATION), wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW, wc.lpszMenuName=NULL, wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&wc);
hWnd=CreateWindow(szClass,_T("64 Bit Grid ActiveX Control"),WS_OVERLAPPEDWINDOW,350,550,425,200,NULL,NULL,hInstance,NULL);
ShowWindow(hWnd,nCmdShow);
while(GetMessage(&Msg, NULL, 0, 0))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
} CoFreeUnusedLibraries(), CoUninitialize(), MessageBox(NULL,_T("... Before I Throw It Out!"),_T("Come And Get It ..."),MB_OK);
return (int)Msg.wParam;
}
continued ...
It was fun to get this working both 32 and 64 bit. The same exact code can be used for either 32 or 64 bit, if compiled with the respective 32 or 64 bit compiler. The neat part of this code is a procedure named ...
void DumpCOMMemory(HWND);
In that code I iterate through every interface and interface member using function pointers. Except for QueryInterface(), AddRef(), and Release(), all the interface functions are just 'stubbed out' and only contain printf output statements showing they were called. Note that to expedite this I modified the interface member function signatures of the function pointers to make iterating through them more in the nature of iterating through an array. Actually, the memory footprint created by COM objects is very much 'array like'. The variable type I used for collecting memory addresses of VTables and VTable pointers was size_t. Using this variable type works for both 32 and 64 bit. It is defined as an unsigned int in x86 and as an unsigned int* in x64, thereby being 4 bytes and 8 bytes respectively.
Client.cpp creates Client.exe and is a GUI program which, however, creates an AllocConsole() console window to show debug output of what's going on. There are two buttons on the form/dialog. The button on the left executes the DumpCOMMemory() procedure. Here is the console output I get when running 64 bit, if I start the program, click on the 'DumpCOMMemory' button, then exit the program. I'm thinking I'll put comments prefaced with the C++ '//' symbol to the right of the various debug output lines to describe details I think are noteworthy or important...
Entering fnWndProc_OnCreate() // OnCreate() is kind of a Constructor for the
sizeof(IGrid*) = 8 // Client's main and only window. In there I create
CoInitialize() Succeeded! // the console then call CoGetClassFactory(). That's
// where the DllGetClassObject() function gets called.
Entering DllGetClassObject() // That would be system code - not any of mine here.
Entering GridClassFactory Constructor! // Note that the sizeof(IGrid*) = 8. Actually, the
this->m_lRef = 0 // size of any pointer is 8 because its a 64 bit app.
Leaving GridClassFactory Constructor! // Anyway, DllGetClassObject() instantiates a
pCF = 2314112 // GridClassFactory() object (look in Grid.cpp), and
Entering GridClassFactory::QueryInterface() // the GridClassFactory object has a method named
*ppv = 2314112 // CreateInstance(), and that's where an FHGrid object
Entering GridClassFactory::AddRef()! // gets created.
this->m_lRef = 0
this->m_lRef = 1 // Which brings us to GridClassFactory::CreateInstance()
Leaving GridClassFactory::AddRef()! // (see below). Take note of the line ...
Leaving GridClassFactory::QueryInterface()
Leaving DllGetClassObject() // sizeof(*this) = 32
Entering GridClassFactory::AddRef()! // What that's saying is that the size in memory of
this->m_lRef = 1 // what's been created/instantiated at pGrid - which
this->m_lRef = 2 // afterall is a pointer to a grid, is 32 bytes. Do
Leaving GridClassFactory::AddRef()! // you know why that is? I guess you know I'm going
// to tell you! If you look at Grid.h you'll see the
Entering GridClassFactory::Release()! // C++ Class declaration for FHGrid. You'll note that
this->m_lRef = 2 // that Class publically inherits from three other
this->m_lRef = 1 // C++ Abstract Base classes, i.e., ...
Leaving GridClassFactory::Release()!
// IGrid
Entering GridClassFactory::QueryInterface() // IConnectionPointContainer
*ppv = 2314112 // IConnectionPoint
Entering GridClassFactory::AddRef()!
this->m_lRef = 1 // An Abstract Base Class is a Class that hasn't of yet
this->m_lRef = 2 // been implemented. If you look at the IGrid interface
Leaving GridClassFactory::AddRef()! // in Interfaces.h you'll see methods in that object
Leaving GridClassFactory::QueryInterface() // prefaced with the 'virtual' keyword and ending with an
// '=0;' IGrid as well as IConnectionPointContainer and
Entering GridClassFactory::Release()! // IConnectionPoint are actually structs, but that
this->m_lRef = 2 // distinction is greatly blurred in C++. The point is
this->m_lRef = 1 // though that these three structs/interfaces define
Leaving GridClassFactory::Release()! // memory blocks that the C++ compiler will create when
// it creates an FHGrid object. The IGrid interface for
CoGetClassObject() Succeeded! // x64 will be 128 bytes because it has 16 member
pClassFactory = 2314112 // functions (count them) including the three of
// IUnknown, from which it inherits. And now get this -
Entering GridClassFactory::CreateInstance() // a pointer to those 128 bytes occupies 8 bytes of the
Entering FHGrid Constructor! // 32 bytes that constitutes the memory allocation for
this->m_lRef = 0 // an FHGrid object. And as the code just left shows,
g_lObjs = 1 // pGrid = 2,314,144. At that address is stored the
sizeof(*this) = 32 // pointer to the IGrid VTable - which itself is a 128
Leaving FHGrid Constructor! // byte (64 for x86) memory allocation/block. So that
pGrid = 2314144 // accounts for 8 of the 32 bytes. What do you think
sizeof(*pGrid) = 32 // is stored at the remaining 24 bytes? Well, After the
Entering FHGrid::QueryInterface() // IGrid VTable pointer comes the
this = 2314144 // IConnectionPointContainer VTable pointer. That would
Entering FHGrid::AddRef() // be stored in the next 8 bytes starting at 2,314,152.
m_lRef = 0 // And following that in the next 8 bytes is the
m_lRef = 1 // IConnectionPoint VTable Pointer at 2,314,160. The
Leaving FHGrid::AddRef() // remaining 8 bytes of the 32 is the single member
*ppv = 2314144 // variable of the FHGrid Class - m_lRef (see FHGrid.h).
Leaving FHGrid::QueryInterface()
Leaving GridClassFactory::CreateInstance() // So, every time an instance of FHGrid is created the
// compiler will allocate 32 bytes for instance data.
IClassFactory->CreateInstance() Succeeded! // It may occur to you that 32 bytes doesn't seem like
pGrid = 2314144 // enough. Shouldn't the code of the interface functions
// be included in the size of the class? Actually not.
Entering GridClassFactory::Release()! // Interfaces are 'stateless'. They have no member
this->m_lRef = 1 // variables where any instance data is stored, and when
this->m_lRef = 0 // a program is loaded which contains a class only one
Entering GridClassFactory Destructor! // copy of the member functions need to be loaded in
this->m_lRef = 0 // memory, and those functions don't belong to any
Leaving GridClassFactory Destructor! // particular instance. They do operate on instance
GridClassFactory Has Been Destroyed! // data but the data they operate on is stored in the
Leaving GridClassFactory::Release()! // class instance data - not in the interfaces. So
Leaving fnWndProc_OnCreate() // that's why we only need 32 bytes in this case. To
// illustrate all this lets look at the output just left
// where somebody clicked on the 'DumpCOMMemory' button
Entering fnWndProc_OnCommand() // on the Client.exe window. There we see that 2,314,144
Entering DumpCOMMemory() // number again. Its a virtual function table pointer -
pGrid = 2314144 // pVTbl. Its the number PowerBASIC would give you if
pVTbl = 2314144 // you used its Objptr() function on the number returned
// by something like so ...
&pVTbl[0] = 2314144
&pVTbl[1] = 2314152 // pObject = NewCom "FHGrid.Grid"
&pVTbl[2] = 2314160
// You can think of these structures as I've described
VTbl = 2147547400 // above as integral arrays, and that's just how the
VTbl[0] = 2147488996 // code in DumpCOMMemory() iterates through them. For
// example, at 2,314,144 is stored the number
Entering FHGrid::QueryInterface() // 2,147,547,400. That number is the base address of
this = 2314144 // the IGrid VTable. You'll see that number in row 1
Leaving FHGrid::QueryInterface() // column 2 just below. Note those numbers increment
// in steps of 8 bytes, and there are 16 'slots' for the
Entering FHGrid::AddRef() // addresses of the 16 member functions of the IGrid
m_lRef = 1 // interface. In column 3 are the numbers stored in
m_lRef = 2 // the VTable slots. Those column three numbers are the
Leaving FHGrid::AddRef() // actual memory addresses for the interface functions.
// In DumpCOMMemory() you'll see where I've created
Entering FHGrid::Release() // function pointer prototypes to iterate through all
m_lRef = 2 // these functions and call them through these addresses.
m_lRef = 1
Leaving FHGrid::Release()
&pVTbl[i] &VTbl[j] pFn=VTbl[j] pFn() Function Pointer Call
===========================================================================
2314144 2147547400 2147488996
2314144 2147547408 2147489260
2314144 2147547416 2147489340
2314144 2147547424 2147489472 Called FHGrid::CreateGrid()
2314144 2147547432 2147489496 Called FHGrid::SetRowCount()
2314144 2147547440 2147489496 Called FHGrid::GetRowCount()
2314144 2147547448 2147489544 Called FHGrid::SetData()
2314144 2147547456 2147489568 Called FHGrid::GetData()
2314144 2147547464 2147489592 Called FHGrid::FlushData()
2314144 2147547472 2147489616 Called FHGrid::Refresh()
2314144 2147547480 2147489640 Called FHGrid::GetVisibleRows()
2314144 2147547488 2147489664 Called FHGrid::GethGrid()
2314144 2147547496 2147489688 Called FHGrid::GethCell()
2314144 2147547504 2147489688 Called FHGrid::GethCell()
2314144 2147547512 2147489712 Called FHGrid::SetCellAttributes()
2314144 2147547520 2147489736 Called FHGrid::DeleteRow()
2314152 2147547368 2147489760 Called FHGrid::EnumConnectionPoints()
2314152 2147547376 2147489788 Called FHGrid::FindConnectionPoint()
2314160 2147547296 2147489816 Called FHGrid::GetConnectionInterface()
2314160 2147547304 2147489844 Called FHGrid::GetConnectionPointContainer()
2314160 2147547312 2147489872 Called FHGrid::Advise()
2314160 2147547320 2147489900 Called FHGrid::Unadvise()
2314160 2147547328 2147489924 Called FHGrid::EnumConnections()
Entering FHGrid::QueryInterface()
this = 2314144 // Let me illustrate how FHGrid::CreateGrid was called.
Entering FHGrid::AddRef() // Note that since this is a member of the IGrid
m_lRef = 1 // interface (VTable), the address 2,314,144 holds the
m_lRef = 2 // base address of its VTable, which is 2,147,547,400.
Leaving FHGrid::AddRef() // Note that QueryInterface(), AddRef(), and Release()
*ppv = 2314152 // are the 1st three functions in the IGrid VTable, i.e.,
Leaving FHGrid::QueryInterface()
pGrid->QueryInterface(IID_IConnectionPointContainer) Succeeded!
pCnPtCnt = 2314152
Entering FHGrid::Release() // VTbl[0], VTbl[1], and VTbl[2], which are stored at
m_lRef = 2 // &VTbl[0], &VTbl[1], and &VTbl[2]. CreateGrid() is the
m_lRef = 1 // 1st member after Release, which, in zero based terms, is
Leaving FHGrid::Release() // at VTbl[3], which is stored at &VTbl[3]. Those numbers
Leaving DumpCOMMemory() // would be 2,147,489,472 and 2,147,547,424, respectively.
Leaving fnWndProc_OnCommand() // To prove this we can make a function pointer call at the
// 2,147,489,472 number, i.e., VTb[3], and we'll get the
// output shown above, i.e., 'Called FHGrid::CreateGrid()'.
Entering fnWndProc_OnCommand() // Of course, that's easier said than done, as calling
Entering DestroyGrid() // object member functions is nasty in PowerBASIC and about
pGrid = 2314144 // five times worse in C syntax, but it can nevertheless be
Entering FHGrid::Release() // done.
m_lRef = 1
m_lRef = 0 // Discussion continued just below ...
Will Now Delete this ...
Leaving FHGrid::Release()
Entering FHGrid Destructor!
g_lObjs = 1
g_lObjs = 0
Leaving FHGrid Destructor!
Leaving DestroyGrid()
Leaving fnWndProc_OnCommand()
Entering DestroyGrid()
pGrid = 0
Leaving DestroyGrid()
Entering DllCanUnloadNow()
g_lObjs = 0
g_lLocks = 0
I'm Outta Here!
Leaving DllCanUnloadNow()
By the way, here's the output from the exact same Client.exe program and COM object but running 32 bit ...
Entering fnWndProc_OnCreate()
sizeof(IGrid*) = 4
CoInitialize() Succeeded!
Entering DllGetClassObject()
Entering GridClassFactory Constructor!
this->m_lRef = 0
Leaving GridClassFactory Constructor!
pCF = 7342712
Entering GridClassFactory::QueryInterface()
*ppv = 7342712
Entering GridClassFactory::AddRef()!
this->m_lRef = 0
this->m_lRef = 1
Leaving GridClassFactory::AddRef()!
Leaving GridClassFactory::QueryInterface()
Leaving DllGetClassObject()
Entering GridClassFactory::AddRef()!
this->m_lRef = 1
this->m_lRef = 2
Leaving GridClassFactory::AddRef()!
Entering GridClassFactory::Release()!
this->m_lRef = 2
this->m_lRef = 1
Leaving GridClassFactory::Release()!
Entering GridClassFactory::QueryInterface()
*ppv = 7342712
Entering GridClassFactory::AddRef()!
this->m_lRef = 1
this->m_lRef = 2
Leaving GridClassFactory::AddRef()!
Leaving GridClassFactory::QueryInterface()
Entering GridClassFactory::Release()!
this->m_lRef = 2
this->m_lRef = 1
Leaving GridClassFactory::Release()!
CoGetClassObject() Succeeded!
pClassFactory = 7342712
Entering GridClassFactory::CreateInstance()
Entering FHGrid Constructor!
this->m_lRef = 0
g_lObjs = 1
Leaving FHGrid Constructor!
pGrid = 7342728
Entering FHGrid::QueryInterface()
this = 7342728
Entering FHGrid::AddRef()
m_lRef = 0
m_lRef = 1
Leaving FHGrid::AddRef()
*ppv = 7342728
Leaving FHGrid::QueryInterface()
Leaving GridClassFactory::CreateInstance()
IClassFactory->CreateInstance() Succeeded!
pGrid = 7342728
Entering GridClassFactory::Release()!
this->m_lRef = 1
this->m_lRef = 0
Entering GridClassFactory Destructor!
this->m_lRef = 0
Leaving GridClassFactory Destructor!
GridClassFactory Has Been Destroyed!
Leaving GridClassFactory::Release()!
Leaving fnWndProc_OnCreate()
Entering fnWndProc_OnCommand()
Entering DumpCOMMemory()
pGrid = 7342728
pVTbl = 7342728
&pVTbl[0] = 7342728
&pVTbl[1] = 7342732
&pVTbl[2] = 7342736
VTbl = 268498668
VTbl[0] = 268440522
Entering FHGrid::QueryInterface()
this = 7342728
Leaving FHGrid::QueryInterface()
Entering FHGrid::AddRef()
m_lRef = 1
m_lRef = 2
Leaving FHGrid::AddRef()
Entering FHGrid::Release()
m_lRef = 2
m_lRef = 1
Leaving FHGrid::Release()
&pVTbl[i] &VTbl[j] pFn=VTbl[j] pFn() Function Pointer Call
===========================================================================
7342728 268498668 268440522
7342728 268498672 268440709
7342728 268498676 268440779
7342728 268498680 268440896 Called FHGrid::CreateGrid()
7342728 268498684 268440912 Called FHGrid::SetRowCount()
7342728 268498688 268440912 Called FHGrid::GetRowCount()
7342728 268498692 268440944 Called FHGrid::SetData()
7342728 268498696 268440960 Called FHGrid::GetData()
7342728 268498700 268440976 Called FHGrid::FlushData()
7342728 268498704 268440992 Called FHGrid::Refresh()
7342728 268498708 268441008 Called FHGrid::GetVisibleRows()
7342728 268498712 268441024 Called FHGrid::GethGrid()
7342728 268498716 268441040 Called FHGrid::GethCell()
7342728 268498720 268441056 Called FHGrid::GethCell()
7342728 268498724 268441072 Called FHGrid::SetCellAttributes()
7342728 268498728 268441088 Called FHGrid::DeleteRow()
7342732 268498656 268441104 Called FHGrid::EnumConnectionPoints()
7342732 268498660 268441123 Called FHGrid::FindConnectionPoint()
7342736 268498620 268441142 Called FHGrid::GetConnectionInterface()
7342736 268498624 268441161 Called FHGrid::GetConnectionPointContainer()
7342736 268498628 268441180 Called FHGrid::Advise()
7342736 268498632 268441199 Called FHGrid::Unadvise()
7342736 268498636 268441215 Called FHGrid::EnumConnections()
Entering FHGrid::QueryInterface()
this = 7342728
Entering FHGrid::AddRef()
m_lRef = 1
m_lRef = 2
Leaving FHGrid::AddRef()
*ppv = 7342732
Leaving FHGrid::QueryInterface()
pGrid->QueryInterface(IID_IConnectionPointContainer) Succeeded!
pConnectionPointContainer = 7342732
Entering FHGrid::Release()
m_lRef = 2
m_lRef = 1
Leaving FHGrid::Release()
Leaving DumpCOMMemory()
Leaving fnWndProc_OnCommand()
Entering DestroyGrid()
pGrid = 7342728
Entering FHGrid::Release()
m_lRef = 1
m_lRef = 0
Will Now Delete this ...
Leaving FHGrid::Release()
Entering FHGrid Destructor!
g_lObjs = 1
g_lObjs = 0
Leaving FHGrid Destructor!
Leaving DestroyGrid()
Entering DllCanUnloadNow()
g_lObjs = 0
g_lLocks = 0
I'm Outta Here!
Leaving DllCanUnloadNow()
Here's how it works, that is, calling IGrid::CreateGrid() through a function pointer. First, if you've attempted to build this code and have run midl.exe on my FHGrid.idl file that utility would have created a number of output files, one of which would be FHGrid.h. I didn't use that file in my project but I could have. It contains, among other things, C++ and C interface definitions derived from the Idl code in the Idl file. Here are the C++ and C declarations of interface function CreateGrid. First C++ ...
virtual HRESULT STDMETHODCALLTYPE CreateGrid
(
/* [in] */ HWND hParent,
/* [in] */ BSTR strSetup,
/* [in] */ int x,
/* [in] */ int y,
/* [in] */ int cx,
/* [in] */ int cy,
/* [in] */ int iRows,
/* [in] */ int iCols,
/* [in] */ int iRowHt,
/* [in] */ int iSelectionBackColor,
/* [in] */ int iSelectionTextColor,
/* [in] */ BSTR strFontName,
/* [in] */ int iFontSize,
/* [in] */ int iFontWeight
) = 0;
In C its not going to be exactly the same because there is a certain amount of nastiness that C++ hides from us. The price you pay for that is you might not understand what's going on behind the scenes. It becomes a function pointer call in C ..
HRESULT (STDMETHODCALLTYPE* CreateGrid)
(
/* [in] */ IGrid* This,
/* [in] */ HWND hParent,
/* [in] */ BSTR strSetup,
/* [in] */ int x,
/* [in] */ int y,
/* [in] */ int cx,
/* [in] */ int cy,
/* [in] */ int iRows,
/* [in] */ int iCols,
/* [in] */ int iRowHt,
/* [in] */ int iSelectionBackColor,
/* [in] */ int iSelectionTextColor,
/* [in] */ BSTR strFontName,
/* [in] */ int iFontSize,
/* [in] */ int iFontWeight
);
If you count them there are more parameters there; 15 to be exact. The other major difference though is that the text 'CreateGrid' is enclosed within parentheses along with the STDMETHODCALLTYPE, and there is that mysterious '*' in there too. That little '*' symbol plus the parentheses is a dead giveaway for a C/C++ function pointer call. In this latter C declaration the 1st parameter is IGrid* This. Remember above where I stated that interfaces are 'stateless', i.e., there are no member variables in interfaces? Well, that's true but interface member functions have to work on instance data somehow, for example Get()/Set() mutators that change class instance data. But how do they know where that instance data is at? Well, you're looking at it. Every time an interface member function is called the first parameter of the call will be a pointer to where the class instance data is at. In C its plainly visible but in C++ or high level PowerBASIC its the hidden this/me pointer.
I wanted to call all these various entities with an array like syntax and iterate through them so this is what I did. I created the variables pVTbl and VTbl of type size_t. This variable type is declared in various low level Windows or C Run Time headers, and is 4 bytes in x86 and 8 bytes in x64. At the top of DumpCOMMemory() I retrieved my stored IGrid* returned from IClassFactory::CreateInstance() in fnWndProc_OnCreate(). So I cast it like so ...
size_t* pVTbl=0;
size_t* pIUnk=0;
size_t* VTbl=0;
IGrid* pGrid=(IGrid*)GetWindowLongPtr(hHost,0);
pVTbl=(size_t*)pGrid;
printf(" pGrid = %u\n", pGrid);
printf(" pVTbl = %u\n", pVTbl);
printf(" &pVTbl[0] = %u\n", &pVTbl[0]);
printf(" &pVTbl[1] = %u\n", &pVTbl[1]);
printf(" &pVTbl[2] = %u\n", &pVTbl[2]);
And that produced this output ...
pGrid = 2314144
pVTbl = 2314144
&pVTbl[0] = 2314144
&pVTbl[1] = 2314152
&pVTbl[2] = 2314160
The only thing I want to change above to use the C function pointer declaration is that first IGrid* parameter. I'd rather use size_t instead, which is OK so long as the correct address is stored in my size_t variable. Also, I'll call my function pointer pCreateGrid instead ...
pCreateGrid=(HRESULT (STDMETHODCALLTYPE*)(size_t*,HWND,BSTR,int,int,int,int,int,int,int,int,int,BSTR,int,int))VTbl[3];
That looks complicated and it is but can be easily understood by taking it apart in pieces. In its simplest form it would be nice if one could do simply this ...
pCreateGrid = VTbl[3];
That says "assign whatever is at VTbl[3] to pCreateGrid". The number 2147489472 is the address of the IGrid::CreateGrid() member function, but C/C++ won't allow it to be assigned that way because pCreateGrid was declared as a function pointer that returns an HRESULT, has STDMETHODCALLTYPE calling convention, and has 15 parameters of various types. So to make the compiler happy one must cast VTbl[3] to a function pointer type that returns an HRESULT, uses STDMETHODCALLTYPE calling convention, and has those 15 various parameters. Casts in C/C++ usually look pretty simple, for example, casting the return from the GetWindowLongPtr() above from a LONG_PTR to an IGrid* i.e. ...
IGrid* pGrid=(IGrid*)GetWindowLongPtr(hHost,0);
... where this simple term does the trick ...
(IGrid*)
But in this function pointer case the expression between the parentheses is rather nasty. In fact, you need the entire function pointer declaration to the right of the equal sign except the name of the function pointer, that is, pCreateGrid ...
(HRESULT (STDMETHODCALLTYPE*) (size_t*,HWND,BSTR,int,int,int,int,int,int,int,int,int,BSTR,int,int))
^ ^ ^
return stack protocol 15 Various Parameters
So if you look up in DumpCOMMemory() you'll see the entire sequence of these lines ...
//IGrid::CreateGrid()
pCreateGrid=(HRESULT (STDMETHODCALLTYPE*)(size_t*,HWND,BSTR,int,int,int,int,int,int,int,int,int,BSTR,int,int))VTbl[3];
printf(" %u\t%u\t%u\t",&pVTbl[0],&VTbl[3],pCreateGrid);
HWND hWnd=NULL;
BSTR strNothing=NULL;
pCreateGrid(&pVTbl[0], hWnd, strNothing, 0, 0, 0, 0, 0, 0, 0, 0, 0, strNothing, 0, 0);
... which created this line of output ...
&pVTbl[i] &VTbl[j] pFn=VTbl[j] pFn() Function Pointer Call
===========================================================================
....... .......... ..........
2314144 2147547424 2147489472 Called FHGrid::CreateGrid()
....... .......... ..........
Enough of this. Its time to start building (rebuilding actually) the grid!!!
I understand that you're likely doing this for the educational experience, but is there a practical matter as to why you're not using ATL? It really does eliminate the vast majority of the COM funk from your code and let you focus on the actual implementation rather than the scaffolding.
Well, maybe I do need more practice, so the educational value isn't to be discounted. It took me awhile to put this together, even though I've done it all before. So I think I benefited from the practice!
COM is involved I would say, and I was somewhat daunted by the issue of doing everything in x64. I've simply not done much x64 and I really had no idea how switching to x64 Operating systems would affect everything having to do with COM. Much to my surprise I've found everything – at least what I've done so far – works just like 32 bit. So having reviewed the fundamentals at this low a level as I did with x64 was a good idea I think for me. So far I'm amazed at how well Microsoft has made everything with x64 work out.
I'm somewhat familiar with ATL and I've made a few learning type projects with it. Its also impressive in terms of how Microsoft set it up to reduce code bloat. I've no better reason for not using it other than my simply not liking it. I prefer to figure out things myself and understand how they work rather than relying on auto-generated code. I think at base its because I like to be somewhat self reliant, and having started with coding before the internet was much of a part of the picture, I've never proceeded with the idea in mind that if I'd get bad stuck, all I'd have to do is shoot out a question on some coding forum and piles of folks would jump in and attempt to solve my problems for me – hence the need to fully understand the basics. So I don't like to get too far ahead of myself.
Then too I'm no friend of templates. I'm sure you've run into C coders like me who've made off with what they wanted from C++ (classes, reference parameters) and left the rest (templates, endless arguments about object design and class inheritance hierarchies).
I realize it was Microsoft's idea that everybody would use ATL to build COM components, simply because so much of the underlying COM infrastructure code was both boilerplate and complex. So C++ had ATL and Visual Basic had what it had – ActiveX Control visual designers and so forth. But none of that helped us much with PowerBASIC. If you wanted to build ActiveX Controls with PowerBASIC you pretty much had to start from scratch and fully understand everything to the point of building Vtables in memory, so on and so forth. Actually, that was my main interest in ATL – to reverse engineer the auto-generated code so to speak, so I could see what it did. The reason for this was because there was little to no documentation out there on building ActiveX Controls in the raw, the way it would have to be done in PowerBASIC, or C for that matter without ATL. And you know, now that I think of it, those internet articles by Jeff Glatt – I'm sure you remember them "COM In Plain C" ...
http://www.codeproject.com/Articles/13601/COM-in-plain-C
... were really, really popular. A lot of C++ coders I believe were somewhat fed up with the various 'text substitution engines', ATL, MFC, etc., coming out of Microsoft, and just wanted to know how stuff worked.
But I'm not above saving a little time. Because so much of the COM infrastructure code is so boilerplate, only a little modification and addition to what I have posted above – a simple window and some logic to call back into an event sink in the server, and I'll have a pretty good control template of my own for future use. And you know, that isn't a whole lot different from how a lot of us SDK coders operate with our many years collections of various template code.
Mike,
My reason for following Fred's work is I use Visual Studio Express 2013 and there is no ATL/MFC so bare to the metal is the only way to go.
James
If you are following the code James I'd appreciate if you would let me know of any issues you run into with the newer compilers. I had thought of doing what you are doing, that is, installing the latest SDK, since I'm doing command line compiling anyway so Visual Studio's fancy wizards aren't helping me anyway! But I paid $550 for it a few years back (pretty much for my Windows CE data collector work - which didn't pan out. I hated Windows Mobile, like just about everyone else, and reverted to Windows CE. And I had free tools for that work (Microsoft's eMbedded Visual C++ 4.0)).
The other thing is, I'd actually prefer to use GNU, but that rotten problem I had years ago with not being able to figure out how to export DllGetClassObject() so that Microsoft's COM Service Control Manager ( SCUM ) could read it - well, that still has me stumped. Maybe that would be something you might want to look into someday because I know you like to use the GNU tools. And I hate like the dickens to admit defeat. But that defeated me no ifs ands or buts. I lost weeks on that. And I know it can be done. I just can't figure out how to do it.
Fred,
A week or so ago I went back to old COM code that I thought I had working based on your original com project.
I beat on it awhile but other things took precedence. I hope to get back to it but I got a bit burned out on it the first time around.
Your code here is a bit beyond what I want to do. I have no need for an ActiveX grid so I lack your motivation.
I'm just hoping I can gain a bit of insight into 64bit COM from your work.
James