File: Internal\Runtime\CompilerHelpers\InteropHelpers.cs
Web Access
Project: src\src\runtime\src\coreclr\nativeaot\System.Private.CoreLib\src\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using System.Runtime.InteropServices.ObjectiveC;
using System.Runtime.Loader;
using System.Text;
using System.Threading;

using Internal.Reflection.Augments;

namespace Internal.Runtime.CompilerHelpers
{
    /// <summary>
    /// These methods are used to throw exceptions from generated code.
    /// </summary>
    internal static class InteropHelpers
    {
        internal static unsafe byte* StringToAnsiString(string str, bool bestFit, bool throwOnUnmappableChar)
        {
            return PInvokeMarshal.StringToAnsiString(str, bestFit, throwOnUnmappableChar);
        }

        public static unsafe string AnsiStringToString(byte* buffer)
        {
            return PInvokeMarshal.AnsiStringToString(buffer);
        }

        internal static unsafe void StringToByValAnsiString(string str, byte* pNative, int charCount, bool bestFit, bool throwOnUnmappableChar)
        {
            if (str != null)
            {
                // Truncate the string if it is larger than specified by SizeConst
                int lenUnicode = str.Length;
                if (lenUnicode >= charCount)
                    lenUnicode = charCount - 1;

                fixed (char* pManaged = str)
                {
                    PInvokeMarshal.StringToAnsiString(pManaged, lenUnicode, pNative, /*terminateWithNull=*/true, bestFit, throwOnUnmappableChar);
                }
            }
            else
            {
                (*pNative) = (byte)'\0';
            }
        }

        public static unsafe string ByValAnsiStringToString(byte* buffer, int length)
        {
            int end = new ReadOnlySpan<byte>(buffer, length).IndexOf((byte)0);
            if (end >= 0)
            {
                length = end;
            }

            return new string((sbyte*)buffer, 0, length);
        }

        internal static unsafe void StringToUnicodeFixedArray(string str, ushort* buffer, int length)
        {
            ReadOnlySpan<char> managed = str;
            Span<char> native = new Span<char>((char*)buffer, length);

            int numChars = Math.Min(managed.Length, length - 1);

            managed.Slice(0, numChars).CopyTo(native);
            native[numChars] = '\0';
        }

        internal static unsafe string UnicodeToStringFixedArray(ushort* buffer, int length)
        {
            int end = new ReadOnlySpan<char>(buffer, length).IndexOf('\0');
            if (end >= 0)
            {
                length = end;
            }

            return new string((char*)buffer, 0, length);
        }

        internal static unsafe char* StringToUnicodeBuffer(string str)
        {
            return (char*)Marshal.StringToCoTaskMemUni(str);
        }

        public static unsafe byte* AllocMemoryForAnsiStringBuilder(StringBuilder sb)
        {
            if (sb == null)
            {
                return null;
            }
            return (byte*)CoTaskMemAllocAndZeroMemory(checked((sb.Capacity + 2) * Marshal.SystemMaxDBCSCharSize));
        }

        public static unsafe char* AllocMemoryForUnicodeStringBuilder(StringBuilder sb)
        {
            if (sb == null)
            {
                return null;
            }
            return (char*)CoTaskMemAllocAndZeroMemory(checked((sb.Capacity + 2) * 2));
        }

        public static unsafe byte* AllocMemoryForAnsiCharArray(char[] chArray)
        {
            if (chArray == null)
            {
                return null;
            }
            return (byte*)CoTaskMemAllocAndZeroMemory(checked((chArray.Length + 2) * Marshal.SystemMaxDBCSCharSize));
        }

        public static unsafe void AnsiStringToStringBuilder(byte* newBuffer, System.Text.StringBuilder stringBuilder)
        {
            if (stringBuilder == null)
                return;

            PInvokeMarshal.AnsiStringToStringBuilder(newBuffer, stringBuilder);
        }

        public static unsafe void UnicodeStringToStringBuilder(ushort* newBuffer, System.Text.StringBuilder stringBuilder)
        {
            if (stringBuilder == null)
                return;

            PInvokeMarshal.UnicodeStringToStringBuilder(newBuffer, stringBuilder);
        }

        public static unsafe void StringBuilderToAnsiString(System.Text.StringBuilder stringBuilder, byte* pNative,
            bool bestFit, bool throwOnUnmappableChar)
        {
            if (pNative == null)
                return;

            PInvokeMarshal.StringBuilderToAnsiString(stringBuilder, pNative, bestFit, throwOnUnmappableChar);
        }

        public static unsafe void StringBuilderToUnicodeString(System.Text.StringBuilder stringBuilder, ushort* destination)
        {
            if (destination == null)
                return;

            PInvokeMarshal.StringBuilderToUnicodeString(stringBuilder, destination);
        }

        public static unsafe void WideCharArrayToAnsiCharArray(char[] managedArray, byte* pNative, bool bestFit, bool throwOnUnmappableChar)
        {
            PInvokeMarshal.WideCharArrayToAnsiCharArray(managedArray, pNative, bestFit, throwOnUnmappableChar);
        }

        /// <summary>
        /// Convert ANSI ByVal byte array to UNICODE wide char array, best fit
        /// </summary>
        /// <remarks>
        /// * This version works with array instead to string, it means that the len must be provided and there will be NO NULL to
        /// terminate the array.
        /// * The buffer to the UNICODE wide char array must be allocated by the caller.
        /// </remarks>
        /// <param name="pNative">Pointer to the ANSI byte array. Could NOT be null.</param>
        /// <param name="managedArray">Wide char array that has already been allocated.</param>
        public static unsafe void AnsiCharArrayToWideCharArray(byte* pNative, char[] managedArray)
        {
            PInvokeMarshal.AnsiCharArrayToWideCharArray(pNative, managedArray);
        }

        /// <summary>
        /// Convert a single UNICODE wide char to a single ANSI byte.
        /// </summary>
        /// <param name="managedValue">single UNICODE wide char value</param>
        /// <param name="bestFit">Enable best-fit mapping behavior</param>
        /// <param name="throwOnUnmappableChar">Throw an exception on an unmappable Unicode character</param>
        public static unsafe byte WideCharToAnsiChar(char managedValue, bool bestFit, bool throwOnUnmappableChar)
        {
            return PInvokeMarshal.WideCharToAnsiChar(managedValue, bestFit, throwOnUnmappableChar);
        }

        /// <summary>
        /// Convert a single ANSI byte value to a single UNICODE wide char value, best fit.
        /// </summary>
        /// <param name="nativeValue">Single ANSI byte value.</param>
        public static unsafe char AnsiCharToWideChar(byte nativeValue)
        {
            return PInvokeMarshal.AnsiCharToWideChar(nativeValue);
        }

        internal static double DateTimeToOleDateTime(DateTime value)
        {
            return value.ToOADate();
        }

        internal static DateTime OleDateTimeToDateTime(double value)
        {
            return DateTime.FromOADate(value);
        }

        internal static long DecimalToOleCurrency(decimal value)
        {
            return decimal.ToOACurrency(value);
        }

        internal static decimal OleCurrencyToDecimal(long value)
        {
            return decimal.FromOACurrency(value);
        }

        internal static unsafe string BstrBufferToString(char* buffer)
        {
            if (buffer == null)
                return null;

            return Marshal.PtrToStringBSTR((IntPtr)buffer);
        }

        internal static unsafe byte* StringToAnsiBstrBuffer(string s)
        {
            if (s is null)
            {
                return (byte*)IntPtr.Zero;
            }

            int stringLength = s.Length;
            fixed (char* pStr = s)
            {
                int nativeLength = PInvokeMarshal.GetByteCount(pStr, stringLength);
                byte* bstr = (byte*)Marshal.AllocBSTRByteLen((uint)nativeLength);
                PInvokeMarshal.ConvertWideCharToMultiByte(pStr, stringLength, bstr, nativeLength, bestFit: false, throwOnUnmappableChar: false);
                return bstr;
            }
        }

        internal static unsafe string AnsiBstrBufferToString(byte* buffer)
        {
            if (buffer == null)
                return null;

            return Marshal.PtrToStringAnsi((IntPtr)buffer, (int)Marshal.SysStringByteLen((IntPtr)buffer));
        }

        internal static unsafe IntPtr ResolvePInvoke(MethodFixupCell* pCell)
        {
            if (pCell->Target != IntPtr.Zero)
                return pCell->Target;

            return ResolvePInvokeSlow(pCell);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        internal static unsafe IntPtr ResolvePInvokeSlow(MethodFixupCell* pCell)
        {
            int lastSystemError = Marshal.GetLastSystemError();

            ModuleFixupCell* pModuleCell = pCell->Module;
            IntPtr hModule = pModuleCell->Handle;
            if (hModule == IntPtr.Zero)
            {
                FixupModuleCell(pModuleCell);
                hModule = pModuleCell->Handle;
            }

            FixupMethodCell(hModule, pCell);

            Marshal.SetLastSystemError(lastSystemError);

            return pCell->Target;
        }

        internal static unsafe void FreeLibrary(IntPtr hModule)
        {
#if !TARGET_UNIX
            Interop.Kernel32.FreeLibrary(hModule);
#else
            Interop.Sys.FreeLibrary(hModule);
#endif
        }

        private static unsafe string GetModuleName(ModuleFixupCell* pCell)
        {
            byte* pModuleName = (byte*)pCell->ModuleName;
            return Encoding.UTF8.GetString(pModuleName, string.strlen(pModuleName));
        }

        internal static unsafe void FixupModuleCell(ModuleFixupCell* pCell)
        {
            string moduleName = GetModuleName(pCell);

            uint dllImportSearchPath = (uint)DllImportSearchPath.AssemblyDirectory;
            bool hasDllImportSearchPath = (pCell->DllImportSearchPathAndCookie & InteropDataConstants.HasDllImportSearchPath) != 0;
            if (hasDllImportSearchPath)
            {
                dllImportSearchPath = pCell->DllImportSearchPathAndCookie & ~InteropDataConstants.HasDllImportSearchPath;
            }

            Assembly callingAssembly = ReflectionAugments.GetAssemblyForHandle(new RuntimeTypeHandle(pCell->CallingAssemblyType));

            // First check if there's a NativeLibrary callback and call it to attempt the resolution
            IntPtr hModule = NativeLibrary.LoadLibraryCallbackStub(moduleName, callingAssembly, hasDllImportSearchPath, dllImportSearchPath);
            if (hModule == IntPtr.Zero)
            {
                // NativeLibrary callback didn't resolve the library. Use built-in rules.
                NativeLibrary.LoadLibErrorTracker loadLibErrorTracker = default;

                hModule = NativeLibrary.LoadBySearch(
                    callingAssembly,
                    hasDllImportSearchPath,
                    searchAssemblyDirectory: (dllImportSearchPath & (uint)DllImportSearchPath.AssemblyDirectory) != 0,
                    dllImportSearchPathFlags: (int)(dllImportSearchPath & ~(uint)DllImportSearchPath.AssemblyDirectory),
                    ref loadLibErrorTracker,
                    moduleName);

                if (hModule == IntPtr.Zero)
                {
                    // Built-in rules didn't resolve the library. Use AssemblyLoadContext as a last chance attempt.
                    AssemblyLoadContext loadContext = AssemblyLoadContext.GetLoadContext(callingAssembly)!;
                    hModule = loadContext.GetResolvedUnmanagedDll(callingAssembly, moduleName);
                }

                if (hModule == IntPtr.Zero)
                {
                    // If the module is still unresolved, this is an error.
                    loadLibErrorTracker.Throw(moduleName);
                }
            }

            Debug.Assert(hModule != IntPtr.Zero);
            var oldValue = Interlocked.CompareExchange(ref pCell->Handle, hModule, IntPtr.Zero);
            if (oldValue != IntPtr.Zero)
            {
                // Some other thread won the race to fix it up.
                FreeLibrary(hModule);
            }
        }

        internal static unsafe void FixupMethodCell(IntPtr hModule, MethodFixupCell* pCell)
        {
            byte* methodName = (byte*)pCell->MethodName;
            IntPtr pTarget;

#if FEATURE_OBJCMARSHAL
#pragma warning disable CA1416
            if (pCell->IsObjectiveCMessageSend && ObjectiveCMarshal.TryGetGlobalMessageSendCallback(pCell->ObjectiveCMessageSendFunction, out pTarget))
            {
                Debug.Assert(pTarget != IntPtr.Zero);
                pCell->Target = pTarget;
                return;
            }
#pragma warning restore CA1416
#endif

#if TARGET_WINDOWS
            CharSet charSetMangling = pCell->CharSetMangling;
            if (charSetMangling == 0)
            {
                // Look for the user-provided entry point name only
                pTarget = GetProcAddressWithMangling(hModule, methodName, pCell);
            }
            else if (charSetMangling == CharSet.Ansi)
            {
                // For ANSI, look for the user-provided entry point name first.
                // If that does not exist, try the charset suffix.
                pTarget = GetProcAddressWithMangling(hModule, methodName, pCell);
                if (pTarget == IntPtr.Zero)
                    pTarget = GetProcAddressWithSuffix(hModule, methodName, (byte)'A', pCell);
            }
            else
            {
                // For Unicode, look for the entry point name with the charset suffix first.
                // The 'W' API takes precedence over the undecorated one.
                pTarget = GetProcAddressWithSuffix(hModule, methodName, (byte)'W', pCell);
                if (pTarget == IntPtr.Zero)
                    pTarget = GetProcAddressWithMangling(hModule, methodName, pCell);
            }
#else
            pTarget = Interop.Sys.GetProcAddress(hModule, methodName);
#endif
            if (pTarget == IntPtr.Zero)
            {
                string entryPointName = Encoding.UTF8.GetString(methodName, string.strlen(methodName));
                throw new EntryPointNotFoundException(SR.Format(SR.Arg_EntryPointNotFoundExceptionParameterized, entryPointName, GetModuleName(pCell->Module)));
            }

            pCell->Target = pTarget;
        }

#if TARGET_WINDOWS
        private static unsafe IntPtr GetProcAddressWithMangling(IntPtr hModule, byte* methodName, MethodFixupCell* pCell)
        {
            IntPtr pMethod = Interop.Kernel32.GetProcAddress(hModule, methodName);
#if TARGET_X86
            if (pMethod == IntPtr.Zero && pCell->IsStdcall)
            {
                int nameLength = string.strlen(methodName);
                // We need to add an extra bytes for the prefix, null terminator and stack size suffix:
                // - 1 byte for '_' prefix
                // - 1 byte for '@' suffix
                // - up to 10 bytes for digits (maximum positive number representable by uint)
                // - 1 byte for NULL termination character
                byte* probedMethodName = stackalloc byte[nameLength + 13];
                probedMethodName[0] = (byte)'_';
                Unsafe.CopyBlock(probedMethodName + 1, methodName, (uint)nameLength);
                probedMethodName[nameLength + 1] = (byte)'@';
                pCell->SignatureBytes.TryFormat(new Span<byte>(probedMethodName + 2 + nameLength, 10), out int bytesWritten);
                probedMethodName[nameLength + 2 + bytesWritten] = 0;
                pMethod = Interop.Kernel32.GetProcAddress(hModule, probedMethodName);
            }
#else
            _ = pCell;
#endif
            return pMethod;
        }

        private static unsafe IntPtr GetProcAddressWithSuffix(IntPtr hModule, byte* methodName, byte suffix, MethodFixupCell* pCell)
        {
            int nameLength = string.strlen(methodName);

            // We need to add an extra byte for the suffix, and an extra byte for the null terminator
            byte* probedMethodName = stackalloc byte[nameLength + 2];
            Unsafe.CopyBlock(probedMethodName, methodName, (uint)nameLength);
            probedMethodName[nameLength] = suffix;
            probedMethodName[nameLength + 1] = 0;

            return GetProcAddressWithMangling(hModule, probedMethodName, pCell);
        }
#endif

        internal static unsafe void* CoTaskMemAllocAndZeroMemory(int size)
        {
            byte* ptr = (byte*)Marshal.AllocCoTaskMem(size);

            // Marshal.AllocCoTaskMem will throw OOMException if out of memory
            Debug.Assert(ptr != null);

            NativeMemory.Clear(ptr, (uint)size);
            return ptr;
        }

        /// <summary>
        /// Retrieves the function pointer for the current open static delegate that is being called
        /// </summary>
        public static IntPtr GetCurrentCalleeOpenStaticDelegateFunctionPointer()
        {
            return PInvokeMarshal.GetCurrentCalleeOpenStaticDelegateFunctionPointer();
        }

        /// <summary>
        /// Retrieves the current delegate that is being called
        /// </summary>
        public static T GetCurrentCalleeDelegate<T>() where T : Delegate
        {
            return PInvokeMarshal.GetCurrentCalleeDelegate<T>();
        }

        public static IntPtr ConvertManagedComInterfaceToNative(object pUnk, Guid interfaceGuid)
        {
            if (pUnk == null)
            {
                return IntPtr.Zero;
            }

#if TARGET_WINDOWS
#pragma warning disable CA1416
            return ComWrappers.ComInterfaceForObject(pUnk, interfaceGuid);
#pragma warning restore CA1416
#else
            throw new PlatformNotSupportedException(SR.PlatformNotSupported_ComInterop);
#endif
        }

        public static IntPtr ConvertManagedComInterfaceToIUnknown(object pUnk)
        {
            if (pUnk == null)
            {
                return IntPtr.Zero;
            }

#if TARGET_WINDOWS
#pragma warning disable CA1416
            return ComWrappers.ComInterfaceForObject(pUnk);
#pragma warning restore CA1416
#else
            throw new PlatformNotSupportedException(SR.PlatformNotSupported_ComInterop);
#endif
        }

        public static object ConvertNativeComInterfaceToManaged(IntPtr pUnk)
        {
            if (pUnk == IntPtr.Zero)
            {
                return null;
            }

#if TARGET_WINDOWS
#pragma warning disable CA1416
            return ComWrappers.ComObjectForInterface(pUnk, CreateObjectFlags.TrackerObject | CreateObjectFlags.Unwrap);
#pragma warning restore CA1416
#else
            throw new PlatformNotSupportedException(SR.PlatformNotSupported_ComInterop);
#endif
        }

        [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
            Justification = "This API will be called from compiler generated code only.")]
        internal static unsafe int AsAnyGetNativeSize(object o)
        {
            // Array, string and StringBuilder are not implemented.
            if (o.GetMethodTable()->IsArray ||
                o is string ||
                o is StringBuilder)
            {
                throw new PlatformNotSupportedException();
            }

            // Assume that this is a type with layout.
            return Marshal.SizeOf(o.GetType());
        }

        [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
            Justification = "This API will be called from compiler generated code only.")]
        internal static unsafe void AsAnyMarshalManagedToNative(object o, IntPtr address)
        {
            // Array, string and StringBuilder are not implemented.
            if (o.GetMethodTable()->IsArray ||
                o is string ||
                o is StringBuilder)
            {
                throw new PlatformNotSupportedException();
            }

            Marshal.StructureToPtr(o, address, fDeleteOld: false);
        }

        internal static unsafe void AsAnyMarshalNativeToManaged(IntPtr address, object o)
        {
            // Array, string and StringBuilder are not implemented.
            if (o.GetMethodTable()->IsArray ||
                o is string ||
                o is StringBuilder)
            {
                throw new PlatformNotSupportedException();
            }

            Marshal.PtrToStructureImpl(address, o);
        }

        [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
            Justification = "This API will be called from compiler generated code only.")]
        internal static unsafe void AsAnyCleanupNative(IntPtr address, object o)
        {
            // Array, string and StringBuilder are not implemented.
            if (o.GetMethodTable()->IsArray ||
                o is string ||
                o is StringBuilder)
            {
                throw new PlatformNotSupportedException();
            }

            Marshal.DestroyStructure(address, o.GetType());
        }

        internal static unsafe object? VariantToObject(IntPtr pSrcNativeVariant)
        {
            if (pSrcNativeVariant == IntPtr.Zero)
            {
                return null;
            }

#if TARGET_WINDOWS
#pragma warning disable CA1416
            return Marshal.GetObjectForNativeVariant(pSrcNativeVariant);
#pragma warning restore CA1416
#else
            throw new PlatformNotSupportedException(SR.PlatformNotSupported_ComInterop);
#endif
        }

        internal static unsafe void ConvertObjectToVariant(object? obj, IntPtr pDstNativeVariant)
        {
#if TARGET_WINDOWS
#pragma warning disable CA1416
            Marshal.GetNativeVariantForObject(obj, pDstNativeVariant);
#pragma warning restore CA1416
#else
            throw new PlatformNotSupportedException(SR.PlatformNotSupported_ComInterop);
#endif
        }

        internal static unsafe void CleanupVariant(IntPtr pDstNativeVariant)
        {
#if TARGET_WINDOWS
#pragma warning disable CA1416
            ComVariant* data = (ComVariant*)pDstNativeVariant;
            data->Dispose();
#pragma warning restore CA1416
#else
            throw new PlatformNotSupportedException(SR.PlatformNotSupported_ComInterop);
#endif
        }

        public static unsafe object InitializeCustomMarshaller(RuntimeTypeHandle pParameterType, RuntimeTypeHandle pMarshallerType, string cookie, delegate*<string, object> getInstanceMethod)
        {
            if (getInstanceMethod == null)
            {
                throw new ApplicationException();
            }

            if (!RuntimeImports.AreTypesAssignable(pMarshallerType.ToMethodTable(), MethodTable.Of<ICustomMarshaler>()))
            {
                throw new ApplicationException();
            }

            var marshaller = CustomMarshallerTable.s_customMarshallersTable.GetOrAdd(new CustomMarshallerKey(pParameterType, pMarshallerType, cookie, getInstanceMethod));
            if (marshaller == null)
            {
                throw new ApplicationException();
            }

            if (!RuntimeImports.AreTypesAssignable(marshaller.GetMethodTable(), MethodTable.Of<ICustomMarshaler>()))
            {
                throw new ApplicationException();
            }

            return marshaller;
        }

        [StructLayout(LayoutKind.Sequential)]
        internal unsafe struct ModuleFixupCell
        {
            public IntPtr Handle;
            public IntPtr ModuleName;
            public MethodTable* CallingAssemblyType;
            public uint DllImportSearchPathAndCookie;
        }

        [StructLayout(LayoutKind.Sequential)]
        internal unsafe struct MethodFixupCell
        {
            public IntPtr Target;
            public IntPtr MethodName;
            public ModuleFixupCell* Module;
            private uint Flags;

            public CharSet CharSetMangling => (CharSet)(Flags & MethodFixupCellFlagsConstants.CharSetMask);
#if FEATURE_OBJCMARSHAL
            public bool IsObjectiveCMessageSend => (Flags & MethodFixupCellFlagsConstants.IsObjectiveCMessageSendMask) != 0;
            public int ObjectiveCMessageSendFunction => (int)((Flags & MethodFixupCellFlagsConstants.ObjectiveCMessageSendFunctionMask) >> MethodFixupCellFlagsConstants.ObjectiveCMessageSendFunctionShift);
#elif TARGET_WINDOWS && TARGET_X86
            public bool IsStdcall => (Flags & MethodFixupCellFlagsConstants.IsStdcall) != 0;
            public ushort SignatureBytes => (ushort)(Flags >> 16);
#endif
        }

        internal unsafe struct CustomMarshallerKey : IEquatable<CustomMarshallerKey>
        {
            public CustomMarshallerKey(RuntimeTypeHandle pParameterType, RuntimeTypeHandle pMarshallerType, string cookie, delegate*<string, object> getInstanceMethod)
            {
                ParameterType = pParameterType;
                MarshallerType = pMarshallerType;
                Cookie = cookie;
                GetInstanceMethod = getInstanceMethod;
            }

            public RuntimeTypeHandle ParameterType { get; }
            public RuntimeTypeHandle MarshallerType { get; }
            public string Cookie { get; }
            public delegate*<string, object> GetInstanceMethod { get; }

            public override bool Equals(object obj)
            {
                if (!(obj is CustomMarshallerKey other))
                    return false;
                return Equals(other);
            }

            public bool Equals(CustomMarshallerKey other)
            {
                return ParameterType.Equals(other.ParameterType)
                    && MarshallerType.Equals(other.MarshallerType)
                    && Cookie.Equals(other.Cookie);
            }

            public override int GetHashCode()
            {
                return ParameterType.GetHashCode()
                    ^ MarshallerType.GetHashCode()
                    ^ Cookie.GetHashCode();
            }
        }

        internal sealed class CustomMarshallerTable : ConcurrentUnifier<CustomMarshallerKey, object>
        {
            internal static readonly CustomMarshallerTable s_customMarshallersTable = new CustomMarshallerTable();

            protected override unsafe object Factory(CustomMarshallerKey key)
            {
                return key.GetInstanceMethod(key.Cookie);
            }
        }
    }
}