Registration-Free COM Interop: Deactivating activa

2019-08-08 11:14发布

问题:

I am currently working on a mixed managed / native work chain and need to create an activation context for registration-free COM support (see Embed a Registration-Free COM manifest into a C# dll with native/managed environment). The following snippet is part of a larger class inside a C# DLL, which holds a reference to a COM Wrapper and establishes the required activation context:

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;
    }
}

The problem lies in the pinvoked DeactivateActCtx() function inside the DestroyActivationContext() method. As soon as it's called, an SEHException is thrown: External component has thrown an exception. 0x80004005.

There is no error code available via the Marshal.GetLastWin32Error() function, which would provide me with some reasonable information.

Things I have tried so far:

  • Moving the DestroyActivationContext() function from the destructor to the Dispose method and vice versa.
  • Removing the IDisposable interface altogether.
  • Changing the threading model of the underlying COM object from Apartment to Free.
  • Providing the DeactivateActCtx() function with DEACTIVATE_ACTCTX_FLAG_FORCE_EARLY_DEACTIVATION as an input argument.
  • Changing the type of the IntPtr instances to UIntPtr.

Unfortunately, none of these options have helped. Is there any possible way of disestablishing the activation context without being confronted with the aforementioned SEHException?

UPDATE

It seems that the thread of the garbage collector is the cause of the problem. The GC always runs in its own distinct thread with no apparent possibility to specify otherwise. There seems to be some sort of access violation going on under the hood when trying to deactivate the activation context (DeactivateActCtx) from this specific thread. So I guess there's no straightforward way to deal with this nuisance, except activating and deactivating the activation context in each wrapped call. Any suggestions which would prove otherwise are still welcome.

回答1:

In order for this to work, each wrapped call needs to be enclosed with an activation and subsequent deactivation request. Thanks to David Heffernan, who provided a reasonable approach to deal with this issue.