I have this IDL in my ATL project:
[
object,
uuid(61B0BFF7-E9DF-4D7E-AFE6-49CC67245257),
dual,
nonextensible,
pointer_default(unique)
]
interface ICrappyCOMService : IDispatch {
typedef
[
uuid(C65F8DE6-EDEF-479C-BD3B-17EC3F9E4A3E),
version(1.0)
]
struct CrapStructure {
INT ErrorCode;
BSTR ErrorMessage;
} CrapStructure;
[id(1)] HRESULT TestCrap([in] INT errorCode, [in] BSTR errorMessage, [in, out] CrapStructure *crapStructure);
};
[
uuid(763B8CA0-16DD-48C8-BB31-3ECD9B9DE441),
version(1.0),
]
library CrappyCOMLib
{
importlib("stdole2.tlb");
[
uuid(F7375DA4-2C1E-400D-88F3-FF816BB21177)
]
coclass CrappyCOMService
{
[default] interface ICrappyCOMService;
};
};
This is my implementation in C++:
STDMETHODIMP CCrappyCOMService::InterfaceSupportsErrorInfo(REFIID riid)
{
static const IID* const arr[] = {
&IID_ICrappyCOMService
};
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
if (InlineIsEqualGUID(*arr[i], riid))
return S_OK;
}
return S_FALSE;
}
STDMETHODIMP CCrappyCOMService::TestCrap(INT errorCode, BSTR errorMessage, CrapStructure *crapStructure) {
memset(crapStructure, 0, sizeof(CrapStructure));
crapStructure->ErrorCode = errorCode;
crapStructure->ErrorMessage = errorMessage;
CComPtr<ICreateErrorInfo> x;
ICreateErrorInfo* pCreateErrorInfo;
CreateErrorInfo(&pCreateErrorInfo);
pCreateErrorInfo->AddRef();
pCreateErrorInfo->SetDescription(errorMessage);
pCreateErrorInfo->SetGUID(IID_ICrappyCOMService);
pCreateErrorInfo->SetSource(L"Component.TestCrap");
IErrorInfo* pErrorInfo;
pCreateErrorInfo->QueryInterface(IID_IErrorInfo, (void**)&pErrorInfo);
pErrorInfo->AddRef();
SetErrorInfo(0, pErrorInfo);
pErrorInfo->Release();
pCreateErrorInfo->Release();
printf("Going to return %d...\n", errorCode);
return errorCode;
}
I am calling it like this in C#:
static void Main(string[] args)
{
var service = new CrappyCOMService();
var crapStructure = new CrapStructure();
try
{
service.TestCrap(-1, "This is bananas.", ref crapStructure);
}
catch (COMException exception)
{
Console.WriteLine(exception.ErrorCode);
Console.WriteLine(exception.Message);
}
Console.WriteLine(crapStructure.ErrorCode);
Console.WriteLine(crapStructure.ErrorMessage);
}
If I run the code from a C# project targeting x64, then everything works fine. The problem is that when I call the TestCrap
method from a project targeting Any CPU in C#, it throws System.AccessViolationException
. Why is that? I can verify that when it throws System.AccessViolationException
, it still prints Going to return -1... to the console window.
Edit: I have narrowed down the reproduction steps. Sorry, but I forgot to add this in. This seems to happen when compiling my code using an x64 build (the ATL project targeting x64 and the C# test project targeting Any CPU), then transitioning back to an Any CPU build (the ATL project targeting Win32 and the C# test project targeting Any CPU).
Edit: I've narrowed the error down a bit more. First of all, it looks like the Any CPU C# project always uses the x64 version of my COM object, so that verion of my ATL project needs to be compiled first for any code to propagate because Any CPU on an x64 system is really going to run in the x64 context. Furthermore, it looks like the line, crapStructure->ErrorMessage = errorMessage;
is causing the System.AccessViolationException
. It will execute code in the COM world past the line but on return to the C# world it throws the exception back.
Edit: In C++, printf("%d\n", sizeof(CrapStructure));
results in 16. From C#, Console.WriteLine(Marshal.SizeOf(typeof(CrapStructure)));
also results in 16. But alas, reading up on it some more, this is a useless check as mentioned by Hans' answer here: How do I check the number of bytes consumed by a structure?
Edit: I tried doing tlbimp CrappyCOM.dll /out:CrappyCOMx86Managed.dll
and using CrappyCOMx86Managed.dll
from my Any CPU-targeted C# project, but it threw the System.AccessViolationException
again. I figured this was because it was again going to the x64 COM object registered on the system, so I used regsvr32
to unregister the x64 COM object. Running the code again and with the x86 COM object registered, it complains that Retrieving the COM class factory for component with CLSID {F7375DA4-2C1E-400D-88F3-FF816BB21177} failed due to the following error: 80040154 Class not registered (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG)). The only way I found to make it target the x86 COM object from my tlbimp
-generated stub was by modifying the build target of my C# project to be x86, and then the code works for testing my x86 COM object, so this kind of seems like a moot point for me because I would like to have Any CPU specifically target my x86 COM object or my x64 COM object with the correct structure size. COM is a disaster in C#.