I have C# Type Library that has multiple interfaces defined. This outputs to a single .tlb file – called BACnetLib.tlb
First is an interface for HTTP communications.
namespace WebServiceLib
{
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("XXX")]
public interface ICxWebServiceLibEvents
{
void COM_REPLY_GET_Success(int id, uint errorCode);
}
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("YYY")]
public interface ICxWebServiceLib
{
void COM_REQUEST_GetJSONObject([MarshalAs(UnmanagedType.I4)] int id,
ICxWebServiceLibEvents callbackClient);
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None), Guid("ZZZ")]
public class CxWebServiceLib : ICxWebServiceLib
{
void ICxWebServiceLib.COM_REQUEST_GetJSONObject(int id, ICxWebServiceLibEvents callbackClient)
{
// implementation
// ...
callbackClient.COM_REPLY_GET_Success(id, errorCode);
}
}
}
Another is an interface to handle BACnet communications:
namespace BACnetLib
{
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("AAA")]
public interface IBACnetLibEvents
{
void COM_REPLY_Finished_Task(int id, int nErrorCode);
}
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("BBB")]
public interface IBACnetLib
{
void COM_REQUEST_ReadProperty([MarshalAs(UnmanagedType.I4)] int id, IBAC-netLibEvents CallbackClient,[MarshalAs(UnmanagedType.I4))
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None), Guid("CCC")]
public class BACnetLib : IBACnetLib
{
public void COM_REQUEST_ReadProperty([MarshalAs(UnmanagedType.I4)] int id, IBACnetLibEvents CallbackClien)
{
// Implementation
// ....
CallbackClient.COM_REPLY_Finished_Task(id, nErrorCode);
}
}
);
This .tlb is imported in a separate C application. The WebServiceLibEvents and BACnetLibEvents interfaces have separate implementations.
C Web Service handler:
#import "..\\lib\\BACnetLib.tlb" raw_interfaces_only, named_guids, no_namespace
class CWebServiceObject : ICxWebServiceLibEvents
{
private:
ICxWebServiceLibPtr m_webServer;
DWORD m_refCount = 1;
public:
CWebServiceObject()
{
const auto hr = m_webServer.CreateInstance(__uuidof(CxWebServiceLib));
if (FAILED(hr))
{
throw exception
}
}
HRESULT __stdcall QueryInterface(const IID&, void**) override
{
if (iid == __uuidof(ICxWebServiceLibEvents) || iid == __uuidof(IUnknown))
{
*pp = this;
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
ULONG __stdcall AddRef(void) override
{
return InterlockedIncrement(&m_refCount);
}
ULONG __stdcall Release(void) override
{
return InterlockedDecrement(&m_refCount);
}
HRESULT __stdcall COM_REPLY_GET_Success(long id, unsigned long errorCode) over-ride;
{
// handle reply
}
}
C BACnet Comms Object:
#import "..\\lib\\BACnetLib.tlb" raw_interfaces_only, named_guids, no_namespace
class CCommsObject : public IBACnetLibEvents
{
private:
IBACnetLibPtr m_server;
DWORD m_refCount = 1;
public:
CCommsObject()
{
auto hr = m_server.CreateInstance(__uuidof(BACnetLib));
if (FAILED(hr))
{
throw exception
}
}
HRESULT __stdcall QueryInterface(const IID &, void **) override
{
if (iid == __uuidof(IBACnetLibEvents) || iid == __uuidof(IUnknown))
{
*pp = this;
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
ULONG __stdcall AddRef(void) override
{
return InterlockedIncrement(&m_refCount);
}
ULONG __stdcall Release(void) override
{
return InterlockedDecrement(&m_refCount);
}
HRESULT __stdcall COM_REPLY_Finished_Task(long id, long nErrorCode) override
{
// handle reply
}
}
I create instances of these in my C app using unique_ptrs, and once created they're valid for the lifetime of of the application.
However, On shutdown I get an access violation in clr.dll:
I'm not sure exactly how to debug this properly. I believe it might be because I'm not handling the reference count correctly or something like that.
Another issue is if I create another, shorter lived instance of one of these objects in my C application, it can cause a crash when calling the other.
Is there something wrong with this design of defining multiple interfaces in a single type library?
Am I handling the reference counting correctly?
CodePudding user response:
At the end of the program, the unique_ptrs are destroyed (freeing the memory) and then the COM cleanup is called.
This cleanup will call Release
on any interface pointers still held, including your CWebServiceObject
and/or CCommsObject
objects. But these objects have already been destroyed and their memory freed, leading to an access violation.
You need to free the memory when Release
is actually called:
ULONG __stdcall AddRef(void) override
{
return InterlockedIncrement(&m_refCount);
}
ULONG __stdcall Release(void) override
{
auto refCount = InterlockedDecrement(&m_refCount);
if (refCount == 0) {
delete this;
}
return refCount;
}
And since the object's life is now managed with the IUnknown interface, you can't use unique_ptr
anymore. It can be replaced with CComPtr
:
// auto webServiceObject = std::make_unique<CWebServiceObject>();
// -> Will call `Release` when destroyed instead of `delete`
CComPtr<CWebServiceObject> webServiceObject(new CWebServiceObject());
(You need to change the refCount to be initialized to 0 instead as CComPtr
's constructor calls AddRef
)