处理嵌入Visual C++(MFC)应用程序中的.NET ActiveX控件



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

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

MFCApplication2.exe中的0x7B7E13C7(mscorwks.dll(处引发异常:0xC0000005:读取位置0xDDDDDE5时发生访问冲突。

只有当我为事件添加接口时,即如果我添加属性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控件";。没有必要使用类向导添加控制变量。只要在编辑器中添加控件,就会出现异常。

大多数情况下,异常发生在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->对无赖指针释放((。

解决方案是调用.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();
}