Calling COM method throws unexplainable COMException in C#, works fine in C++

1

I'm learning about consuming a COM interface in C# by example, so I've written a coclass and interfaces for the (I)DxDiagProvider and IDxDiagContainer COM objects.

However, while the apparently equivalent code works fine in C++, calling one of it's methods fails with a COMException (HRESULT is -1) in C#. I probably messed something up porting it to C#, but can't see the error.

Native code (working)

The objects are defined in raw C as follows, which also is the only information I have on them (not having a library for tlbimp or anything):

struct IDxDiagContainerVtbl;
struct IDxDiagContainer { IDxDiagContainerVtbl* lpVtbl; };
struct IDxDiagContainerVtbl
{
    HRESULT(__stdcall* QueryInterface)(IDxDiagContainer* This, const IID* const riid, void** ppvObject);
    ULONG(__stdcall* AddRef)(IDxDiagContainer* This);
    ULONG(__stdcall* Release)(IDxDiagContainer* This);
    HRESULT(__stdcall* EnumChildContainerNames)(IDxDiagContainer* This, DWORD dwIndex, LPWSTR pwszContainer, DWORD cchContainer);
    HRESULT(__stdcall* EnumPropNames)(IDxDiagContainer* This, DWORD dwIndex, LPWSTR pwszPropName, DWORD cchPropName);
    HRESULT(__stdcall* GetChildContainer)(IDxDiagContainer* This, LPCWSTR pwszContainer, IDxDiagContainer** ppInstance);
    HRESULT(__stdcall* GetNumberOfChildContainers)(IDxDiagContainer* This, DWORD* pdwCount);
    HRESULT(__stdcall* GetNumberOfProps)(IDxDiagContainer* This, DWORD* pdwCount);
    HRESULT(__stdcall* GetProp)(IDxDiagContainer* This, LPCWSTR pwszPropName, VARIANT* pvarProp);
};

struct DXDIAG_INIT_PARAMS
{
    DWORD dwSize;
    DWORD dwDxDiagHeaderVersion;
    BOOL bAllowWHQLChecks;
    void* pReserved;
};
struct IDxDiagProviderVtbl;
struct IDxDiagProvider { IDxDiagProviderVtbl* lpVtbl; };
struct IDxDiagProviderVtbl
{
    HRESULT(__stdcall* QueryInterface)(IDxDiagProvider* This, const IID* const riid, void** ppvObject);
    ULONG(__stdcall* AddRef)(IDxDiagProvider* This);
    ULONG(__stdcall* Release)(IDxDiagProvider* This);
    HRESULT(__stdcall* Initialize)(IDxDiagProvider* This, DXDIAG_INIT_PARAMS* pParams);
    HRESULT(__stdcall* GetRootContainer)(IDxDiagProvider* This, IDxDiagContainer** ppInstance);
};

Creating this and calling Initialize in native C++ works just fine:

int main()
{
    GUID clsid;
    GUID iid;
    CLSIDFromString(L"{A65B8071-3BFE-4213-9A5B-491DA4461CA7}", &clsid);
    CLSIDFromString(L"{9C6B4CB0-23F8-49CC-A3ED-45A55000A6D2}", &iid);

    CoInitialize(NULL);
    IDxDiagProvider* pDxDiagProvider;
    HRESULT hr = CoCreateInstance(clsid, NULL, 1, iid, (LPVOID*)&pDxDiagProvider); // S_OK

    DXDIAG_INIT_PARAMS params;
    params.dwSize = sizeof(DXDIAG_INIT_PARAMS);
    params.dwDxDiagHeaderVersion = 111;
    params.bAllowWHQLChecks = 0;
    params.pReserved = 0;

    // Sorry for the C-like access, I don't have anything better than the structs above.
    hr = pDxDiagProvider->lpVtbl->Initialize(pDxDiagProvider, &params); // S_OK
}

Managed code (broken)

So then I went ahead and ported this to an STAThread x86 .NET 4.6.1 C# console program with the following definitions:

[ComImport]
[Guid("A65B8071-3BFE-4213-9A5B-491DA4461CA7")]
public class DxDiagProvider { }
[Guid("9C6B4CB0-23F8-49CC-A3ED-45A55000A6D2")]
public interface IDxDiagProvider
{
    void Initialize(ref DXDIAG_INIT_PARAMS pParams);
    void GetRootContainer(ref IDxDiagContainer ppInstance);
}
[StructLayout(LayoutKind.Sequential)]
public struct DXDIAG_INIT_PARAMS
{
    public uint dwSize;
    public uint dwDxDiagHeaderVersion;
    public bool bAllowWHQLChecks;
    public IntPtr pReserved;
};

[Guid("7D0F462F-4064-4862-BC7F-933E5058C10F")]
public interface IDxDiagContainer
{
    void EnumChildContainerNames(uint dwIndex, string pwszContainer, uint cchContainer);
    void EnumPropNames(uint dwIndex, string pwszPropName, uint cchPropName);
    void GetChildContainer(string pwszContainer, ref IDxDiagContainer ppInstance);
    void GetNumberOfChildContainers(ref uint pdwCount);
    void GetNumberOfProps(ref uint pdwCount);
    void GetProp(string pwszPropName, ref IntPtr pvarProp);
}

Now, while creating the class and casting the interface works, the call to Initialize breaks with COMException: Exception from HRESULT: 0xFFFFFFFF:

[STAThread]
static void Main(string[] args)
{
    // Working fine.
    DxDiagProvider dxDiagProviderClass = new DxDiagProvider();
    IDxDiagProvider dxDiagProvider = (IDxDiagProvider)dxDiagProviderClass;

    DXDIAG_INIT_PARAMS initParams = new DXDIAG_INIT_PARAMS
    {
        dwSize = (uint)Marshal.SizeOf<DXDIAG_INIT_PARAMS>(),
        dwDxDiagHeaderVersion = 111
    };
    dxDiagProvider.Initialize(ref initParams); // causes COMException
}

I'm not sure what I did wrong. So far I've tested and checked the following things:

  • Ensured the DXDIAG_INIT_PARAMS struct is of correct size, alignment, layout
  • Ensured my methods are in the right order and I did not add the IUnknown methods
  • Tried adding DispIdAttribute to the interface methods, even though I think that's only relevant when hosting COM objects and not using them.
  • Tried passing the parameters as non-ref, even though they should be ref as the parameter is a pointer.

I really hope this has nothing to do with the COM object not working correctly when used in a managed application (that would definitely make this question too broad). So I want to ensure my approach and my definitions are right at least. Is there anything else that has to be done and that I'm missing?

c#
c++
com-interop
asked on Stack Overflow Feb 22, 2019 by Ray • edited Feb 22, 2019 by Ray

1 Answer

3

I found out what I was missing and could fix it:

Apparently, I forgot to decorate the C# interfaces with the InterfaceTypeAttribute and specifying ComInterfaceType.InterfaceIsIUnknown. At least for these COM objects, this is required, as the default ComInterfaceType.InterfaceIsDual causes the COMExceptions to be thrown.

E.g., the interface definitions now look like this in C# (plus I fixed some ref to out parameters, which however were not the cause of this issue):

[Guid("9C6B4CB0-23F8-49CC-A3ED-45A55000A6D2")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] // <-- need this
public interface IDxDiagProvider
{
    void Initialize(ref DXDIAG_INIT_PARAMS pParams);
    void GetRootContainer(out IDxDiagContainer ppInstance);
}

[Guid("7D0F462F-4064-4862-BC7F-933E5058C10F")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] // <-- need this
public interface IDxDiagContainer
{
    void EnumChildContainerNames(uint dwIndex, string pwszContainer, uint cchContainer);
    void EnumPropNames(uint dwIndex, string pwszPropName, uint cchPropName);
    void GetChildContainer(string pwszContainer, out IDxDiagContainer ppInstance);
    void GetNumberOfChildContainers(out uint pdwCount);
    void GetNumberOfProps(out uint pdwCount);
    void GetProp(string pwszPropName, out IntPtr pvarProp);
}
answered on Stack Overflow Feb 22, 2019 by Ray • edited Feb 22, 2019 by Ray

User contributions licensed under CC BY-SA 3.0