免注册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)时,似乎有某种访问冲突在幕后进行。所以我想除了激活和停用每个包装调用中的激活上下文之外,没有什么简单的方法可以处理这种麻烦。任何能够证明并非如此的建议仍然是受欢迎的。

免注册COM互操作:在终结器中停用激活上下文会引发SEHException

为了实现这一点,每个封装的调用都需要包含一个激活和随后的去激活请求。感谢David Heffernan,他提供了一个合理的方法来处理这个问题。