Registration-Free COM Interop: Deactivating activation context in finalizer throws SEHException
Asked Answered
S

1

1

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.

Sedate answered 22/10, 2014 at 18:53 Comment(8)
You cannot use a finalizer here, wrong thread.Valuate
@HansPassant Could you elaborate a bit on why this isn't possible? Leaving it up to runtime to implicitly clean up the activation context after the DLL is unloaded just doesn't feel right.Sedate
@DavidHeffernan That would merely mask the problem. I would like to figure out a way deactivate the activation context in a clean way, that is without an exception.Sedate
Not at all. I said finally not catch. The finally block ensures you finalise in a deterministic way.Plerre
@DavidHeffernan I see what you mean there. However, I don't want to let the exception walk up the call stack, since the responsibility of dealing with the activation context should stay with the dll. That's why I'd like to avoid the SEHException in the first place.Sedate
Nope, you still don't see what I mean. Clearly you don't want an exception to be thrown. So make sure you deactivate from the thread which activated. Do that with a try/finally. In pseudo code, activate; try { do stuff } finally { deactivate; } The finally is to ensure that the deactivate happens. Since you are no longer relying on GC. When dealing with unmanaged resources, try/finally is your go to technique.Plerre
@DavidHeffernan Seems I misunderstood you indeed. No doubt your suggestion would work, however this would require me to activate and deactivate the context for each thread / wrapped COM call, thereby resulting in recurring activations and deactivations. I had hoped for a single activation and deactivation request.Sedate
This is it really. You can wrap it up by making a method that activates and deactivates and in between executes an anon method that you pass inPlerre
S
0

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.

Sedate answered 2/1, 2015 at 20:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.