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

问题描述

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

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

在 MFCApplication2.exe 中的 0x7B7E13C7 (mscorwks.dll) 处抛出异常:0xC0000005:访问冲突读取位置 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("","{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 控件”的主对话框中。没有必要使用类向导添加控制变量。只需在编辑器中添加控件,异常就会出现。

大多数时候异常发生在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,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,CServerChannel * pChnl,unsigned long * pdwFault) Line 113  C++
    [Inline Frame] combase.dll!AppInvoke(ServerCall * pStub,CServerChannel *) Line 1122    C++
    combase.dll!ComInvokeWithLockAndIPID(ServerCall * pServerCall,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 * lpdwindex) Line 122 C++
    mscorwks.dll!NT5WaitRoutine(int,unsigned long,int,void * *,int) UnkNown
    mscorwks.dll!MsgWaitHelper(int,int)  UnkNown
    mscorwks.dll!Thread::DoAppropriateAptStateWait(int,enum WaitMode)    UnkNown
    mscorwks.dll!Thread::DoAppropriateWaitWorker(int,enum WaitMode)  UnkNown
    mscorwks.dll!Thread::DoAppropriateWait(int,enum WaitMode,struct PendingSync *)   UnkNown
    mscorwks.dll!CLREvent::WaitEx(unsigned long,struct PendingSync *) UnkNown
    mscorwks.dll!CLREvent::Wait(unsigned long,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;
}

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

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

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

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