See the question and my original answer on StackOverflow

What happens is .NET holds references to some native COM pointers (provided by MFC) because there are bidirectional connections established (events).

If MFC objects referenced by .NET are deleted first, when .NET wants to release its references (when garbage collection happens which is not deterministic), it's too late and it calls IUnknown->Release() on rogue pointers.

The solution is to call a .NET provided native method: CoEEShutDownCOM but how to call it depends on the .NET Framework version. Here is a helper method that handles both cases:

#include "MetaHost.h"

HRESULT CoEEShutDownCOM()
{
    typedef void(WINAPI* CoEEShutDownCOMfn)();
    typedef HRESULT(WINAPI* CLRCreateInstanceFn)(REFCLSID, REFIID, LPVOID*);

    HMODULE mscoree = GetModuleHandleW(L"mscoree.dll");
    if (!mscoree)
        return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);

    CLRCreateInstanceFn createInstance = (CLRCreateInstanceFn)GetProcAddress(mscoree, "CLRCreateInstance");
    if (createInstance)
    {
        // .NET 4+
        ICLRMetaHost* host;
        HRESULT hr = createInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&host));
        if (FAILED(hr))
            return hr;

        IEnumUnknown* enumunk;
        hr = host->EnumerateLoadedRuntimes(GetCurrentProcess(), &enumunk);
        if (FAILED(hr))
        {
            host->Release();
            return hr;
        }

        ICLRRuntimeInfo* info;
        while (S_OK == enumunk->Next(1, (IUnknown**)&info, NULL))
        {
            CoEEShutDownCOMfn shutdown = NULL;
            info->GetProcAddress("CoEEShutDownCOM", (LPVOID*)&shutdown);
            if (shutdown)
            {
                shutdown();
            }

            info->Release();
        }

        enumunk->Release();
        host->Release();
    }
    else
    {
        // other .NET
        CoEEShutDownCOMfn shutdown = (CoEEShutDownCOMfn)GetProcAddress(mscoree, "CoEEShutDownCOM");
        if (shutdown)
        {
            shutdown();
        }
    }
    FreeLibrary(mscoree);
    return S_OK;
}

You must call it before ActiveX controls are destroyed from MFC application, for example when the dialog is destroyed:

class CMFCApplication3Dlg : public CDialogEx
{
    ...
protected:
    afx_msg void OnDestroy();
};

BEGIN_MESSAGE_MAP(CMFCApplication3Dlg, CDialogEx)
    ...
    ON_WM_DESTROY()
END_MESSAGE_MAP()

void CMFCApplication3Dlg::OnDestroy()
{
    CoEEShutDownCOM();
}