我目前正在开发混合托管/本机工作链,需要为免注册COM支持创建激活上下文(请参阅Embed a Registration-Free COM manifest into a C# dll with native/managed environment)。以下代码段是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;
}
}
问题在于DeactivateActCtx()
方法中的DestroyActivationContext()
函数。一旦调用它,就会抛出SEHException
:外部组件抛出异常。 0x80004005的。
Marshal.GetLastWin32Error()
函数没有可用的错误代码,这将为我提供一些合理的信息。
到目前为止我尝试过的事情:
DestroyActivationContext()
函数从析构函数移动到Dispose
方法,反之亦然。IDisposable
界面。DeactivateActCtx()
函数DEACTIVATE_ACTCTX_FLAG_FORCE_EARLY_DEACTIVATION
作为输入参数。IntPtr
个实例的类型更改为UIntPtr
。不幸的是,这些选项都没有帮助。是否有任何可能的方法来解除激活上下文而不遇到前面提到的SEHException
?
更新
似乎垃圾收集器的线程是导致问题的原因。 GC总是在自己独特的线程中运行,没有明显的可能性来指定。当尝试从此特定线程停用激活上下文(DeactivateActCtx
)时,似乎存在某种类型的访问冲突。所以我猜除了在每个包装调用中激活和停用激活上下文之外,没有直接的方法来处理这种麻烦。任何证明不然的建议仍然是受欢迎的。