Home > Back-end >  What is the correct way to handle IUnknown Reference count with multiple interfaces in type library?
What is the correct way to handle IUnknown Reference count with multiple interfaces in type library?

Time:12-02

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: stack trace when Access Violation is thrown

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.

  1. Is there something wrong with this design of defining multiple interfaces in a single type library?

  2. 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)

  • Related