Showing posts with label IClassFactory. Show all posts
Showing posts with label IClassFactory. Show all posts

Friday, 9 August 2019

C++ code to debug/investigate COM class creation problems

With technology sometimes debugging and diagnostics are required, COM is no exception. Presently, I have been debugging a Python COM class that was not instantiating in VBA using the New keyword. That code works now, so watch out for a post on that soon. The following code was to be an appendix to that post but is useful in its own right so I am depositing it here.

Low level C++ Code to instantiate a COM Component

This code is to probe error messages of a COM component instantiation. VBA gives some error codes which are not necessarily useful. In VBA, when an object is created with the New keyword, 'under-the-hood' a call to CoCreateInstance is made, which in turn is made up of calls to CoGetClassObject to get a class factory and then a call to CreateInstance is called on the class factory. These steps which are implicit to VBA are given explicitly below in C++ so one can step through and better diagnose any errors.

In you want to use this code you will no doubt have to change CLSID_FooBar and IID_FooBar for your specific case.

Problems that can be examined with this technique include but are not limited to (a) registration issues, (b) path problems, (c) 32bit/64 bit mismatch problems.

The code is for a C++ console application.


#include <iostream>
#include "objbase.h"
#include <combaseapi.h>
#include <assert.h>

int main()
{
 ::CoInitialize(0);
 HRESULT hr = S_OK;

 GUID CLSID_FooBar;
 CLSIDFromString(L"{25F9C67B-8DBB-4787-AA84-D3D667ED0457}", &CLSID_FooBar);

 GUID IID_FooBar;
 CLSIDFromString(L"{B8FFDEFA-3EFB-4725-8CDD-1F6A9E35DD7C}", &IID_FooBar);

 GUID IID_IUnknown;
 CLSIDFromString(L"{00000000-0000-0000-c000-000000000046", &IID_IUnknown);

 GUID IID_IDispatch;
 CLSIDFromString(L"{00020400-0000-0000-c000-000000000046", &IID_IDispatch);

 GUID IID_IClassFactory;
 CLSIDFromString(L"{00000001-0000-0000-c000-000000000046", &IID_IClassFactory);

 IClassFactory *pFactoryFooBar;
 // CLSCTX_INPROC_SERVER |  CLSCTX_LOCAL_SERVER

 { // Test 1 Create the FooBar class via class factory requesting IUnknown 
  hr = ::CoGetClassObject(CLSID_FooBar, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, reinterpret_cast<void**>(&pFactoryFooBar));
  assert(S_OK == hr);

  IUnknown *pUnkFooBar;
  hr = pFactoryFooBar->CreateInstance(NULL, IID_IUnknown, reinterpret_cast<void**>(&pUnkFooBar));
  assert(S_OK == hr);

  IUnknown *pFooBar;
  hr = pUnkFooBar->QueryInterface(IID_FooBar, reinterpret_cast<void**>(&pUnkFooBar));
  assert(S_OK == hr);

 }

 IDispatch *pDispFooBar;
 hr = CoCreateInstance(CLSID_FooBar, NULL, CLSCTX_INPROC_SERVER, IID_IDispatch,
  reinterpret_cast<void**>(&pDispFooBar));
 assert(S_OK == hr);

 // get disp id
 DISPID id = -1; //default
 LPOLESTR string = const_cast <LPOLESTR>(L"Sum");
 hr = pDispFooBar->GetIDsOfNames(IID_NULL, &string, DISPATCH_METHOD, LOCALE_USER_DEFAULT, &id);
 assert(S_OK == hr);

 UINT ctinfo = -1;
 hr = pDispFooBar->GetTypeInfoCount(&ctinfo);
 assert(S_OK == hr);

 ::CoUninitialize();

 
}

P.S. It is possible to drill down even further because many COM servers are implemented as DLLs, we could write code to load the DLL into memory and then manually get the entry point DllGetClassFactory and call into it to get the class factory manually. One for another day perhaps because at the moment I am working with Python which does not use Dlls in that sense (at least I don't think so)

Wednesday, 14 June 2017

ATL Notes 1 - Inheritance Hierarchy

So Excel VBA developers may wonder how to make their code run as fast as compiled VB6 and I'm, afraid they can't as VB6 is no longer supported. To increase the speed of your code you need to use C++ and then call in from VBA using COM. So you need a C++ COM technology and this is what Active Template Library is.

The Books

So I have completed reading one ATL book, Beginning ATL Programming by Richard Grimes et al (Wrox 1999) and in the final stages of reading another, Inside ATL by King and Shepherd (MSPress 1999). So it is worth putting up some revision notes so I don't forget all that I have learnt. Whilst the prose of the books was better than the dry Microsoft documentation, the MS website remains the place to link to. To follow these notes you'll need to understand key C++ features such as templates and multiple inheritance. You also need to be very familiar with the COM specification, we shall not here explain the role of IUnknown or IDispatch. We'll limit the focus to in-process DLLs and ignore .EXEs

Walkthrough

So I am walking through creating a new ATL project, my project name is ATLProject2. Once through the new ATL project wizard one is confronted with a great many files but don't be intimidated, there are more wizards from the Class View so ensure the Class View is visible.

From the Class View go to right-click menu and take Add Class and then take ATL Simple Object which throws the ATL Simple Object Wizard. On the Names Dialog, in the C++ Short Name type "CoolCode" and the other fields are auto-generated for you. Click through the File Type Handler Options to the last dialog, Options which looks like this



So I have checked Support ISupportErrorInfo because we will want to throw rich error information from C++ to VBA. Click Finish and some code is generated for you.

// CCoolCode

class ATL_NO_VTABLE CCoolCode :
 public CComObjectRootEx<CComSingleThreadModel>,
 public CComCoClass<CCoolCode, &CLSID_CoolCode>,
 public ISupportErrorInfo,
 public IDispatchImpl<ICoolCode, &IID_ICoolCode, &LIBID_ATLProject2Lib, 
                                       /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
 CCoolCode()
 {
 }

DECLARE_REGISTRY_RESOURCEID(IDR_COOLCODE)


BEGIN_COM_MAP(CCoolCode)
 COM_INTERFACE_ENTRY(ICoolCode)
 COM_INTERFACE_ENTRY(IDispatch)
 COM_INTERFACE_ENTRY(ISupportErrorInfo)
END_COM_MAP()

// ISupportsErrorInfo
 STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);


 DECLARE_PROTECT_FINAL_CONSTRUCT()

 HRESULT FinalConstruct()
 {
  return S_OK;
 }

 void FinalRelease()
 {
 }

public:



};

OBJECT_ENTRY_AUTO(__uuidof(CoolCode), CCoolCode)

So it's worth showing the inheritance hierarchy image/svg+xml CComObjectRootBase ISupportErrorInfo IDispatchImpl<ICoolCode, &IID_ICoolCode, &LIBID> CComObject<CCoolCode> CComObjectRootBase CComCoClass<CCoolCode, &CLSID_CoolCode> CComObjectRootEx<CComSingleThreadModel> CCoolCode And we can give information as to what each class in the hierarchy does
ClassNamePurpose
CComObjectRootBaseHolds the reference count member
CComObjectRootExHandles reference counting based on the threading model
CComSingleThreadModelPassed as template parameter to CComObjectRootEx. This means reference counting need not be thread safe.
CComCoClassImplements IClassFactory with the help of a creator class.
ISupportErrorInfoDrives rich error information familiar to VBA devs.
IDispatchImplIf you selected a Dual interface (I recommend) then you get an implementation of IDispatch driven off the type library hence the parameters
CCoolCodeYour class and your logic but never gets directly instantiated
CComObject<CCoolCode>What gets instantiated and what implements IUnknown::QueryInterface

Never new your class

So your class never gets instantiated with the new keyword, it can't because it has no vtable because of the ATL_NO_VTABLE macro. Instead, a creator class such creates an instance of CComObject (when not aggregated) with your class as a template.

To illustrate, it is worth looking at what happens when a client gets hold of IClassFactory and calls IClassFactory::CreateInstance, so find the definition of CComCoClass (select, F12) to get to this (abridged) code

template <class T, const CLSID* pclsid = &CLSID_NULL>
class CComCoClass
{
public:
 DECLARE_CLASSFACTORY()
 DECLARE_AGGREGATABLE(T)
 typedef T _CoClass;

        ...

 template <class Q>
 static HRESULT CreateInstance(
  _Inout_opt_ IUnknown* punkOuter,
  _COM_Outptr_ Q** pp)
 {
  return T::_CreatorClass::CreateInstance(punkOuter, __uuidof(Q), 
                  (void**) pp);
 }
 template <class Q>
 static HRESULT CreateInstance(_COM_Outptr_ Q** pp)
 {
  return T::_CreatorClass::CreateInstance(NULL, __uuidof(Q),
                  (void**) pp);
 }
};

So in the above code one can see CreateInstance being called in two use cases, (i) where there is an aggregating object and (ii) where there isn't but the code shares a common element of T::_CreatorClass::CreateInstance. It is worth knowing that T::_CreatorClass is defined by the DECLARE_AGGREGATABLE macro which was generated by your choice in the wizard to allow aggregation. This macro is defined as

#define DECLARE_AGGREGATABLE(x) public:\
 typedef ATL::CComCreator2< ATL::CComCreator< ATL::CComObject< x > >,
               ATL::CComCreator< ATL::CComAggObject< x > > > _CreatorClass;

Wow, that is really a complicated syntax and I won't try to explain it because that would replicate the book/documentation. Suffice to say one can see the CComObject as referred to in the class diagram above. I will give some links though ...
ClassNamePurpose
CComObjectThis class implements IUnknown for a nonaggregated object.
CComAggObjectThis class implements the IUnknown interface for an aggregated object. By definition, an aggregated object is contained within an outer object. The CComAggObject class is similar to the CComObject Class, except that it exposes an interface that is directly accessible to external clients.
CComCreator & CComCreator2These are undocumented though referenced in a Don Box article

If not new then what?

So I mentioned above that one doesn't use new on your class. Let's suppose you have a use case where you have two com classes in your server project and the method on one returns an instance of the other. Without calling the COM API CoCreateInstance (which would be the long way round) how do you create an instance of your com class and return it to a client? The answer is use (some of) the same classes as the class factory above. So here is some sample code

STDMETHODIMP CUncoolCode::CreateCoolCode(ICoolCode ** ppCool)
{
    // From Grimes et al (1999) p. 143 
    *ppCool = NULL;
    return CComCreator< CComObject<CCoolCode> >::CreateInstance(
          NULL, IID_ICoolCode, reinterpret_cast<void**>(ppCool) ) ;

}

Summary

Well, ATL is complicated if you come from a VBA background, it is advised to never change code generated by the wizards unless you totally know what you are doing. ATL demonstrates not just the power of templates but also multiple inheritance and templates. Awesome.

Miscelaneous Links

As always surfing around in preparation of a blog post throws up some interesting links that are worth saving.
How ATL 7 uses attributes to save lines ATL 3 code
Microsoft Documentation ATL
MSDN ATL

Tuesday, 2 May 2017

DispCallFunc opens a new door to COM interfaces and VBA Function Pointers

So during my research on Type Information it seems that Dispatch carries with it some amazing type querying functionality. Not only that it seems IDispatch and Type Libraries interrogation interfaces are pretty much the same

I always knew IDispatch had to do some work but looking into it seems both amazing and fascinating. Also, it seems that there are some COM system functions that act as low level helpers, one such function is DispCallFunc found in oleaut32.dll.

A clever man called Krivous Anatolii Anatolevich has used this to allow VBA programmers call into IClassFactory::CreateInstance; a problem I solved only with resort to C++/VBA hybrid code. Lots of interesting stuff there at his GitHub page.

Another clever man Mikael Katajamäki has discovered how to use DispCallFunc to form part of a solution for a recursive mathematical root finder. His code is over at Windows API DispCallFunc as function pointer in VBA

Whilst VBA already has AddressOf and Application.OnTime, often it is required to pass parameters to a callback function. DispCallFunc may give the solution to that problem instance.

This idea is debated in an excellent thread discussion over at vbforums.com

As a footnote and coda and not related to DispCallFunc is an incredible project that writes assembly code into memory before calling it. This can be found at wonderful website called FreeVBcode.com to which I shall be returning. The said code is found at Call API functions by Name, without Declare, v 2.0

Thursday, 13 April 2017

Look ma, no Registry! Get IClassFactory direct!

A lot of criticism of COM centres on the Registry. The Registry is a single point of failure and a quick Google will yield many articles condemning it. Com servers store a great deal in the Registry but actually it is possible to bypass the Registry completely.

If you know the location of the COM Server Dll then you can load it with LoadLibrary, you can then use GetProcessAddress to get a function pointer to the entry point DllGetClassObject. Calling DllGetClassObject gets you an interface pointer to IClassFactory and then one can call CreateInstance on that interface. You'll need to know the GUID of the CoClass you want to instantiate as well as the GUID of the interface you're requesting.

Some of this can be written in VBA but getting a function pointer and calling on it are solidly C++ tasks. Here I present code which does the above. First the C++ which needs to be housed in a Win32 Dll project with exports (a .Def file).

#include "Objbase.h"

COMCREATEVIACLASSFACTORY_API HRESULT __stdcall ClassFactoryCreateInstance(
 _In_ HMODULE hModule,
 _In_ _GUID *clsiid,
 _In_ _GUID *iid,
 void** itfUnknown)
{
 HRESULT hr = S_OK;

 IClassFactory* pClassFactory;

 // Declare a pointer to the DllGetClassObject function.
 typedef HRESULT(__stdcall *PFNDLLGETCLASSOBJECT)(REFCLSID clsiid, 
  REFIID RIID, void** PPV);

 PFNDLLGETCLASSOBJECT DllGetClassObject =
   (PFNDLLGETCLASSOBJECT)::GetProcAddress(hModule, "DllGetClassObject");

 // Call DllGetClassObject to get a pointer to the class factory.
 hr = DllGetClassObject(*clsiid, *iid, (void**) &pClassFactory);
 if (hr == S_OK)
 {
  // IClassFactory::CreateInstance and IUnknown::Release
  hr = pClassFactory->CreateInstance(NULL, IID_IUnknown, 
   (void**) itfUnknown);

  pClassFactory->Release();
 }
 return hr;

}



COMCREATEVIACLASSFACTORY_API void TestClassFactoryCreateInstance()
{

 HMODULE hModule = 0;
 hModule = LoadLibrary(L"C:\\Windows\\System32\\scrrun.dll");

 _GUID clsiid, iid;
 ::CLSIDFromString(L"{EE09B103-97E0-11CF-978F-00A02463E06F}", &clsiid);
 ::CLSIDFromString(L"{00000000-0000-0000-C000-000000000046}", &iid);

 HRESULT hr = S_OK;
 IUnknown* pUnknown = 0;

 ClassFactoryCreateInstance(hModule,
  &clsiid,
  &iid, (void**) &pUnknown);

}


And some client VBA

Option Explicit

Declare Function ClassFactoryCreateInstance Lib "ComCreateViaClassFactory.dll" _
           (ByVal hModule As Long, _
            ByRef pguidClass As GUID, _
            ByRef pguidInterface As GUID, _
            ByRef itfUnknown As stdole.IUnknown) As Long

Declare Sub TestClassFactoryCreateInstance Lib "ComCreateViaClassFactory.dll" ()

Declare Function LoadLibrary Lib "Kernel32" Alias "LoadLibraryA" _
            (ByVal lpLibFileName As String) As Long

Const IID_IUnknown          As String = "{00000000-0000-0000-C000-000000000046}"
Const IID_IClassFactory     As String = "{00000001-0000-0000-C000-000000000046}"
Const IID_IClassFactory2    As String = "{B196B28F-BAB4-101A-B69C-00AA00341D07}"

Declare Function CLSIDFromString Lib "OLE32" _
    (ByVal lpszCLSID As String, pclsid As GUID) As Long

Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(7) As Byte
End Type


Sub Test_ClassFactoryCreateInstance()
    
    Debug.Assert Dir(ThisWorkbook.Path & "\ComCreateViaClassFactory.dll") = _
                "ComCreateViaClassFactory.dll"
    Call LoadLibrary(ThisWorkbook.Path & "\ComCreateViaClassFactory.dll")
    
    Dim clsiid As GUID
    Debug.Assert CLSIDFromString(StrConv( _
        "{EE09B103-97E0-11CF-978F-00A02463E06F}", vbUnicode), clsiid) = 0
    
    Dim riid As GUID
    Debug.Assert CLSIDFromString(StrConv(IID_IUnknown, vbUnicode), riid) = 0
    
    Dim hModule As Long
    hModule = LoadLibrary("C:\Windows\System32\scrrun.dll")
    
    Dim itfUnknown As stdole.IUnknown, hr As Long
    hr = ClassFactoryCreateInstance(hModule, clsiid, riid, itfUnknown)
    If hr <> 0 Then Err.Raise hr
    
    Dim oDict As Scripting.Dictionary
    Set oDict = itfUnknown
    oDict.Add "Foo", 2
    oDict.Add "Bar", 3
    Debug.Assert oDict.Keys()(0) = "Foo"
    Debug.Assert oDict.Keys()(1) = "Bar"
    

End Sub

Sub Test_TestClassFactoryCreateInstance()
    Debug.Assert Dir(ThisWorkbook.Path & "\ComCreateViaClassFactory.dll") = _
            "ComCreateViaClassFactory.dll"
    Call LoadLibrary(ThisWorkbook.Path & "\ComCreateViaClassFactory.dll")

    Call TestClassFactoryCreateInstance

End Sub


A nice diagram here shows what we are doing