0

Here is my IDL:

[
    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;
    };
};

Here is my C++ implementation:

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;
    ICreateErrorInfo* pCreateErrorInfo;
    CreateErrorInfo(&pCreateErrorInfo);
    pCreateErrorInfo->AddRef();
    pCreateErrorInfo->SetDescription(errorMessage);
    pCreateErrorInfo->SetGUID(IID_ICrappyCOMService);
    pCreateErrorInfo->SetSource(L"CCrappyCOMService::TestCrap");
    IErrorInfo* pErrorInfo;
    pCreateErrorInfo->QueryInterface(IID_IErrorInfo, (void**)&pErrorInfo);
    pErrorInfo->AddRef();
    SetErrorInfo(errorCode, pErrorInfo);
    pErrorInfo->Release();
    pCreateErrorInfo->Release();
    return E_FAIL;
}

Here is my C# code which calls the TestCrap method:

static void Main(string[] args)
{
    var service = new CrappyCOMService();
    var crapStructure = new CrapStructure();
    try
    {
        service.TestCrap(1337, "This is bananas.", ref crapStructure);
    }
    catch (Exception exception)
    {
        Console.WriteLine(exception.ToString());
    }
    Console.WriteLine(crapStructure.ErrorCode);
    Console.WriteLine(crapStructure.ErrorMessage);
}

I can't seem to figure out how to read back the error message I am passing to IErrorInfo. Am I missing something here? I implemented ISupportErrorInfo following this guide and it says on Wikipedia that:

In the .NET Framework, HRESULT/IErrorInfo error codes are translated into CLR exceptions when transitioning from native to managed code; and CLR exceptions are translated to HRESULT/IErrorInfo error codes when transitioning from managed to native COM code.

How can I properly set and get back the error message?

The current exception message is

Error HRESULT E_FAIL has been returned from a call to a COM component.

...but I expect it to return This is bananas..

Alexandru
  • 12,264
  • 17
  • 113
  • 208
  • You should get into the habit of using the ATL safe COM pointer classes https://msdn.microsoft.com/en-us/library/ezzw7k98.aspx –  Jul 20 '17 at 02:35
  • Are you assuming that my error code and message are being garbage collected, or is it because of my lack of using `AddRef`? Anyways, I updated the code but we have bigger fish to fry here, as they say. I'm still trying to figure out how to get that error message to map itself into a CLR exception. – Alexandru Jul 20 '17 at 02:47
  • No I don't think the COM object is being released at all, it's just that using raw pointers is terribly error prone and unkind to kittens. I just did some research and looked at a few MS examples and your code appears fine to me. :) –  Jul 20 '17 at 02:55
  • Kittens should keep their claws #. – Alexandru Jul 20 '17 at 03:04
  • Your question does not show whether your C++ code correctly supports `ISupportsErrorInfo` in its `QueryInterface`. – Roman R. Jul 20 '17 at 05:01
  • I am far from being COM expert, but should not be `ISupportErrorInfo` also in the IDL? – vasek Jul 20 '17 at 05:03
  • @vasek You can create a new ATL project and add a class to it with support for error information, and you will see that the IDL does not contain any information about this interface, because presumably, it is just a standard interface and not defined by your code. – Alexandru Jul 20 '17 at 09:55

1 Answers1

1

You should always do proper error checking in your code. This call:

SetErrorInfo(errorCode, pErrorInfo);

returns E_INVALIDARG because the first argument must be 0 as per official documentation. Replace it by:

SetErrorInfo(0, pErrorInfo);

And it will work.

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • Hey Simon, thanks for your help! I have a follow-up question, is there any way to support adding a custom error code to be returned back? – Alexandru Jul 20 '17 at 09:52
  • I can set a non-COM HRESULT and that seems to set the ErrorCode in the C# world but on other questions of mine people were complaining that this is a non-standard approach: https://stackoverflow.com/questions/45203290/calling-my-com-method-from-c-sharp-results-in-a-null-structure-returned-if-hresu – Alexandru Jul 20 '17 at 11:31
  • 1
    you can just replace E_FAIL by any number (with the hi bit set), and you will get that number in `COMException.HResult`. – Simon Mourier Jul 20 '17 at 11:32
  • Yes, any negative number. That is what I thought. Some other people on here were being a bit misleading, saying that -1 for example is an invalid HRESULT, but ultimately I can set my error code to whatever I want it to be, and this still will follow COM standards. – Alexandru Jul 20 '17 at 11:43
  • I have a related follow-up question to all of this: https://stackoverflow.com/questions/45215419/why-does-this-code-throw-system-accessviolationexception-when-called-from-any-cp I have a feeling you may have seen this one before! Please have a look if you can spare any time to help me out, Simon! – Alexandru Jul 20 '17 at 12:57