First Steps With 64 Bit COM

Started by Frederick J. Harris, November 17, 2013, 10:45:19 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Frederick J. Harris

     Just within the past couple weeks I started playing around with 64 bit COM and have learned a few interesting things I'd like to share.  Whether this stuff is obvious to everyone else or not I don't know, but to me some of it was new and very interesting, and I'd like to share it. 

     So here is the context for my beginnings here with 64 bit COM.  Within the past year I've incorporated my COM based grid control into my work apps and it works great!  Of course, its as I posted it here on my board, and I did it with low level PowerBASIC 32 bit code.  While I expect 32 bit code to be functional for a long time, I felt it incumbent upon myself to explore the possibilities of converting my code to 64 bit C++ (mostly C really).  And since 32 bit dlls don't work that great hosted by 64 bit code my starting point has to be the ActiveX grid really that is used throughout my apps.

     So I figured my first steps would be experimenting with the VC++ 64 bit compiler tool chain.   In most of my C and C++ work in COM I used Microsoft's old VC98 from the Enterprise Version of Visual Studio 6.  Old, I know, but a standard in its day and in around that time, i.e., 1998 or so, Microsoft pretty much had COM completely worked out - particularly with regard to ActiveX Controls.  The documentation that came with that old version on the MSDN disks was really good and complete.

     But this is 2013 and VC98 can't do 64 bit so that forced me to move up to Visual Studio 2008 Pro (Every 10 years I get a new version of Visual Studio whether I need it or not.  My next will be 2018 I guess.)  So that version allows me to create either 32 bit or 64 bit code. 

     I'll let the 'cat out of the bag'!  I know I'm overly verbose so I won't keep you waiting.  So here in a nutshell is what I've found out that is really neat!  One can use just about the same exact code to create 32 bit or 64 bit COM dlls!  And unbelievably enough, that also goes for the registry entries!  That whole issue gave me some concern, and when I first started I gave my 64 bit COM code different program ids and CLSIDs than my 32 bit C++ code.  After compiling both versions the question arose in my mind as to just how much overlap in code and registry entries would be possible before things started to go haywire and one would start getting crashes and load failures.  So I experimented with it and thought about it some and have pretty much determined that total overlap is possible.  What I mean by that is that on the same 64 bit operating system it would be possible to have both a 32 bit version and a 64 bit version of an app working.  And both apps could consume a registered COM dll in 32 bit and 64 bit flavors.  The Dlls could be named exactly the same and have the exact same registry entries insofar as Program IDs.  The only caveat is that they be in different directories, obviously.  One can't have two files of the same exact name in the same directory.  The key to the whole thing and what seems to make it work is Microsoft's creation of the ...

\\HKEY_CLASSES_ROOT\Wow6432Node

in the Registry.  In something confusing like this, which involves the inner details of how Microsoft's SCUM layer finds and loads COM objects, I think it might help if I provide concrete specifics as to my little test projects, the paths involved, and the exact registry entries.  So here goes.  My 64 bit COM dll working directory is here ...

C:\Code\VStudio\VC++9\64_Bit\FHGrid

and my 32 bit COM dll working directory is here ...

C:\Code\VStudio\VC++9\FHGrid

     My CLSIDs, IIDs, and LIBIDs are as follows for both my 32 and 64 bit code (in fact, here's my whole Interface.h file - used exactly like below for 32/64 compiles) ...


//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;
};


     Again, that will work for both 32/64 bit compiled code.  My Program ID information from my Server.cpp file looks like so ...


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");   


     Now let me describe how and where this information goes in the Registry to make everything work out the way I said for both 32/64 bit versions.  For my purposes, I need to place things in three separate places in the Windows Registry.  First, there is the CLSID.  In 64 bit code it works exactly the same as the older 32 bit systems, i.e., it goes here ...


Key                                                                          Value


\HKEY_CLASSES_ROOT\CLSID\{30000000-0000-0000-0000-000000000000}              Fred Harris Grid Control v1
                        \InProcServer32                                      C:\Code\VStudio\VC++9\64_Bit\FHGrid.dll
                        \ProgID                                              FHGrid.Grid.1
                        \VersionIndependentProgID                            FHGrid.Grid



However, on 32 bit registrations on 64 bit Operating Systems it goes here ...


\HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{30000000-0000-0000-0000-000000000000}  Fred Harris Grid Control v1
                        \InProcServer32                                      C:\Code\VStudio\VC++9\64_Bit\FHGrid.dll
                        \ProgID                                              FHGrid.Grid.1
                        \VersionIndependentProgID                            FHGrid.Grid


     Note that the path to the dll goes in an InProcServer32 sub key whether the dll is 32 or 64 bit.  However, as can be seen above, a 32 bit application will look in one place for the dll to load and a 64 bit application will look in another place.  That is basically the key that makes it possible to have two identical COM dlls in terms of Program IDs and CLSIDs work on the same system. 

     The 2nd place Registry entries need to go is under HKEY_CLASSES_ROOT\"Your Program ID"  For my dll it looks like this and note that in terms of ProgIDs the 32 bit objects are mixed in with the 64 bit objects ...

Key                                                                          Value

\HKEY_CLASSES_ROOT\FHGrid.Grid                                               Fred Harris Grid Control v1
                  \CLSID                                                     {30000000-0000-0000-0000-000000000000}
                  \CurVer                                                    FHGrid.Grid.1
\HKEY_CLASSES_ROOT\FHGrid.Grid.1                                             Fred Harris Grid Control v1
                  \CLSID                                                     {30000000-0000-0000-0000-000000000000}


     So in other words, just looking at those program ids in the Registry there is no way of knowing whether that object is a 32 bit or 64 bit entity.  To determine that one would have to see where that CLSID is located, the two choices being under HKEY_CLASSES_ROOT\CLSID for 64 bit objects and HKEY_CLASSES_ROOT\Wow6432Node\CLSID for 32 bit objects.  Yet another way of looking at it is that if you first create a 32 bit COM object and register it, then you create the same COM object with the same program IDs and CLSIDs but in 64 bit binary form and register that, there will be no changes to that part of the registry that you can find.

     The 3rd place I put registries in the Windows Registry is under the HKEY_CLASSES_ROOT\Typelib key.  Here the plot thickens a bit.  If you want type libraries exposed to IDE products such as the PowerBASIC COM Browser or Jose's Type Lib Browser you need this key.  Let's start with the scenario you first register a 32 bit COM object.  And I might point out here that the typelib entries are not usually made directly by server code as in the previous cases but indirectly by system code through calls to LoadTypeLibEx().  In the case of the 32 bit registration, it will look like this ...


Key                                                                          Value

\HKEY_CLASSES_ROOT\TypeLib\{30000000-0000-0000-0000-000000000003}            (value not set)
                          \1.0                                               FHGrid TypeLib
                              \0                                             (value not set)
                                \win32                                       C:\Code\VStudio\VC++9\FHGrid32\FHGrid.dll
                              \FLAGS                                         0
                              \HELPDIR                                       C:\Code\VStudio\VC++9\FHGrid32


     If you then register the same named dll with the same program ids, CLSIDs, and LIBIDs in 64 bit configuration, but of course in a different directory, the above will change to this ...


\HKEY_CLASSES_ROOT\TypeLib\{30000000-0000-0000-0000-000000000003}            (value not set)
                          \1.0                                               FHGrid TypeLib
                              \0                                             (value not set)
                                \win32                                       C:\Code\VStudio\VC++9\FHGrid32\FHGrid.dll
                                \win64                                       C:\Code\VStudio\VC++9\64_Bit\FHGrid\FHGrid.dll
                              \FLAGS                                         0
                              \HELPDIR                                       C:\Code\VStudio\VC++9\64_Bit\FHGrid\FHGrid.dll

     If you haven't spotted the difference, all that happened is that a win64 sub key was added about midway down below the win32 key, and the value was set to the path string to the 64 bit dll.  Interesting, isn't it?

     In thinking about it, except for perhaps the 'InProcServer32' key which holds paths to both 32/64 bit binaries, it all makes sense.  COM objects are usually loaded by the COM Service Control Manager (SCUM) in response to either CoCreateInstance() or CoGetClassObject() COM Api function calls.  Both of these functions take as a parameter the CLSID of the object desired.  System code knows whether it is a 32 bit or 64 bit process running, so it will have no problem finding the path to the correct Dll to load.  It'll just look at the InProcServer32 key under either ...

\HKEY_CLASSES_ROOT\CLSID\{.....}

for 64 bit processes or ...

\HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{.....}

for 32 bit processes.  Language tools such as Visual Studio, etc., are helped by the TypeLib key.  If the language tool's user is wanting an enumeration of 32 bit type libraries, there is the appropriate sub key under the TypeLib key for those.  Likewise for 64 bit requests.  So it all seems to hang together.

     Shortly I'll post the code I'm working on which can be compiled for both 32 and 64 bit binaries.  Basically, I haven't even started with the grid code yet (its just stubbed out) as I felt I first had to get these foundational COM infrastructure and registry issues resolved and understood before starting to write GUI code.