Reputation: 8881
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, ¶ms); // 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:
DXDIAG_INIT_PARAMS
struct is of correct size, alignment, layoutIUnknown
methodsDispIdAttribute
to the interface methods, even though I think that's only relevant when hosting COM objects and not using them.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?
Upvotes: 2
Views: 328
Reputation: 8881
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 COMException
s 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);
}
Upvotes: 3