diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Resources/Strings.resx b/src/coreclr/nativeaot/System.Private.CoreLib/src/Resources/Strings.resx index c1578ad1a155..28eac7190c2e 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Resources/Strings.resx @@ -3097,4 +3097,7 @@ The argv[0] argument cannot include a double quote. + + Attempt to update previously set global instance. + diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj index 9cbef4c2c297..0c237f8af254 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -208,6 +208,7 @@ + diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.CoreRT.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.CoreRT.cs new file mode 100644 index 000000000000..6f26cba112c1 --- /dev/null +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.CoreRT.cs @@ -0,0 +1,384 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections; +using System.Threading; +using System.Runtime.CompilerServices; +using Internal.Runtime.CompilerServices; + +namespace System.Runtime.InteropServices +{ + /// + /// Enumeration of flags for . + /// + [Flags] + public enum CreateComInterfaceFlags + { + None = 0, + + /// + /// The caller will provide an IUnknown Vtable. + /// + /// + /// This is useful in scenarios when the caller has no need to rely on an IUnknown instance + /// that is used when running managed code is not possible (i.e. during a GC). In traditional + /// COM scenarios this is common, but scenarios involving Reference Tracker hosting + /// calling of the IUnknown API during a GC is possible. + /// + CallerDefinedIUnknown = 1, + + /// + /// Flag used to indicate the COM interface should implement IReferenceTrackerTarget. + /// When this flag is passed, the resulting COM interface will have an internal implementation of IUnknown + /// and as such none should be supplied by the caller. + /// + TrackerSupport = 2, + } + + /// + /// Enumeration of flags for . + /// + [Flags] + public enum CreateObjectFlags + { + None = 0, + + /// + /// Indicate if the supplied external COM object implements the IReferenceTracker. + /// + TrackerObject = 1, + + /// + /// Ignore any internal caching and always create a unique instance. + /// + UniqueInstance = 2, + } + + /// + /// Internal enumeration used by the runtime to indicate the scenario for which ComWrappers is being used. + /// + internal enum ComWrappersScenario + { + Instance = 0, + TrackerSupportGlobalInstance = 1, + MarshallingGlobalInstance = 2, + } + + /// + /// Class for managing wrappers of COM IUnknown types. + /// + [CLSCompliant(false)] + public abstract partial class ComWrappers + { + /// + /// Interface type and pointer to targeted VTable. + /// + public struct ComInterfaceEntry + { + /// + /// Interface IID. + /// + public Guid IID; + + /// + /// Memory must have the same lifetime as the memory returned from the call to . + /// + public IntPtr Vtable; + } + + /// + /// ABI for function dispatch of a COM interface. + /// + public struct ComInterfaceDispatch + { + public IntPtr Vtable; + + /// + /// Given a from a generated Vtable, convert to the target type. + /// + /// Desired type. + /// Pointer supplied to Vtable function entry. + /// Instance of type associated with dispatched function call. + public static unsafe T GetInstance(ComInterfaceDispatch* dispatchPtr) where T : class + { + // See the dispatch section in the runtime for details on the masking below. + const long DispatchThisPtrMask = ~0xfL; + var comInstance = *(ComInterfaceInstance**)(((long)dispatchPtr) & DispatchThisPtrMask); + + return Unsafe.As(GCHandle.InternalGet(comInstance->GcHandle)); + } + + [StructLayout(LayoutKind.Sequential)] + private struct ComInterfaceInstance + { + internal IntPtr GcHandle; + } + } + + /// + /// Globally registered instance of the ComWrappers class for reference tracker support. + /// + private static ComWrappers? s_globalInstanceForTrackerSupport; + + /// + /// Globally registered instance of the ComWrappers class for marshalling. + /// + private static ComWrappers? s_globalInstanceForMarshalling; + + /// + /// Create a COM representation of the supplied object that can be passed to a non-managed environment. + /// + /// The managed object to expose outside the .NET runtime. + /// Flags used to configure the generated interface. + /// The generated COM interface that can be passed outside the .NET runtime. + public IntPtr GetOrCreateComInterfaceForObject(object instance, CreateComInterfaceFlags flags) + { + IntPtr ptr; + if (!TryGetOrCreateComInterfaceForObjectInternal(this, instance, flags, out ptr)) + throw new ArgumentException(); + + return ptr; + } + + /// + /// Create a COM representation of the supplied object that can be passed to a non-managed environment. + /// + /// The implementation to use when creating the COM representation. + /// The managed object to expose outside the .NET runtime. + /// Flags used to configure the generated interface. + /// The generated COM interface that can be passed outside the .NET runtime or IntPtr.Zero if it could not be created. + /// Returns true if a COM representation could be created, false otherwise + /// + /// If is null, the global instance (if registered) will be used. + /// + private static bool TryGetOrCreateComInterfaceForObjectInternal(ComWrappers? impl, object instance, CreateComInterfaceFlags flags, out IntPtr retValue) + { + if (instance == null) + throw new ArgumentNullException(nameof(instance)); + + throw new NotImplementedException(); + } + + /// + /// Compute the desired Vtable for respecting the values of . + /// + /// Target of the returned Vtables. + /// Flags used to compute Vtables. + /// The number of elements contained in the returned memory. + /// pointer containing memory for all COM interface entries. + /// + /// All memory returned from this function must either be unmanaged memory, pinned managed memory, or have been + /// allocated with the API. + /// + /// If the interface entries cannot be created and a negative or null and a non-zero are returned, + /// the call to will throw a . + /// + protected unsafe abstract ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count); + + // Called by the runtime to execute the abstract instance function + internal static unsafe void* CallComputeVtables(ComWrappersScenario scenario, ComWrappers? comWrappersImpl, object obj, CreateComInterfaceFlags flags, out int count) + { + ComWrappers? impl = null; + switch (scenario) + { + case ComWrappersScenario.Instance: + impl = comWrappersImpl; + break; + case ComWrappersScenario.TrackerSupportGlobalInstance: + impl = s_globalInstanceForTrackerSupport; + break; + case ComWrappersScenario.MarshallingGlobalInstance: + impl = s_globalInstanceForMarshalling; + break; + } + + if (impl is null) + { + count = -1; + return null; + } + + return impl.ComputeVtables(obj, flags, out count); + } + + /// + /// Get the currently registered managed object or creates a new managed object and registers it. + /// + /// Object to import for usage into the .NET runtime. + /// Flags used to describe the external object. + /// Returns a managed object associated with the supplied external COM object. + public object GetOrCreateObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags) + { + object? obj; + if (!TryGetOrCreateObjectForComInstanceInternal(this, externalComObject, flags, null, out obj)) + throw new ArgumentNullException(); + + return obj!; + } + + /// + /// Create a managed object for the object pointed at by respecting the values of . + /// + /// Object to import for usage into the .NET runtime. + /// Flags used to describe the external object. + /// Returns a managed object associated with the supplied external COM object. + /// + /// If the object cannot be created and null is returned, the call to will throw a . + /// + protected abstract object? CreateObject(IntPtr externalComObject, CreateObjectFlags flags); + + // Called by the runtime to execute the abstract instance function. + internal static object? CallCreateObject(ComWrappersScenario scenario, ComWrappers? comWrappersImpl, IntPtr externalComObject, CreateObjectFlags flags) + { + ComWrappers? impl = null; + switch (scenario) + { + case ComWrappersScenario.Instance: + impl = comWrappersImpl; + break; + case ComWrappersScenario.TrackerSupportGlobalInstance: + impl = s_globalInstanceForTrackerSupport; + break; + case ComWrappersScenario.MarshallingGlobalInstance: + impl = s_globalInstanceForMarshalling; + break; + } + + if (impl == null) + return null; + + return impl.CreateObject(externalComObject, flags); + } + + /// + /// Get the currently registered managed object or uses the supplied managed object and registers it. + /// + /// Object to import for usage into the .NET runtime. + /// Flags used to describe the external object. + /// The to be used as the wrapper for the external object + /// Returns a managed object associated with the supplied external COM object. + /// + /// If the instance already has an associated external object a will be thrown. + /// + public object GetOrRegisterObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags, object wrapper) + { + if (wrapper == null) + throw new ArgumentNullException(nameof(externalComObject)); + + object? obj; + if (!TryGetOrCreateObjectForComInstanceInternal(this, externalComObject, flags, wrapper, out obj)) + throw new ArgumentNullException(); + + return obj!; + } + + /// + /// Get the currently registered managed object or creates a new managed object and registers it. + /// + /// The implementation to use when creating the managed object. + /// Object to import for usage into the .NET runtime. + /// Flags used to describe the external object. + /// The to be used as the wrapper for the external object. + /// The managed object associated with the supplied external COM object or null if it could not be created. + /// Returns true if a managed object could be retrieved/created, false otherwise + /// + /// If is null, the global instance (if registered) will be used. + /// + private static bool TryGetOrCreateObjectForComInstanceInternal(ComWrappers? impl, IntPtr externalComObject, CreateObjectFlags flags, object? wrapperMaybe, out object? retValue) + { + if (externalComObject == IntPtr.Zero) + throw new ArgumentNullException(nameof(externalComObject)); + + object? wrapperMaybeLocal = wrapperMaybe; + retValue = null; + throw new NotImplementedException(); + } + + /// + /// Called when a request is made for a collection of objects to be released outside of normal object or COM interface lifetime. + /// + /// Collection of objects to release. + protected abstract void ReleaseObjects(IEnumerable objects); + + // Call to execute the virtual instance function + internal static void CallReleaseObjects(ComWrappers? comWrappersImpl, IEnumerable objects) + => (comWrappersImpl ?? s_globalInstanceForTrackerSupport!).ReleaseObjects(objects); + + /// + /// Register a instance to be used as the global instance for reference tracker support. + /// + /// Instance to register + /// + /// This function can only be called a single time. Subsequent calls to this function will result + /// in a being thrown. + /// + /// Scenarios where this global instance may be used are: + /// * Object tracking via the and flags. + /// + public static void RegisterForTrackerSupport(ComWrappers instance) + { + if (instance == null) + throw new ArgumentNullException(nameof(instance)); + + if (null != Interlocked.CompareExchange(ref s_globalInstanceForTrackerSupport, instance, null)) + { + throw new InvalidOperationException(SR.InvalidOperation_ResetGlobalComWrappersInstance); + } + + throw new NotImplementedException(); + } + + + /// + /// Register a instance to be used as the global instance for marshalling in the runtime. + /// + /// Instance to register + /// + /// This function can only be called a single time. Subsequent calls to this function will result + /// in a being thrown. + /// + /// Scenarios where this global instance may be used are: + /// * Usage of COM-related Marshal APIs + /// * P/Invokes with COM-related types + /// * COM activation + /// + public static void RegisterForMarshalling(ComWrappers instance) + { + if (instance == null) + throw new ArgumentNullException(nameof(instance)); + + if (null != Interlocked.CompareExchange(ref s_globalInstanceForMarshalling, instance, null)) + { + throw new InvalidOperationException(SR.InvalidOperation_ResetGlobalComWrappersInstance); + } + + // Indicate to the runtime that a global instance has been registered for marshalling. + // This allows the native runtime know to call into the managed ComWrappers only if a + // global instance is registered for marshalling. + throw new NotImplementedException(); + } + + /// + /// Get the runtime provided IUnknown implementation. + /// + /// Function pointer to QueryInterface. + /// Function pointer to AddRef. + /// Function pointer to Release. + protected static void GetIUnknownImpl(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease) + => throw new NotImplementedException(); + + internal static int CallICustomQueryInterface(object customQueryInterfaceMaybe, ref Guid iid, out IntPtr ppObject) + { + var customQueryInterface = customQueryInterfaceMaybe as ICustomQueryInterface; + if (customQueryInterface is null) + { + ppObject = IntPtr.Zero; + return -1; // See TryInvokeICustomQueryInterfaceResult + } + + return (int)customQueryInterface.GetInterface(ref iid, out ppObject); + } + } +}