// 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.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Runtime.Versioning;
namespace Internal.Runtime.InteropServices
{
internal static partial class ComponentActivator
{
private const string TrimIncompatibleWarningMessage = "Native hosting is not trim compatible and this warning will be seen if trimming is enabled.";
private const string NativeAOTIncompatibleWarningMessage = "The native code for the method requested might not be available at runtime.";
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("browser")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("maccatalyst")]
[UnsupportedOSPlatform("tvos")]
private static readonly Dictionary<string, IsolatedComponentLoadContext> s_assemblyLoadContexts = new Dictionary<string, IsolatedComponentLoadContext>(StringComparer.InvariantCulture);
private static readonly Dictionary<IntPtr, Delegate> s_delegates = new Dictionary<IntPtr, Delegate>();
private static readonly HashSet<string> s_loadedInDefaultContext = new HashSet<string>(StringComparer.InvariantCulture);
// Use a value defined in https://github.com/dotnet/runtime/blob/main/docs/design/features/host-error-codes.md
// To indicate the specific error when IsSupported is false
private const int HostFeatureDisabled = unchecked((int)0x800080a7);
[FeatureSwitchDefinition("System.Runtime.InteropServices.EnableConsumingManagedCodeFromNativeHosting")]
private static bool IsSupported { get; } = AppContext.TryGetSwitch("System.Runtime.InteropServices.EnableConsumingManagedCodeFromNativeHosting", out bool isSupported) ? isSupported : true;
public delegate int ComponentEntryPoint(IntPtr args, int sizeBytes);
private static string MarshalToString(IntPtr arg, string argName)
{
string? result = Marshal.PtrToStringAuto(arg);
ArgumentNullException.ThrowIfNull(result, argName);
return result;
}
/// <summary>
/// Native hosting entry point for creating a native delegate
/// </summary>
/// <param name="assemblyPathNative">Fully qualified path to assembly</param>
/// <param name="typeNameNative">Assembly qualified type name</param>
/// <param name="methodNameNative">Public static method name compatible with delegateType</param>
/// <param name="delegateTypeNative">Assembly qualified delegate type name</param>
/// <param name="reserved">Extensibility parameter (currently unused)</param>
/// <param name="functionHandle">Pointer where to store the function pointer result</param>
[RequiresDynamicCode(NativeAOTIncompatibleWarningMessage)]
[RequiresUnreferencedCode(TrimIncompatibleWarningMessage, Url = "https://aka.ms/dotnet-illink/nativehost")]
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("browser")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("maccatalyst")]
[UnsupportedOSPlatform("tvos")]
[UnmanagedCallersOnly]
public static unsafe int LoadAssemblyAndGetFunctionPointer(IntPtr assemblyPathNative,
IntPtr typeNameNative,
IntPtr methodNameNative,
IntPtr delegateTypeNative,
IntPtr reserved,
IntPtr functionHandle)
{
if (functionHandle != IntPtr.Zero)
*(IntPtr*)functionHandle = 0;
if (!IsSupported)
return HostFeatureDisabled;
try
{
// Validate all parameters first.
string assemblyPath = MarshalToString(assemblyPathNative, nameof(assemblyPathNative));
string typeName = MarshalToString(typeNameNative, nameof(typeNameNative));
string methodName = MarshalToString(methodNameNative, nameof(methodNameNative));
ArgumentOutOfRangeException.ThrowIfNotEqual(reserved, IntPtr.Zero);
ArgumentNullException.ThrowIfNull(functionHandle);
// Set up the AssemblyLoadContext for this delegate.
AssemblyLoadContext alc = GetIsolatedComponentLoadContext(assemblyPath);
// Create the function pointer.
*(IntPtr*)functionHandle = InternalGetFunctionPointer(alc, typeName, methodName, delegateTypeNative);
}
catch (Exception e)
{
return e.HResult;
}
return 0;
}
/// <summary>
/// Native hosting entry point for loading an assembly from a path
/// </summary>
/// <param name="assemblyPathNative">Fully qualified path to assembly</param>
/// <param name="loadContext">Extensibility parameter (currently unused)</param>
/// <param name="reserved">Extensibility parameter (currently unused)</param>
[RequiresDynamicCode(NativeAOTIncompatibleWarningMessage)]
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("browser")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("maccatalyst")]
[UnsupportedOSPlatform("tvos")]
[UnmanagedCallersOnly]
public static int LoadAssembly(IntPtr assemblyPathNative, IntPtr loadContext, IntPtr reserved)
{
if (!IsSupported)
return HostFeatureDisabled;
try
{
string assemblyPath = MarshalToString(assemblyPathNative, nameof(assemblyPathNative));
ArgumentOutOfRangeException.ThrowIfNotEqual(loadContext, IntPtr.Zero);
ArgumentOutOfRangeException.ThrowIfNotEqual(reserved, IntPtr.Zero);
LoadAssemblyLocal(assemblyPath);
}
catch (Exception e)
{
return e.HResult;
}
return 0;
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "The same feature switch applies to GetFunctionPointer and this function. We rely on the warning from GetFunctionPointer.")]
static void LoadAssemblyLocal(string assemblyPath) => LoadAssemblyImpl(assemblyPath);
}
[RequiresUnreferencedCode(TrimIncompatibleWarningMessage, Url = "https://aka.ms/dotnet-illink/nativehost")]
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("browser")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("maccatalyst")]
[UnsupportedOSPlatform("tvos")]
private static void LoadAssemblyImpl(string assemblyPath)
{
lock (s_loadedInDefaultContext)
{
if (s_loadedInDefaultContext.Contains(assemblyPath))
return;
var resolver = new AssemblyDependencyResolver(assemblyPath);
AssemblyLoadContext.Default.Resolving +=
(context, assemblyName) =>
{
string? assemblyPath = resolver.ResolveAssemblyToPath(assemblyName);
return assemblyPath != null
? context.LoadFromAssemblyPath(assemblyPath)
: null;
};
AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
s_loadedInDefaultContext.Add(assemblyPath);
}
}
/// <summary>
/// Native hosting entry point for loading an assembly from a byte array
/// </summary>
/// <param name="assembly">Bytes of the assembly to load</param>
/// <param name="assemblyByteLength">Byte length of the assembly to load</param>
/// <param name="symbols">Optional. Bytes of the symbols for the assembly</param>
/// <param name="symbolsByteLength">Optional. Byte length of the symbols for the assembly</param>
/// <param name="loadContext">Extensibility parameter (currently unused)</param>
/// <param name="reserved">Extensibility parameter (currently unused)</param>
[RequiresDynamicCode(NativeAOTIncompatibleWarningMessage)]
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("browser")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("maccatalyst")]
[UnsupportedOSPlatform("tvos")]
[UnmanagedCallersOnly]
public static unsafe int LoadAssemblyBytes(byte* assembly, nint assemblyByteLength, byte* symbols, nint symbolsByteLength, IntPtr loadContext, IntPtr reserved)
{
if (!IsSupported)
return HostFeatureDisabled;
try
{
ArgumentNullException.ThrowIfNull(assembly);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(assemblyByteLength);
ArgumentOutOfRangeException.ThrowIfGreaterThan(assemblyByteLength, int.MaxValue);
ArgumentOutOfRangeException.ThrowIfNotEqual(loadContext, IntPtr.Zero);
ArgumentOutOfRangeException.ThrowIfNotEqual(reserved, IntPtr.Zero);
ReadOnlySpan<byte> assemblySpan = new ReadOnlySpan<byte>(assembly, (int)assemblyByteLength);
ReadOnlySpan<byte> symbolsSpan = default;
if (symbols != null && symbolsByteLength > 0)
{
symbolsSpan = new ReadOnlySpan<byte>(symbols, (int)symbolsByteLength);
}
LoadAssemblyBytesLocal(assemblySpan, symbolsSpan);
}
catch (Exception e)
{
return e.HResult;
}
return 0;
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "The same feature switch applies to GetFunctionPointer and this function. We rely on the warning from GetFunctionPointer.")]
static void LoadAssemblyBytesLocal(ReadOnlySpan<byte> assemblyBytes, ReadOnlySpan<byte> symbolsBytes) => AssemblyLoadContext.Default.InternalLoad(assemblyBytes, symbolsBytes);
}
/// <summary>
/// Native hosting entry point for creating a native delegate
/// </summary>
/// <param name="typeNameNative">Assembly qualified type name</param>
/// <param name="methodNameNative">Public static method name compatible with delegateType</param>
/// <param name="delegateTypeNative">Assembly qualified delegate type name</param>
/// <param name="loadContext">Extensibility parameter (currently unused)</param>
/// <param name="reserved">Extensibility parameter (currently unused)</param>
/// <param name="functionHandle">Pointer where to store the function pointer result</param>
[RequiresDynamicCode(NativeAOTIncompatibleWarningMessage)]
[UnmanagedCallersOnly]
public static unsafe int GetFunctionPointer(IntPtr typeNameNative,
IntPtr methodNameNative,
IntPtr delegateTypeNative,
IntPtr loadContext,
IntPtr reserved,
IntPtr functionHandle)
{
if (functionHandle != IntPtr.Zero)
*(IntPtr*)functionHandle = 0;
if (!IsSupported)
{
#if CORECLR
try
{
OnDisabledGetFunctionPointerCall(typeNameNative, methodNameNative);
}
catch (Exception e)
{
// The callback can intentionally throw NotSupportedException to provide errors to consumers,
// so we let that one through. Any other exceptions must not be leaked out.
if (e is NotSupportedException)
throw;
return e.HResult;
}
#endif
return HostFeatureDisabled;
}
try
{
// Validate all parameters first.
string typeName = MarshalToString(typeNameNative, nameof(typeNameNative));
string methodName = MarshalToString(methodNameNative, nameof(methodNameNative));
ArgumentOutOfRangeException.ThrowIfNotEqual(loadContext, IntPtr.Zero);
ArgumentOutOfRangeException.ThrowIfNotEqual(reserved, IntPtr.Zero);
ArgumentNullException.ThrowIfNull(functionHandle);
#pragma warning disable IL2026 // suppressed in ILLink.Suppressions.LibraryBuild.xml
// Create the function pointer.
*(IntPtr*)functionHandle = InternalGetFunctionPointer(AssemblyLoadContext.Default, typeName, methodName, delegateTypeNative);
#pragma warning restore IL2026
}
catch (Exception e)
{
return e.HResult;
}
return 0;
}
[RequiresUnreferencedCode(TrimIncompatibleWarningMessage, Url = "https://aka.ms/dotnet-illink/nativehost")]
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("browser")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("tvos")]
private static IsolatedComponentLoadContext GetIsolatedComponentLoadContext(string assemblyPath)
{
IsolatedComponentLoadContext? alc;
lock (s_assemblyLoadContexts)
{
if (!s_assemblyLoadContexts.TryGetValue(assemblyPath, out alc))
{
alc = new IsolatedComponentLoadContext(assemblyPath);
s_assemblyLoadContexts.Add(assemblyPath, alc);
}
}
return alc;
}
[RequiresDynamicCode(NativeAOTIncompatibleWarningMessage)]
[RequiresUnreferencedCode(TrimIncompatibleWarningMessage, Url = "https://aka.ms/dotnet-illink/nativehost")]
private static IntPtr InternalGetFunctionPointer(AssemblyLoadContext alc,
string typeName,
string methodName,
IntPtr delegateTypeNative)
{
// Create a resolver callback for types.
Func<AssemblyName, Assembly> resolver = alc.LoadFromAssemblyName;
// Determine the signature of the type. There are 3 possibilities:
// * No delegate type was supplied - use the default (i.e. ComponentEntryPoint).
// * A sentinel value was supplied - the function is marked UnmanagedCallersOnly. This means
// a function pointer can be returned without creating a delegate.
// * A delegate type was supplied - Load the type and create a delegate for that method.
Type? delegateType;
if (delegateTypeNative == IntPtr.Zero)
{
delegateType = typeof(ComponentEntryPoint);
}
else if (delegateTypeNative == (IntPtr)(-1))
{
delegateType = null;
}
else
{
string delegateTypeName = MarshalToString(delegateTypeNative, nameof(delegateTypeNative));
delegateType = Type.GetType(delegateTypeName, resolver, null, throwOnError: true)!;
}
// Get the requested type.
Type type = Type.GetType(typeName, resolver, null, throwOnError: true)!;
IntPtr functionPtr;
if (delegateType == null)
{
// Match search semantics of the CreateDelegate() function below.
const BindingFlags bindingFlags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
MethodInfo methodInfo = type.GetMethod(methodName, bindingFlags) ??
throw new MissingMethodException(typeName, methodName);
// Verify the function is properly marked.
if (null == methodInfo.GetCustomAttribute<UnmanagedCallersOnlyAttribute>())
throw new InvalidOperationException(SR.InvalidOperation_FunctionMissingUnmanagedCallersOnly);
functionPtr = methodInfo.MethodHandle.GetFunctionPointer();
}
else
{
Delegate d = Delegate.CreateDelegate(delegateType, type, methodName);
functionPtr = Marshal.GetFunctionPointerForDelegate(d);
lock (s_delegates)
{
// Keep a reference to the delegate to prevent it from being garbage collected
s_delegates[functionPtr] = d;
}
}
return functionPtr;
}
}
}