免注册COM互操作:在终结器中停用激活上下文会引发SEHException
本文关键字:激活 上下文 SEHException 互操作 COM 注册 | 更新日期: 2023-09-27 18:27:25
我目前在一个混合的托管/本机工作链上工作,需要创建一个无注册COM支持的激活上下文(请参阅将无注册COM清单嵌入到具有本机/托管环境的C#dll中)。以下代码段是C#DLL中一个较大类的一部分,该类包含对COM包装器的引用,并建立所需的激活上下文:
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()
方法中的pinvoked DeactivateActCtx()
函数。一旦调用它,就会抛出一个SEHException
:外部组件抛出了一个异常。0x80004005.
Marshal.GetLastWin32Error()
函数没有可用的错误代码,这将为我提供一些合理的信息。
到目前为止我尝试过的东西:
- 将
DestroyActivationContext()
函数从析构函数移到Dispose
方法,反之亦然 - 完全删除
IDisposable
接口 - 将底层COM对象的线程模型从Apartment更改为Free
- 为
DeactivateActCtx()
函数提供DEACTIVATE_ACTCTX_FLAG_FORCE_EARLY_DEACTIVATION
作为输入参数 - 将
IntPtr
实例的类型更改为UIntPtr
不幸的是,这些选择都无济于事。有没有任何可能的方法可以在不面对上述SEHException
的情况下解除激活上下文?
更新
垃圾收集器的线程似乎是问题的原因。GC总是在自己的不同线程中运行,没有明显的其他指定可能性。当试图从这个特定线程停用激活上下文(DeactivateActCtx
)时,似乎有某种访问冲突在幕后进行。所以我想除了激活和停用每个包装调用中的激活上下文之外,没有什么简单的方法可以处理这种麻烦。任何能够证明并非如此的建议仍然是受欢迎的。
为了实现这一点,每个封装的调用都需要包含一个激活和随后的去激活请求。感谢David Heffernan,他提供了一个合理的方法来处理这个问题。