Disposing a .NET-ActiveX-Control embedded in a Visual C++ (MFC) application

1

I am trying to add a third-party .NET Framework 3.5 DLL with a WinForms control to my unmanaged Visual C++ MFC application. Therefore, I have built a C# com-interop-wrapper DLL, which is registered as an ActiveX control.

It is working well, but exiting the MFC container application leads to an access exception every time.

Exception thrown at 0x7B7E13C7 (mscorwks.dll) in MFCApplication2.exe: 0xC0000005: Access violation reading location 0xDDDDDDE5.

The error occurs only, if I add an interface for events, i.e. if I add the attribute ComSourceInterface. The underneath example would work well without the line [ComSourceInterfaces(typeof(IUserControlEvents))].

Here is the minimal example:

using System.Windows.Forms;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using System.Reflection;

namespace WindowsFormsControlLibrary1
{
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    [ComVisible(true)]
    [Guid("F80C042C-ABEA-458D-96E0-F1A4DD620A72")]
    public interface IUserControl
    {
        [DispId(1)]
        void testMethod();
    }

    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    [ComVisible(true)]
    [Guid("CFF6DA90-2B8A-4D57-A4B8-581A47BA5226")]
    public interface IUserControlEvents
    {
        [DispId(2)]
        void click();
    }

    [ProgId("WindowsFormsControlLibrary1.UserControl1")]
    [ClassInterface(ClassInterfaceType.None)]
    [ComDefaultInterface(typeof(IUserControl))]
    [ComSourceInterfaces(typeof(IUserControlEvents))]
    [ComVisible(true)]
    [Guid("E5A1E243-AFD2-4443-8A54-D5FE9A8633C8")]
    public partial class UserControl1: UserControl, IUserControl
    {
        [ComVisible(true)]
        public delegate void UserControlClick();

        public event UserControlClick click;

        [ComVisible(true)]
        public void testMethod()
        {
            MessageBox.Show("hallo");
        }

        public UserControl1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (click != null)
                click();
        }

        [ComRegisterFunction()]
        public static void RegisterClass(string i_Key)
        {
            i_Key = i_Key.Replace(@"HKEY_CLASSES_ROOT\", "");

            // open the CLSID\{guid} key for write access
            using (RegistryKey clsidRegisterKey = Registry.ClassesRoot.CreateSubKey(i_Key))
            {
                // and create the 'Control' key - this allows it to show up in 
                // the ActiveX control container 
                clsidRegisterKey.CreateSubKey("Control").Close();

                // next create the CodeBase entry - needed if not string named and GACced.
                using (RegistryKey inprocServer32 = clsidRegisterKey.CreateSubKey("InprocServer32"))
                {
                    inprocServer32.SetValue("CodeBase", Assembly.GetExecutingAssembly().CodeBase);
                }

                using (RegistryKey miscStatus = clsidRegisterKey.CreateSubKey("MiscStatus"))
                {
                    //??????
                    miscStatus.SetValue("", 0x20191);//0x00000801
                }

                using (RegistryKey typeLib = clsidRegisterKey.CreateSubKey("TypeLib"))
                {
                    Guid libid = Marshal.GetTypeLibGuidForAssembly(typeof(UserControl1).Assembly);
                    typeLib.SetValue("", libid.ToString("B"));
                }

                using (RegistryKey version = clsidRegisterKey.CreateSubKey("Version"))
                {
                    Marshal.GetTypeLibVersionForAssembly(typeof(UserControl1).Assembly, out int major, out int minor);
                    version.SetValue("", $"{major}.{minor}");
                }

            }

            using (RegistryKey interfaceRegisterKey = Registry.ClassesRoot.CreateSubKey(@"Interface\" + typeof(IUserControl).GUID.ToString("B")))
            {
                interfaceRegisterKey.SetValue("", "IUserControl");

                using (RegistryKey subkey = interfaceRegisterKey.CreateSubKey("TypeLib"))
                {
                    Guid libid = Marshal.GetTypeLibGuidForAssembly(typeof(IUserControl).Assembly);
                    subkey.SetValue("", libid.ToString("B"));
                }

                using (RegistryKey subkey = interfaceRegisterKey.CreateSubKey("ProxyStubClsid32"))
                {
                    subkey.SetValue("", "{00020420-0000-0000-C000-000000000046}");
                }
            }

            using (RegistryKey interfaceRegisterKey = Registry.ClassesRoot.CreateSubKey(@"Interface\" + typeof(IUserControlEvents).GUID.ToString("B")))
            {
                interfaceRegisterKey.SetValue("", "IUserControlEvents");

                using (RegistryKey subkey = interfaceRegisterKey.CreateSubKey("TypeLib"))
                {
                    Guid libid = Marshal.GetTypeLibGuidForAssembly(typeof(IUserControlEvents).Assembly);
                    subkey.SetValue("", libid.ToString("B"));
                }

                using (RegistryKey subkey = interfaceRegisterKey.CreateSubKey("ProxyStubClsid32"))
                {
                    subkey.SetValue("", "{00020420-0000-0000-C000-000000000046}");
                }
            }
        }

        [ComUnregisterFunction()]
        public static void UnregisterClass(string i_Key)
        {
            i_Key = i_Key.Replace(@"HKEY_CLASSES_ROOT\", "");

            try
            {
                Registry.ClassesRoot.DeleteSubKeyTree(i_Key);
            }
            catch (ArgumentException e)
            {
            }

            try
            {
                Registry.ClassesRoot.DeleteSubKeyTree(@"Interface\" + typeof(IUserControl).GUID.ToString("B"));
            }
            catch (ArgumentException e)
            {
            }


            try
            {
                Registry.ClassesRoot.DeleteSubKeyTree(@"Interface\" + typeof(IUserControlEvents).GUID.ToString("B"));
            }
            catch (ArgumentException e)
            {
            }

        }

    }
}

In my MFC application, I have placed the ActiveX control in the main dialog with "Insert ActiveX control". It is not necessary to add a control variable with class wizard. Just add the control in the editor, and the exception will already appear.

Most of the time the exception occurs in the method SafeReleaseHelper of mscorwks.dll:

    mscorwks.dll!SafeReleaseHelper(struct IUnknown *,struct RCW *)  Unknown
    mscorwks.dll!SafeRelease(struct IUnknown *,struct RCW *)    Unknown
    mscorwks.dll!IUnkEntry::Free(struct RCW *,int)  Unknown
    mscorwks.dll!RCW::ReleaseAllInterfaces(void)    Unknown
    mscorwks.dll!RCW::ReleaseAllInterfacesCallBack(void *)  Unknown
    mscorwks.dll!RCW::Cleanup(void) Unknown
    mscorwks.dll!RCWCleanupList::ReleaseRCWListRaw(struct RCW *)    Unknown
    mscorwks.dll!RCWCleanupList::ReleaseRCWListInCorrectCtx(void *) Unknown
    mscorwks.dll!CtxEntry::EnterContextCallback(struct tagComCallData *)    Unknown
    combase.dll!CRemoteUnknown::DoCallback(tagXAptCallback * pCallbackData) Line 1882   C++
    rpcrt4.dll!_Invoke@12() Unknown
    rpcrt4.dll!_NdrStubCall2@16()   Unknown
    combase.dll!CStdStubBuffer_Invoke(IRpcStubBuffer * This, tagRPCOLEMESSAGE * prpcmsg, IRpcChannelBuffer * pRpcChannelBuffer) Line 1531   C++
    [Inline Frame] combase.dll!InvokeStubWithExceptionPolicyAndTracing::__l6::<lambda_ee1df801181086a03fa4f8f75bd5617f>::operator()() Line 1279 C++
    combase.dll!ObjectMethodExceptionHandlingAction<<lambda_ee1df801181086a03fa4f8f75bd5617f>>(InvokeStubWithExceptionPolicyAndTracing::__l6::<lambda_ee1df801181086a03fa4f8f75bd5617f> action, ObjectMethodExceptionHandlingInfo * pExceptionHandlingInfo, ExceptionHandlingResult * pExceptionHandlingResult, void *) Line 87 C++
    [Inline Frame] combase.dll!InvokeStubWithExceptionPolicyAndTracing(IRpcStubBuffer * pMsg, tagRPCOLEMESSAGE *) Line 1277 C++
    combase.dll!DefaultStubInvoke(bool bIsAsyncBeginMethod, IServerCall * pServerCall, IRpcChannelBuffer * pChannel, IRpcStubBuffer * pStub, unsigned long * pdwFault) Line 1346    C++
    [Inline Frame] combase.dll!SyncStubCall::Invoke(IServerCall *) Line 1403    C++
    [Inline Frame] combase.dll!SyncServerCall::StubInvoke(IRpcChannelBuffer *) Line 780 C++
    [Inline Frame] combase.dll!StubInvoke(tagRPCOLEMESSAGE * pMsg, CStdIdentity * pStdID, IRpcStubBuffer *) Line 1628   C++
    combase.dll!ServerCall::ContextInvoke(tagRPCOLEMESSAGE * pMessage, IRpcStubBuffer * pStub, CServerChannel * pChannel, tagIPIDEntry * pIPIDEntry, unsigned long * pdwFault) Line 1423    C++
    [Inline Frame] combase.dll!CServerChannel::ContextInvoke(tagRPCOLEMESSAGE *) Line 1332  C++
    [Inline Frame] combase.dll!DefaultInvokeInApartment(tagRPCOLEMESSAGE *) Line 3297   C++
    combase.dll!ReentrantSTAInvokeInApartment(tagRPCOLEMESSAGE * pMsg, unsigned long dwCallCat, bool bIsTouchedASTACall, IRpcStubBuffer * pStub, CServerChannel * pChnl, tagIPIDEntry * pIPIDEntry, unsigned long * pdwFault) Line 113  C++
    [Inline Frame] combase.dll!AppInvoke(ServerCall * pStub, CServerChannel *) Line 1122    C++
    combase.dll!ComInvokeWithLockAndIPID(ServerCall * pServerCall, tagIPIDEntry * pIPIDEntry, bool * pbCallerResponsibleForRequestMessageCleanup) Line 2210 C++
    [Inline Frame] combase.dll!ComInvoke(ServerCall *) Line 1697    C++
    [Inline Frame] combase.dll!ThreadDispatch(ServerCall *) Line 414    C++
    combase.dll!ThreadWndProc(HWND__ * window, unsigned int message, unsigned int wparam, long params) Line 740 C++
    user32.dll!__InternalCallWinProc@20()   Unknown
    user32.dll!UserCallWinProcCheckWow()    Unknown
    user32.dll!DispatchMessageWorker()  Unknown
    user32.dll!_DispatchMessageW@4()    Unknown
    [Inline Frame] combase.dll!CCliModalLoop::MyDispatchMessage(tagMSG *) Line 2989 C++
    combase.dll!CCliModalLoop::PeekRPCAndDDEMessage() Line 2616 C++
    combase.dll!CCliModalLoop::FindMessage(unsigned long dwStatus) Line 2706    C++
    combase.dll!CCliModalLoop::HandleWakeForMsg() Line 2302 C++
    combase.dll!CCliModalLoop::BlockFn(void * * ahEvent, unsigned long cEvents, unsigned long * lpdwSignaled) Line 2239 C++
    combase.dll!ClassicSTAThreadWaitForHandles(unsigned long dwFlags, unsigned long dwTimeout, unsigned long cHandles, void * * pHandles, unsigned long * pdwIndex) Line 51 C++
    combase.dll!CoWaitForMultipleHandles(unsigned long dwFlags, unsigned long dwTimeout, unsigned long cHandles, void * * pHandles, unsigned long * lpdwindex) Line 122 C++
    mscorwks.dll!NT5WaitRoutine(int,unsigned long,int,void * *,int) Unknown
    mscorwks.dll!MsgWaitHelper(int,void * *,int,unsigned long,int)  Unknown
    mscorwks.dll!Thread::DoAppropriateAptStateWait(int,void * *,int,unsigned long,enum WaitMode)    Unknown
    mscorwks.dll!Thread::DoAppropriateWaitWorker(int,void * *,int,unsigned long,enum WaitMode)  Unknown
    mscorwks.dll!Thread::DoAppropriateWait(int,void * *,int,unsigned long,enum WaitMode,struct PendingSync *)   Unknown
    mscorwks.dll!CLREvent::WaitEx(unsigned long,enum WaitMode,struct PendingSync *) Unknown
    mscorwks.dll!CLREvent::Wait(unsigned long,int,struct PendingSync *) Unknown
    mscorwks.dll!_CorExitProcess@4()    Unknown
    mscorwks.dll!WaitForEndOfShutdown(void) Unknown
    mscorwks.dll!EEShutDown(int)    Unknown
    mscorwks.dll!DisableRuntime(void)   Unknown
    mscorwks.dll!_CorExitProcess@4()    Unknown
    mscoreei.dll!RuntimeDesc::ShutdownAllActiveRuntimes(unsigned int,class RuntimeDesc *,enum RuntimeDesc::ShutdownCompatMode)  Unknown
    mscoreei.dll!_CorExitProcess@4()    Unknown
    mscoree.dll!_ShellShim_CorExitProcess@4()   Unknown
    ucrtbased.dll!try_cor_exit_process(const unsigned int return_code) Line 98  C++
    ucrtbased.dll!exit_or_terminate_process(const unsigned int return_code) Line 139    C++
    ucrtbased.dll!common_exit(const int return_code, const _crt_exit_cleanup_mode cleanup_mode, const _crt_exit_return_mode return_mode) Line 280   C++
    ucrtbased.dll!exit(int return_code) Line 293    C++
>   MFCApplication2.exe!__scrt_common_main_seh() Line 297   C++
    MFCApplication2.exe!__scrt_common_main() Line 331   C++
    MFCApplication2.exe!wWinMainCRTStartup(void * __formal) Line 17 C++
    kernel32.dll!@BaseThreadInitThunk@12()  Unknown
    ntdll.dll!__RtlUserThreadStart()    Unknown
    ntdll.dll!__RtlUserThreadStart@8()  Unknown
c#
.net
visual-c++
activex
com-interop
asked on Stack Overflow Apr 19, 2021 by Christian Sattlecker • edited Apr 26, 2021 by Christian Sattlecker

1 Answer

0

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();
}
answered on Stack Overflow Apr 20, 2021 by Simon Mourier

User contributions licensed under CC BY-SA 3.0