// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #if USE_NETCORE using System; using System.Collections.Generic; using System.Globalization; using System.Linq; #if !USE_AOT using System.Linq.Expressions; #endif using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Loader; using System.Runtime.CompilerServices; using FlaxEngine.Assertions; using System.Collections.Concurrent; using System.IO; using System.Text; using System.Threading; namespace FlaxEngine.Interop { /// /// Provides a Mono-like API for native code to access managed runtime. /// [HideInEditor] public static unsafe partial class NativeInterop { internal static Dictionary AssemblyLocations = new(); private static bool firstAssemblyLoaded = false; private static Dictionary typeCache = new(); private static IntPtr boolTruePtr = ManagedHandle.ToIntPtr(ManagedHandle.Alloc((int)1, GCHandleType.Pinned)); private static IntPtr boolFalsePtr = ManagedHandle.ToIntPtr(ManagedHandle.Alloc((int)0, GCHandleType.Pinned)); private static List methodHandles = new(); private static ConcurrentDictionary cachedDelegates = new(); private static Dictionary managedTypes = new(new TypeComparer()); private static List fieldHandleCache = new(); #if FLAX_EDITOR private static List methodHandlesCollectible = new(); private static ConcurrentDictionary cachedDelegatesCollectible = new(); private static Dictionary managedTypesCollectible = new(new TypeComparer()); private static List fieldHandleCacheCollectible = new(); #endif private static Dictionary classAttributesCacheCollectible = new(); private static Dictionary assemblyHandles = new(); private static ConcurrentDictionary _typeSizeCache = new(); private static Dictionary loadedNativeLibraries = new(); internal static Dictionary libraryPaths = new(); private static Dictionary assemblyOwnedNativeLibraries = new(); internal static AssemblyLoadContext scriptingAssemblyLoadContext; [System.Diagnostics.DebuggerStepThrough] private static IntPtr NativeLibraryImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? dllImportSearchPath) { if (!loadedNativeLibraries.TryGetValue(libraryName, out IntPtr nativeLibrary)) { if (!libraryPaths.TryGetValue(libraryName, out var nativeLibraryPath)) nativeLibraryPath = libraryName; nativeLibrary = NativeLibrary.Load(nativeLibraryPath, assembly, dllImportSearchPath); loadedNativeLibraries.Add(libraryName, nativeLibrary); assemblyOwnedNativeLibraries.Add(assembly, libraryName); } return nativeLibrary; } private static void InitScriptingAssemblyLoadContext() { #if FLAX_EDITOR var isCollectible = true; #else var isCollectible = false; #endif scriptingAssemblyLoadContext = new AssemblyLoadContext("Flax", isCollectible); #if FLAX_EDITOR scriptingAssemblyLoadContext.Resolving += OnScriptingAssemblyLoadContextResolving; #endif } [UnmanagedCallersOnly] internal static unsafe void Init() { NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), NativeLibraryImportResolver); // Change default culture to match with Mono runtime default culture CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture; CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture; System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; System.Threading.Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; InitScriptingAssemblyLoadContext(); DelegateHelpers.InitMethods(); } #if FLAX_EDITOR private static Assembly OnScriptingAssemblyLoadContextResolving(AssemblyLoadContext assemblyLoadContext, AssemblyName assemblyName) { // FIXME: There should be a better way to resolve the path to EditorTargetPath where the dependencies are stored foreach (string libraryPath in libraryPaths.Values) { string editorTargetPath = Path.GetDirectoryName(libraryPath); var assemblyPath = Path.Combine(editorTargetPath, assemblyName.Name + ".dll"); if (File.Exists(assemblyPath)) return assemblyLoadContext.LoadFromAssemblyPath(assemblyPath); } return null; } #endif [UnmanagedCallersOnly] internal static unsafe void Exit() { } #if !USE_AOT // Cache offsets to frequently accessed fields of FlaxEngine.Object private static int unmanagedPtrFieldOffset = IntPtr.Size + (Unsafe.Read((typeof(FlaxEngine.Object).GetField("__unmanagedPtr", BindingFlags.Instance | BindingFlags.NonPublic).FieldHandle.Value + 4 + IntPtr.Size).ToPointer()) & 0xFFFFFF); private static int internalIdFieldOffset = IntPtr.Size + (Unsafe.Read((typeof(FlaxEngine.Object).GetField("__internalId", BindingFlags.Instance | BindingFlags.NonPublic).FieldHandle.Value + 4 + IntPtr.Size).ToPointer()) & 0xFFFFFF); [UnmanagedCallersOnly] internal static void ScriptingObjectSetInternalValues(ManagedHandle objectHandle, IntPtr unmanagedPtr, IntPtr idPtr) { object obj = objectHandle.Target; if (obj is not Object) return; { ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(unmanagedPtrFieldOffset, ref obj); fieldRef = unmanagedPtr; } if (idPtr != IntPtr.Zero) { ref Guid nativeId = ref Unsafe.AsRef(idPtr.ToPointer()); ref Guid fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(internalIdFieldOffset, ref obj); fieldRef = nativeId; } } [UnmanagedCallersOnly] internal static ManagedHandle ScriptingObjectCreate(ManagedHandle typeHandle, IntPtr unmanagedPtr, IntPtr idPtr) { TypeHolder typeHolder = Unsafe.As(typeHandle.Target); try { object obj = typeHolder.CreateScriptingObject(unmanagedPtr, idPtr); return ManagedHandle.Alloc(obj); } catch (Exception ex) { Debug.LogException(ex); } return new ManagedHandle(); } #endif internal static void* NativeAlloc(int byteCount) { return NativeMemory.AlignedAlloc((UIntPtr)byteCount, 16); } internal static void* NativeAlloc(int elementCount, int elementSize) { return NativeMemory.AlignedAlloc((UIntPtr)(elementCount * elementSize), 16); } internal static IntPtr NativeAllocStringAnsi(string str) { if (str is null) return IntPtr.Zero; int length = str.Length + 1; void* ptr = NativeMemory.AlignedAlloc((UIntPtr)length, 16); Span byteSpan = new Span(ptr, length); Encoding.ASCII.GetBytes(str, byteSpan); byteSpan[length - 1] = 0; return (IntPtr)ptr; } internal static void NativeFree(void* ptr) { NativeMemory.AlignedFree(ptr); } /// /// Converts a delegate into a function pointer that is callable from unmanaged code via but cached delegate to prevent collecting it by GC. /// /// The type of delegate to convert. /// The delegate to be passed to unmanaged code. /// A value that can be passed to unmanaged code, which, in turn, can use it to call the underlying managed delegate. public static IntPtr GetFunctionPointerForDelegate(TDelegate d) where TDelegate : notnull { // Example use-case: C# script runs actions via JobSystem.Dispatch which causes crash due to GC collecting Delegate object ManagedHandle.Alloc(d, GCHandleType.Weak); return Marshal.GetFunctionPointerForDelegate(d); } /// /// Converts array of GC Handles from native runtime to managed array. /// /// Array element type. /// Input array. /// Output array. public static T[] GCHandleArrayToManagedArray(ManagedArray ptrArray) where T : class { Span span = ptrArray.ToSpan(); T[] managedArray = new T[ptrArray.Length]; for (int i = 0; i < managedArray.Length; i++) managedArray[i] = span[i] != IntPtr.Zero ? (T)ManagedHandle.FromIntPtr(span[i]).Target : default; return managedArray; } /// /// Converts managed array wrapper into array of GC Handles for native runtime. /// /// Input array. /// Output array. public static IntPtr[] ManagedArrayToGCHandleArray(Array array) { if (array.Length == 0) return Array.Empty(); IntPtr[] pointerArray = new IntPtr[array.Length]; for (int i = 0; i < pointerArray.Length; i++) { var obj = array.GetValue(i); if (obj != null) pointerArray.SetValue(ManagedHandle.ToIntPtr(obj), i); } return pointerArray; } /// /// Converts managed array wrapper into array of GC Handles for native runtime. /// /// Input array. /// Output array. public static ManagedArray ManagedArrayToGCHandleWrappedArray(Array array) { IntPtr[] pointerArray = ManagedArrayToGCHandleArray(array); return ManagedArray.WrapNewArray(pointerArray, array.GetType()); } /// /// Converts array with a custom converter function for each element. /// /// Input data type. /// Output data type. /// The input array. /// Converter callback. /// The output array. public static TDst[] ConvertArray(this Span src, Func convertFunc) { TDst[] dst = new TDst[src.Length]; for (int i = 0; i < src.Length; i++) dst[i] = convertFunc(src[i]); return dst; } /// /// Converts array with a custom converter function for each element. /// /// Input data type. /// Output data type. /// The input array. /// Converter callback. /// The output array. public static TDst[] ConvertArray(this TSrc[] src, Func convertFunc) { TDst[] dst = new TDst[src.Length]; for (int i = 0; i < src.Length; i++) dst[i] = convertFunc(src[i]); return dst; } private static Type FindType(string typeName) { if (typeCache.TryGetValue(typeName, out Type type)) return type; type = Type.GetType(typeName, ResolveAssembly, null); if (type == null) type = ResolveSlow(typeName); if (type == null) { string fullTypeName = typeName; typeName = typeName.Substring(0, typeName.IndexOf(',')); type = Type.GetType(typeName, ResolveAssembly, null); if (type == null) type = ResolveSlow(typeName); typeName = fullTypeName; } typeCache.Add(typeName, type); return type; static Type ResolveSlow(string typeName) { foreach (var assembly in scriptingAssemblyLoadContext.Assemblies) { var type = assembly.GetType(typeName); if (type != null) return type; } return null; } static Assembly ResolveAssembly(AssemblyName name) => ResolveScriptingAssemblyByName(name, allowPartial: false); } /// Find among the scripting assemblies. /// The name to find /// If true, partial names should be allowed to be resolved. /// The resolved assembly, or null if none could be found. internal static Assembly ResolveScriptingAssemblyByName(AssemblyName assemblyName, bool allowPartial = false) { var lc = scriptingAssemblyLoadContext; if (lc is null) return null; foreach (Assembly assembly in lc.Assemblies) { var curName = assembly.GetName(); if (curName == assemblyName) return assembly; } if (allowPartial) // Check partial names if full name isn't found { string partialName = assemblyName.Name; foreach (Assembly assembly in lc.Assemblies) { var curName = assembly.GetName(); if (curName.Name == partialName) return assembly; } } return null; } internal static bool IsBlittable(Type type) { if (type.IsPrimitive || type.IsEnum) return true; if (type.IsArray && IsBlittable(type.GetElementType())) return true; if (type.IsPointer && type.HasElementType && type.GetElementType().IsPrimitive) return true; if (type.IsClass) return false; if (type.IsValueType) { var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo field in fields) { if (!IsBlittable(field.FieldType)) return false; } } return true; } /// /// Returns blittable internal type for given type. /// internal static Type GetInternalType(Type type) { string[] splits = type.AssemblyQualifiedName.Split(','); string @namespace = string.Join('.', splits[0].Split('.').SkipLast(1)); string className = @namespace.Length > 0 ? splits[0].Substring(@namespace.Length + 1) : splits[0]; string parentClassName = ""; if (className.Contains('+')) { parentClassName = className.Substring(0, className.LastIndexOf('+') + 1); className = className.Substring(parentClassName.Length); } string marshallerName = className + "Marshaller"; string internalAssemblyQualifiedName = $"{@namespace}.{parentClassName}{marshallerName}+{className}Internal,{String.Join(',', splits.Skip(1))}"; return FindType(internalAssemblyQualifiedName); } internal class ReferenceTypePlaceholder { } internal struct ValueTypePlaceholder { } internal delegate object MarshalToManagedDelegate(IntPtr nativePtr, bool byRef); internal delegate void MarshalToNativeDelegate(object managedObject, IntPtr nativePtr); internal delegate void MarshalToNativeFieldDelegate(FieldInfo field, int fieldOffset, object fieldOwner, IntPtr nativePtr, out int fieldSize); internal static ConcurrentDictionary toManagedMarshallers = new ConcurrentDictionary(1, 3); internal static ConcurrentDictionary toNativeMarshallers = new ConcurrentDictionary(1, 3); internal static ConcurrentDictionary toNativeFieldMarshallers = new ConcurrentDictionary(1, 3); internal static object MarshalToManaged(IntPtr nativePtr, Type type) { static MarshalToManagedDelegate Factory(Type type) { Type marshalType = type; if (marshalType.IsByRef) marshalType = marshalType.GetElementType(); else if (marshalType.IsPointer) marshalType = typeof(IntPtr); MethodInfo method = typeof(MarshalHelper<>).MakeGenericType(marshalType).GetMethod(nameof(MarshalHelper.ToManagedWrapper), BindingFlags.Static | BindingFlags.NonPublic); return method.CreateDelegate(); } if (!toManagedMarshallers.TryGetValue(type, out var deleg)) deleg = toManagedMarshallers.GetOrAdd(type, Factory); return deleg(nativePtr, type.IsByRef); } internal static void MarshalToNative(object managedObject, IntPtr nativePtr, Type type) { static MarshalToNativeDelegate Factory(Type type) { MethodInfo method; if (type.IsValueType) method = typeof(MarshalHelperValueType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperValueType.ToNativeWrapper), BindingFlags.Static | BindingFlags.NonPublic); else method = typeof(MarshalHelperReferenceType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperReferenceType.ToNativeWrapper), BindingFlags.Static | BindingFlags.NonPublic); return method.CreateDelegate(); } if (!toNativeMarshallers.TryGetValue(type, out var deleg)) deleg = toNativeMarshallers.GetOrAdd(type, Factory); deleg(managedObject, nativePtr); } internal static MarshalToNativeFieldDelegate GetToNativeFieldMarshallerDelegate(FieldInfo field, Type type) { static MarshalToNativeFieldDelegate Factory(Type type) { MethodInfo method; if (type.IsValueType) method = typeof(MarshalHelperValueType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperValueType.ToNativeFieldWrapper), BindingFlags.Static | BindingFlags.NonPublic); else method = typeof(MarshalHelperReferenceType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperReferenceType.ToNativeFieldWrapper), BindingFlags.Static | BindingFlags.NonPublic); return method.CreateDelegate(); } if (toNativeFieldMarshallers.TryGetValue(type, out var deleg)) return deleg; return toNativeFieldMarshallers.GetOrAdd(type, Factory); } internal static class FieldHelper { /// /// Returns the address of the field, relative to field owner. /// internal static int GetFieldOffset(FieldInfo field, Type type) { if (field.IsLiteral) return 0; // Get the address of the field, source: https://stackoverflow.com/a/56512720 int fieldOffset = Unsafe.Read((field.FieldHandle.Value + 4 + IntPtr.Size).ToPointer()) & 0xFFFFFF; if (!type.IsValueType) fieldOffset += IntPtr.Size; return fieldOffset; } #if USE_AOT /// /// Helper utility to set field of the referenced value via reflection. /// internal static void SetReferenceTypeField(FieldInfo field, ref T fieldOwner, object fieldValue) { if (typeof(T).IsValueType) { // Value types need setting via boxed object to properly propagate value object fieldOwnerBoxed = fieldOwner; field.SetValue(fieldOwnerBoxed, fieldValue); fieldOwner = (T)fieldOwnerBoxed; } else field.SetValue(fieldOwner, fieldValue); } #else /// /// Returns a reference to the value of the field. /// internal static ref TField GetReferenceTypeFieldReference(int fieldOffset, ref object fieldOwner) { byte* fieldPtr = (byte*)Unsafe.As(ref fieldOwner) + fieldOffset; return ref Unsafe.AsRef(fieldPtr); } /// /// Returns a reference to the value of the field. /// internal static ref TField GetValueTypeFieldReference(int fieldOffset, ref T fieldOwner) //where T : struct { byte* fieldPtr = (byte*)Unsafe.AsPointer(ref fieldOwner) + fieldOffset; return ref Unsafe.AsRef(fieldPtr); } /// /// Returns a reference to the value of the field. /// internal static ref TField GetReferenceTypeFieldReference(int fieldOffset, ref T fieldOwner) //where T : class { byte* fieldPtr = (byte*)Unsafe.As(ref fieldOwner) + fieldOffset; return ref Unsafe.AsRef(fieldPtr); } #endif } /// /// Helper class for managing stored marshaller delegates for each type. /// internal static class MarshalHelper { private delegate void MarshalToNativeTypedDelegate(ref T managedValue, IntPtr nativePtr); private delegate void MarshalToManagedTypedDelegate(ref T managedValue, IntPtr nativePtr, bool byRef); internal delegate void MarshalFieldTypedDelegate(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize); internal delegate void* GetBasePointer(ref T fieldOwner); internal static FieldInfo[] marshallableFields; internal static int[] marshallableFieldOffsets; internal static MarshalFieldTypedDelegate[] toManagedFieldMarshallers; internal static MarshalFieldTypedDelegate[] toNativeFieldMarshallers; private static MarshalToNativeTypedDelegate toNativeTypedMarshaller; private static MarshalToManagedTypedDelegate toManagedTypedMarshaller; static MarshalHelper() { Type type = typeof(T); // Setup field-by-field marshallers for reference types or structures containing references if (!type.IsPrimitive && !type.IsPointer && type != typeof(bool)) { marshallableFields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (type.IsValueType && !marshallableFields.Any(x => (x.FieldType.IsClass && !x.FieldType.IsPointer) || x.FieldType.Name == "Boolean")) marshallableFields = null; else if (!type.IsValueType && !marshallableFields.Any()) marshallableFields = null; if (marshallableFields != null) { toManagedFieldMarshallers = new MarshalFieldTypedDelegate[marshallableFields.Length]; toNativeFieldMarshallers = new MarshalFieldTypedDelegate[marshallableFields.Length]; marshallableFieldOffsets = new int[marshallableFields.Length]; BindingFlags bindingFlags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public; for (int i = 0; i < marshallableFields.Length; i++) { FieldInfo field = marshallableFields[i]; Type fieldType = field.FieldType; MethodInfo toManagedFieldMethod; MethodInfo toNativeFieldMethod; if (fieldType.IsPointer) { if (type.IsValueType) { toManagedFieldMethod = typeof(MarshalHelper<>).MakeGenericType(type).GetMethod(nameof(MarshalHelper.ToManagedFieldPointerValueType), bindingFlags); toNativeFieldMethod = typeof(MarshalHelper<>).MakeGenericType(type).GetMethod(nameof(MarshalHelper.ToNativeFieldPointerValueType), bindingFlags); } else { toManagedFieldMethod = typeof(MarshalHelper<>).MakeGenericType(type).GetMethod(nameof(MarshalHelper.ToManagedFieldPointerReferenceType), bindingFlags); toNativeFieldMethod = typeof(MarshalHelper<>).MakeGenericType(type).GetMethod(nameof(MarshalHelper.ToNativeFieldPointerReferenceType), bindingFlags); } } else if (fieldType.IsValueType) { if (Nullable.GetUnderlyingType(fieldType) != null) { toManagedFieldMethod = typeof(MarshalHelper<>.NullableValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.NullableValueTypeField.ToManagedField), bindingFlags); toNativeFieldMethod = typeof(MarshalHelper<>.NullableValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.NullableValueTypeField.ToNativeField), bindingFlags); } else { if (type.IsValueType) { toManagedFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToManagedFieldValueType), bindingFlags); toNativeFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToNativeFieldValueType), bindingFlags); } else { toManagedFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToManagedFieldReferenceType), bindingFlags); toNativeFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToNativeFieldReferenceType), bindingFlags); } } } else if (fieldType.IsArray) { Type arrayElementType = fieldType.GetElementType(); if (arrayElementType.IsValueType) { if (type.IsValueType) { toManagedFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, arrayElementType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToManagedFieldArrayValueType), bindingFlags); toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldValueType), bindingFlags); } else { toManagedFieldMethod = typeof(MarshalHelper<>.ValueTypeField<>).MakeGenericType(type, arrayElementType).GetMethod(nameof(MarshalHelper.ValueTypeField.ToManagedFieldArrayReferenceType), bindingFlags); toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldReferenceType), bindingFlags); } } else { if (type.IsValueType) { toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, arrayElementType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedFieldArrayValueType), bindingFlags); toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldValueType), bindingFlags); } else { toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, arrayElementType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedFieldArrayReferenceType), bindingFlags); toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldReferenceType), bindingFlags); } } } else { if (type.IsValueType) { toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedFieldValueType), bindingFlags); toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldValueType), bindingFlags); } else { toManagedFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToManagedFieldReferenceType), bindingFlags); toNativeFieldMethod = typeof(MarshalHelper<>.ReferenceTypeField<>).MakeGenericType(type, fieldType).GetMethod(nameof(MarshalHelper.ReferenceTypeField.ToNativeFieldReferenceType), bindingFlags); } } toManagedFieldMarshallers[i] = toManagedFieldMethod.CreateDelegate(); toNativeFieldMarshallers[i] = toNativeFieldMethod.CreateDelegate(); marshallableFieldOffsets[i] = FieldHelper.GetFieldOffset(field, type); } } } // Setup marshallers for managed and native directions MethodInfo toManagedMethod; if (type.IsValueType) { string methodName; if (type == typeof(IntPtr)) methodName = nameof(MarshalHelperValueType.ToManagedPointer); else if (type == typeof(ManagedHandle)) methodName = nameof(MarshalHelperValueType.ToManagedHandle); else if (marshallableFields != null) methodName = nameof(MarshalHelperValueType.ToManagedWithMarshallableFields); else methodName = nameof(MarshalHelperValueType.ToManaged); toManagedMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic); } else if (type.IsArray) { Type elementType = type.GetElementType(); if (elementType.IsValueType) { string methodName; if (ArrayFactory.GetMarshalledType(elementType) == elementType) methodName = nameof(MarshalHelperValueType.ToManagedArray); else methodName = nameof(MarshalHelperValueType.ToManagedArrayMarshalled); toManagedMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type.GetElementType()).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic); } else toManagedMethod = typeof(MarshalHelperReferenceType<>).MakeGenericType(type.GetElementType()).GetMethod(nameof(MarshalHelperReferenceType.ToManagedArray), BindingFlags.Static | BindingFlags.NonPublic); } else { string methodName; if (type == typeof(string)) methodName = nameof(MarshalHelperReferenceType.ToManagedString); else if (type == typeof(Type)) methodName = nameof(MarshalHelperReferenceType.ToManagedType); else if (type.IsClass) methodName = nameof(MarshalHelperReferenceType.ToManagedClass); else if (type.IsInterface) // Dictionary methodName = nameof(MarshalHelperReferenceType.ToManagedInterface); else throw new NativeInteropException($"Unsupported type '{type.FullName}'"); toManagedMethod = typeof(MarshalHelperReferenceType<>).MakeGenericType(type).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic); } toManagedTypedMarshaller = toManagedMethod.CreateDelegate(); MethodInfo toNativeMethod; if (type.IsValueType) { if (type.IsByRef) throw new NotImplementedException(); // Is this possible? if (marshallableFields != null) toNativeMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperValueType.ToNativeWithMarshallableFields), BindingFlags.Static | BindingFlags.NonPublic); else toNativeMethod = typeof(MarshalHelperValueType<>).MakeGenericType(type).GetMethod(nameof(MarshalHelperValueType.ToNative), BindingFlags.Static | BindingFlags.NonPublic); } else { string methodName; if (type == typeof(string)) methodName = nameof(MarshalHelperReferenceType.ToNativeString); else if (type == typeof(Type)) methodName = nameof(MarshalHelperReferenceType.ToNativeType); else if (type.IsPointer) methodName = nameof(MarshalHelperReferenceType.ToNativePointer); else if (type.IsArray) methodName = nameof(MarshalHelperReferenceType.ToNativeArray); else methodName = nameof(MarshalHelperReferenceType.ToNative); toNativeMethod = typeof(MarshalHelperReferenceType<>).MakeGenericType(type).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic); } toNativeTypedMarshaller = toNativeMethod.CreateDelegate(); } internal static object ToManagedWrapper(IntPtr nativePtr, bool byRef) { T managed = default; toManagedTypedMarshaller(ref managed, nativePtr, byRef); return managed; } internal static void ToManaged(ref T managedValue, IntPtr nativePtr, bool byRef) { toManagedTypedMarshaller(ref managedValue, nativePtr, byRef); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static T ToManagedUnbox(IntPtr nativePtr) { T value = default; if (nativePtr == IntPtr.Zero) return value; if (typeof(T).IsValueType) value = (T)ManagedHandle.FromIntPtr(nativePtr).Target; else MarshalHelper.ToManaged(ref value, nativePtr, false); return value; } internal static Array ToManagedArray(Span ptrSpan) { T[] arr = new T[ptrSpan.Length]; for (int i = 0; i < arr.Length; i++) toManagedTypedMarshaller(ref arr[i], ptrSpan[i], false); return arr; } internal static Array ToManagedArray(ManagedArray nativeArray) { T[] arr = new T[nativeArray.Length]; IntPtr nativePtr = nativeArray.Pointer; for (int i = 0; i < arr.Length; i++) { toManagedTypedMarshaller(ref arr[i], nativePtr, false); nativePtr += nativeArray.ElementSize; } return arr; } internal static void ToNative(ref T managedValue, IntPtr nativePtr) { toNativeTypedMarshaller(ref managedValue, nativePtr); } internal static void ToNativeField(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativePtr, out int fieldSize) { if (marshallableFields != null) { for (int i = 0; i < marshallableFields.Length; i++) { if (marshallableFieldOffsets[i] == fieldOffset) { toNativeFieldMarshallers[i](field, fieldOffset, ref fieldOwner, nativePtr, out fieldSize); return; } } } throw new NativeInteropException($"Invalid field with offset {fieldOffset} to marshal for type {typeof(T).Name}"); } private static void ToManagedFieldPointerValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { #if USE_AOT IntPtr fieldValue = Unsafe.Read(nativeFieldPtr.ToPointer()); FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue); #else ref IntPtr fieldValueRef = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); fieldValueRef = Unsafe.Read(nativeFieldPtr.ToPointer()); #endif fieldSize = IntPtr.Size; } private static void ToManagedFieldPointerReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { #if USE_AOT IntPtr fieldValue = Unsafe.Read(nativeFieldPtr.ToPointer()); FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue); #else ref IntPtr fieldValueRef = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); fieldValueRef = Unsafe.Read(nativeFieldPtr.ToPointer()); #endif fieldSize = IntPtr.Size; } private static void ToNativeFieldPointerValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { #if USE_AOT object boxed = field.GetValue(fieldOwner); IntPtr fieldValue = new IntPtr(Pointer.Unbox(boxed)); #else IntPtr fieldValue = FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); #endif Unsafe.Write(nativeFieldPtr.ToPointer(), fieldValue); fieldSize = IntPtr.Size; } private static void ToNativeFieldPointerReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { #if USE_AOT object boxed = field.GetValue(fieldOwner); IntPtr fieldValue = new IntPtr(Pointer.Unbox(boxed)); #else IntPtr fieldValue = FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); #endif Unsafe.Write(nativeFieldPtr.ToPointer(), fieldValue); fieldSize = IntPtr.Size; } private static IntPtr EnsureAlignment(IntPtr ptr, int alignment) { if (ptr % alignment != 0) ptr = IntPtr.Add(ptr, alignment - (int)(ptr % alignment)); return ptr; } private static class ValueTypeField where TField : struct { private static int fieldAlignment; static ValueTypeField() { Type fieldType = typeof(TField); if (fieldType.IsEnum) fieldType = fieldType.GetEnumUnderlyingType(); else if (fieldType == typeof(bool)) fieldType = typeof(byte); if (fieldType.IsValueType && !fieldType.IsEnum && !fieldType.IsPrimitive) // Is it a structure? { } else if (fieldType.IsClass || fieldType.IsPointer) fieldAlignment = IntPtr.Size; else fieldAlignment = GetTypeSize(fieldType); } internal static void ToManagedFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { fieldSize = Unsafe.SizeOf(); if (fieldAlignment > 1) { IntPtr fieldStartPtr = nativeFieldPtr; nativeFieldPtr = EnsureAlignment(nativeFieldPtr, fieldAlignment); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); } #if USE_AOT TField fieldValue = default; #else ref TField fieldValue = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); #endif MarshalHelper.ToManaged(ref fieldValue, nativeFieldPtr, false); #if USE_AOT FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue); #endif } internal static void ToManagedFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { fieldSize = Unsafe.SizeOf(); if (fieldAlignment > 1) { IntPtr fieldStartPtr = nativeFieldPtr; nativeFieldPtr = EnsureAlignment(nativeFieldPtr, fieldAlignment); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); } #if USE_AOT TField fieldValue = default; #else ref TField fieldValue = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); #endif MarshalHelper.ToManaged(ref fieldValue, nativeFieldPtr, false); #if USE_AOT FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue); #endif } internal static void ToManagedFieldArrayValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { // Follows the same marshalling semantics with reference types fieldSize = Unsafe.SizeOf(); IntPtr fieldStartPtr = nativeFieldPtr; nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); #if USE_AOT TField[] fieldValue = (TField[])field.GetValue(fieldOwner); #else ref TField[] fieldValue = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); #endif MarshalHelper.ToManaged(ref fieldValue, Unsafe.Read(nativeFieldPtr.ToPointer()), false); #if USE_AOT FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue); #endif } internal static void ToManagedFieldArrayReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { // Follows the same marshalling semantics with reference types fieldSize = Unsafe.SizeOf(); IntPtr fieldStartPtr = nativeFieldPtr; nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); #if USE_AOT TField[] fieldValue = null; #else ref TField[] fieldValue = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); #endif MarshalHelper.ToManaged(ref fieldValue, Unsafe.Read(nativeFieldPtr.ToPointer()), false); #if USE_AOT FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue); #endif } internal static void ToNativeFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { fieldSize = Unsafe.SizeOf(); if (fieldAlignment > 1) { IntPtr startPtr = nativeFieldPtr; nativeFieldPtr = EnsureAlignment(nativeFieldPtr, fieldAlignment); fieldSize += (nativeFieldPtr - startPtr).ToInt32(); } #if USE_AOT TField fieldValue = (TField)field.GetValue(fieldOwner); #else ref TField fieldValue = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); #endif MarshalHelper.ToNative(ref fieldValue, nativeFieldPtr); } internal static void ToNativeFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { fieldSize = Unsafe.SizeOf(); if (fieldAlignment > 1) { IntPtr startPtr = nativeFieldPtr; nativeFieldPtr = EnsureAlignment(nativeFieldPtr, fieldAlignment); fieldSize += (nativeFieldPtr - startPtr).ToInt32(); } #if USE_AOT TField fieldValue = (TField)field.GetValue(fieldOwner); #else ref TField fieldValue = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); #endif MarshalHelper.ToNative(ref fieldValue, nativeFieldPtr); } } private static class NullableValueTypeField { static NullableValueTypeField() { } internal static void ToManagedField(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) { fieldSize = 0; } internal static void ToNativeField(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) { fieldSize = 0; } } private static class ReferenceTypeField where TField : class { internal static void ToManagedFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { fieldSize = Unsafe.SizeOf(); IntPtr fieldStartPtr = nativeFieldPtr; nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); #if USE_AOT TField fieldValue = null; #else ref TField fieldValue = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); #endif MarshalHelper.ToManaged(ref fieldValue, Unsafe.Read(nativeFieldPtr.ToPointer()), false); #if USE_AOT FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue); #endif } internal static void ToManagedFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { fieldSize = Unsafe.SizeOf(); IntPtr fieldStartPtr = nativeFieldPtr; nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); #if USE_AOT TField fieldValue = default; #else ref TField fieldValue = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); #endif MarshalHelper.ToManaged(ref fieldValue, Unsafe.Read(nativeFieldPtr.ToPointer()), false); #if USE_AOT FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue); #endif } internal static void ToManagedFieldArrayValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { fieldSize = Unsafe.SizeOf(); IntPtr fieldStartPtr = nativeFieldPtr; nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); #if USE_AOT TField[] fieldValue = null; #else ref TField[] fieldValue = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); #endif MarshalHelper.ToManaged(ref fieldValue, Unsafe.Read(nativeFieldPtr.ToPointer()), false); #if USE_AOT FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue); #endif } internal static void ToManagedFieldArrayReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { fieldSize = Unsafe.SizeOf(); IntPtr fieldStartPtr = nativeFieldPtr; nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); #if USE_AOT TField[] fieldValue = null; #else ref TField[] fieldValue = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); #endif MarshalHelper.ToManaged(ref fieldValue, Unsafe.Read(nativeFieldPtr.ToPointer()), false); #if USE_AOT FieldHelper.SetReferenceTypeField(field, ref fieldOwner, fieldValue); #endif } internal static void ToNativeFieldValueType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : struct { fieldSize = Unsafe.SizeOf(); IntPtr fieldStartPtr = nativeFieldPtr; nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); #if USE_AOT TField fieldValue = (TField)field.GetValue(fieldOwner); #else ref TField fieldValue = ref FieldHelper.GetValueTypeFieldReference(fieldOffset, ref fieldOwner); #endif MarshalHelper.ToNative(ref fieldValue, nativeFieldPtr); } internal static void ToNativeFieldReferenceType(FieldInfo field, int fieldOffset, ref T fieldOwner, IntPtr nativeFieldPtr, out int fieldSize) // where T : class { fieldSize = Unsafe.SizeOf(); IntPtr fieldStartPtr = nativeFieldPtr; nativeFieldPtr = EnsureAlignment(nativeFieldPtr, IntPtr.Size); fieldSize += (nativeFieldPtr - fieldStartPtr).ToInt32(); #if USE_AOT TField fieldValue = (TField)field.GetValue(fieldOwner); #else ref TField fieldValue = ref FieldHelper.GetReferenceTypeFieldReference(fieldOffset, ref fieldOwner); #endif MarshalHelper.ToNative(ref fieldValue, nativeFieldPtr); } } } internal static class MarshalHelperValueType where T : struct { internal static void ToNativeWrapper(object managedObject, IntPtr nativePtr) { MarshalHelper.ToNative(ref Unsafe.Unbox(managedObject), nativePtr); } internal static void ToNativeFieldWrapper(FieldInfo field, int fieldOffset, object fieldOwner, IntPtr nativePtr, out int fieldSize) { MarshalHelper.ToNativeField(field, fieldOffset, ref Unsafe.Unbox(fieldOwner), nativePtr, out fieldSize); } internal static void ToManagedPointer(ref IntPtr managedValue, IntPtr nativePtr, bool byRef) { Type type = typeof(T); byRef |= type.IsByRef; // Is this needed? if (type.IsByRef) Assert.IsTrue(type.GetElementType().IsValueType); managedValue = byRef ? nativePtr : Unsafe.Read(nativePtr.ToPointer()); } internal static void ToManagedHandle(ref ManagedHandle managedValue, IntPtr nativePtr, bool byRef) { managedValue = ManagedHandle.FromIntPtr(nativePtr); } internal static void ToManagedWithMarshallableFields(ref T managedValue, IntPtr nativePtr, bool byRef) { IntPtr fieldPtr = nativePtr; var fields = MarshalHelper.marshallableFields; var offsets = MarshalHelper.marshallableFieldOffsets; var marshallers = MarshalHelper.toManagedFieldMarshallers; for (int i = 0; i < fields.Length; i++) { marshallers[i](fields[i], offsets[i], ref managedValue, fieldPtr, out int fieldSize); fieldPtr += fieldSize; } //Assert.IsTrue((fieldPtr - nativePtr) <= GetTypeSize(typeof(T))); } internal static void ToManaged(ref T managedValue, IntPtr nativePtr, bool byRef) { managedValue = Unsafe.Read(nativePtr.ToPointer()); } internal static void ToManagedArray(ref T[] managedValue, IntPtr nativePtr, bool byRef) { if (byRef) nativePtr = Unsafe.Read(nativePtr.ToPointer()); if (nativePtr != IntPtr.Zero) { ManagedArray managedArray = Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); managedValue = Unsafe.As(managedArray.ToArray()); } else managedValue = null; } internal static void ToManagedArrayMarshalled(ref T[] managedValue, IntPtr nativePtr, bool byRef) { if (byRef) nativePtr = Unsafe.Read(nativePtr.ToPointer()); if (nativePtr != IntPtr.Zero) { ManagedArray managedArray = Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); managedValue = Unsafe.As(MarshalHelper.ToManagedArray(managedArray)); } else managedValue = null; } internal static void ToNativeWithMarshallableFields(ref T managedValue, IntPtr nativePtr) { IntPtr fieldPtr = nativePtr; var fields = MarshalHelper.marshallableFields; var offsets = MarshalHelper.marshallableFieldOffsets; var marshallers = MarshalHelper.toNativeFieldMarshallers; for (int i = 0; i < fields.Length; i++) { marshallers[i](fields[i], offsets[i], ref managedValue, nativePtr, out int fieldSize); nativePtr += fieldSize; } //Assert.IsTrue((nativePtr - fieldPtr) <= GetTypeSize(typeof(T))); } internal static void ToNative(ref T managedValue, IntPtr nativePtr) { Unsafe.AsRef(nativePtr.ToPointer()) = managedValue; } } internal static class MarshalHelperReferenceType where T : class { internal static void ToNativeWrapper(object managedObject, IntPtr nativePtr) { T managedValue = Unsafe.As(managedObject); MarshalHelper.ToNative(ref managedValue, nativePtr); } internal static void ToNativeFieldWrapper(FieldInfo field, int fieldOffset, object fieldOwner, IntPtr nativePtr, out int fieldSize) { T managedValue = Unsafe.As(fieldOwner); MarshalHelper.ToNativeField(field, fieldOffset, ref managedValue, nativePtr, out fieldSize); } internal static void ToManagedString(ref string managedValue, IntPtr nativePtr, bool byRef) { if (byRef) nativePtr = Unsafe.Read(nativePtr.ToPointer()); managedValue = ManagedString.ToManaged(nativePtr); } internal static void ToManagedType(ref Type managedValue, IntPtr nativePtr, bool byRef) { if (byRef) nativePtr = Unsafe.Read(nativePtr.ToPointer()); managedValue = nativePtr == IntPtr.Zero ? null : Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); } internal static void ToManagedClass(ref T managedValue, IntPtr nativePtr, bool byRef) { if (byRef) nativePtr = Unsafe.Read(nativePtr.ToPointer()); managedValue = nativePtr == IntPtr.Zero ? null : Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); } internal static void ToManagedInterface(ref T managedValue, IntPtr nativePtr, bool byRef) // Dictionary { if (byRef) nativePtr = Unsafe.Read(nativePtr.ToPointer()); managedValue = nativePtr == IntPtr.Zero ? null : Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); } internal static void ToManagedArray(ref T[] managedValue, IntPtr nativePtr, bool byRef) { if (byRef) nativePtr = Unsafe.Read(nativePtr.ToPointer()); if (nativePtr != IntPtr.Zero) { ManagedArray managedArray = Unsafe.As(ManagedHandle.FromIntPtr(nativePtr).Target); managedValue = Unsafe.As(MarshalHelper.ToManagedArray(managedArray.ToSpan())); } else managedValue = null; } internal static void ToNativeString(ref string managedValue, IntPtr nativePtr) { Unsafe.Write(nativePtr.ToPointer(), ManagedString.ToNativeWeak(managedValue)); } internal static void ToNativeType(ref Type managedValue, IntPtr nativePtr) { Unsafe.Write(nativePtr.ToPointer(), managedValue != null ? ManagedHandle.ToIntPtr(GetTypeManagedHandle(managedValue)) : IntPtr.Zero); } internal static void ToNativePointer(ref T managedValue, IntPtr nativePtr) { IntPtr managedPtr; if (Pointer.Unbox(managedValue) == null) managedPtr = IntPtr.Zero; else if (managedValue is FlaxEngine.Object obj) managedPtr = FlaxEngine.Object.GetUnmanagedPtr(obj); else managedPtr = ManagedHandle.ToIntPtr(managedValue, GCHandleType.Weak); Unsafe.Write(nativePtr.ToPointer(), managedPtr); } internal static void ToNativeArray(ref T managedValue, IntPtr nativePtr) { IntPtr managedPtr; if (managedValue == null) managedPtr = IntPtr.Zero; else { Type type = typeof(T); var elementType = type.GetElementType(); var arr = Unsafe.As(managedValue); var marshalledType = ArrayFactory.GetMarshalledType(elementType); ManagedArray managedArray; if (marshalledType == elementType) managedArray = ManagedArray.WrapNewArray(arr, type); else if (elementType.IsValueType) { // Convert array of custom structures into internal native layout managedArray = ManagedArray.AllocateNewArray(arr.Length, type, marshalledType); IntPtr managedArrayPtr = managedArray.Pointer; for (int i = 0; i < arr.Length; i++) { MarshalToNative(arr.GetValue(i), managedArrayPtr, elementType); managedArrayPtr += managedArray.ElementSize; } } else managedArray = ManagedArrayToGCHandleWrappedArray(arr); managedPtr = ManagedHandle.ToIntPtr(managedArray, GCHandleType.Weak); } Unsafe.Write(nativePtr.ToPointer(), managedPtr); } internal static void ToNative(ref T managedValue, IntPtr nativePtr) { Unsafe.Write(nativePtr.ToPointer(), managedValue != null ? ManagedHandle.ToIntPtr(managedValue, GCHandleType.Weak) : IntPtr.Zero); } } internal class MethodHolder { internal Type[] parameterTypes; internal MethodInfo method; internal Type returnType; #if !USE_AOT private Invoker.MarshalAndInvokeDelegate invokeDelegate; private object delegInvoke; #endif internal MethodHolder(MethodInfo method) { this.method = method; returnType = method.ReturnType; parameterTypes = method.GetParameterTypes(); } #if !USE_AOT internal bool TryGetDelegate(out Invoker.MarshalAndInvokeDelegate outDeleg, out object outDelegInvoke) { // Skip using in-built delegate for value types (eg. Transform) to properly handle instance value passing to method if (invokeDelegate == null && !method.DeclaringType.IsValueType) { // Thread-safe creation lock (typeCache) { if (invokeDelegate == null) { TryCreateDelegate(); } } } outDeleg = invokeDelegate; outDelegInvoke = delegInvoke; return outDeleg != null; } private void TryCreateDelegate() { var methodTypes = new List(); if (!method.IsStatic) methodTypes.Add(method.DeclaringType); if (returnType != typeof(void)) methodTypes.Add(returnType); methodTypes.AddRange(parameterTypes); var genericParamTypes = new List(); foreach (var type in methodTypes) { if (type.IsByRef) genericParamTypes.Add(type.GetElementType()); else if (type.IsPointer) genericParamTypes.Add(typeof(IntPtr)); else genericParamTypes.Add(type); } string invokerTypeName = $"{typeof(Invoker).FullName}+Invoker{(method.IsStatic ? "Static" : "")}{(returnType != typeof(void) ? "Ret" : "NoRet")}{parameterTypes.Length}{(genericParamTypes.Count > 0 ? "`" + genericParamTypes.Count : "")}"; Type invokerType = Type.GetType(invokerTypeName); if (invokerType != null) { if (genericParamTypes.Count != 0) invokerType = invokerType.MakeGenericType(genericParamTypes.ToArray()); delegInvoke = invokerType.GetMethod(nameof(Invoker.InvokerStaticNoRet0.CreateDelegate), BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { method }); invokeDelegate = invokerType.GetMethod(nameof(Invoker.InvokerStaticNoRet0.MarshalAndInvoke), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); } } #endif } internal static ManagedHandle GetMethodGCHandle(MethodInfo method) { MethodHolder methodHolder = new MethodHolder(method); ManagedHandle handle = ManagedHandle.Alloc(methodHolder); #if FLAX_EDITOR if (methodHolder.parameterTypes.Any(x => x.IsCollectible) || method.IsCollectible) methodHandlesCollectible.Add(handle); else #endif { methodHandles.Add(handle); } return handle; } internal static ManagedHandle GetAssemblyHandle(Assembly assembly) { if (!assemblyHandles.TryGetValue(assembly, out ManagedHandle handle)) { handle = ManagedHandle.Alloc(assembly); assemblyHandles.Add(assembly, handle); } return handle; } internal class FieldHolder { internal FieldInfo field; internal MarshalToNativeFieldDelegate toNativeMarshaller; internal int fieldOffset; internal FieldHolder(FieldInfo field, Type type) { this.field = field; toNativeMarshaller = GetToNativeFieldMarshallerDelegate(field, type); fieldOffset = FieldHelper.GetFieldOffset(field, type); } } internal class TypeComparer : IEqualityComparer { public bool Equals(Type x, Type y) => x == y; public int GetHashCode(Type obj) => obj.GetHashCode(); } internal class TypeHolder { internal Type type; internal Type wrappedType; internal ConstructorInfo ctor; internal IntPtr managedClassPointer; // MClass* internal TypeHolder(Type type) { this.type = type; wrappedType = type; if (type.IsAbstract) { // Dotnet doesn't allow to instantiate abstract type thus allow to use generated mock class usage (eg. for Script or GPUResource) for generated abstract types var abstractWrapper = type.GetNestedType("AbstractWrapper", BindingFlags.NonPublic); if (abstractWrapper != null) wrappedType = abstractWrapper; } ctor = wrappedType.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null); } internal object CreateObject() { return RuntimeHelpers.GetUninitializedObject(wrappedType); } #if !USE_AOT internal object CreateScriptingObject(IntPtr unmanagedPtr, IntPtr idPtr) { object obj = RuntimeHelpers.GetUninitializedObject(wrappedType); if (obj is Object) { // TODO: use UnsafeAccessorAttribute on .NET 8 and use this path on all platforms (including non-Desktop, see MCore::ScriptingObject::CreateScriptingObject) { ref IntPtr fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(unmanagedPtrFieldOffset, ref obj); fieldRef = unmanagedPtr; } if (idPtr != IntPtr.Zero) { ref Guid nativeId = ref Unsafe.AsRef(idPtr.ToPointer()); ref Guid fieldRef = ref FieldHelper.GetReferenceTypeFieldReference(internalIdFieldOffset, ref obj); fieldRef = nativeId; } } if (ctor != null) ctor.Invoke(obj, null); else throw new NativeInteropException($"Missing empty constructor in type '{wrappedType}'."); return obj; } #endif public static implicit operator Type(TypeHolder holder) => holder?.type; public bool Equals(TypeHolder other) => type == other.type; public bool Equals(Type other) => type == other; public override int GetHashCode() => type.GetHashCode(); } internal static class ArrayFactory { private delegate Array CreateArrayDelegate(long size); private static ConcurrentDictionary marshalledTypes = new ConcurrentDictionary(1, 3); private static ConcurrentDictionary arrayTypes = new ConcurrentDictionary(1, 3); private static ConcurrentDictionary createArrayDelegates = new ConcurrentDictionary(1, 3); internal static Type GetMarshalledType(Type elementType) { static Type Factory(Type type) { Type marshalType; if (IsBlittable(type)) marshalType = type; else marshalType = GetInternalType(type) ?? typeof(IntPtr); return marshalType; } if (marshalledTypes.TryGetValue(elementType, out var marshalledType)) return marshalledType; return marshalledTypes.GetOrAdd(elementType, Factory); } internal static Type GetArrayType(Type elementType) { static Type Factory(Type type) => type.MakeArrayType(); if (arrayTypes.TryGetValue(elementType, out var arrayType)) return arrayType; return arrayTypes.GetOrAdd(elementType, Factory); } internal static Array CreateArray(Type type, long size) { static CreateArrayDelegate Factory(Type type) { Type marshalledType = GetMarshalledType(type); MethodInfo method = typeof(Internal<>).MakeGenericType(marshalledType).GetMethod(nameof(Internal.CreateArrayDelegate), BindingFlags.Static | BindingFlags.NonPublic); return method.CreateDelegate(); } if (createArrayDelegates.TryGetValue(type, out var deleg)) return deleg(size); return createArrayDelegates.GetOrAdd(type, Factory)(size); } private static class Internal { internal static Array CreateArrayDelegate(long size) => new T[size]; } } internal static class ValueTypeUnboxer { private static GCHandle[] pinnedBoxedValues = new GCHandle[256]; private static uint pinnedBoxedValuesPointer = 0; private static (IntPtr ptr, int size)[] pinnedAllocations = new (IntPtr ptr, int size)[256]; private static uint pinnedAllocationsPointer = 0; private delegate TInternal ToNativeDelegate(T value); private delegate IntPtr UnboxerDelegate(object value, object converter); private static ConcurrentDictionary unboxers = new(1, 3); private static MethodInfo unboxerMethod = typeof(ValueTypeUnboxer).GetMethod(nameof(ValueTypeUnboxer.UnboxPointer), BindingFlags.Static | BindingFlags.NonPublic); private static MethodInfo unboxerToNativeMethod = typeof(ValueTypeUnboxer).GetMethod(nameof(ValueTypeUnboxer.UnboxPointerWithConverter), BindingFlags.Static | BindingFlags.NonPublic); internal static IntPtr GetPointer(object value, Type type) { if (!unboxers.TryGetValue(type, out var tuple)) { // Non-POD structures use internal layout (eg. SpriteHandleManaged in C++ with SpriteHandleMarshaller.SpriteHandleInternal in C#) so convert C# data into C++ data var attr = type.GetCustomAttribute(); var toNativeMethod = attr?.NativeType.GetMethod("ToNative", BindingFlags.Static | BindingFlags.NonPublic); if (toNativeMethod != null) { tuple.deleg = unboxerToNativeMethod.MakeGenericMethod(type, toNativeMethod.ReturnType).CreateDelegate(); tuple.toNativeDeleg = toNativeMethod.CreateDelegate(typeof(ToNativeDelegate<,>).MakeGenericType(type, toNativeMethod.ReturnType)); } else { tuple.deleg = unboxerMethod.MakeGenericMethod(type).CreateDelegate(); } tuple = unboxers.GetOrAdd(type, tuple); } return tuple.deleg(value, tuple.toNativeDeleg); } private static void PinValue(object value) { // Prevent garbage collector from relocating the boxed value by pinning it temporarily. // The pointer should remain valid quite long time but will be eventually unpinned. uint index = Interlocked.Increment(ref pinnedBoxedValuesPointer) % (uint)pinnedBoxedValues.Length; ref GCHandle handle = ref pinnedBoxedValues[index]; if (handle.IsAllocated) handle.Free(); handle = GCHandle.Alloc(value, GCHandleType.Pinned); } private static IntPtr PinValue(T value) where T : struct { // Store the converted value in unmanaged memory so it will not be relocated by the garbage collector. int size = TypeHelpers.MarshalSize; uint index = Interlocked.Increment(ref pinnedAllocationsPointer) % (uint)pinnedAllocations.Length; ref (IntPtr ptr, int size) alloc = ref pinnedAllocations[index]; if (alloc.size < size) { if (alloc.ptr != IntPtr.Zero) NativeFree(alloc.ptr.ToPointer()); alloc.ptr = new IntPtr(NativeAlloc(size)); alloc.size = size; } Unsafe.Write(alloc.ptr.ToPointer(), value); return alloc.ptr; } private static IntPtr UnboxPointer(object value, object converter) where T : struct { if (RuntimeHelpers.IsReferenceOrContainsReferences()) // Cannot pin structure with references return IntPtr.Zero; PinValue(value); return new IntPtr(Unsafe.AsPointer(ref Unsafe.Unbox(value))); } private static IntPtr UnboxPointerWithConverter(object value, object converter) where T : struct where TInternal : struct { ToNativeDelegate toNative = Unsafe.As>(converter); return PinValue(toNative(Unsafe.Unbox(value))); } } private delegate IntPtr InvokeThunkDelegate(ManagedHandle instanceHandle, IntPtr param1, IntPtr param2, IntPtr param3, IntPtr param4, IntPtr param5, IntPtr param6, IntPtr param7); /// /// Returns all types owned by this assembly. /// private static Type[] GetAssemblyTypes(Assembly assembly) { var referencedAssemblies = assembly.GetReferencedAssemblies(); var allAssemblies = Utils.GetAssemblies(); var referencedTypes = new List(); foreach (var assemblyName in referencedAssemblies) { var asm = allAssemblies.FirstOrDefault(x => x.GetName().Name == assemblyName.Name); if (asm == null) continue; referencedTypes.AddRange(asm.DefinedTypes.Select(x => x.FullName).ToArray()); } // TODO: use MetadataReader to read types without loading any of the referenced assemblies? // https://makolyte.com/csharp-get-a-list-of-types-defined-in-an-assembly-without-loading-it/ // We need private types of this assembly too, DefinedTypes contains a lot of types from other assemblies... var types = referencedTypes.Any() ? assembly.DefinedTypes.Where(x => !referencedTypes.Contains(x.FullName)).ToArray() : assembly.DefinedTypes.ToArray(); Assert.IsTrue(Utils.GetAssemblies().Count(x => x.GetName().Name == "FlaxEngine.CSharp") == 1); return types; } internal static TypeHolder GetTypeHolder(Type type) { if (managedTypes.TryGetValue(type, out (TypeHolder typeHolder, ManagedHandle handle) tuple)) return tuple.typeHolder; #if FLAX_EDITOR if (managedTypesCollectible.TryGetValue(type, out tuple)) return tuple.typeHolder; #endif return RegisterType(type, true).typeHolder; } internal static (TypeHolder typeHolder, ManagedHandle handle) GetTypeHolderAndManagedHandle(Type type) { if (managedTypes.TryGetValue(type, out (TypeHolder typeHolder, ManagedHandle handle) tuple)) return tuple; #if FLAX_EDITOR if (managedTypesCollectible.TryGetValue(type, out tuple)) return tuple; #endif return RegisterType(type, true); } /// /// Returns a static ManagedHandle to TypeHolder for given Type, and caches it if needed. /// internal static ManagedHandle GetTypeManagedHandle(Type type) { if (type.IsInterface && type.IsGenericType) type = type.GetGenericTypeDefinition(); // Generic type to use type definition handle if (managedTypes.TryGetValue(type, out (TypeHolder typeHolder, ManagedHandle handle) tuple)) return tuple.handle; #if FLAX_EDITOR if (managedTypesCollectible.TryGetValue(type, out tuple)) return tuple.handle; #endif return RegisterType(type, true).handle; } internal static (TypeHolder typeHolder, ManagedHandle handle) RegisterType(Type type, bool registerNativeType = false) { // TODO: should this strip by-ref? (TypeHolder typeHolder, ManagedHandle handle) tuple; tuple.typeHolder = new TypeHolder(type); tuple.handle = ManagedHandle.Alloc(tuple.typeHolder); #if FLAX_EDITOR bool isCollectible = type.IsCollectible; if (!isCollectible && type.IsGenericType && !type.Assembly.IsCollectible) { // The owning assembly of a generic type with type arguments referencing // collectible assemblies must be one of the collectible assemblies. foreach (var genericType in type.GetGenericArguments()) { if (genericType.Assembly.IsCollectible) { isCollectible = true; break; } } } if (isCollectible) managedTypesCollectible.Add(type, tuple); else #endif { managedTypes.Add(type, tuple); } if (registerNativeType) RegisterNativeClassFromType(tuple.typeHolder, tuple.handle); return tuple; } internal static class TypeHelpers { public static readonly int MarshalSize; static TypeHelpers() { Type type = typeof(T); try { var marshalType = type; if (type.IsEnum) marshalType = type.GetEnumUnderlyingType(); MarshalSize = Marshal.SizeOf(marshalType); } catch { // Workaround the issue where structure defined within generic type instance (eg. MyType.MyStruct) fails to get size // https://github.com/dotnet/runtime/issues/46426 var obj = RuntimeHelpers.GetUninitializedObject(type); MarshalSize = Marshal.SizeOf(obj); } } } internal static int GetTypeSize(Type type) { if (!_typeSizeCache.TryGetValue(type, out int size)) { var marshalSizeField = typeof(TypeHelpers<>).MakeGenericType(type).GetField(nameof(TypeHelpers.MarshalSize), BindingFlags.Static | BindingFlags.Public); size = (int)marshalSizeField.GetValue(null); _typeSizeCache.AddOrUpdate(type, size, (t, v) => size); } return size; } private static class DelegateHelpers { #if USE_AOT internal static void InitMethods() { } #else private static Func MakeNewCustomDelegateFunc; #if FLAX_EDITOR private static Func MakeNewCustomDelegateFuncCollectible; #endif internal static void InitMethods() { MakeNewCustomDelegateFunc = typeof(Expression).Assembly.GetType("System.Linq.Expressions.Compiler.DelegateHelpers") .GetMethod("MakeNewCustomDelegate", BindingFlags.NonPublic | BindingFlags.Static).CreateDelegate>(); #if FLAX_EDITOR // Load System.Linq.Expressions assembly to collectible ALC. // The dynamic assembly where delegates are stored is cached in the DelegateHelpers class, so we should // use the DelegateHelpers in collectible ALC to make sure the delegates are also stored in the same ALC. Assembly assembly = scriptingAssemblyLoadContext.LoadFromAssemblyPath(typeof(Expression).Assembly.Location); MakeNewCustomDelegateFuncCollectible = assembly.GetType("System.Linq.Expressions.Compiler.DelegateHelpers") .GetMethod("MakeNewCustomDelegate", BindingFlags.NonPublic | BindingFlags.Static).CreateDelegate>(); // Create dummy delegates to force the dynamic Snippets assembly to be loaded in correcet ALCs MakeNewCustomDelegateFunc(new[] { typeof(void) }); { // Ensure the new delegate is placed in the collectible ALC using var ctx = scriptingAssemblyLoadContext.EnterContextualReflection(); MakeNewCustomDelegateFuncCollectible(new[] { typeof(void) }); } #endif } internal static Type MakeNewCustomDelegate(Type[] parameters) { #if FLAX_EDITOR if (parameters.Any(x => x.IsCollectible)) return MakeNewCustomDelegateFuncCollectible(parameters); #endif return MakeNewCustomDelegateFunc(parameters); } #endif } #if !USE_AOT /// /// Wrapper class for invoking function pointers from unmanaged code. /// internal class ThunkContext { internal MethodInfo method; internal Type[] parameterTypes; internal Invoker.InvokeThunkDelegate methodDelegate; internal object methodDelegateContext; internal ThunkContext(MethodInfo method) { this.method = method; parameterTypes = method.GetParameterTypes(); // Thunk delegates don't support IsByRef parameters (use generic invocation that handles 'out' and 'ref' prams) foreach (var type in parameterTypes) { if (type.IsByRef) return; } List methodTypes = new List(); if (!method.IsStatic) methodTypes.Add(method.DeclaringType); if (method.ReturnType != typeof(void)) methodTypes.Add(method.ReturnType); methodTypes.AddRange(parameterTypes); List genericParamTypes = new List(); foreach (var type in methodTypes) { if (type.IsByRef) genericParamTypes.Add(type.GetElementType()); else if (type.IsPointer) genericParamTypes.Add(typeof(IntPtr)); else genericParamTypes.Add(type); } string invokerTypeName = $"{typeof(Invoker).FullName}+Invoker{(method.IsStatic ? "Static" : "")}{(method.ReturnType != typeof(void) ? "Ret" : "NoRet")}{parameterTypes.Length}{(genericParamTypes.Count > 0 ? "`" + genericParamTypes.Count : "")}"; Type invokerType = Type.GetType(invokerTypeName); if (invokerType != null) { if (genericParamTypes.Count != 0) invokerType = invokerType.MakeGenericType(genericParamTypes.ToArray()); methodDelegate = invokerType.GetMethod(nameof(Invoker.InvokerStaticNoRet0.InvokeThunk), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(); methodDelegateContext = invokerType.GetMethod(nameof(Invoker.InvokerStaticNoRet0.CreateInvokerDelegate), BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { method }); } if (methodDelegate != null) Assert.IsTrue(methodDelegateContext != null); } public IntPtr InvokeThunk(ManagedHandle instanceHandle, IntPtr param1, IntPtr param2, IntPtr param3, IntPtr param4, IntPtr param5, IntPtr param6, IntPtr param7) { IntPtr* nativePtrs = stackalloc IntPtr[] { param1, param2, param3, param4, param5, param6, param7 }; if (methodDelegate != null) { IntPtr returnValue; try { returnValue = methodDelegate(methodDelegateContext, instanceHandle, nativePtrs); } catch (Exception exception) { // Returned exception is the last parameter IntPtr exceptionPtr = nativePtrs[parameterTypes.Length]; if (exceptionPtr != IntPtr.Zero) Unsafe.Write(exceptionPtr.ToPointer(), ManagedHandle.ToIntPtr(exception, GCHandleType.Weak)); return IntPtr.Zero; } return returnValue; } else { // The parameters are wrapped (boxed) in GCHandles object returnObject; int numParams = parameterTypes.Length; object[] methodParameters = new object[numParams]; for (int i = 0; i < numParams; i++) { IntPtr nativePtr = nativePtrs[i]; object managed = null; if (nativePtr != IntPtr.Zero) { Type type = parameterTypes[i]; Type elementType = type.GetElementType(); if (type.IsByRef) { // References use indirection to support value returning nativePtr = Unsafe.Read(nativePtr.ToPointer()); type = elementType; } if (type.IsArray) managed = MarshalToManaged(nativePtr, type); // Array might be in internal format of custom structs so unbox if need to else managed = ManagedHandle.FromIntPtr(nativePtr).Target; } methodParameters[i] = managed; } try { returnObject = method.Invoke(instanceHandle.IsAllocated ? instanceHandle.Target : null, methodParameters); } catch (Exception exception) { // Returned exception is the last parameter IntPtr exceptionPtr = nativePtrs[numParams]; if (exceptionPtr != IntPtr.Zero) Unsafe.Write(exceptionPtr.ToPointer(), ManagedHandle.ToIntPtr(exception, GCHandleType.Weak)); return IntPtr.Zero; } // Marshal reference parameters back to original unmanaged references for (int i = 0; i < numParams; i++) { IntPtr nativePtr = nativePtrs[i]; Type type = parameterTypes[i]; object managed = methodParameters[i]; if (nativePtr != IntPtr.Zero && type.IsByRef) { type = type.GetElementType(); if (managed == null) Unsafe.Write(nativePtr.ToPointer(), IntPtr.Zero); else if (type.IsArray) MarshalToNative(managed, nativePtr, type); else Unsafe.Write(nativePtr.ToPointer(), ManagedHandle.ToIntPtr(ManagedHandle.Alloc(managed, GCHandleType.Weak))); } } // Return value return Invoker.MarshalReturnValueThunkGeneric(method.ReturnType, returnObject); } } } #endif } internal class NativeInteropException : Exception { public NativeInteropException(string message) : base(message) { #if !BUILD_RELEASE Debug.Logger.LogHandler.LogWrite(LogType.Error, "Native interop exception!"); #endif } } } #endif