0

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#.

Alexandru
  • 12,264
  • 17
  • 113
  • 208
  • You could try narrowing it down i the `c++` code by logs and pointer checks to see which call exactly causes the exception. – Griffin Jul 20 '17 at 13:16
  • Try passing `crapStructure` without `ref`. Like this `service.TestCrap(-1, "This is bananas.", crapStructure);` – Griffin Jul 20 '17 at 13:26
  • @Griffin I updated my question because I narrowed it down a bit more. Also, can't use `ref` as its auto-generated the proxy stub class like that because its marked as `[in, out]` in the IDL. – Alexandru Jul 20 '17 at 13:39
  • Allocate the memory for `This is bananas.` in `C#` code and pass the variable. – Griffin Jul 20 '17 at 13:48
  • @Griffin How can I allocate the memory for that string from the C# world? – Alexandru Jul 20 '17 at 13:50
  • `IntPtr inPtr = Marshal.StringToBSTR("This is bananas.")` and pass `inPtr`. – Griffin Jul 20 '17 at 13:53
  • @Griffin Where would you have me pass `inPtr`? – Alexandru Jul 20 '17 at 13:58
  • OK. Can you replace `"This is bananas."` in line `service.TestCrap(-1, "This is bananas.", ref crapStructure);` with this: `Marshal.StringToBSTR("This is bananas.")` ? – Griffin Jul 20 '17 at 14:02
  • @Griffin No, *Argument 2: cannot convert from 'System.IntPtr' to 'string'*. The reason for this is that when you add a COM service reference to a C# project, it generates a C# proxy stub I have no control over the method signature of. For example, in this case its generated this proxy stub: `public interface ICrappyCOMService { [DispId(1)] void TestCrap(int ErrorCode, string ErrorMessage, ref CrapStructure CrapStructure); }`. – Alexandru Jul 20 '17 at 14:08
  • Well, it looks like your `c++` code is trying to access a managed memory which is protected hence you get the `System.AccessViolationException`. I'm no `c#` expert maybe other can help. – Griffin Jul 20 '17 at 14:24
  • @Griffin As others have said and from my own experiments, the issue is with the following code: `crapStructure->ErrorMessage = errorMessage;`. I can only guess that this is because targeting *Any CPU* in C# causes C# to think that it needs to allocate an *x86*-sized structure for `CrapStructure`, and then it goes and calls the *x64* COM method with this *x86*-sized structure. I just don't know what to do to fix it though, or what the best solution is for this moving forward. – Alexandru Jul 20 '17 at 14:29
  • Which is the 'x64 COM Method' that you are referring? – Griffin Jul 20 '17 at 14:31
  • @Griffin `TestCrap`, which lives under both an x64 COM DLL and an x86 COM DLL. The problem here is Any CPU is auto-targeting the x64 COM DLL but using x86-sized structures. – Alexandru Jul 20 '17 at 14:33

1 Answers1

1

What probably happens is you're (implicitely) using the same Type Library (.TLB), or DLL containing the .TLB, for the x64 and x86 versions.

The problem is your TLB defines an unmanaged structure, and this structure layout will be different in 32 bit and 64 bit mode (sizes, offsets, ...).

From .NET you want to make sure you're not referencing the same exact Interop Assembly (produced from the TLB by implicit tlbimp COM reference) when compiling with different processor architectures.

It can be tricky to get it right with standard Visual Studio tooling.

If you still want to use structs like this (which is kinda strange in the Automation world), I recommend you build Interop Assemblies using tlbimp.exe by yourself, and simply reference these Interop Assemblies like normal .NET assemblies, but depending on bitness (you will be able to tweak that using 'Condition' attributes in the .csproj) instead of using COM references directly.

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • Hey Simon, I've edited my question because I narrowed it down a bit more. It seems to be calling the x64 version of my COM object. The problem is that `crapStructure->ErrorMessage = errorMessage;` is causing that exception, but I have no clue as to why - although, I am wondering if your suggestion would help remedy this. Do you have any ideas? Should I not expect that if its calling the x64 version of my COM method, its setting a correct size to its structure, or is that not the case here because its targeting Any CPU? – Alexandru Jul 20 '17 at 13:46
  • 1
    Yes, it's a problem with the CrapStructure layout in x86 vs x64. If you're using C# x64, then you must use an interop assembly that is based on the x64 version of the TLB, so the CrapStructure is properly described for x64. – Simon Mourier Jul 20 '17 at 14:30
  • Oh, I understand what you're saying now. You're saying I should test out what happens after generating both versions of my proxy stubs in C# using `tlbimp.exe`. Man, I can't thank you enough. Time and time again, on StackOverflow, you've helped me out so much! I wish I could work with people like you and learn from you more. – Alexandru Jul 20 '17 at 14:32
  • Hey Simon, I just edited the question. Do you know if there is any way to target the x86 version of my COM object if my C# project is set to target Any CPU? It seems it keeps wanting to go to the x64 implementation despite me trying to use the output DLL of the x86 COM object using `tlbimp.exe`. – Alexandru Jul 20 '17 at 15:53
  • Like I said, tooling is tricky :-) You choose a dangerous path with this structure, because it means the TLB is bitness-sensitive wich is somewhat unusual. The easiest move is to stop doing this and keep with pure Automation types. Otherwise, make sure you remove "COM references", and keep up with standard .NET references on interop/per-bitness/tlbimp generated .net assemblies like you seem to do on your last edit. Marshal.Sizeof(CrapStructure) is 8 in x86 and 16 in x64. Use IntPtr.Size in C# to determine bitness. Uncheck 'Prefer 32-bit' in C# project props, for your tests, don't use AnyCpu. – Simon Mourier Jul 20 '17 at 16:38
  • 0x80040154 is super common, it's an error on your part. You have not registered the corresponding COM object. If your .NET is X-bit (x=32 or x=64), then ensure the native X-bit COM object is registered. – Simon Mourier Jul 20 '17 at 16:40
  • It certainly seems that way. I may as well refactor my code to remove any structures whatsoever, right? There doesn't appear to be a way of calling an x86 COM function exclusively from an Any CPU-targeted C# project. – Alexandru Jul 20 '17 at 16:47