简体   繁体   中英

What is the correct way to handle IUnknown Reference count with multiple interfaces in type library?

I have C# Type Library that has multiple interfaces defined. This outputs to a single.tlb file – called BA.netLib.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 BA.net 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 BA.netLibEvents 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++ BA.net 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.

  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?

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 )

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM