Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

How to Call CoEEShutDownCom in .NET Framework 4.0 and Use It to Solve Hang Issue When Connection Point Being Used Between .NET and Native COM Object

5.00/5 (1 vote)
23 Apr 2016CPOL3 min read 8.1K  
Force .NET Framework release RCW objects when necessary

Introduction

In this article, I would like to discuss a solution to solve a hang issue caused by connection point between .NET and native COM object.

In short, the hang issue is caused by garbage collection do not release native COM interface (RCW), but the native COM object is waiting for all references to be released.

Paresh Suthar's blog provides one solution, which could call Marshal.ReleaseComObject in .NET code, to release the RCW to avoid the problem, for details, see this link.

But, if we could not change .NET code, we may call CoEEShutDownCom to release all RCW references. Adam Nathan only talks about the possibility of this solution (.NET and COM: The Complete Interoperability Guide), but does not give out sample. Especially, in .NET 4.0, the global function CoEEShutDownCom is deprecated. We need some work to get the solution.

Background - RCW

RCW definition could be found here.

In our case, the RCW object is the wrapper of native sink object.

How to Reproduce the Problem

If you have an MFC application (as COM client) calling a COM server implemented in C#, the COM server supports connection point, by C# propery ComSourceInterfaces, you may reproduce the problem while exiting.

Paresh Suthar did a very good job of explaining what will be held if you use ComSourceInterfaces property, see the blog url in the Introduction section. When the native application is MFC, the issue becomes more serious. The application may hang when attempting to exit. (In fact, if you make a low memory event, which will cause a gather collection operation, the application will exit. But making a low memory event is not a stable work.)

The exit hang is caused by the AfxOleCanExitApp() check by MFC framework. In the MFC world, it is common to call AfxOleLockApp in COM object's constructor and AfxOleUnlockApp in destructor. In our case, the AfxOleUnlockApp is not called because the sink object is not released, the latest reference of sink object is in RCW.

The following sample code could show the exit hang issue,

C++
class Sink : public EventInterfac
{
public:
    Sink()
    {
        AfxOleLockApp();
    }

    ~Sink()
    {
        AfxOleUnlockApp();
    }
public:
    HRESULT STDMETHODCALLTYPE QueryInterface(const IID &iid, void **ppvObj)
    {
        if (iid == __uuidof(IUnknown) || iid == __uuidof(IDispatch) || iid == __uuidof(EventInterfac))
        {
            *ppvObj = this;
            this->AddRef();
            return S_OK;
        }

        return E_NOINTERFACE;
    }

    ULONG STDMETHODCALLTYPE AddRef(void)
    {
        m_dwRef++;
        return m_dwRef;
    }
    ULONG STDMETHODCALLTYPE Release(void)
    {
        m_dwRef--;
        if (m_dwRef == 0)
        {
            delete this;
            return 0;
        }
        return m_dwRef;
    }
    HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT *){ return E_NOTIMPL; }
    HRESULT STDMETHODCALLTYPE GetTypeInfo(UINT, LCID, ITypeInfo **){ return E_NOTIMPL; }
    HRESULT STDMETHODCALLTYPE GetIDsOfNames
	(const IID &, LPOLESTR *, UINT, LCID, DISPID *){ return E_NOTIMPL; }
    HRESULT STDMETHODCALLTYPE Invoke
	(DISPID, const IID &, LCID, WORD, DISPPARAMS *, VARIANT *, EXCEPINFO *, UINT *)
    {
        return S_OK;
    }

public:
    DWORD m_dwRef = 1;
};

int _tmain(int argc, _TCHAR* argv[])
{
    Sink* pSink = new Sink;

    CoInitialize(NULL);

    IManagedInterface* cpi;
    HRESULT hResult = CoCreateInstance(__uuidof(ComServer),
        NULL, CLSCTX_INPROC_SERVER,
        __uuidof(IManagedInterface), reinterpret_cast<void**>(&cpi));

    DWORD dwCookie = -1;

    IConnectionPointContainerPtr pCPContainer;
    hResult = cpi->QueryInterface(IID_IConnectionPointContainer,
        (void**)&pCPContainer);
    if (SUCCEEDED(hResult))
    {
        IConnectionPointPtr pEventCP = NULL;
        hResult = pCPContainer->FindConnectionPoint(__uuidof(EventInterfac),
            &pEventCP);
        if (SUCCEEDED(hResult))
        {
            pEventCP->Advise(pSink, &dwCookie);
        }

        pEventCP->Release();
        pEventCP = NULL;
    }
   
    for (int i = 0; i < 100; i++)
    {
        CComBSTR bstrTest(L"Test");
        cpi->PrintHi((BSTR)bstrTest);
    }

    IConnectionPointPtr pEventCP = NULL;
    hResult = pCPContainer->FindConnectionPoint(__uuidof(EventInterfac),
        &pEventCP);
    if (SUCCEEDED(hResult))
    {
        pEventCP->Unadvise(dwCookie);
    }

    pEventCP->Release();
    pEventCP = NULL;

    pSink->Release();

    while (!AfxOleCanExitApp())
    {
        Sleep(1000);
    }

    CoUninitialize();

    return 0;
}

The C# COM server object source code is as follows:

C#
namespace CSharpComwithCP
{
    [ComVisible(true)]
    [Guid("901EE2A0-C47C-43ec-B433-985C02004321")]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    // The public interface describing the events of the control
    public interface EventInterfac
    {
        [DispId(1)]
        void Hello();
    
    }

    [ComVisible(true)]
    [Guid("DBE0E8C4-1C61-41f3-B6A4-4E2F35354321")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IManagedInterface
    {
        int PrintHi(string name);
    }

    [ComVisible(true),
    ComSourceInterfaces(typeof(EventInterfac)),
    ClassInterface(ClassInterfaceType.None),
    Guid("80B59B58-98EA-303C-BE83-D26E5D8D4321")]
    public class ComServer : IManagedInterface
    {
        public void FireHelloEvent()
        {
            if (null != _Hello)
                _Hello();
        }

        public int PrintHi(string name)
        {
            Func1();
            return name.Length;
        }

        [ComVisible(false)]
        public delegate void HelloEventHandler();
        public event HelloEventHandler Hello
        {
            add { _Hello += value; }
            remove
            {
                _Hello -= value;
            }
        }

        private event HelloEventHandler _Hello = null;
    }
}

These are only samples, in the real world, if you are implementing ActiveX container by MFC and implementing ActiveX by C#, you will encounter the same problem.

Solution

In my real case, I could not take Paresh Suthar's suggestion to change .NET code (they are my customers' source), I could only change native sink object. After deep search on Google and read code, I believe Adam Nathan's suggestion, use CoEEShutDownCom to release all RCW's reference should be the solution.

"Calling CoEEShutDownCOM forces the CLR to release all interface pointers, it holds onto inside RCWs. This method usually doesn’t need to be called, but can be necessary if you’re running leak-detection code or somehow depend on all interface pointers being released before process tremination."
--
.NET and COM - The Complete Interoperability Guide by Adam Nathan.

Then, I checked MSDN, the global CoEEShutDownCOM has deprecated. We need to use the following code to implement a global function in the natvie world.

C++
void CoEEShutdownCOM()
{
    ULONG aFetched = 1;
    ICLRMetaHost *meta = NULL;
    ICLRRuntimeInfo *info = NULL;

    HRESULT hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (void **)&meta);

    IEnumUnknown * pRtEnum = NULL;

    DWORD Aid = 0;
    ULONGLONG bytes = 0;
    ULONG fetched = 0;

    hr = meta->EnumerateLoadedRuntimes(GetCurrentProcess(), &pRtEnum);

    while ((hr = pRtEnum->Next(1, (IUnknown **)&info, &fetched)) == S_OK && fetched > 0)
    {
        void (*pfunc)();
        info->GetProcAddress("CoEEShutDownCOM", (void**)&pfunc);
        if (pfunc)
        {
            pfunc();
        }

        info->Release();
    }

    pRtEnum->Release();
    meta->Release();
}

Call this function before AfxOleCanExitApp() check, world saved.

Discussion

Even the problem could be solved by this function, but it is not perfect. When exiting, we are forcing all RCW object references to be released. Maybe, some references are still needed by someone. Before applying this solution, we need to consider the possibilities.

I am wondering, if we have a method (in the native world) to release one specific RCW object, which we know is our sink's wrapper, will be perfect solution. But I don't find any solution on this, even it is possible or not. You are very welcome to provide comments on this method. Or any other suggestions?

Conclusion

In this article, I gave out the sample code to reproduce the hang problem caused by connection point technology betwen .NET and native sink object. And provided solution codes.

.NET solution could be found in Paresh Suthar's blog.

Native solution is first mentioned by Adam Nathan, I only provide the sample code, in .NET Framework 4.0.

History

  • April 23, 2016 - First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)