From 2283a15172edbc25848d003eb6adc77a92b22c28 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Mon, 1 Apr 2024 16:01:57 +0300 Subject: [PATCH 1/4] Refactor struct custom marshalling generation --- .../Bindings/BindingsGenerator.CSharp.cs | 180 +++++++++--------- 1 file changed, 92 insertions(+), 88 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index a1ad9880d..c4cefdacc 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -1467,51 +1467,35 @@ namespace Flax.Build.Bindings string structNativeMarshaling = ""; if (UseCustomMarshalling(buildData, structureInfo, structureInfo)) { + // NOTE: Permanent FlaxEngine.Object GCHandles must not be released when marshalling from native to managed. + string marshallerName = structureInfo.Name + "Marshaller"; structNativeMarshaling = $"[NativeMarshalling(typeof({marshallerName}))]"; - contents.Append(indent).AppendLine($"/// "); - contents.Append(indent).AppendLine($"/// Marshaller for type ."); - contents.Append(indent).AppendLine($"/// "); - if (buildData.Target != null & buildData.Target.IsEditor) - contents.Append(indent).AppendLine("[HideInEditor]"); - contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.ManagedToUnmanagedIn, typeof({marshallerName}.ManagedToNative))]"); - contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.UnmanagedToManagedOut, typeof({marshallerName}.ManagedToNative))]"); - contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.ElementIn, typeof({marshallerName}.ManagedToNative))]"); - contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.ManagedToUnmanagedOut, typeof({marshallerName}.NativeToManaged))]"); - contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.UnmanagedToManagedIn, typeof({marshallerName}.NativeToManaged))]"); - contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.ElementOut, typeof({marshallerName}.NativeToManaged))]"); - contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.ManagedToUnmanagedRef, typeof({marshallerName}.Bidirectional))]"); - contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.UnmanagedToManagedRef, typeof({marshallerName}.Bidirectional))]"); - contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({structureInfo.Name}), MarshalMode.ElementRef, typeof({marshallerName}))]"); - contents.Append(indent).AppendLine($"{GenerateCSharpAccessLevel(structureInfo.Access)}static unsafe class {marshallerName}"); - contents.Append(indent).AppendLine("{"); - contents.AppendLine("#pragma warning disable 1591"); - - indent += " "; - var toManagedContent = GetStringBuilder(); var toNativeContent = GetStringBuilder(); var freeContents = GetStringBuilder(); var freeContents2 = GetStringBuilder(); + var structContents = GetStringBuilder(); { // Native struct begin // TODO: skip using this utility structure if the auto-generated C# struct is already the same as XXXInternal here below + var structIndent = ""; if (buildData.Target != null & buildData.Target.IsEditor) - contents.Append(indent).AppendLine("[HideInEditor]"); - contents.Append(indent).AppendLine("[StructLayout(LayoutKind.Sequential)]"); - contents.Append(indent).Append("public struct ").Append(structureInfo.Name).Append("Internal"); + structContents.Append(structIndent).AppendLine("[HideInEditor]"); + structContents.Append(structIndent).AppendLine("[StructLayout(LayoutKind.Sequential)]"); + structContents.Append(structIndent).Append("public struct ").Append(structureInfo.Name).Append("Internal"); if (structureInfo.BaseType != null && structureInfo.IsPod) - contents.Append(" : ").Append(GenerateCSharpNativeToManaged(buildData, new TypeInfo { Type = structureInfo.BaseType.Name }, structureInfo)); - contents.AppendLine(); - contents.Append(indent + "{"); - indent += " "; + structContents.Append(" : ").Append(GenerateCSharpNativeToManaged(buildData, new TypeInfo { Type = structureInfo.BaseType.Name }, structureInfo)); + structContents.AppendLine(); + structContents.Append(structIndent + "{"); + structIndent += " "; toNativeContent.Append($"var unmanaged = new {structureInfo.Name}Internal();").AppendLine(); toManagedContent.Append($"var managed = new {structureInfo.Name}();").AppendLine(); - contents.AppendLine(); + structContents.AppendLine(); foreach (var fieldInfo in structureInfo.Fields) { if (fieldInfo.IsStatic || fieldInfo.IsConstexpr) @@ -1528,7 +1512,7 @@ namespace Flax.Build.Bindings else originalType = type = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, structureInfo); - contents.Append(indent).Append("public "); + structContents.Append(structIndent).Append("public "); var apiType = FindApiTypeInfo(buildData, fieldInfo.Type, structureInfo); bool internalType = apiType is StructureInfo fieldStructureInfo && UseCustomMarshalling(buildData, fieldStructureInfo, structureInfo); @@ -1540,7 +1524,7 @@ namespace Flax.Build.Bindings if (GenerateCSharpUseFixedBuffer(originalType)) { // Use fixed statement with primitive types of buffers - contents.Append($"fixed {originalType} {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}];").AppendLine(); + structContents.Append($"fixed {originalType} {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}];").AppendLine(); // Copy fixed-size array toManagedContent.AppendLine($"FlaxEngine.Utils.MemoryCopy(new IntPtr(managed.{fieldInfo.Name}0), new IntPtr(unmanaged.{fieldInfo.Name}0), sizeof({originalType}) * {fieldInfo.Type.ArraySize}ul);"); @@ -1550,12 +1534,12 @@ namespace Flax.Build.Bindings #endif { // Padding in structs for fixed-size array - contents.Append(type).Append(' ').Append(fieldInfo.Name).Append("0;").AppendLine(); + structContents.Append(type).Append(' ').Append(fieldInfo.Name).Append("0;").AppendLine(); for (int i = 1; i < fieldInfo.Type.ArraySize; i++) { - GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic); - contents.Append(indent).Append("public "); - contents.Append(type).Append(' ').Append(fieldInfo.Name + i).Append(';').AppendLine(); + GenerateCSharpAttributes(buildData, structContents, structIndent, structureInfo, fieldInfo, fieldInfo.IsStatic); + structContents.Append(structIndent).Append("public "); + structContents.Append(type).Append(' ').Append(fieldInfo.Name + i).Append(';').AppendLine(); } // Copy fixed-size array item one-by-one @@ -1601,8 +1585,7 @@ namespace Flax.Build.Bindings } //else if (type == "Guid") // type = "GuidNative"; - - contents.Append(type).Append(' ').Append(fieldInfo.Name).Append(';').AppendLine(); + structContents.Append(type).Append(' ').Append(fieldInfo.Name).Append(';').AppendLine(); } // Generate struct constructor/getter and deconstructor/setter function @@ -1721,71 +1704,92 @@ namespace Flax.Build.Bindings } // Native struct end - indent = indent.Substring(0, indent.Length - 4); - contents.AppendLine(indent + "}").AppendLine(); + structIndent = structIndent.Substring(0, structIndent.Length - 4); + structContents.AppendLine(structIndent + "}").AppendLine(); toNativeContent.Append("return unmanaged;"); toManagedContent.Append("return managed;"); } - var indent2 = indent + " "; - var indent3 = indent2 + " "; + contents.AppendLine(string.Join("\n" + indent, (indent + $$""" + /// + /// Marshaller for type . + /// + {{InsertHideInEditorSection()}} + [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ManagedToUnmanagedIn, typeof({{marshallerName}}.ManagedToNative))] + [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.UnmanagedToManagedOut, typeof({{marshallerName}}.ManagedToNative))] + [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ElementIn, typeof({{marshallerName}}.ManagedToNative))] + [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ManagedToUnmanagedOut, typeof({{marshallerName}}.NativeToManaged))] + [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.UnmanagedToManagedIn, typeof({{marshallerName}}.NativeToManaged))] + [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ElementOut, typeof({{marshallerName}}.NativeToManaged))] + [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ManagedToUnmanagedRef, typeof({{marshallerName}}.Bidirectional))] + [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.UnmanagedToManagedRef, typeof({{marshallerName}}.Bidirectional))] + [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ElementRef, typeof({{marshallerName}}))] + {{GenerateCSharpAccessLevel(structureInfo.Access)}}static unsafe class {{marshallerName}} + { + #pragma warning disable 1591 + {{structContents.Replace("\n", "\n" + " ").ToString().TrimEnd()}} - // NativeToManaged stateless shape - // NOTE: GCHandles of FlaxEngine.Object must not be released in this case - if (buildData.Target != null && buildData.Target.IsEditor) - contents.Append(indent).AppendLine("[HideInEditor]"); - contents.Append(indent).AppendLine("public static class NativeToManaged").Append(indent).AppendLine("{"); - contents.Append(indent2).AppendLine($"public static {structureInfo.Name} ConvertToManaged({structureInfo.Name}Internal unmanaged) => {marshallerName}.ToManaged(unmanaged);"); - contents.Append(indent2).AppendLine($"public static {structureInfo.Name}Internal ConvertToUnmanaged({structureInfo.Name} managed) => {marshallerName}.ToNative(managed);"); - contents.Append(indent2).AppendLine($"public static void Free({structureInfo.Name}Internal unmanaged)"); - contents.Append(indent2).AppendLine("{").Append(indent3).AppendLine(freeContents2.Replace("\n", "\n" + indent3).ToString().TrimEnd()).Append(indent2).AppendLine("}"); - contents.Append(indent).AppendLine("}"); + {{InsertHideInEditorSection()}} + public static class NativeToManaged + { + public static {{structureInfo.Name}} ConvertToManaged({{structureInfo.Name}}Internal unmanaged) => {{marshallerName}}.ToManaged(unmanaged); + public static {{structureInfo.Name}}Internal ConvertToUnmanaged({{structureInfo.Name}} managed) => {{marshallerName}}.ToNative(managed); + public static void Free({{structureInfo.Name}}Internal unmanaged) + { + {{freeContents2.Replace("\n", "\n" + " ").ToString().TrimEnd()}} + } + } + {{InsertHideInEditorSection()}} + public static class ManagedToNative + { + public static {{structureInfo.Name}} ConvertToManaged({{structureInfo.Name}}Internal unmanaged) => {{marshallerName}}.ToManaged(unmanaged); + public static {{structureInfo.Name}}Internal ConvertToUnmanaged({{structureInfo.Name}} managed) => {{marshallerName}}.ToNative(managed); + public static void Free({{structureInfo.Name}}Internal unmanaged) => {{marshallerName}}.Free(unmanaged); + } + {{InsertHideInEditorSection()}} + public struct Bidirectional + { + {{structureInfo.Name}} managed; + {{structureInfo.Name}}Internal unmanaged; + public void FromManaged({{structureInfo.Name}} managed) => this.managed = managed; + public {{structureInfo.Name}}Internal ToUnmanaged() { unmanaged = {{marshallerName}}.ToNative(managed); return unmanaged; } + //public void FromUnmanaged({{structureInfo.Name}}Internal unmanaged) { {{marshallerName}}.Free(this.unmanaged.Value); this.unmanaged = unmanaged; } + public void FromUnmanaged({{structureInfo.Name}}Internal unmanaged) => this.unmanaged = unmanaged; + public {{structureInfo.Name}} ToManaged() { managed = {{marshallerName}}.ToManaged(unmanaged); return managed; } + public void Free() => NativeToManaged.Free(unmanaged); + } + internal static {{structureInfo.Name}} ConvertToManaged({{structureInfo.Name}}Internal unmanaged) => ToManaged(unmanaged); + internal static {{structureInfo.Name}}Internal ConvertToUnmanaged({{structureInfo.Name}} managed) => ToNative(managed); + internal static void Free({{structureInfo.Name}}Internal unmanaged) + { + {{freeContents.Replace("\n", "\n" + " ").ToString().TrimEnd()}} + } - // ManagedToNative stateless shape - if (buildData.Target != null && buildData.Target.IsEditor) - contents.Append(indent).AppendLine("[HideInEditor]"); - contents.Append(indent).AppendLine($"public static class ManagedToNative").Append(indent).AppendLine("{"); - contents.Append(indent2).AppendLine($"public static {structureInfo.Name} ConvertToManaged({structureInfo.Name}Internal unmanaged) => {marshallerName}.ToManaged(unmanaged);"); - contents.Append(indent2).AppendLine($"public static {structureInfo.Name}Internal ConvertToUnmanaged({structureInfo.Name} managed) => {marshallerName}.ToNative(managed);"); - contents.Append(indent2).AppendLine($"public static void Free({structureInfo.Name}Internal unmanaged) => {marshallerName}.Free(unmanaged);"); - contents.Append(indent).AppendLine("}"); + internal static {{structureInfo.Name}} ToManaged({{structureInfo.Name}}Internal unmanaged) + { + {{toManagedContent.Replace("\n", "\n" + " ").ToString().TrimEnd()}} + } + internal static {{structureInfo.Name}}Internal ToNative({{structureInfo.Name}} managed) + { + {{toNativeContent.Replace("\n", "\n" + " ").ToString().TrimEnd()}} + } + #pragma warning restore 1591 + } + """).Split(new char[] { '\n' }))); - // Bidirectional stateful shape - // NOTE: GCHandles of FlaxEngine.Object must not be released unless they were allocated by this marshaller - if (buildData.Target != null && buildData.Target.IsEditor) - contents.Append(indent).AppendLine("[HideInEditor]"); - contents.Append(indent).AppendLine($"public struct Bidirectional").Append(indent).AppendLine("{"); - contents.Append(indent2).AppendLine($"{structureInfo.Name} managed;"); - contents.Append(indent2).AppendLine($"{structureInfo.Name}Internal unmanaged;"); - contents.Append(indent2).AppendLine($"public void FromManaged({structureInfo.Name} managed) => this.managed = managed;"); - contents.Append(indent2).AppendLine($"public {structureInfo.Name}Internal ToUnmanaged() {{ unmanaged = {marshallerName}.ToNative(managed); return unmanaged; }}"); - //contents.Append(indent2).AppendLine($"public void FromUnmanaged({structureInfo.Name}Internal unmanaged) {{ {marshallerName}.Free(this.unmanaged.Value); this.unmanaged = unmanaged; }}"); - contents.Append(indent2).AppendLine($"public void FromUnmanaged({structureInfo.Name}Internal unmanaged) => this.unmanaged = unmanaged;"); - contents.Append(indent2).AppendLine($"public {structureInfo.Name} ToManaged() {{ managed = {marshallerName}.ToManaged(unmanaged); return managed; }}"); - contents.Append(indent2).AppendLine($"public void Free() => NativeToManaged.Free(unmanaged);"); - contents.Append(indent).AppendLine("}"); - - // Bidirectional stateless shape - contents.Append(indent).AppendLine($"internal static {structureInfo.Name} ConvertToManaged({structureInfo.Name}Internal unmanaged) => ToManaged(unmanaged);"); - contents.Append(indent).AppendLine($"internal static {structureInfo.Name}Internal ConvertToUnmanaged({structureInfo.Name} managed) => ToNative(managed);"); - contents.Append(indent).AppendLine($"internal static void Free({structureInfo.Name}Internal unmanaged)"); - contents.Append(indent).AppendLine("{").Append(indent2).AppendLine(freeContents.Replace("\n", "\n" + indent2).ToString().TrimEnd()).Append(indent).AppendLine("}"); - - // Managed/native converters - contents.Append(indent).AppendLine($"internal static {structureInfo.Name} ToManaged({structureInfo.Name}Internal unmanaged)"); - contents.Append(indent).AppendLine("{").Append(indent2).AppendLine(toManagedContent.Replace("\n", "\n" + indent2).ToString().TrimEnd()).Append(indent).AppendLine("}"); - contents.Append(indent).AppendLine($"internal static {structureInfo.Name}Internal ToNative({structureInfo.Name} managed)"); - contents.Append(indent).AppendLine("{").Append(indent2).AppendLine(toNativeContent.Replace("\n", "\n" + indent2).ToString().TrimEnd()).Append(indent).AppendLine("}"); - - contents.AppendLine("#pragma warning restore 1591"); - indent = indent.Substring(0, indent.Length - 4); - contents.Append(indent).AppendLine("}").AppendLine(); + string InsertHideInEditorSection() + { + return (buildData.Target != null & buildData.Target.IsEditor) ? $$""" + [HideInEditor] + """ : ""; + } PutStringBuilder(toManagedContent); PutStringBuilder(toNativeContent); PutStringBuilder(freeContents); PutStringBuilder(freeContents2); + PutStringBuilder(structContents); } #endif // Struct docs From 974e3e192bf57071f59228cbf75b79e941bc6dc3 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Mon, 1 Apr 2024 16:03:34 +0300 Subject: [PATCH 2/4] Include original type of the fields in blittable struct in comments --- Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index c4cefdacc..aefeeac26 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -1585,7 +1585,7 @@ namespace Flax.Build.Bindings } //else if (type == "Guid") // type = "GuidNative"; - structContents.Append(type).Append(' ').Append(fieldInfo.Name).Append(';').AppendLine(); + structContents.Append($"{type} {fieldInfo.Name};").Append(type == "IntPtr" ? $" // {originalType}" : "").AppendLine(); } // Generate struct constructor/getter and deconstructor/setter function From adbe43c2c2b83c34a8edcb62ad6775869b2abeb0 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Mon, 1 Apr 2024 19:08:53 +0300 Subject: [PATCH 3/4] Move generated marshallers into separate namespace Avoid polluting the `FlaxEngine` namespace with interop related marshallers, move those to nested namespace called `Interop` where most of the common marshallers are placed already. --- Source/Engine/Animations/AnimationGraph.cs | 1 + Source/Engine/Engine/NativeInterop.cs | 2 +- .../Bindings/BindingsGenerator.CSharp.cs | 131 ++++++++++++------ 3 files changed, 88 insertions(+), 46 deletions(-) diff --git a/Source/Engine/Animations/AnimationGraph.cs b/Source/Engine/Animations/AnimationGraph.cs index 995b42662..3dae30b81 100644 --- a/Source/Engine/Animations/AnimationGraph.cs +++ b/Source/Engine/Animations/AnimationGraph.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; +using FlaxEngine.Interop; namespace FlaxEngine { diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 9e871e2fb..68208d7fe 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -388,7 +388,7 @@ namespace FlaxEngine.Interop className = className.Substring(parentClassName.Length); } string marshallerName = className + "Marshaller"; - string internalAssemblyQualifiedName = $"{@namespace}.{parentClassName}{marshallerName}+{className}Internal,{String.Join(',', splits.Skip(1))}"; + string internalAssemblyQualifiedName = $"{@namespace}.Interop.{parentClassName}{marshallerName}+{className}Internal,{String.Join(',', splits.Skip(1))}"; return FindType(internalAssemblyQualifiedName); } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index aefeeac26..2d5666b51 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -582,8 +582,13 @@ namespace Flax.Build.Bindings returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.ManagedHandleMarshaller))"; else if (FindApiTypeInfo(buildData, functionInfo.ReturnType, caller)?.IsInterface ?? false) { + var apiType = FindApiTypeInfo(buildData, functionInfo.ReturnType, caller); + var fullReturnValueType = returnValueType; + if (!string.IsNullOrEmpty(apiType?.Namespace)) + fullReturnValueType = $"{apiType.Namespace}.Interop.{returnValueType}"; + // Interfaces are not supported by NativeMarshallingAttribute, marshal the parameter - returnMarshalType = $"MarshalUsing(typeof({returnValueType}Marshaller))"; + returnMarshalType = $"MarshalUsing(typeof({fullReturnValueType}Marshaller))"; } else if (functionInfo.ReturnType.Type == "MonoArray" || functionInfo.ReturnType.Type == "MArray") returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemArrayMarshaller))"; @@ -941,10 +946,11 @@ namespace Flax.Build.Bindings contents.AppendLine(); // Namespace begin + string interopNamespace = ""; if (!string.IsNullOrEmpty(classInfo.Namespace)) { - contents.AppendFormat("namespace "); - contents.AppendLine(classInfo.Namespace); + interopNamespace = $"{classInfo.Namespace}.Interop"; + contents.AppendLine($"namespace {classInfo.Namespace}"); contents.AppendLine("{"); indent += " "; } @@ -956,10 +962,12 @@ namespace Flax.Build.Bindings GenerateCSharpAttributes(buildData, contents, indent, classInfo, useUnmanaged); #if USE_NETCORE string marshallerName = ""; + string marshallerFullName = ""; if (!classInfo.IsStatic) { marshallerName = classInfo.Name + "Marshaller"; - contents.Append(indent).AppendLine($"[NativeMarshalling(typeof({marshallerName}))]"); + marshallerFullName = !string.IsNullOrEmpty(interopNamespace) ? $"{interopNamespace}.{marshallerName}" : marshallerName; + contents.Append(indent).AppendLine($"[NativeMarshalling(typeof({marshallerFullName}))]"); } #endif contents.Append(indent).Append(GenerateCSharpAccessLevel(classInfo.Access)); @@ -1384,6 +1392,13 @@ namespace Flax.Build.Bindings #if USE_NETCORE if (!string.IsNullOrEmpty(marshallerName)) { + if (!string.IsNullOrEmpty(interopNamespace)) + { + contents.AppendLine("}"); + contents.AppendLine($"namespace {interopNamespace}"); + contents.AppendLine("{"); + } + contents.AppendLine(); contents.AppendLine(string.Join("\n" + indent, (indent + $$""" /// @@ -1392,15 +1407,15 @@ namespace Flax.Build.Bindings #if FLAX_EDITOR [HideInEditor] #endif - [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ManagedToUnmanagedIn, typeof({{marshallerName}}.ManagedToNative))] - [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.UnmanagedToManagedOut, typeof({{marshallerName}}.ManagedToNative))] - [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ElementIn, typeof({{marshallerName}}.ManagedToNative))] - [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ManagedToUnmanagedOut, typeof({{marshallerName}}.NativeToManaged))] - [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.UnmanagedToManagedIn, typeof({{marshallerName}}.NativeToManaged))] - [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ElementOut, typeof({{marshallerName}}.NativeToManaged))] - [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ManagedToUnmanagedRef, typeof({{marshallerName}}.Bidirectional))] - [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.UnmanagedToManagedRef, typeof({{marshallerName}}.Bidirectional))] - [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ElementRef, typeof({{marshallerName}}))] + [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ManagedToUnmanagedIn, typeof({{marshallerFullName}}.ManagedToNative))] + [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.UnmanagedToManagedOut, typeof({{marshallerFullName}}.ManagedToNative))] + [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ElementIn, typeof({{marshallerFullName}}.ManagedToNative))] + [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ManagedToUnmanagedOut, typeof({{marshallerFullName}}.NativeToManaged))] + [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.UnmanagedToManagedIn, typeof({{marshallerFullName}}.NativeToManaged))] + [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ElementOut, typeof({{marshallerFullName}}.NativeToManaged))] + [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ManagedToUnmanagedRef, typeof({{marshallerFullName}}.Bidirectional))] + [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.UnmanagedToManagedRef, typeof({{marshallerFullName}}.Bidirectional))] + [CustomMarshaller(typeof({{classInfo.Name}}), MarshalMode.ElementRef, typeof({{marshallerFullName}}))] {{GenerateCSharpAccessLevel(classInfo.Access)}}static class {{marshallerName}} { #pragma warning disable 1591 @@ -1454,23 +1469,26 @@ namespace Flax.Build.Bindings { contents.AppendLine(); - // Namespace begin - if (!string.IsNullOrEmpty(structureInfo.Namespace)) - { - contents.AppendFormat("namespace "); - contents.AppendLine(structureInfo.Namespace); - contents.AppendLine("{"); - indent += " "; - } #if USE_NETCORE // Generate blittable structure string structNativeMarshaling = ""; if (UseCustomMarshalling(buildData, structureInfo, structureInfo)) { + // Namespace begin + string interopNamespace = ""; + if (!string.IsNullOrEmpty(structureInfo.Namespace)) + { + interopNamespace = $"{structureInfo.Namespace}.Interop"; + contents.AppendLine($"namespace {interopNamespace}"); + contents.AppendLine("{"); + indent += " "; + } + // NOTE: Permanent FlaxEngine.Object GCHandles must not be released when marshalling from native to managed. string marshallerName = structureInfo.Name + "Marshaller"; - structNativeMarshaling = $"[NativeMarshalling(typeof({marshallerName}))]"; + string marshallerFullName = !string.IsNullOrEmpty(interopNamespace) ? $"{interopNamespace}.{marshallerName}" : marshallerName; + structNativeMarshaling = $"[NativeMarshalling(typeof({marshallerFullName}))]"; var toManagedContent = GetStringBuilder(); var toNativeContent = GetStringBuilder(); @@ -1632,7 +1650,7 @@ namespace Flax.Build.Bindings if (internalType) { // Marshal blittable array elements back to original non-blittable elements - string originalElementTypeMarshaller = $"{originalElementType}Marshaller"; + string originalElementTypeMarshaller = (!string.IsNullOrEmpty(apiType?.Namespace) ? $"{apiType?.Namespace}.Interop." : "") + $"{originalElementType}Marshaller"; string originalElementTypeName = originalElementType.Substring(originalElementType.LastIndexOf('.') + 1); // Strip namespace string internalElementType = $"{originalElementTypeMarshaller}.{originalElementTypeName}Internal"; toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.ConvertArray((Unsafe.As(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target)).ToSpan<{internalElementType}>(), {originalElementTypeMarshaller}.ToManaged) : null;"); @@ -1716,15 +1734,15 @@ namespace Flax.Build.Bindings /// Marshaller for type . /// {{InsertHideInEditorSection()}} - [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ManagedToUnmanagedIn, typeof({{marshallerName}}.ManagedToNative))] - [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.UnmanagedToManagedOut, typeof({{marshallerName}}.ManagedToNative))] - [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ElementIn, typeof({{marshallerName}}.ManagedToNative))] - [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ManagedToUnmanagedOut, typeof({{marshallerName}}.NativeToManaged))] - [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.UnmanagedToManagedIn, typeof({{marshallerName}}.NativeToManaged))] - [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ElementOut, typeof({{marshallerName}}.NativeToManaged))] - [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ManagedToUnmanagedRef, typeof({{marshallerName}}.Bidirectional))] - [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.UnmanagedToManagedRef, typeof({{marshallerName}}.Bidirectional))] - [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ElementRef, typeof({{marshallerName}}))] + [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ManagedToUnmanagedIn, typeof({{marshallerFullName}}.ManagedToNative))] + [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.UnmanagedToManagedOut, typeof({{marshallerFullName}}.ManagedToNative))] + [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ElementIn, typeof({{marshallerFullName}}.ManagedToNative))] + [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ManagedToUnmanagedOut, typeof({{marshallerFullName}}.NativeToManaged))] + [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.UnmanagedToManagedIn, typeof({{marshallerFullName}}.NativeToManaged))] + [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ElementOut, typeof({{marshallerFullName}}.NativeToManaged))] + [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ManagedToUnmanagedRef, typeof({{marshallerFullName}}.Bidirectional))] + [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.UnmanagedToManagedRef, typeof({{marshallerFullName}}.Bidirectional))] + [CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ElementRef, typeof({{marshallerFullName}}))] {{GenerateCSharpAccessLevel(structureInfo.Access)}}static unsafe class {{marshallerName}} { #pragma warning disable 1591 @@ -1733,8 +1751,8 @@ namespace Flax.Build.Bindings {{InsertHideInEditorSection()}} public static class NativeToManaged { - public static {{structureInfo.Name}} ConvertToManaged({{structureInfo.Name}}Internal unmanaged) => {{marshallerName}}.ToManaged(unmanaged); - public static {{structureInfo.Name}}Internal ConvertToUnmanaged({{structureInfo.Name}} managed) => {{marshallerName}}.ToNative(managed); + public static {{structureInfo.Name}} ConvertToManaged({{structureInfo.Name}}Internal unmanaged) => {{marshallerFullName}}.ToManaged(unmanaged); + public static {{structureInfo.Name}}Internal ConvertToUnmanaged({{structureInfo.Name}} managed) => {{marshallerFullName}}.ToNative(managed); public static void Free({{structureInfo.Name}}Internal unmanaged) { {{freeContents2.Replace("\n", "\n" + " ").ToString().TrimEnd()}} @@ -1743,9 +1761,9 @@ namespace Flax.Build.Bindings {{InsertHideInEditorSection()}} public static class ManagedToNative { - public static {{structureInfo.Name}} ConvertToManaged({{structureInfo.Name}}Internal unmanaged) => {{marshallerName}}.ToManaged(unmanaged); - public static {{structureInfo.Name}}Internal ConvertToUnmanaged({{structureInfo.Name}} managed) => {{marshallerName}}.ToNative(managed); - public static void Free({{structureInfo.Name}}Internal unmanaged) => {{marshallerName}}.Free(unmanaged); + public static {{structureInfo.Name}} ConvertToManaged({{structureInfo.Name}}Internal unmanaged) => {{marshallerFullName}}.ToManaged(unmanaged); + public static {{structureInfo.Name}}Internal ConvertToUnmanaged({{structureInfo.Name}} managed) => {{marshallerFullName}}.ToNative(managed); + public static void Free({{structureInfo.Name}}Internal unmanaged) => {{marshallerFullName}}.Free(unmanaged); } {{InsertHideInEditorSection()}} public struct Bidirectional @@ -1753,10 +1771,10 @@ namespace Flax.Build.Bindings {{structureInfo.Name}} managed; {{structureInfo.Name}}Internal unmanaged; public void FromManaged({{structureInfo.Name}} managed) => this.managed = managed; - public {{structureInfo.Name}}Internal ToUnmanaged() { unmanaged = {{marshallerName}}.ToNative(managed); return unmanaged; } - //public void FromUnmanaged({{structureInfo.Name}}Internal unmanaged) { {{marshallerName}}.Free(this.unmanaged.Value); this.unmanaged = unmanaged; } + public {{structureInfo.Name}}Internal ToUnmanaged() { unmanaged = {{marshallerFullName}}.ToNative(managed); return unmanaged; } + //public void FromUnmanaged({{structureInfo.Name}}Internal unmanaged) { {{marshallerFullName}}.Free(this.unmanaged.Value); this.unmanaged = unmanaged; } public void FromUnmanaged({{structureInfo.Name}}Internal unmanaged) => this.unmanaged = unmanaged; - public {{structureInfo.Name}} ToManaged() { managed = {{marshallerName}}.ToManaged(unmanaged); return managed; } + public {{structureInfo.Name}} ToManaged() { managed = {{marshallerFullName}}.ToManaged(unmanaged); return managed; } public void Free() => NativeToManaged.Free(unmanaged); } internal static {{structureInfo.Name}} ConvertToManaged({{structureInfo.Name}}Internal unmanaged) => ToManaged(unmanaged); @@ -1790,8 +1808,23 @@ namespace Flax.Build.Bindings PutStringBuilder(freeContents); PutStringBuilder(freeContents2); PutStringBuilder(structContents); + + // Namespace end + if (!string.IsNullOrEmpty(structureInfo.Namespace)) + { + contents.AppendLine("}"); + indent = indent.Substring(0, indent.Length - 4); + } } #endif + // Namespace begin + if (!string.IsNullOrEmpty(structureInfo.Namespace)) + { + contents.AppendLine($"namespace {structureInfo.Namespace}"); + contents.AppendLine("{"); + indent += " "; + } + // Struct docs GenerateCSharpComment(contents, indent, structureInfo.Comment); @@ -1983,8 +2016,7 @@ namespace Flax.Build.Bindings // Namespace begin if (!string.IsNullOrEmpty(enumInfo.Namespace)) { - contents.AppendFormat("namespace "); - contents.AppendLine(enumInfo.Namespace); + contents.AppendLine($"namespace {enumInfo.Namespace}"); contents.AppendLine("{"); indent += " "; } @@ -2030,10 +2062,11 @@ namespace Flax.Build.Bindings { // Begin contents.AppendLine(); + string interopNamespace = ""; if (!string.IsNullOrEmpty(interfaceInfo.Namespace)) { - contents.AppendFormat("namespace "); - contents.AppendLine(interfaceInfo.Namespace); + interopNamespace = $"{interfaceInfo.Namespace}.Interop"; + contents.AppendLine($"namespace {interfaceInfo.Namespace}"); contents.AppendLine("{"); indent += " "; } @@ -2100,14 +2133,22 @@ namespace Flax.Build.Bindings #if USE_NETCORE { + if (!string.IsNullOrEmpty(interopNamespace)) + { + contents.AppendLine("}"); + contents.AppendLine($"namespace {interopNamespace}"); + contents.AppendLine("{"); + } + string marshallerName = interfaceInfo.Name + "Marshaller"; + string marshallerFullName = !string.IsNullOrEmpty(interopNamespace) ? $"{interopNamespace}.{marshallerName}" : marshallerName; contents.AppendLine(); contents.Append(indent).AppendLine("/// "); contents.Append(indent).AppendLine($"/// Marshaller for type ."); contents.Append(indent).AppendLine("/// "); if (buildData.Target != null & buildData.Target.IsEditor) contents.Append(indent).AppendLine("[HideInEditor]"); - contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({interfaceInfo.Name}), MarshalMode.Default, typeof({marshallerName}))]"); + contents.Append(indent).AppendLine($"[CustomMarshaller(typeof({interfaceInfo.Name}), MarshalMode.Default, typeof({marshallerFullName}))]"); contents.Append(indent).AppendLine($"public static class {marshallerName}"); contents.Append(indent).AppendLine("{"); contents.AppendLine("#pragma warning disable 1591"); From bf5e5d12542144a064e21e1737df636edfa49c32 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Tue, 2 Apr 2024 01:09:04 +0300 Subject: [PATCH 4/4] Refactor native interop internal type lookup --- .../Engine/Engine/NativeInterop.Unmanaged.cs | 3 - Source/Engine/Engine/NativeInterop.cs | 63 +++---------------- 2 files changed, 10 insertions(+), 56 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index 638b58223..52218c2f4 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -1019,9 +1019,6 @@ namespace FlaxEngine.Interop { #if FLAX_EDITOR // Clear all caches which might hold references to assemblies in collectible ALC - typeCache.Clear(); - - // Release all references in collectible ALC cachedDelegatesCollectible.Clear(); foreach (var pair in managedTypesCollectible) pair.Value.handle.Free(); diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 68208d7fe..162334112 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -31,8 +31,6 @@ namespace FlaxEngine.Interop 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)); @@ -279,44 +277,6 @@ namespace FlaxEngine.Interop 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. @@ -378,18 +338,15 @@ namespace FlaxEngine.Interop /// 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}.Interop.{parentClassName}{marshallerName}+{className}Internal,{String.Join(',', splits.Skip(1))}"; - return FindType(internalAssemblyQualifiedName); + Type marshallerType = type.GetCustomAttribute()?.NativeType; + if (marshallerType == null) + return null; + + Type internalType = marshallerType.GetNestedType($"{type.Name}Internal"); + if (internalType == null) + return null; + + return internalType; } internal class ReferenceTypePlaceholder { } @@ -1338,7 +1295,7 @@ namespace FlaxEngine.Interop if (invokeDelegate == null && !method.DeclaringType.IsValueType) { // Thread-safe creation - lock (typeCache) + lock (method) { if (invokeDelegate == null) {