Description
Description
I have 2 COM interfaces, one is implemented in native and one in managed (using source generated COM). The native implementation calls the managed implementation.
When the managed implementation throws InvalidOperationException, it is correctly translated to an HRESULT value in native code; The native code then deals with the error and returns E_FAIL;
I am testing this and the testing code in c# should capture COMException (because E_FAIL translats to that) but - and that is the bug here - it is translated to InvalidOperationException.
I don't see this documented in as a limitation so seems like a bug to me.
Reproduction Steps
//c++ code
struct __declspec(uuid("...")) __declspec(novtable) IRunnable : IUnknown
{
virtual HRESULT STDCALL Run();
};
struct __declspec(uuid("...")) __declspec(novtable) IRunnableCaller : IUnknown
{
virtual HRESULT STDCALL CallRunnable(IRunnable *pRunnable) = 0;
};
const GUID IID_IRunnable = {...}
const GUID IID_IRunnableCaller = {...}
class RunnableCaller : public IRunnableCaller
{
public:
// IUnknown
HRESULT STDCALL QueryInterface(REFIID riid, void **ppvObject) override
{
if (!ppvObject) return E_POINTER;
if (InlineIsEqualGUID(riid, IID_IUnknown))
{
*ppvObject = static_cast<IUnknown *>(this);
this->AddRef();
return S_OK;
}
if (InlineIsEqualGUID(riid, IID_IRunnableCaller))
{
*ppvObject = this;
this->AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
ULONG32 STDCALL AddRef() override
{
return ++m_refCount;
}
ULONG32 STDCALL Release() override
{
if (m_refCount > 0 && --m_refCount < 1)
{
delete this;
}
return m_refCount;
}
// IRunnableCaller
HRESULT STDCALL CallRunnable(IRunnable *pRunnable) override
{
if (!pRunnable) return E_POINTER;
const auto hr = pRunnable->Run();
if (FAILED(hr)) return E_FAIL;
return S_OK;
}
private:
ULONG32 m_refCount{ 0 };
};
extern "C" DOTNETHOST_TESTS_NATIVE_LIB_DLLEXPORT IUnknown * STDCALL CreateRunnableCaller()
{
const auto pInst = new RunnableCaller();
const auto ppUnk = static_cast<IUnknown*>(pInst);
pInst->AddRef();
return pInst;
}
// c# code
[GeneratedComInterface, Guid("...")]
internal partial interface IRunnableCaller
{
void CallRunnable(IRunnable runnable);
}
[GeneratedComInterface, Guid("382E8E43-B6D6-44B6-AE1F-D178E17D755E")]
public partial interface IRunnable
{
void Run();
}
[GeneratedComClass, Guid("C2063F0D-92EA-410C-9AA2-9D08AF79D623")]
public unsafe partial class Runnable : IRunnable
{
public void Run()
{
throw new InvalidOperationException();
}
}
internal static class RunnableCallerFactory
{
[DllImport("RunnableCaller.dll")] public static extern IntPtr CreateRunnableCaller();
}
[Test]
public unsafe void PassingInterfaceReturningFailureWorks()
{
var ptr = RunnableCallerFactory.CreateRunnableCaller();
var obj = ComInterfaceMarshaller<IRunnableCaller>.ConvertToManaged(ptr.ToPointer());
Assert.That(obj, Is.Not.Null);
var runnable = new Runnable();
Assert.Throws<COMException>(() => obj.CallRunnable(runnable)); // expected COMException but was InvalidOperationException
}
Expected behavior
E_FAIL should be translated to COMException regardless of whether the runtime caught other managed exception previously.
Assert.Throws(() => obj.CallRunnable(runnable)); should succeed
Actual behavior
Assert.Throws(() => obj.CallRunnable(runnable)); fails because InvalidOperationException is captured
Regression?
No response
Known Workarounds
No response
Configuration
No response
Other information
Vaguely looking at the runtime code.. IErrorInfo might be the culprit ?
Metadata
Metadata
Assignees
Type
Projects
Status