C# 免注册COM互操作:在终结器中停用激活上下文会引发SEHException
我目前正在处理一个混合的托管/本机工作链,需要为免注册COM支持创建一个激活上下文(请参阅)。以下代码段是C#DLL中较大类的一部分,该类包含对COM包装的引用,并建立所需的激活上下文:C# 免注册COM互操作:在终结器中停用激活上下文会引发SEHException,c#,pinvoke,com-interop,regfreecom,activation-context-api,C#,Pinvoke,Com Interop,Regfreecom,Activation Context Api,我目前正在处理一个混合的托管/本机工作链,需要为免注册COM支持创建一个激活上下文(请参阅)。以下代码段是C#DLL中较大类的一部分,该类包含对COM包装的引用,并建立所需的激活上下文: using System; using System.Runtime.InteropServices; using System.Diagnostics; namespace FirstClient { public class FirstClientDLL : IDisposable {
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace FirstClient
{
public class FirstClientDLL : IDisposable
{
~FirstClientDLL()
{
Dispose(false);
}
void IDisposable.Dispose()
{
Dispose(true);
}
private void Dispose(bool disposing)
{
DestroyActivationContext();
}
private bool DestroyActivationContext()
{
if (m_cookie != IntPtr.Zero)
{
try
{
//When being invoked from the destructor or the dispose method, the following line always fails...
if (!DeactivateActCtx(0, m_cookie))
return false;
m_cookie = IntPtr.Zero;
}
catch (SEHException ex)
{
// Always gets hit. Why??
Debug.Print(ex.Message + " " + "0x" + ex.ErrorCode.ToString("X"));
return false;
}
if (!ReleaseActCtx(m_hActCtx))
return false;
m_hActCtx = IntPtr.Zero;
}
return true;
}
public bool EstablishActivationContext()
{
ACTCTX info = new ACTCTX();
info.cbSize = Marshal.SizeOf(typeof(ACTCTX));
info.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID;
info.lpSource = System.Reflection.Assembly.GetExecutingAssembly().Location;
info.lpResourceName = ISOLATIONAWARE_MANIFEST_RESOURCE_ID;
m_hActCtx = CreateActCtx(ref info);
if (m_hActCtx == new IntPtr(-1))
return false;
m_cookie = IntPtr.Zero;
if (!ActivateActCtx(m_hActCtx, out m_cookie))
return false;
m_iCOMInstance = new atlw.TestClass();
// --> If I destroy the activation context here, no exception is thrown. Obviously, the COM wrapper will get invalidated and can no longer accept any calls.
//DestroyActivationContext();
return true;
}
public string CallCOMMethod()
{
return m_iCOMInstance.SayHello();
}
private const uint ACTCTX_FLAG_RESOURCE_NAME_VALID = 0x008;
private const UInt16 ISOLATIONAWARE_MANIFEST_RESOURCE_ID = 2;
private const UInt16 DEACTIVATE_ACTCTX_FLAG_FORCE_EARLY_DEACTIVATION = 1;
[DllImport("Kernel32.dll")]
private extern static IntPtr CreateActCtx(ref ACTCTX actctx);
[DllImport("Kernel32.dll")]
private extern static bool ActivateActCtx(IntPtr hActCtx, out IntPtr lpCookie);
[DllImport("Kernel32.dll")]
private extern static bool DeactivateActCtx(uint dwFlags, IntPtr lpCookie);
[DllImport("Kernel32.dll")]
private extern static bool ReleaseActCtx(IntPtr hActCtx);
private struct ACTCTX
{
public int cbSize;
public uint dwFlags;
public string lpSource;
public ushort wProcessorArchitecture;
public ushort wLangId;
public string lpAssemblyDirectory;
public UInt16 lpResourceName;
public string lpApplicationName;
public IntPtr hModule;
}
private atlw.ITestClass m_iCOMInstance;
private IntPtr m_cookie;
private IntPtr m_hActCtx;
}
}
问题在于DestroyActivationContext()
方法中的pinvokedDeactivateACTCx()
函数。一旦调用它,就会抛出一个SEHException
:外部组件抛出了一个异常。0x80004005
通过Marshal.GetLastWin32Error()
函数没有可用的错误代码,这将为我提供一些合理的信息
到目前为止我已经尝试过的事情:
- 将
函数从析构函数移动到DestroyActivationContext()
方法,反之亦然Dispose
- 完全删除
接口IDisposable
- 将基础COM对象的线程模型从单元更改为自由
- 提供带有
DEACTIVATE\u ACTCTX\u FLAG\u FORCE\u EARLY\u DEACTIVATION的
函数作为输入参数deactivateActtx()
- 将
实例的类型更改为IntPtr
UIntPtr
SEHException
的情况下解除激活上下文的建立
更新
垃圾收集器的线程似乎是问题的原因。GC总是在它自己的独立线程中运行,显然不可能指定其他线程。当尝试从此特定线程中停用激活上下文(
DeactivateActCtx
)时,似乎存在某种访问冲突。所以我想除了在每个包装调用中激活和停用激活上下文之外,没有直接的方法来处理这种麻烦。如果有任何建议证明不是这样,我们仍然欢迎。为了实现这一点,每个包装好的调用都需要附上一个激活和随后的停用请求。感谢,他提供了一个合理的方法来处理这个问题。你不能在这里使用终结器,错误的线程。@HansPassant你能详细解释一下为什么这是不可能的吗?让运行时在卸载DLL后隐式清理激活上下文是不对的。@DavidHeffernan这只会掩盖问题。我想找出一种方法,以一种干净的方式停用激活上下文,这是没有例外的。一点也不。我说最后没赶上。最后一块确保你以确定的方式完成。不,你仍然不明白我的意思。显然,您不希望抛出异常。因此,请确保从激活的线程中停用。试一下/最后一次。在伪代码中,激活;试试{do stuff}finally{deactivate;}
最后一步是确保执行停用。因为你不再依赖GC了。在处理非托管资源时,try/finally是您的首选技术。