0

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?

2
  • 1
    *pp = this; is not good, among other issues. You have a good example on how to implement a COM object here: jackmin.home.blog/2019/01/11/com-tutorial However, you can also use Microsoft's tooling such as ATL from Visual Studio. Do you have a full reproducing project? How you call all this is important too. Rpcrt4 in the stack means some cross threads calls likely happen, etc. Commented Dec 1, 2022 at 13:36
  • @SimonMourier No, this is part of a much larger project. I haven't narrowed it down to a smaller reproducible project. But thanks for the link, it contains useful information on the implementation. I see using static cast is probably better than *pp = this so will update my code accordingly. The reference count and access violation issue is fixed with the suggestion to use CComPtr below. Commented Dec 1, 2022 at 14:30

1 Answer 1

2

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)

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks Artyer. Simply adding the "delete this" to the Release() function still has the same issue when using the unique_ptr - the memory is freed before the cleanup can call Release on the objects. I think using the raw pointer is the way to go as it stops the access violation on shutdown. Even though using new without a corresponding delete goes against my instincts as a programmer because when I do this, it doesn't get to the delete in Release while debugging.
@ChrisJ On second thought, leaking an IUnknown might be a bad idea if the base class has some resources that need to be flushed or something. Edited to use a different smart pointer instead.
CComPtr is the way to go. I can see the memory being deleted on shutdown on shutdown and prevents the access violation. Thanks for your help

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.