none
Interop: Symmetric Resource Acquisition and Release RRS feed

  • Question

  • I need a better way of managing finalizers for interop.  I'll describe what I'm doing, please tell me what I could do differently.

    I can use finalizers for classes I intend to pass from managed to native code, like this:

            [StructLayout(LayoutKind.Sequential)]
            public class HTTP_SERVICE_CONFIG_SSL_KEY
            {
                public IntPtr pIpPort;

                public HTTP_SERVICE_CONFIG_SSL_KEY(SOCKADDR sockInfo)
                {
                    pIpPort = Marshal.AllocHGlobal(Marshal.SizeOf(sockInfo));
                    Marshal.StructureToPtr(sockInfo, pIpPort, true);
                }
                ~HTTP_SERVICE_CONFIG_SSL_KEY()
                {
                    Marshal.FreeHGlobal(pIpPort);
                }
            }

    But doom follows if I use the "usual" mechanism to do the opposite, to receive that same structure from a native code allocation into managed memory:

        answer = (HTTP_SERVICE_CONFIG_SSL_KEY)Marshal.PtrToStructure(
            pOutputInfo,
            typeof(HTTP_SERVICE_CONFIG_SSL_KEY));


    That call will work, but eventually the new managed HTTP_SERVICE_CONFIG_SLL_KEY instance will be finalized, and then BOOM!!!!  The memory being freed in the finalizer was allocated in a different fashion, and so the whole thing falls apart.

    To solve this problem, I've used a couple different techniques.  First, I declare duplicate structures...those that have finalizers (for managed to unmanaged passing) and those that don't (for unmanaged to managed passing).  I hate that.  Second, I've tried catching exceptions in finalizers to "notice" that I've got a alloc/free mismatch.  Look at the finalizer in this sample:

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public class HTTP_SERVICE_CONFIG_SSL_PARAM
        {
            public int SslHashLength;
            public IntPtr pSslHash;
            public Guid AppId;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string pSslCertStoreName;
            public uint DefaultCertCheckMode;
            public int DefaultRevocationFreshnessTime;
            public int DefaultRevocationUrlRetrievalTimeout;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string pDefaultSslCtlIdentifier;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string pDefaultSslCtlStoreName;
            public uint DefaultFlags;

            public HTTP_SERVICE_CONFIG_SSL_PARAM(byte[] hash)
            {
                GCHandle gch = GCHandle.Alloc(hash, GCHandleType.Pinned);
                pSslHash = gch.AddrOfPinnedObject();
                SslHashLength = hash.Length;
            }
            ~HTTP_SERVICE_CONFIG_SSL_PARAM()
            {
                try
                {
                    GCHandle temp = GCHandle.FromIntPtr(pSslHash);
                    temp.Free();
                }
                catch (ArgumentException ex)
                {
                    Evdiently we did not allocate the memory, so we don't free it either.
                }
            }
        }

    There has got to be a better way to perform my finalizer cleanup, without resorting to catching exceptions.

    I've considered using boolean flags as part of the structs that indicate whether or not I was the one who did the allocation.  But once I introduce a bool flag, I change the size of the structure I'm in, and break all kinds of interop (particularly where nested structures are concerned).  Even if I could find a way to make that flag work for managed to unmanaged marshalling, the opposite certainly would not work.

    Certainly MS has a plain and obvious solution that covers this whole issue, and I've just not spotted it yet.  Ideas?


    -Brent Arias
    • Edited by Brent Arias Monday, June 29, 2009 9:34 PM fix code sample
    Monday, June 29, 2009 9:27 PM

Answers

  • But doom follows if I use the "usual" mechanism to do the opposite, to receive that same structure from a native code allocation into managed memory:

        answer = (HTTP_SERVICE_CONFIG_SSL_KEY)Marshal.PtrToStructure(
            pOutputInfo,
            typeof(HTTP_SERVICE_CONFIG_SSL_KEY));



    Hang on, that code doesn't do what I think you want it to do.

    The code dereferences pOutputInfo and copies sizeof(HTTP_SERVICE_CONFIG_SSL_KEY) == IntPtr.Size bytes from where it points to into your struct. Don't you actually want your HTTP_SERVICE_CONFIG_SSL_KEY to wrap the returned pointer, i.e. make pIpPort = pOutputInfo? If that's what you want, just add a regular consturctor or conversion operator that assigns pIpPort. This of course assumes that the returned pointer can be free'd using FreeHGlobal. If not, you have to keep track of who allocated the memory and how to free it.

    Mattias, C# MVP
    • Marked as answer by Zhi-Xin Ye Monday, July 6, 2009 11:42 AM
    Tuesday, June 30, 2009 2:48 PM
    Moderator