处理嵌入在 Visual C++ (MFC) 应用程序中的 .NET-ActiveX-Control

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

我正在尝试将带有 WinForms 控件的第三方 .NET Framework 3.5 DLL 添加到我的非托管 Visual C++ MFC 应用程序中。因此,我构建了一个C# com-interop-wrapper DLL,注册为一个ActiveX控件。

运行良好,但每次退出MFC容器应用程序都会导致访问异常。

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

错误仅在我为事件添加接口时发生,即如果我添加属性 ComSourceInterface。下面的示例在没有 [ComSourceInterfaces(typeof(IUserControlEvents))].

行的情况下也能正常工作

这是最简单的例子:

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)
            {
            }

        }

    }
}

在我的 MFC 应用程序中,我通过“插入 ActiveX 控件”将 ActiveX 控件放置在主对话框中。不需要使用 class 向导添加控制变量。只要在编辑器中添加控件,异常就已经出现了

大多数时候异常发生在mscorwks.dll:

的SafeReleaseHelper方法中
    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

由于建立了双向连接(事件),.NET 持有对某些本机 COM 指针(由 MFC 提供)的引用。

如果 .NET 引用的 MFC 对象首先被删除,当 .NET 想要释放它的引用时(当发生不确定的垃圾收集时),为时已晚,它会在流氓指针上调用 IUnknown->Release()

解决方案是调用 .NET 提供的本机方法:CoEEShutDownCOM 但如何调用它取决于 .NET Framework 版本。这是处理这两种情况的辅助方法:

#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;
}

您必须在 ActiveX 控件从 MFC 应用程序销毁之前调用它,例如当对话框被销毁时:

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

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

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