// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. //#define AUTO_DOC_TOOLTIPS using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using BuildData = Flax.Build.Builder.BuildData; namespace Flax.Build.Bindings { partial class BindingsGenerator { private static readonly HashSet CSharpUsedNamespaces = new HashSet(); private static readonly List CSharpUsedNamespacesSorted = new List(); private static readonly List CSharpAdditionalCode = new List(); private static readonly Dictionary CSharpAdditionalCodeCache = new Dictionary(); #if USE_NETCORE private static readonly TypeInfo CSharpEventBindReturn = new TypeInfo("void"); private static readonly List CSharpEventBindParams = new List() { new FunctionInfo.ParameterInfo() { Name = "bind", Type = new TypeInfo("bool") } }; #endif public static event Action GenerateCSharpTypeInternals; internal static readonly Dictionary CSharpNativeToManagedBasicTypes = new Dictionary() { // Language types { "bool", "bool" }, { "int8", "sbyte" }, { "int16", "short" }, { "int32", "int" }, { "short", "short" }, { "int", "int" }, { "int64", "long" }, { "uint8", "byte" }, { "byte", "byte" }, { "uint16", "ushort" }, { "uint32", "uint" }, { "uint64", "ulong" }, { "char", "sbyte" }, { "Char", "char" }, { "float", "float" }, { "double", "double" }, }; internal static readonly Dictionary CSharpNativeToManagedDefault = new Dictionary() { // Engine types { "String", "string" }, { "StringView", "string" }, { "StringAnsi", "string" }, { "StringAnsiView", "string" }, { "ScriptingObject", "FlaxEngine.Object" }, { "ScriptingTypeHandle", "System.Type" }, { "PersistentScriptingObject", "FlaxEngine.Object" }, { "ManagedScriptingObject", "FlaxEngine.Object" }, { "Variant", "object" }, { "VariantType", "System.Type" }, // Mono types { "MonoObject", "object" }, { "MonoClass", "System.Type" }, { "MonoReflectionType", "System.Type" }, { "MonoType", "IntPtr" }, { "MonoArray", "Array" }, // Managed types { "MObject", "object" }, { "MClass", "System.Type" }, { "MType", "System.Type" }, { "MTypeObject", "System.Type" }, { "MArray", "Array" }, }; private static readonly string[] CSharpVectorTypes = { "Vector2", "Vector3", "Vector4", "Float2", "Float3", "Float4", "Double2", "Double2", "Double3", "Int2", "Int3", "Int4", }; private static string GenerateCSharpDefaultValueNativeToManaged(BuildData buildData, string value, ApiTypeInfo caller, TypeInfo valueType = null, bool attribute = false, string managedType = null) { if (string.IsNullOrEmpty(value)) return null; // Special case for Engine TEXT macro if (value.StartsWith("TEXT(\"") && value.EndsWith("\")")) return value.Substring(5, value.Length - 6); // Pointer constant value if (managedType != null && managedType == "IntPtr" && (value == "nullptr" || value == "NULL" || value == "0")) { return "new IntPtr()"; } // In-built constants switch (value) { case "nullptr": case "NULL": case "String::Empty": case "StringAnsi::Empty": case "StringAnsiView::Empty": case "StringView::Empty": return "null"; case "MAX_int8": return "sbyte.MaxValue"; case "MAX_uint8": return "byte.MaxValue"; case "MAX_int16": return "short.MaxValue"; case "MAX_uint16": return "ushort.MaxValue"; case "MAX_int32": return "int.MaxValue"; case "MAX_uint32": return "uint.MaxValue"; case "MAX_int64": return "long.MaxValue"; case "MAX_uint64": return "ulong.MaxValue"; case "MAX_float": return "float.MaxValue"; case "MAX_double": return "double.MaxValue"; case "true": case "false": return value; } // Numbers if (float.TryParse(value, out _) || (value[value.Length - 1] == 'f' && float.TryParse(value.Substring(0, value.Length - 1), out _))) { // If the value type is different than the value (eg. value is int but the field is float) then cast it for the [DefaultValue] attribute if (valueType != null && attribute) return $"({GenerateCSharpNativeToManaged(buildData, valueType, caller)}){value}"; return value; } value = value.Replace("::", "."); var dot = value.LastIndexOf('.'); ApiTypeInfo apiType = null; if (dot != -1) { var type = new TypeInfo(value.Substring(0, dot)); apiType = FindApiTypeInfo(buildData, type, caller); } if (attribute) { // Value constructors (eg. Vector2(1, 2)) if (value.Contains('(') && value.Contains(')')) { // Support for in-built types foreach (var vectorType in CSharpVectorTypes) { if (value.Length > vectorType.Length + 4 && value.StartsWith(vectorType) && value[vectorType.Length] == '(') return $"typeof({vectorType}), \"{value.Substring(vectorType.Length + 1, value.Length - vectorType.Length - 2).Replace("f", "")}\""; } return null; } // Constants (eg. Vector2::Zero) if (value.Contains('.')) { // Support for in-built constants switch (value) { case "Vector2.Zero": return "typeof(Vector2), \"0,0\""; case "Vector2.One": return "typeof(Vector2), \"1,1\""; case "Float2.Zero": return "typeof(Float2), \"0,0\""; case "Float2.One": return "typeof(Float2), \"1,1\""; case "Double2.Zero": return "typeof(Double2), \"0,0\""; case "Double2.One": return "typeof(Double2), \"1,1\""; case "Int2.Zero": return "typeof(Int2), \"0,0\""; case "Int2.One": return "typeof(Int2), \"1,1\""; case "Vector3.Zero": return "typeof(Vector3), \"0,0,0\""; case "Vector3.One": return "typeof(Vector3), \"1,1,1\""; case "Float3.Zero": return "typeof(Float3), \"0,0,0\""; case "Float3.One": return "typeof(Float3), \"1,1,1\""; case "Double3.Zero": return "typeof(Double3), \"0,0,0\""; case "Double3.One": return "typeof(Double3), \"1,1,1\""; case "Int3.Zero": return "typeof(Int3), \"0,0,0\""; case "Int3.One": return "typeof(Int3), \"1,1,1\""; case "Vector4.Zero": return "typeof(Vector4), \"0,0,0,0\""; case "Vector4.One": return "typeof(Vector4), \"1,1,1,1\""; case "Double4.Zero": return "typeof(Double4), \"0,0,0,0\""; case "Double4.One": return "typeof(Double4), \"1,1,1,1\""; case "Float4.Zero": return "typeof(Float4), \"0,0,0,0\""; case "Float4.One": return "typeof(Float4), \"1,1,1,1\""; case "Int4.Zero": return "typeof(Float4), \"0,0,0,0\""; case "Int4.One": return "typeof(Float4), \"1,1,1,1\""; case "Quaternion.Identity": return "typeof(Quaternion), \"0,0,0,1\""; } // Enums if (apiType != null && apiType.IsEnum) return value; return null; } } // Skip constants unsupported in C# if (apiType != null && apiType.IsStruct) return null; // Special case for array initializers if (value.StartsWith("{") && value.EndsWith("}") && !attribute) { if (valueType == null) throw new Exception("Unknown type for value initializer of default value " + value); TypeInfo itemType; if (valueType.Type == "Array") itemType = valueType.GenericArgs[0]; else if (valueType.IsArray) { itemType = (TypeInfo)value.Clone(); itemType.IsArray = false; } else throw new Exception("Cannot use array initializer as default value for type " + valueType); var sb = GetStringBuilder(); var items = new List(); var braces = 0; for (int i = 1; i < value.Length - 1; i++) { var c = value[i]; if (c == ',' && braces == 0) { items.Add(sb.ToString()); sb.Clear(); continue; } if (c == '(') braces++; else if (c == ')') braces--; sb.Append(c); } items.Add(sb.ToString()); sb.Clear(); sb.Append("new ").Append(GenerateCSharpNativeToManaged(buildData, valueType, caller)).Append('{'); for (int i = 0; i < items.Count; i++) sb.Append(GenerateCSharpDefaultValueNativeToManaged(buildData, items[i], caller, itemType, attribute)).Append(','); sb.Append('}'); var result = sb.ToString(); PutStringBuilder(sb); return result; } // Special case for value constructors if (value.Contains('(') && value.Contains(')')) return "new " + value; return value; } private static string GenerateCSharpNativeToManaged(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller) { string result; if (typeInfo?.Type == null) throw new ArgumentNullException(); // Use dynamic array as wrapper container for fixed-size native arrays if (typeInfo.IsArray) { typeInfo.IsArray = false; result = GenerateCSharpNativeToManaged(buildData, typeInfo, caller); typeInfo.IsArray = true; return result + "[]"; } // Special case for bit-fields if (typeInfo.IsBitField) { if (typeInfo.BitSize == 1) return "bool"; throw new NotImplementedException("TODO: support bit-fields with more than 1 bit."); } // In-build basic if (CSharpNativeToManagedBasicTypes.TryGetValue(typeInfo.Type, out result)) { if (typeInfo.IsPtr) return result + '*'; return result; } // In-build default if (CSharpNativeToManagedDefault.TryGetValue(typeInfo.Type, out result)) return result; // Object reference property if (typeInfo.IsObjectRef) return GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller); if (typeInfo.Type == "SoftTypeReference" || typeInfo.Type == "SoftObjectReference") return typeInfo.Type; // Array or Span or DataContainer #if USE_NETCORE if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer" || typeInfo.Type == "MonoArray" || typeInfo.Type == "MArray") && typeInfo.GenericArgs != null) #else if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && typeInfo.GenericArgs != null) #endif return GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller) + "[]"; // Dictionary if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null) return string.Format("System.Collections.Generic.Dictionary<{0}, {1}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller), GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[1], caller)); // HashSet if (typeInfo.Type == "HashSet" && typeInfo.GenericArgs != null) return string.Format("System.Collections.Generic.HashSet<{0}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller)); // BitArray if (typeInfo.Type == "BitArray" && typeInfo.GenericArgs != null) return "bool[]"; // BytesContainer if (typeInfo.Type == "BytesContainer" && typeInfo.GenericArgs == null) return "byte[]"; // Function if (typeInfo.Type == "Function" && typeInfo.GenericArgs != null) { if (typeInfo.GenericArgs.Count == 0) throw new Exception("Missing function return type."); #if USE_NETCORE // NetCore doesn't allow using Marshal.GetDelegateForFunctionPointer on generic delegate type (eg. Action) thus generate explicit delegate type for function parameter // https://github.com/dotnet/runtime/issues/36110 if (typeInfo.GenericArgs.Count == 1 && typeInfo.GenericArgs[0].Type == "void") return "Action"; // TODO: generate delegates globally in the module namespace to share more code (smaller binary size) var key = string.Empty; for (int i = 0; i < typeInfo.GenericArgs.Count; i++) key += GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[i], caller); if (!CSharpAdditionalCodeCache.TryGetValue(key, out var delegateName)) { delegateName = "Delegate" + CSharpAdditionalCodeCache.Count; var signature = $"public delegate {GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller)} {delegateName}("; for (int i = 1; i < typeInfo.GenericArgs.Count; i++) { if (i != 1) signature += ", "; signature += GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[i], caller); signature += $" arg{(i - 1)}"; } signature += ");"; CSharpAdditionalCode.Add("/// Function delegate."); CSharpAdditionalCode.Add(signature); CSharpAdditionalCodeCache.Add(key, delegateName); } return delegateName; #else if (typeInfo.GenericArgs.Count > 1) { var args = string.Empty; args += GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[1], caller); for (int i = 2; i < typeInfo.GenericArgs.Count; i++) args += ", " + GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[i], caller); if (typeInfo.GenericArgs[0].Type == "void") return string.Format("Action<{0}>", args); return string.Format("Func<{0}, {1}>", args, GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller)); } if (typeInfo.GenericArgs[0].Type == "void") return "Action"; return string.Format("Func<{0}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller)); #endif } // Find API type info var apiType = FindApiTypeInfo(buildData, typeInfo, caller); var typeName = typeInfo.Type.Replace("::", "."); if (apiType != null) { // Add reference to the namespace CSharpUsedNamespaces.Add(apiType.Namespace); var apiTypeParent = apiType.Parent; while (apiTypeParent != null && !(apiTypeParent is FileInfo)) { CSharpUsedNamespaces.Add(apiTypeParent.Namespace); apiTypeParent = apiTypeParent.Parent; } if (apiType.IsScriptingObject || apiType.IsInterface) return typeName; if (typeInfo.IsPtr && apiType.IsPod) return typeName + '*'; if (apiType is LangType && CSharpNativeToManagedBasicTypes.TryGetValue(apiType.Name, out result)) return result; } // Pointer if (typeInfo.IsPtr) return "IntPtr"; return typeName; } private static string GenerateCSharpManagedToNativeType(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller) { // Fixed-size array if (typeInfo.IsArray) return GenerateCSharpNativeToManaged(buildData, typeInfo, caller); // Find API type info var apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null) { // Add reference to the namespace CSharpUsedNamespaces.Add(apiType.Namespace); var apiTypeParent = apiType.Parent; while (apiTypeParent != null && !(apiTypeParent is FileInfo)) { CSharpUsedNamespaces.Add(apiTypeParent.Namespace); apiTypeParent = apiTypeParent.Parent; } if (apiType.IsScriptingObject || apiType.IsInterface) return "IntPtr"; } // Object reference property if (typeInfo.IsObjectRef) return "IntPtr"; // Function if (typeInfo.Type == "Function" && typeInfo.GenericArgs != null) return "IntPtr"; return GenerateCSharpNativeToManaged(buildData, typeInfo, caller); } private static string GenerateCSharpManagedToNativeConverter(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller) { // Use dynamic array as wrapper container for fixed-size native arrays if (typeInfo.IsArray) return string.Empty; // Special case for bit-fields if (typeInfo.IsBitField) { if (typeInfo.BitSize == 1) return string.Empty; throw new NotImplementedException("TODO: support bit-fields with more than 1 bit."); } switch (typeInfo.Type) { case "String": case "StringView": case "StringAnsi": case "StringAnsiView": // String return string.Empty; case "ScriptingObject": case "PersistentScriptingObject": case "ManagedScriptingObject": // object return "FlaxEngine.Object.GetUnmanagedPtr({0})"; case "Function": // delegate return "NativeInterop.GetFunctionPointerForDelegate({0})"; default: var apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null) { // Scripting Object if (apiType.IsScriptingObject) return "FlaxEngine.Object.GetUnmanagedPtr({0})"; // interface if (apiType.IsInterface) return string.Format("FlaxEngine.Object.GetUnmanagedInterface({{0}}, typeof({0}))", apiType.FullNameManaged); } // Object reference property if (typeInfo.IsObjectRef) return "FlaxEngine.Object.GetUnmanagedPtr({0})"; // Default return string.Empty; } } private static void GenerateCSharpManagedTypeInternals(BuildData buildData, ApiTypeInfo apiTypeInfo, StringBuilder contents, string indent) { if (CSharpAdditionalCode.Count != 0) { contents.AppendLine(); foreach (var e in CSharpAdditionalCode) contents.Append(indent).AppendLine(e); CSharpAdditionalCode.Clear(); CSharpAdditionalCodeCache.Clear(); } GenerateCSharpTypeInternals?.Invoke(buildData, apiTypeInfo, contents, indent); } private static void GenerateCSharpWrapperFunction(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo caller, FunctionInfo functionInfo) { string returnValueType; if (UsePassByReference(buildData, functionInfo.ReturnType, caller)) { returnValueType = "void"; } else { returnValueType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType, caller); } #if USE_NETCORE var returnNativeType = GenerateCSharpManagedToNativeType(buildData, functionInfo.ReturnType, caller); string returnMarshalType = ""; if (returnValueType == "bool") returnMarshalType = "MarshalAs(UnmanagedType.U1)"; else if (returnValueType == "System.Type") returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemTypeMarshaller))"; else if (returnValueType == "CultureInfo") returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.CultureInfoMarshaller))"; else if (functionInfo.ReturnType.Type == "Variant") // object returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.ManagedHandleMarshaller))"; else if (FindApiTypeInfo(buildData, functionInfo.ReturnType, caller)?.IsInterface ?? false) { // Interfaces are not supported by NativeMarshallingAttribute, marshal the parameter returnMarshalType = $"MarshalUsing(typeof({returnValueType}Marshaller))"; } else if (functionInfo.ReturnType.Type == "MonoArray" || functionInfo.ReturnType.Type == "MArray") returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemArrayMarshaller))"; else if (returnValueType == "object[]") returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemObjectArrayMarshaller))"; else if (functionInfo.ReturnType.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer" || functionInfo.ReturnType.Type == "BytesContainer" || returnNativeType == "Array") returnMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = nameof(__returnCount))"; else if (functionInfo.ReturnType.Type == "Dictionary") returnMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)"; else if (returnValueType == "byte[]") returnMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = \"__returnCount\")"; else if (returnValueType == "bool[]") { // Boolean arrays does not support custom marshalling for some unknown reason returnMarshalType = $"MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = {functionInfo.Parameters.Count + (functionInfo.Glue.CustomParameters?.Count ?? 0)})"; } #endif #if !USE_NETCORE contents.AppendLine().Append(indent).Append("[MethodImpl(MethodImplOptions.InternalCall)]"); contents.AppendLine().Append(indent).Append("internal static partial "); #else if (string.IsNullOrEmpty(functionInfo.Glue.LibraryEntryPoint)) throw new Exception($"Function {caller.FullNameNative}::{functionInfo.Name} has missing entry point for library import."); contents.AppendLine().Append(indent).Append($"[LibraryImport(\"{caller.ParentModule.Module.BinaryModuleName}\", EntryPoint = \"{functionInfo.Glue.LibraryEntryPoint}\", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.Interop.StringMarshaller))]"); if (!string.IsNullOrEmpty(returnMarshalType)) contents.AppendLine().Append(indent).Append($"[return: {returnMarshalType}]"); contents.AppendLine().Append(indent).Append("internal static partial "); #endif contents.Append(returnValueType).Append(" Internal_").Append(functionInfo.UniqueName).Append('('); var separator = false; if (!functionInfo.IsStatic) { contents.Append("IntPtr __obj"); separator = true; } foreach (var parameterInfo in functionInfo.Parameters) { if (separator) contents.Append(", "); separator = true; var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller); #if USE_NETCORE string parameterMarshalType = ""; if (nativeType == "System.Type") parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemTypeMarshaller))"; else if (parameterInfo.Type.Type == "CultureInfo") parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.CultureInfoMarshaller))"; else if (parameterInfo.Type.Type == "Variant") // object parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.ManagedHandleMarshaller))"; else if (parameterInfo.Type.Type == "MonoArray" || parameterInfo.Type.Type == "MArray") parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemArrayMarshaller))"; else if (nativeType == "object[]") parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemObjectArrayMarshaller))"; else if (parameterInfo.Type.Type == "Array" && parameterInfo.Type.GenericArgs.Count > 0 && parameterInfo.Type.GenericArgs[0].Type == "bool") parameterMarshalType = $"MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = {(!functionInfo.IsStatic ? 1 : 0) + functionInfo.Parameters.Count + (functionInfo.Glue.CustomParameters.FindIndex(x => x.Name == $"__{parameterInfo.Name}Count"))})"; else if (parameterInfo.Type.Type == "Array" || parameterInfo.Type.Type == "Span" || parameterInfo.Type.Type == "DataContainer" || parameterInfo.Type.Type == "BytesContainer" || nativeType == "Array") parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = \"__{parameterInfo.Name}Count\")"; else if (parameterInfo.Type.Type == "Dictionary") parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)"; else if (nativeType == "bool") parameterMarshalType = "MarshalAs(UnmanagedType.U1)"; else if (nativeType == "char") parameterMarshalType = "MarshalAs(UnmanagedType.I2)"; if (!string.IsNullOrEmpty(parameterMarshalType)) contents.Append($"[{parameterMarshalType}] "); #endif if (parameterInfo.IsOut) contents.Append("out "); else if (parameterInfo.IsRef || UsePassByReference(buildData, parameterInfo.Type, caller)) contents.Append("ref "); // Out parameters that need additional converting will be converted at the native side (eg. object reference) if (parameterInfo.IsOut && !string.IsNullOrEmpty(GenerateCSharpManagedToNativeConverter(buildData, parameterInfo.Type, caller))) nativeType = parameterInfo.Type.Type; contents.Append(nativeType); contents.Append(' '); contents.Append(parameterInfo.Name); } var customParametersCount = functionInfo.Glue.CustomParameters?.Count ?? 0; for (var i = 0; i < customParametersCount; i++) { var parameterInfo = functionInfo.Glue.CustomParameters[i]; if (separator) contents.Append(", "); separator = true; var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller); #if USE_NETCORE string parameterMarshalType = ""; if (parameterInfo.IsOut && parameterInfo.DefaultValue == "var __resultAsRef") { if (parameterInfo.Type.Type == "Array" || parameterInfo.Type.Type == "Span" || parameterInfo.Type.Type == "DataContainer" || parameterInfo.Type.Type == "BytesContainer") parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = \"{parameterInfo.Name}Count\")"; else if (parameterInfo.Type.Type == "Dictionary") parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)"; } if (nativeType == "System.Type") parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemTypeMarshaller))"; if (!string.IsNullOrEmpty(parameterMarshalType)) contents.Append($"[{parameterMarshalType}] "); #endif if (parameterInfo.IsOut) contents.Append("out "); else if (parameterInfo.IsRef || UsePassByReference(buildData, parameterInfo.Type, caller)) contents.Append("ref "); contents.Append(nativeType); contents.Append(' '); contents.Append(parameterInfo.Name); } contents.Append(')').Append(';').AppendLine(); } private static void GenerateCSharpWrapperFunctionCall(BuildData buildData, StringBuilder contents, ApiTypeInfo caller, FunctionInfo functionInfo, bool isSetter = false) { #if USE_NETCORE for (var i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; if (parameterInfo.Type.IsArray || parameterInfo.Type.Type == "Array" || parameterInfo.Type.Type == "Span" || parameterInfo.Type.Type == "BytesContainer" || parameterInfo.Type.Type == "DataContainer" || parameterInfo.Type.Type == "BitArray") { if (!parameterInfo.IsOut) contents.Append($"var __{parameterInfo.Name}Count = {(isSetter ? "value" : parameterInfo.Name)}?.Length ?? 0; "); } } #endif if (functionInfo.Glue.UseReferenceForResult) { } else if (!functionInfo.ReturnType.IsVoid) { contents.Append("return "); } contents.Append("Internal_").Append(functionInfo.UniqueName).Append('('); // Pass parameters var separator = false; if (!functionInfo.IsStatic) { contents.Append("__unmanagedPtr"); separator = true; } for (var i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; if (separator) contents.Append(", "); separator = true; if (parameterInfo.IsOut) contents.Append("out "); else if (parameterInfo.IsRef || UsePassByReference(buildData, parameterInfo.Type, caller)) contents.Append("ref "); var convertFunc = GenerateCSharpManagedToNativeConverter(buildData, parameterInfo.Type, caller); var paramName = isSetter ? "value" : parameterInfo.Name; if (string.IsNullOrWhiteSpace(convertFunc) || parameterInfo.IsOut) { // Pass value contents.Append(paramName); } else { if (parameterInfo.IsRef) throw new Exception($"Cannot use Ref meta on parameter {parameterInfo} in function {functionInfo.Name} in {caller}. Use API_PARAM(Out) if you want to pass it as a result reference."); // Convert value contents.Append(string.Format(convertFunc, paramName)); } } // Pass additional parameters var customParametersCount = functionInfo.Glue.CustomParameters?.Count ?? 0; for (var i = 0; i < customParametersCount; i++) { var parameterInfo = functionInfo.Glue.CustomParameters[i]; if (separator) contents.Append(", "); separator = true; var convertFunc = GenerateCSharpManagedToNativeConverter(buildData, parameterInfo.Type, caller); if (string.IsNullOrWhiteSpace(convertFunc) || parameterInfo.IsOut) { if (parameterInfo.IsOut) contents.Append("out "); else if (parameterInfo.IsRef || UsePassByReference(buildData, parameterInfo.Type, caller)) contents.Append("ref "); // Pass value contents.Append(parameterInfo.DefaultValue); } else { // Convert value contents.Append(string.Format(convertFunc, parameterInfo.DefaultValue)); } } contents.Append(");"); // Return result if (functionInfo.Glue.UseReferenceForResult) { contents.Append(" return __resultAsRef;"); } } private static void GenerateCSharpComment(StringBuilder contents, string indent, string[] comment, bool skipMeta = false) { if (comment == null) return; foreach (var c in comment) { if (skipMeta && (c.Contains("/// ") || c.Contains("= 3 && comment[0] == "/// ") { // Write documentation comment as tooltip string tooltip = null; for (int i = 1; i < comment.Length && comment[i] != "/// "; i++) { if (comment[i].StartsWith("/// ")) { if (tooltip == null) tooltip = string.Empty; else tooltip += " "; tooltip += comment[i].Substring(4); } } if (tooltip != null) { if (tooltip.StartsWith("Gets the ")) tooltip = "The " + tooltip.Substring(9); tooltip = tooltip.Replace("\"", "\"\""); contents.Append(indent).Append("[Tooltip(@\"").Append(tooltip).Append("\")]").AppendLine(); } } #endif if (writeDefaultValue) { // Write default value attribute defaultValue = GenerateCSharpDefaultValueNativeToManaged(buildData, defaultValue, apiTypeInfo, defaultValueType, true); if (defaultValue != null) contents.Append(indent).Append("[DefaultValue(").Append(defaultValue).Append(")]").AppendLine(); } } private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo apiTypeInfo, bool useUnmanaged, string defaultValue = null, TypeInfo defaultValueType = null) { GenerateCSharpAttributes(buildData, contents, indent, apiTypeInfo, apiTypeInfo.Attributes, apiTypeInfo.Comment, true, useUnmanaged, defaultValue, false, defaultValueType); } private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo apiTypeInfo, MemberInfo memberInfo, bool useUnmanaged, string defaultValue = null, TypeInfo defaultValueType = null) { GenerateCSharpAttributes(buildData, contents, indent, apiTypeInfo, memberInfo.Attributes, memberInfo.Comment, true, useUnmanaged, defaultValue, memberInfo.IsDeprecated, defaultValueType); } private static bool GenerateCSharpStructureUseDefaultInitialize(BuildData buildData, StructureInfo structureInfo) { foreach (var fieldInfo in structureInfo.Fields) { // Ignore non-member fields if (fieldInfo.IsStatic || fieldInfo.IsReadOnly || fieldInfo.IsConstexpr) continue; // Check if any field has default value if (fieldInfo.HasAttribute("DefaultValue") || fieldInfo.HasDefaultValue) return true; // Check any nested structure var apiType = FindApiTypeInfo(buildData, fieldInfo.Type, structureInfo); if (apiType is StructureInfo fieldTypeStruct && GenerateCSharpStructureUseDefaultInitialize(buildData, fieldTypeStruct)) return true; } return false; } private static string GenerateCSharpAccessLevel(AccessLevel access) { if (access == AccessLevel.Protected) return "protected "; if (access == AccessLevel.Private) return "private "; return "public "; } private static void GenerateCSharpClass(BuildData buildData, StringBuilder contents, string indent, ClassInfo classInfo) { var useUnmanaged = classInfo.IsStatic || classInfo.IsScriptingObject; contents.AppendLine(); // Namespace begin if (!string.IsNullOrEmpty(classInfo.Namespace)) { contents.AppendFormat("namespace "); contents.AppendLine(classInfo.Namespace); contents.AppendLine("{"); indent += " "; } // Class docs GenerateCSharpComment(contents, indent, classInfo.Comment); // Class begin GenerateCSharpAttributes(buildData, contents, indent, classInfo, useUnmanaged); #if USE_NETCORE string marshallerName = ""; if (!classInfo.IsStatic) { marshallerName = classInfo.Name + "Marshaller"; contents.Append(indent).AppendLine($"[NativeMarshalling(typeof({marshallerName}))]"); } #endif contents.Append(indent).Append(GenerateCSharpAccessLevel(classInfo.Access)); if (classInfo.IsStatic) contents.Append("static "); else if (classInfo.IsSealed) contents.Append("sealed "); else if (classInfo.IsAbstract) contents.Append("abstract "); contents.Append("unsafe partial class ").Append(classInfo.Name); var hasBase = classInfo.BaseType != null && !classInfo.IsBaseTypeHidden; if (hasBase) contents.Append(" : ").Append(GenerateCSharpNativeToManaged(buildData, new TypeInfo(classInfo.BaseType), classInfo)); var hasInterface = false; if (classInfo.Interfaces != null) { foreach (var interfaceInfo in classInfo.Interfaces) { if (interfaceInfo.Access != AccessLevel.Public) continue; if (hasInterface || hasBase) contents.Append(", "); else contents.Append(" : "); hasInterface = true; contents.Append(interfaceInfo.FullNameManaged); } } contents.AppendLine(); contents.Append(indent + "{"); indent += " "; // Constructor for managed objects if (!classInfo.IsStatic && !classInfo.NoConstructor) { contents.AppendLine(); contents.Append(indent).AppendLine("/// "); contents.Append(indent).Append("/// Initializes a new instance of the ."); contents.Append(indent).AppendLine("/// "); contents.Append(indent).Append(classInfo.IsAbstract ? "protected " : "public ").Append(classInfo.Name).AppendLine("() : base()"); contents.Append(indent + "{").AppendLine(); contents.Append(indent + "}").AppendLine(); } // Events foreach (var eventInfo in classInfo.Events) { if (eventInfo.IsHidden) continue; if (!useUnmanaged) throw new NotImplementedException("TODO: support events inside non-static and non-scripting API class types."); var paramsCount = eventInfo.Type.GenericArgs?.Count ?? 0; contents.AppendLine(); var useCustomDelegateSignature = false; for (var i = 0; i < paramsCount; i++) { var paramType = eventInfo.Type.GenericArgs[i]; var result = GenerateCSharpNativeToManaged(buildData, paramType, classInfo); if ((paramType.IsRef && !paramType.IsConst) || result[result.Length - 1] == '*') useCustomDelegateSignature = true; CppParamsWrappersCache[i] = result; } string eventType; if (useCustomDelegateSignature) { contents.Append(indent).Append($"/// The delegate for event {eventInfo.Name}.").AppendLine(); contents.Append(indent).Append("public delegate void ").Append(eventInfo.Name).Append("Delegate("); for (var i = 0; i < paramsCount; i++) { var paramType = eventInfo.Type.GenericArgs[i]; if (i != 0) contents.Append(", "); if (paramType.IsRef && !paramType.IsConst) contents.Append("ref "); contents.Append(CppParamsWrappersCache[i]).Append(" arg").Append(i); } contents.Append(");").AppendLine().AppendLine(); eventType = eventInfo.Name + "Delegate"; } else { eventType = "Action"; if (paramsCount != 0) { eventType += '<'; for (var i = 0; i < paramsCount; i++) { if (i != 0) eventType += ", "; CppParamsWrappersCache[i] = GenerateCSharpNativeToManaged(buildData, eventInfo.Type.GenericArgs[i], classInfo); eventType += CppParamsWrappersCache[i]; } eventType += '>'; } } string eventSignature = "event " + eventType; GenerateCSharpComment(contents, indent, eventInfo.Comment, true); GenerateCSharpAttributes(buildData, contents, indent, classInfo, eventInfo, useUnmanaged); contents.Append(indent).Append(GenerateCSharpAccessLevel(eventInfo.Access)); if (eventInfo.IsStatic) contents.Append("static "); contents.Append(eventSignature); contents.Append(' ').AppendLine(eventInfo.Name); contents.Append(indent).Append('{').AppendLine(); indent += " "; var eventInstance = eventInfo.IsStatic ? string.Empty : "__unmanagedPtr, "; contents.Append(indent).Append($"add {{ Internal_{eventInfo.Name} += value; if (Internal_{eventInfo.Name}_Count++ == 0) Internal_{eventInfo.Name}_Bind({eventInstance}true); }}").AppendLine(); contents.Append(indent).Append($"remove {{ var __delegate = ({eventType})Delegate.Remove(Internal_{eventInfo.Name}, value); if (__delegate != Internal_{eventInfo.Name}) {{ Internal_{eventInfo.Name} = __delegate; if (--Internal_{eventInfo.Name}_Count == 0) Internal_{eventInfo.Name}_Bind({eventInstance}false); }} }}").AppendLine(); indent = indent.Substring(0, indent.Length - 4); contents.Append(indent).Append('}').AppendLine(); contents.AppendLine(); contents.Append(indent).Append("private "); if (eventInfo.IsStatic) contents.Append("static "); contents.Append($"int Internal_{eventInfo.Name}_Count;"); contents.AppendLine(); contents.Append("#pragma warning disable 67").AppendLine(); contents.Append(indent).Append("private "); if (eventInfo.IsStatic) contents.Append("static "); contents.Append($"{eventSignature} Internal_{eventInfo.Name};"); contents.AppendLine(); contents.Append("#pragma warning restore 67").AppendLine(); contents.AppendLine(); contents.Append(indent).Append("internal "); if (eventInfo.IsStatic) contents.Append("static "); contents.Append($"void Internal_{eventInfo.Name}_Invoke("); for (var i = 0; i < paramsCount; i++) { var paramType = eventInfo.Type.GenericArgs[i]; if (i != 0) contents.Append(", "); if (paramType.IsRef && !paramType.IsConst) contents.Append("ref "); contents.Append(CppParamsWrappersCache[i]); contents.Append(" arg").Append(i); } contents.Append(')').AppendLine(); contents.Append(indent).Append('{').AppendLine(); contents.Append(indent).Append(" Internal_").Append(eventInfo.Name); contents.Append('('); for (var i = 0; i < paramsCount; i++) { var paramType = eventInfo.Type.GenericArgs[i]; if (i != 0) contents.Append(", "); if (paramType.IsRef && !paramType.IsConst) contents.Append("ref "); contents.Append("arg").Append(i); } contents.Append(");").AppendLine(); contents.Append(indent).Append('}').AppendLine(); contents.AppendLine(); #if !USE_NETCORE contents.Append(indent).Append("[MethodImpl(MethodImplOptions.InternalCall)]").AppendLine(); contents.Append(indent).Append($"internal static extern void Internal_{eventInfo.Name}_Bind("); if (!eventInfo.IsStatic) contents.Append("IntPtr obj, "); contents.Append("bool bind);"); #else string libraryEntryPoint; if (buildData.Toolchain?.Compiler == TargetCompiler.MSVC) libraryEntryPoint = $"{classInfo.FullNameManaged}::Internal_{eventInfo.Name}_Bind"; // MSVC allows to override exported symbol name else libraryEntryPoint = CppNameMangling.MangleFunctionName(buildData, eventInfo.Name + "_ManagedBind", classInfo.FullNameNativeInternal + "Internal", CSharpEventBindReturn, eventInfo.IsStatic ? null : new TypeInfo(classInfo.FullNameNative) { IsPtr = true }, CSharpEventBindParams); contents.Append(indent).Append($"[LibraryImport(\"{classInfo.ParentModule.Module.BinaryModuleName}\", EntryPoint = \"{libraryEntryPoint}\", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.Interop.StringMarshaller))]").AppendLine(); contents.Append(indent).Append($"internal static partial void Internal_{eventInfo.Name}_Bind("); if (!eventInfo.IsStatic) contents.Append("IntPtr obj, "); contents.Append("[MarshalAs(UnmanagedType.U1)] bool bind);"); #endif contents.AppendLine(); } // Fields foreach (var fieldInfo in classInfo.Fields) { if (fieldInfo.Getter == null || fieldInfo.IsHidden) continue; contents.AppendLine(); GenerateCSharpComment(contents, indent, fieldInfo.Comment, true); GenerateCSharpAttributes(buildData, contents, indent, classInfo, fieldInfo, useUnmanaged, fieldInfo.DefaultValue, fieldInfo.Type); contents.Append(indent).Append(GenerateCSharpAccessLevel(fieldInfo.Access)); if (fieldInfo.IsConstexpr) contents.Append("const "); else if (fieldInfo.IsStatic) contents.Append("static "); var managedType = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, classInfo); contents.Append(managedType).Append(' ').Append(fieldInfo.Name); if (!useUnmanaged || fieldInfo.IsConstexpr) { var defaultValue = GenerateCSharpDefaultValueNativeToManaged(buildData, fieldInfo.DefaultValue, classInfo, fieldInfo.Type, false, managedType); if (!string.IsNullOrEmpty(defaultValue)) contents.Append(" = ").Append(defaultValue); contents.AppendLine(";"); continue; } contents.AppendLine().AppendLine(indent + "{"); indent += " "; contents.Append(indent).Append("get { "); GenerateCSharpWrapperFunctionCall(buildData, contents, classInfo, fieldInfo.Getter); contents.Append(" }").AppendLine(); if (!fieldInfo.IsReadOnly) { contents.Append(indent).Append("set { "); GenerateCSharpWrapperFunctionCall(buildData, contents, classInfo, fieldInfo.Setter, useUnmanaged); contents.Append(" }").AppendLine(); } indent = indent.Substring(0, indent.Length - 4); contents.AppendLine(indent + "}"); GenerateCSharpWrapperFunction(buildData, contents, indent, classInfo, fieldInfo.Getter); if (!fieldInfo.IsReadOnly) GenerateCSharpWrapperFunction(buildData, contents, indent, classInfo, fieldInfo.Setter); } // Properties foreach (var propertyInfo in classInfo.Properties) { if (propertyInfo.IsHidden || !useUnmanaged) continue; contents.AppendLine(); GenerateCSharpComment(contents, indent, propertyInfo.Comment, true); GenerateCSharpAttributes(buildData, contents, indent, classInfo, propertyInfo, useUnmanaged); contents.Append(indent).Append(GenerateCSharpAccessLevel(propertyInfo.Access)); if (propertyInfo.IsStatic) contents.Append("static "); var returnValueType = GenerateCSharpNativeToManaged(buildData, propertyInfo.Type, classInfo); contents.Append(returnValueType).Append(' ').AppendLine(propertyInfo.Name); contents.AppendLine(indent + "{"); indent += " "; if (propertyInfo.Getter != null) { contents.Append(indent); if (propertyInfo.Access != propertyInfo.Getter.Access) contents.Append(GenerateCSharpAccessLevel(propertyInfo.Getter.Access)); contents.Append("get { "); GenerateCSharpWrapperFunctionCall(buildData, contents, classInfo, propertyInfo.Getter); contents.Append(" }").AppendLine(); } if (propertyInfo.Setter != null) { contents.Append(indent); if (propertyInfo.Access != propertyInfo.Setter.Access) contents.Append(GenerateCSharpAccessLevel(propertyInfo.Setter.Access)); contents.Append("set { "); GenerateCSharpWrapperFunctionCall(buildData, contents, classInfo, propertyInfo.Setter, true); contents.Append(" }").AppendLine(); } indent = indent.Substring(0, indent.Length - 4); contents.AppendLine(indent + "}"); if (propertyInfo.Getter != null) { GenerateCSharpWrapperFunction(buildData, contents, indent, classInfo, propertyInfo.Getter); } if (propertyInfo.Setter != null) { GenerateCSharpWrapperFunction(buildData, contents, indent, classInfo, propertyInfo.Setter); } } // Functions foreach (var functionInfo in classInfo.Functions) { if (functionInfo.IsHidden) continue; if (!useUnmanaged) throw new Exception($"Not supported function {functionInfo.Name} inside non-static and non-scripting class type {classInfo.Name}."); if (!functionInfo.NoProxy) { contents.AppendLine(); GenerateCSharpComment(contents, indent, functionInfo.Comment); GenerateCSharpAttributes(buildData, contents, indent, classInfo, functionInfo, true); contents.Append(indent).Append(GenerateCSharpAccessLevel(functionInfo.Access)); if (functionInfo.IsStatic) contents.Append("static "); if (functionInfo.IsVirtual && !classInfo.IsSealed) contents.Append("virtual "); var returnValueType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType, classInfo); contents.Append(returnValueType).Append(' ').Append(functionInfo.Name).Append('('); for (var i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; if (i != 0) contents.Append(", "); if (!string.IsNullOrEmpty(parameterInfo.Attributes)) contents.Append('[').Append(parameterInfo.Attributes).Append(']').Append(' '); var managedType = GenerateCSharpNativeToManaged(buildData, parameterInfo.Type, classInfo); if (parameterInfo.IsOut) contents.Append("out "); else if (parameterInfo.IsRef) contents.Append("ref "); else if (parameterInfo.IsThis) contents.Append("this "); else if (parameterInfo.IsParams) contents.Append("params "); contents.Append(managedType); contents.Append(' '); contents.Append(parameterInfo.Name); var defaultValue = GenerateCSharpDefaultValueNativeToManaged(buildData, parameterInfo.DefaultValue, classInfo, parameterInfo.Type, false, managedType); if (!string.IsNullOrEmpty(defaultValue)) contents.Append(" = ").Append(defaultValue); } contents.Append(')').AppendLine().AppendLine(indent + "{"); indent += " "; contents.Append(indent); GenerateCSharpWrapperFunctionCall(buildData, contents, classInfo, functionInfo); indent = indent.Substring(0, indent.Length - 4); contents.AppendLine(); contents.AppendLine(indent + "}"); } GenerateCSharpWrapperFunction(buildData, contents, indent, classInfo, functionInfo); } // Interface implementation if (hasInterface) { foreach (var interfaceInfo in classInfo.Interfaces) { if (interfaceInfo.Access != AccessLevel.Public) continue; foreach (var functionInfo in interfaceInfo.Functions) { if (!classInfo.IsScriptingObject) throw new Exception($"Class {classInfo.Name} cannot implement interface {interfaceInfo.Name} because it requires ScriptingObject as a base class."); contents.AppendLine(); if (functionInfo.Comment != null && functionInfo.Comment.Length != 0) contents.Append(indent).AppendLine("/// "); GenerateCSharpAttributes(buildData, contents, indent, classInfo, functionInfo.Attributes, null, false, useUnmanaged); contents.Append(indent).Append(GenerateCSharpAccessLevel(functionInfo.Access)); if (functionInfo.IsVirtual && !classInfo.IsSealed) contents.Append("virtual "); var returnValueType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType, classInfo); contents.Append(returnValueType).Append(' ').Append(functionInfo.Name).Append('('); for (var i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; if (i != 0) contents.Append(", "); if (!string.IsNullOrEmpty(parameterInfo.Attributes)) contents.Append('[').Append(parameterInfo.Attributes).Append(']').Append(' '); var managedType = GenerateCSharpNativeToManaged(buildData, parameterInfo.Type, classInfo); if (parameterInfo.IsOut) contents.Append("out "); else if (parameterInfo.IsRef) contents.Append("ref "); else if (parameterInfo.IsThis) contents.Append("this "); else if (parameterInfo.IsParams) contents.Append("params "); contents.Append(managedType); contents.Append(' '); contents.Append(parameterInfo.Name); var defaultValue = GenerateCSharpDefaultValueNativeToManaged(buildData, parameterInfo.DefaultValue, classInfo, parameterInfo.Type, false, managedType); if (!string.IsNullOrEmpty(defaultValue)) contents.Append(" = ").Append(defaultValue); } contents.Append(')').AppendLine().AppendLine(indent + "{"); indent += " "; contents.Append(indent); GenerateCSharpWrapperFunctionCall(buildData, contents, classInfo, functionInfo); indent = indent.Substring(0, indent.Length - 4); contents.AppendLine(); contents.AppendLine(indent + "}"); GenerateCSharpWrapperFunction(buildData, contents, indent, classInfo, functionInfo); } } } GenerateCSharpManagedTypeInternals(buildData, classInfo, contents, indent); // Abstract wrapper (to ensure C# class can be created for Visual Scripts, see NativeInterop.NewObject) if (classInfo.IsAbstract) contents.AppendLine().Append(indent).Append("[Unmanaged] [HideInEditor] private sealed class AbstractWrapper : ").Append(classInfo.Name).AppendLine(" { }"); // Nested types foreach (var apiTypeInfo in classInfo.Children) { GenerateCSharpType(buildData, contents, indent, apiTypeInfo); } // Class end indent = indent.Substring(0, indent.Length - 4); contents.AppendLine(indent + "}"); #if USE_NETCORE if (!string.IsNullOrEmpty(marshallerName)) { contents.AppendLine(); contents.AppendLine(string.Join("\n" + indent, (indent + $$""" /// /// Marshaller for type . /// "); #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}}))] {{GenerateCSharpAccessLevel(classInfo.Access)}}static class {{marshallerName}} { #pragma warning disable 1591 #if FLAX_EDITOR [HideInEditor] #endif public static class NativeToManaged { public static {{classInfo.Name}} ConvertToManaged(IntPtr unmanaged) => Unsafe.As<{{classInfo.Name}}>(ManagedHandleMarshaller.NativeToManaged.ConvertToManaged(unmanaged)); public static IntPtr ConvertToUnmanaged({{classInfo.Name}} managed) => ManagedHandleMarshaller.ManagedToNative.ConvertToUnmanaged(managed); public static void Free(IntPtr unmanaged) => ManagedHandleMarshaller.NativeToManaged.Free(unmanaged); } #if FLAX_EDITOR [HideInEditor] #endif public static class ManagedToNative { public static {{classInfo.Name}} ConvertToManaged(IntPtr unmanaged) => Unsafe.As<{{classInfo.Name}}>(ManagedHandleMarshaller.NativeToManaged.ConvertToManaged(unmanaged)); public static IntPtr ConvertToUnmanaged({{classInfo.Name}} managed) => ManagedHandleMarshaller.ManagedToNative.ConvertToUnmanaged(managed); public static void Free(IntPtr unmanaged) => ManagedHandleMarshaller.ManagedToNative.Free(unmanaged); } #if FLAX_EDITOR [HideInEditor] #endif public struct Bidirectional { ManagedHandleMarshaller.Bidirectional marsh; public void FromManaged({{classInfo.Name}} managed) => marsh.FromManaged(managed); public IntPtr ToUnmanaged() => marsh.ToUnmanaged(); public void FromUnmanaged(IntPtr unmanaged) => marsh.FromUnmanaged(unmanaged); public {{classInfo.Name}} ToManaged() => Unsafe.As<{{classInfo.Name}}>(marsh.ToManaged()); public void Free() => marsh.Free(); } internal static {{classInfo.Name}} ConvertToManaged(IntPtr unmanaged) => Unsafe.As<{{classInfo.Name}}>(ManagedHandleMarshaller.ConvertToManaged(unmanaged)); internal static IntPtr ConvertToUnmanaged({{classInfo.Name}} managed) => ManagedHandleMarshaller.ConvertToUnmanaged(managed); internal static void Free(IntPtr unmanaged) => ManagedHandleMarshaller.Free(unmanaged); internal static {{classInfo.Name}} ToManaged(IntPtr managed) => Unsafe.As<{{classInfo.Name}}>(ManagedHandleMarshaller.ToManaged(managed)); internal static IntPtr ToNative({{classInfo.Name}} managed) => ManagedHandleMarshaller.ToNative(managed); #pragma warning restore 1591 } """).Split(new char[] { '\n' }))); } #endif // Namespace end if (!string.IsNullOrEmpty(classInfo.Namespace)) contents.AppendLine("}"); } private static void GenerateCSharpStructure(BuildData buildData, StringBuilder contents, string indent, StructureInfo structureInfo) { 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)) { 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 += " "; StringBuilder toManagedContent = new StringBuilder(); StringBuilder toNativeContent = new StringBuilder(); StringBuilder freeContents = new StringBuilder(); StringBuilder freeContents2 = new StringBuilder(); { // Native struct begin // TODO: skip using this utility structure if the auto-generated C# struct is already the same as XXXInternal here below 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"); if (structureInfo.BaseType != null && structureInfo.IsPod) contents.Append(" : ").Append(GenerateCSharpNativeToManaged(buildData, new TypeInfo { Type = structureInfo.BaseType.Name }, structureInfo)); contents.AppendLine(); contents.Append(indent + "{"); indent += " "; toNativeContent.Append($"return new {structureInfo.Name}Internal() {{ "); toManagedContent.Append($"return new {structureInfo.Name}() {{ "); bool useSeparator = false; contents.AppendLine(); foreach (var fieldInfo in structureInfo.Fields) { if (fieldInfo.IsStatic || fieldInfo.IsConstexpr) continue; string type, originalType; if (fieldInfo.Type.IsArray && (fieldInfo.NoArray || structureInfo.IsPod)) { // Fixed-size array that needs to be inlined into structure instead of passing it as managed array fieldInfo.Type.IsArray = false; originalType = type = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, structureInfo); fieldInfo.Type.IsArray = true; } else originalType = type = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, structureInfo); contents.Append(indent); if (fieldInfo.Access == AccessLevel.Public) contents.Append("public "); else if (fieldInfo.Access == AccessLevel.Protected) contents.Append("protected "); else if (fieldInfo.Access == AccessLevel.Private) contents.Append("private "); if (fieldInfo.IsConstexpr) contents.Append("const "); else if (fieldInfo.IsStatic) contents.Append("static "); var apiType = FindApiTypeInfo(buildData, fieldInfo.Type, structureInfo); bool internalType = apiType is StructureInfo fieldStructureInfo && UseCustomMarshalling(buildData, fieldStructureInfo, structureInfo); string internalTypeMarshaller = ""; if (fieldInfo.Type.IsArray && (fieldInfo.NoArray || structureInfo.IsPod)) { contents.Append(type).Append(' ').Append(fieldInfo.Name + "0;").AppendLine(); for (int i = 1; i < fieldInfo.Type.ArraySize; i++) { contents.AppendLine(); GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic); contents.Append(indent); if (fieldInfo.Access == AccessLevel.Public) contents.Append("public "); else if (fieldInfo.Access == AccessLevel.Protected) contents.Append("protected "); else if (fieldInfo.Access == AccessLevel.Private) contents.Append("private "); if (fieldInfo.IsStatic) contents.Append("static "); contents.Append(type).Append(' ').Append(fieldInfo.Name + i).Append(';').AppendLine(); } } else { if (fieldInfo.Type.IsObjectRef || fieldInfo.Type.Type == "Dictionary") type = "IntPtr"; else if (fieldInfo.Type.IsPtr && !originalType.EndsWith("*")) type = "IntPtr"; else if (fieldInfo.Type.Type == "Array" || fieldInfo.Type.Type == "Span" || fieldInfo.Type.Type == "DataContainer" || fieldInfo.Type.Type == "BytesContainer") { type = "IntPtr"; apiType = FindApiTypeInfo(buildData, fieldInfo.Type.GenericArgs[0], structureInfo); internalType = apiType is StructureInfo elementStructureInfo && UseCustomMarshalling(buildData, elementStructureInfo, structureInfo); } else if (fieldInfo.Type.Type == "Version") type = "IntPtr"; else if (type == "string") type = "IntPtr"; else if (type == "bool") type = "byte"; else if (fieldInfo.Type.Type == "Variant") type = "IntPtr"; else if (internalType) { internalTypeMarshaller = type + "Marshaller"; type = $"{internalTypeMarshaller}.{type}Internal"; } //else if (type == "Guid") // type = "GuidNative"; contents.Append(type).Append(' ').Append(fieldInfo.Name); contents.Append(';').AppendLine(); } // Generate struct constructor/getter and deconstructor/setter function if (fieldInfo.NoArray && fieldInfo.Type.IsArray) continue; if (useSeparator) { toManagedContent.Append(", "); toNativeContent.Append(", "); freeContents2.Append(""); freeContents.Append(""); } useSeparator = true; toManagedContent.Append(fieldInfo.Name); toManagedContent.Append(" = "); toNativeContent.Append(fieldInfo.Name); toNativeContent.Append(" = "); if (fieldInfo.Type.IsObjectRef) { var managedType = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type.GenericArgs[0], structureInfo); toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{managedType}>(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null"); toNativeContent.Append($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); // Permanent ScriptingObject handle is passed from native side, do not release it //freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); } else if (fieldInfo.Type.Type == "ScriptingObject") { toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null"); toNativeContent.Append($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); // Permanent ScriptingObject handle is passed from native side, do not release it //freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); } else if (fieldInfo.Type.IsPtr && originalType != "IntPtr" && !originalType.EndsWith("*")) { toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null"); toNativeContent.Append($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); // Permanent ScriptingObject handle is passed from native side, do not release it //freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); } else if (fieldInfo.Type.Type == "Dictionary") { toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null"); toNativeContent.Append($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak)"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); } else if (fieldInfo.Type.Type == "Array" || fieldInfo.Type.Type == "Span" || fieldInfo.Type.Type == "DataContainer" || fieldInfo.Type.Type == "BytesContainer") { string originalElementType = originalType.Substring(0, originalType.Length - 2); if (internalType) { // Marshal blittable array elements back to original non-blittable elements string originalElementTypeMarshaller = originalElementType + "Marshaller"; string internalElementType = $"{originalElementTypeMarshaller}.{originalElementType}Internal"; toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.ConvertArray((Unsafe.As(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target)).ToSpan<{internalElementType}>(), {originalElementTypeMarshaller}.ToManaged) : null"); toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(NativeInterop.ConvertArray(managed.{fieldInfo.Name}, {originalElementTypeMarshaller}.ToNative)), GCHandleType.Weak) : IntPtr.Zero"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = (Unsafe.As(handle.Target)).ToSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = (Unsafe.As(handle.Target)).ToSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); } else if (fieldInfo.Type.GenericArgs[0].IsObjectRef) { // Array elements passed as GCHandles toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.GCHandleArrayToManagedArray<{originalElementType}>(Unsafe.As(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target)) : null"); toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(NativeInterop.ManagedArrayToGCHandleWrappedArray(managed.{fieldInfo.Name}), GCHandleType.Weak) : IntPtr.Zero"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span ptrs = (Unsafe.As(handle.Target)).ToSpan(); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(ptr).Free(); }} }} (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); // Permanent ScriptingObject handle is passed from native side, do not release it //freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span ptrs = (Unsafe.As(handle.Target)).ToSpan(); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(ptr).Free(); }} }} (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); } else { // Blittable array elements toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? (Unsafe.As(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target)).ToArray<{originalElementType}>() : null"); toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(managed.{fieldInfo.Name}), GCHandleType.Weak) : IntPtr.Zero"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); } } else if (fieldInfo.Type.Type == "Version") { toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null"); toNativeContent.Append($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak)"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); } else if (originalType == "string") { toManagedContent.Append($"ManagedString.ToManaged(managed.{fieldInfo.Name})"); toNativeContent.Append($"ManagedString.ToNative(managed.{fieldInfo.Name})"); freeContents.AppendLine($"ManagedString.Free(unmanaged.{fieldInfo.Name});"); freeContents2.AppendLine($"ManagedString.Free(unmanaged.{fieldInfo.Name});"); } else if (originalType == "bool") { toManagedContent.Append($"managed.{fieldInfo.Name} != 0"); toNativeContent.Append($"managed.{fieldInfo.Name} ? (byte)1 : (byte)0"); } else if (fieldInfo.Type.Type == "Variant") { // Variant passed as boxed object handle toManagedContent.Append($"ManagedHandleMarshaller.NativeToManaged.ConvertToManaged(managed.{fieldInfo.Name})"); toNativeContent.Append($"ManagedHandleMarshaller.NativeToManaged.ConvertToUnmanaged(managed.{fieldInfo.Name})"); } else if (internalType) { toManagedContent.Append($"{internalTypeMarshaller}.ToManaged(managed.{fieldInfo.Name})"); toNativeContent.Append($"{internalTypeMarshaller}.ToNative(managed.{fieldInfo.Name})"); freeContents.AppendLine($"{internalTypeMarshaller}.Free(unmanaged.{fieldInfo.Name});"); freeContents2.AppendLine($"{internalTypeMarshaller}.Free(unmanaged.{fieldInfo.Name});"); } /*else if (originalType == "Guid") { toManagedContent.Append("(Guid)managed.").Append(fieldInfo.Name); toNativeContent.Append("(GuidNative)managed.").Append(fieldInfo.Name); }*/ else { toManagedContent.Append("managed.").Append(fieldInfo.Name); toNativeContent.Append("managed.").Append(fieldInfo.Name); } } // Native struct end indent = indent.Substring(0, indent.Length - 4); contents.AppendLine(indent + "}").AppendLine(); toManagedContent.AppendLine(" };"); toNativeContent.AppendLine(" };"); } var indent2 = indent + " "; var indent3 = indent2 + " "; // 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("}"); // 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("}"); // 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 managed)"); 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(); } #endif // Struct docs GenerateCSharpComment(contents, indent, structureInfo.Comment); // Struct begin GenerateCSharpAttributes(buildData, contents, indent, structureInfo, true); contents.Append(indent).AppendLine("[StructLayout(LayoutKind.Sequential)]"); #if USE_NETCORE if (!string.IsNullOrEmpty(structNativeMarshaling)) contents.Append(indent).AppendLine(structNativeMarshaling); #endif contents.Append(indent).Append(GenerateCSharpAccessLevel(structureInfo.Access)); contents.Append("unsafe partial struct ").Append(structureInfo.Name); if (structureInfo.BaseType != null && structureInfo.IsPod) contents.Append(" : ").Append(GenerateCSharpNativeToManaged(buildData, new TypeInfo(structureInfo.BaseType), structureInfo)); contents.AppendLine(); contents.Append(indent + "{"); indent += " "; // Fields bool hasDefaultMember = false; foreach (var fieldInfo in structureInfo.Fields) { contents.AppendLine(); GenerateCSharpComment(contents, indent, fieldInfo.Comment); GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic, fieldInfo.DefaultValue, fieldInfo.Type); #if USE_NETCORE if (fieldInfo.Type.Type == "String") contents.Append(indent).AppendLine("[MarshalAs(UnmanagedType.LPWStr)]"); else if (fieldInfo.Type.Type == "bool") contents.Append(indent).AppendLine("[MarshalAs(UnmanagedType.U1)]"); #endif contents.Append(indent).Append(GenerateCSharpAccessLevel(fieldInfo.Access)); if (fieldInfo.IsConstexpr) contents.Append("const "); else if (fieldInfo.IsStatic) contents.Append("static "); hasDefaultMember |= string.Equals(fieldInfo.Name, "Default", StringComparison.Ordinal); string managedType; if (fieldInfo.Type.IsArray && (fieldInfo.NoArray || structureInfo.IsPod)) { fieldInfo.Type.IsArray = false; managedType = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, structureInfo); fieldInfo.Type.IsArray = true; #if USE_NETCORE // Use fixed statement with primitive types of buffers if (managedType == "char") { // char's are not blittable, store as short instead contents.Append($"fixed short {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}]; // {managedType}*").AppendLine(); } else if (managedType == "byte") { contents.Append($"fixed byte {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}]; // {managedType}*").AppendLine(); } else #endif { // Padding in structs for fixed-size array contents.Append(managedType).Append(' ').Append(fieldInfo.Name + "0;").AppendLine(); for (int i = 1; i < fieldInfo.Type.ArraySize; i++) { contents.AppendLine(); GenerateCSharpComment(contents, indent, fieldInfo.Comment); GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic); contents.Append(indent).Append(GenerateCSharpAccessLevel(fieldInfo.Access)); if (fieldInfo.IsStatic) contents.Append("static "); contents.Append(managedType).Append(' ').Append(fieldInfo.Name + i).Append(';').AppendLine(); } } continue; } managedType = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, structureInfo); contents.Append(managedType).Append(' ').Append(fieldInfo.Name); if (fieldInfo.IsConstexpr) { // Compile-time constant var defaultValue = GenerateCSharpDefaultValueNativeToManaged(buildData, fieldInfo.DefaultValue, structureInfo, fieldInfo.Type, false, managedType); if (!string.IsNullOrEmpty(defaultValue)) contents.Append(" = ").Append(defaultValue); contents.AppendLine(";"); } else if (fieldInfo.IsStatic) { // Static fields are using C++ static value accessed via getter function binding contents.AppendLine(); contents.AppendLine(indent + "{"); indent += " "; contents.Append(indent).Append("get { "); GenerateCSharpWrapperFunctionCall(buildData, contents, structureInfo, fieldInfo.Getter); contents.Append(" }").AppendLine(); if (!fieldInfo.IsReadOnly) { contents.Append(indent).Append("set { "); GenerateCSharpWrapperFunctionCall(buildData, contents, structureInfo, fieldInfo.Setter, true); contents.Append(" }").AppendLine(); } indent = indent.Substring(0, indent.Length - 4); contents.AppendLine(indent + "}"); GenerateCSharpWrapperFunction(buildData, contents, indent, structureInfo, fieldInfo.Getter); if (!fieldInfo.IsReadOnly) GenerateCSharpWrapperFunction(buildData, contents, indent, structureInfo, fieldInfo.Setter); } else { contents.Append(';').AppendLine(); } } // Default value if (!structureInfo.NoDefault && !hasDefaultMember) { var useDefaultValueInit = GenerateCSharpStructureUseDefaultInitialize(buildData, structureInfo); if (useDefaultValueInit) { contents.AppendLine(); contents.Append(indent).AppendLine("private static bool __defaultValid;"); contents.Append(indent).AppendLine($"private static {structureInfo.Name} __default;"); } contents.AppendLine(); contents.Append(indent).AppendLine("/// "); contents.Append(indent).AppendLine($"/// The default ."); contents.Append(indent).AppendLine("/// "); contents.Append(indent).AppendLine($"public static {structureInfo.Name} Default"); contents.Append(indent).AppendLine("{"); indent += " "; contents.Append(indent).AppendLine("get"); contents.Append(indent).AppendLine("{"); if (useDefaultValueInit) { indent += " "; contents.Append(indent).AppendLine("if (!__defaultValid)"); contents.Append(indent).AppendLine("{"); indent += " "; contents.Append(indent).AppendLine("__defaultValid = true;"); contents.Append(indent).AppendLine("object obj = __default;"); contents.Append(indent).AppendLine($"FlaxEngine.Utils.InitStructure(obj, typeof({structureInfo.Name}));"); contents.Append(indent).AppendLine($"__default = ({structureInfo.Name})obj;"); indent = indent.Substring(0, indent.Length - 4); contents.Append(indent).AppendLine("}"); contents.Append(indent).AppendLine("return __default;"); indent = indent.Substring(0, indent.Length - 4); } else { contents.Append(indent).AppendLine($" return new {structureInfo.Name}();"); } contents.Append(indent).AppendLine("}"); indent = indent.Substring(0, indent.Length - 4); contents.Append(indent).AppendLine("}"); if (useDefaultValueInit) { contents.AppendLine(); contents.Append(indent).AppendLine("[System.Runtime.Serialization.OnDeserializing]"); contents.Append(indent).AppendLine("internal void OnDeserializing(System.Runtime.Serialization.StreamingContext context)"); contents.Append(indent).AppendLine("{"); contents.Append(indent).AppendLine(" // Initialize structure with default values to replicate C++ deserialization behavior"); contents.Append(indent).AppendLine(" this = Default;"); contents.Append(indent).AppendLine("}"); } } GenerateCSharpManagedTypeInternals(buildData, structureInfo, contents, indent); // Nested types foreach (var apiTypeInfo in structureInfo.Children) { GenerateCSharpType(buildData, contents, indent, apiTypeInfo); } // Struct end indent = indent.Substring(0, indent.Length - 4); contents.AppendLine(indent + "}"); // Namespace end if (!string.IsNullOrEmpty(structureInfo.Namespace)) { contents.AppendLine("}"); } } private static void GenerateCSharpEnum(BuildData buildData, StringBuilder contents, string indent, EnumInfo enumInfo) { contents.AppendLine(); // Namespace begin if (!string.IsNullOrEmpty(enumInfo.Namespace)) { contents.AppendFormat("namespace "); contents.AppendLine(enumInfo.Namespace); contents.AppendLine("{"); indent += " "; } // Enum docs GenerateCSharpComment(contents, indent, enumInfo.Comment); // Enum begin GenerateCSharpAttributes(buildData, contents, indent, enumInfo, true); contents.Append(indent).Append(GenerateCSharpAccessLevel(enumInfo.Access)); contents.Append("enum ").Append(enumInfo.Name); if (enumInfo.UnderlyingType != null) contents.Append(" : ").Append(GenerateCSharpNativeToManaged(buildData, enumInfo.UnderlyingType, enumInfo)); contents.AppendLine(); contents.Append(indent + "{"); indent += " "; // Entries foreach (var entryInfo in enumInfo.Entries) { contents.AppendLine(); GenerateCSharpComment(contents, indent, entryInfo.Comment); GenerateCSharpAttributes(buildData, contents, indent, enumInfo, entryInfo.Attributes, entryInfo.Comment, true, false); contents.Append(indent).Append(entryInfo.Name); if (!string.IsNullOrEmpty(entryInfo.Value)) contents.Append(" = ").Append(entryInfo.Value); contents.Append(','); contents.AppendLine(); } // Enum end indent = indent.Substring(0, indent.Length - 4); contents.AppendLine(indent + "}"); // Namespace end if (!string.IsNullOrEmpty(enumInfo.Namespace)) { contents.AppendLine("}"); } } private static void GenerateCSharpInterface(BuildData buildData, StringBuilder contents, string indent, InterfaceInfo interfaceInfo) { // Begin contents.AppendLine(); if (!string.IsNullOrEmpty(interfaceInfo.Namespace)) { contents.AppendFormat("namespace "); contents.AppendLine(interfaceInfo.Namespace); contents.AppendLine("{"); indent += " "; } GenerateCSharpComment(contents, indent, interfaceInfo.Comment); GenerateCSharpAttributes(buildData, contents, indent, interfaceInfo, true); contents.Append(indent).Append(GenerateCSharpAccessLevel(interfaceInfo.Access)); contents.Append("unsafe partial interface ").Append(interfaceInfo.Name); contents.AppendLine(); contents.Append(indent + "{"); indent += " "; // Functions foreach (var functionInfo in interfaceInfo.Functions) { if (functionInfo.IsStatic) throw new Exception($"Not supported {"static"} function {functionInfo.Name} inside interface {interfaceInfo.Name}."); if (functionInfo.NoProxy) throw new Exception($"Not supported {"NoProxy"} function {functionInfo.Name} inside interface {interfaceInfo.Name}."); if (!functionInfo.IsVirtual) throw new Exception($"Not supported {"non-virtual"} function {functionInfo.Name} inside interface {interfaceInfo.Name}."); if (functionInfo.Access != AccessLevel.Public) throw new Exception($"Not supported {"non-public"} function {functionInfo.Name} inside interface {interfaceInfo.Name}."); contents.AppendLine(); GenerateCSharpComment(contents, indent, functionInfo.Comment); GenerateCSharpAttributes(buildData, contents, indent, interfaceInfo, functionInfo, true); var returnValueType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType, interfaceInfo); contents.Append(indent).Append(returnValueType).Append(' ').Append(functionInfo.Name).Append('('); for (var i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; if (i != 0) contents.Append(", "); if (!string.IsNullOrEmpty(parameterInfo.Attributes)) contents.Append('[').Append(parameterInfo.Attributes).Append(']').Append(' '); var managedType = GenerateCSharpNativeToManaged(buildData, parameterInfo.Type, interfaceInfo); if (parameterInfo.IsOut) contents.Append("out "); else if (parameterInfo.IsRef) contents.Append("ref "); else if (parameterInfo.IsThis) contents.Append("this "); else if (parameterInfo.IsParams) contents.Append("params "); contents.Append(managedType); contents.Append(' '); contents.Append(parameterInfo.Name); var defaultValue = GenerateCSharpDefaultValueNativeToManaged(buildData, parameterInfo.DefaultValue, interfaceInfo, parameterInfo.Type, false, managedType); if (!string.IsNullOrEmpty(defaultValue)) contents.Append(" = ").Append(defaultValue); } contents.Append(");").AppendLine(); } GenerateCSharpManagedTypeInternals(buildData, interfaceInfo, contents, indent); // End indent = indent.Substring(0, indent.Length - 4); contents.AppendLine(indent + "}"); #if USE_NETCORE { string marshallerName = interfaceInfo.Name + "Marshaller"; 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($"public static class {marshallerName}"); contents.Append(indent).AppendLine("{"); contents.AppendLine("#pragma warning disable 1591"); contents.Append(indent).Append(" ").AppendLine($"internal static {interfaceInfo.Name} ConvertToManaged(IntPtr unmanaged) => ({interfaceInfo.Name})ManagedHandleMarshaller.ConvertToManaged(unmanaged);"); contents.Append(indent).Append(" ").AppendLine($"internal static IntPtr ConvertToUnmanaged({interfaceInfo.Name} managed) => ManagedHandleMarshaller.ConvertToUnmanaged(managed);"); contents.AppendLine("#pragma warning restore 1591"); contents.Append(indent).AppendLine("}"); } #endif if (!string.IsNullOrEmpty(interfaceInfo.Namespace)) contents.AppendLine("}"); } private static bool GenerateCSharpType(BuildData buildData, StringBuilder contents, string indent, object type) { if (type is ApiTypeInfo apiTypeInfo && apiTypeInfo.SkipGeneration) return false; try { if (type is ClassInfo classInfo) GenerateCSharpClass(buildData, contents, indent, classInfo); else if (type is StructureInfo structureInfo) GenerateCSharpStructure(buildData, contents, indent, structureInfo); else if (type is EnumInfo enumInfo) GenerateCSharpEnum(buildData, contents, indent, enumInfo); else if (type is InterfaceInfo interfaceInfo) GenerateCSharpInterface(buildData, contents, indent, interfaceInfo); else if (type is InjectCodeInfo injectCodeInfo && string.Equals(injectCodeInfo.Lang, "csharp", StringComparison.OrdinalIgnoreCase)) { // `using` directives needs to go above the generated code foreach (var code in injectCodeInfo.Code.Split(';')) { if (code.StartsWith("using")) CSharpUsedNamespaces.Add(code.Substring(6)); else if (code.Length > 0) contents.Append(code).AppendLine(";"); } } else return false; } catch { Log.Error($"Failed to generate C# bindings for {type}."); throw; } return true; } private static void GenerateCSharp(BuildData buildData, ModuleInfo moduleInfo, ref BindingsResult bindings) { var contents = GetStringBuilder(); buildData.Modules.TryGetValue(moduleInfo.Module, out var moduleBuildInfo); // Header contents.Append("#if "); foreach (var define in moduleBuildInfo.ScriptingAPI.Defines) contents.Append(define).Append(" && "); contents.Append("true").AppendLine(); contents.AppendLine("// This code was auto-generated. Do not modify it."); contents.AppendLine(); contents.AppendLine("#pragma warning disable 0612"); // Disable Obsolete warnings in generated code var headerPos = contents.Length; CSharpUsedNamespaces.Clear(); CSharpUsedNamespaces.Add(null); CSharpUsedNamespaces.Add(string.Empty); CSharpUsedNamespaces.Add("System"); CSharpUsedNamespaces.Add("System.ComponentModel"); CSharpUsedNamespaces.Add("System.Globalization"); CSharpUsedNamespaces.Add("System.Runtime.CompilerServices"); CSharpUsedNamespaces.Add("System.Runtime.InteropServices"); #if USE_NETCORE CSharpUsedNamespaces.Add("System.Runtime.InteropServices.Marshalling"); #endif CSharpUsedNamespaces.Add("FlaxEngine"); CSharpUsedNamespaces.Add("FlaxEngine.Interop"); // Process all API types from the file var useBindings = false; foreach (var child in moduleInfo.Children) { foreach (var apiTypeInfo in child.Children) { if (GenerateCSharpType(buildData, contents, string.Empty, apiTypeInfo)) useBindings = true; } } if (!useBindings) return; { var header = GetStringBuilder(); // Using declarations CSharpUsedNamespacesSorted.Clear(); CSharpUsedNamespacesSorted.AddRange(CSharpUsedNamespaces); CSharpUsedNamespacesSorted.Sort(); for (var i = 2; i < CSharpUsedNamespacesSorted.Count; i++) header.AppendLine($"using {CSharpUsedNamespacesSorted[i]};"); contents.Insert(headerPos, header.ToString()); PutStringBuilder(header); } // Save generated file contents.AppendLine(); contents.AppendLine("#endif"); Utilities.WriteFileIfChanged(bindings.GeneratedCSharpFilePath, contents.ToString()); PutStringBuilder(contents); } internal struct GuidInterop { public uint A; public uint B; public uint C; public uint D; } private static unsafe void GenerateCSharp(BuildData buildData, IGrouping binaryModule) { // Skip generating C# bindings code for native-only modules if (binaryModule.All(x => !x.BuildCSharp)) return; var contents = GetStringBuilder(); var binaryModuleName = binaryModule.Key; var project = Builder.GetModuleProject(binaryModule.First(), buildData); // Generate binary module ID GuidInterop idInterop; idInterop.A = Utilities.GetHashCode(binaryModuleName); idInterop.B = Utilities.GetHashCode(project.Name); idInterop.C = Utilities.GetHashCode(project.Company); idInterop.D = Utilities.GetHashCode(project.Copyright); var id = *(Guid*)&idInterop; // Generate C# binary module information var dstFilePath = Path.Combine(project.ProjectFolderPath, "Source", binaryModuleName + ".Gen.cs"); contents.AppendLine("// This code was auto-generated. Do not modify it."); contents.AppendLine(); contents.AppendLine("using System.Reflection;"); contents.AppendLine("using System.Runtime.InteropServices;"); #if USE_NETCORE contents.AppendLine("using System.Runtime.CompilerServices;"); #endif contents.AppendLine(); contents.AppendLine($"[assembly: AssemblyTitle(\"{binaryModuleName}\")]"); contents.AppendLine("[assembly: AssemblyDescription(\"\")]"); contents.AppendLine("[assembly: AssemblyConfiguration(\"\")]"); contents.AppendLine($"[assembly: AssemblyCompany(\"{project.Company}\")]"); contents.AppendLine("[assembly: AssemblyProduct(\"FlaxEngine\")]"); contents.AppendLine($"[assembly: AssemblyCopyright(\"{project.Copyright}\")]"); contents.AppendLine("[assembly: AssemblyTrademark(\"\")]"); contents.AppendLine("[assembly: AssemblyCulture(\"\")]"); contents.AppendLine("[assembly: ComVisible(false)]"); contents.AppendLine($"[assembly: Guid(\"{id:d}\")]"); contents.AppendLine($"[assembly: AssemblyVersion(\"{project.Version}\")]"); contents.AppendLine($"[assembly: AssemblyFileVersion(\"{project.Version}\")]"); #if USE_NETCORE contents.AppendLine("[assembly: DisableRuntimeMarshalling]"); #endif Utilities.WriteFileIfChanged(dstFilePath, contents.ToString()); PutStringBuilder(contents); } } }