// Copyright (c) Wojciech Figat. All rights reserved. //#define AUTO_DOC_TOOLTIPS //#define MARSHALLER_FULL_NAME 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 bool GenerateCSharpUseFixedBuffer(string managedType) { return managedType == "byte" || managedType == "char" || managedType == "short" || managedType == "ushort" || managedType == "int" || managedType == "uint" || managedType == "long" || managedType == "ulong" || managedType == "float" || managedType == "double"; } private static string GenerateCSharpDefaultValueNativeToManaged(BuildData buildData, string value, ApiTypeInfo caller, TypeInfo valueType = null, bool attribute = false, string managedType = null) { ApiTypeInfo apiType = null; if (string.IsNullOrEmpty(value)) { if (attribute && valueType != null && !valueType.IsArray) { //if (valueType.Type == "") //ScriptingObjectReference apiType = FindApiTypeInfo(buildData, valueType, caller); // Object reference if (apiType != null && apiType.IsScriptingObject) return "null"; } 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; } // Handle float{_} style type of default values if (valueType != null && value.StartsWith($"{valueType.Type}") && value.EndsWith("}")) { value = value.Replace($"{valueType.Type}", "").Replace("{", "").Replace("}", "").Trim(); if (string.IsNullOrEmpty(value)) { value = $"default({valueType.Type})"; return value; } } // Handle C++ bracket default values that are not arrays if (value.StartsWith("{") && value.EndsWith("}") && valueType != null && !valueType.IsArray && valueType.Type != "Array") { value = value.Replace("{", "").Replace("}", "").Trim(); if (string.IsNullOrEmpty(value)) { value = $"default({valueType.Type})"; return value; } } // Numbers if (float.TryParse(value, out _) || (value[value.Length - 1] == 'f' && float.TryParse(value.Substring(0, value.Length - 1), out _))) { value = value.Replace(".f", ".0f"); // 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('.'); 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", ".0").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; // Special case for non-strongly typed enums if (valueType != null && !value.Contains('.', StringComparison.Ordinal)) { apiType = FindApiTypeInfo(buildData, valueType, caller); if (apiType is EnumInfo) return $"{apiType.Name}.{value}"; } return value; } private static string GenerateCSharpNativeToManaged(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, bool marshalling = false) { string result; if (typeInfo == null || 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, marshalling); 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, marshalling); 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 { var arrayTypeInfo = typeInfo.GenericArgs[0]; if (marshalling) { // Convert array that uses different type for marshalling var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); if (arrayApiType != null && arrayApiType.MarshalAs != null) arrayTypeInfo = arrayApiType.MarshalAs; } return GenerateCSharpNativeToManaged(buildData, arrayTypeInfo, caller) + "[]"; } // Dictionary if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null) return string.Format("System.Collections.Generic.Dictionary<{0}, {1}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling), GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[1], caller, marshalling)); // HashSet if (typeInfo.Type == "HashSet" && typeInfo.GenericArgs != null) return string.Format("System.Collections.Generic.HashSet<{0}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling)); // 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, marshalling); if (!CSharpAdditionalCodeCache.TryGetValue(key, out var delegateName)) { delegateName = "Delegate" + CSharpAdditionalCodeCache.Count; var signature = $"public delegate {GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling)} {delegateName}("; for (int i = 1; i < typeInfo.GenericArgs.Count; i++) { if (i != 1) signature += ", "; signature += GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[i], caller, marshalling); 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 (typeInfo.GenericArgs != null) { typeName += '<'; foreach (var arg in typeInfo.GenericArgs) typeName += GenerateCSharpNativeToManaged(buildData, arg, caller, marshalling); typeName += '>'; } if (apiType != null) { if (marshalling && apiType.MarshalAs != null) return GenerateCSharpNativeToManaged(buildData, apiType.MarshalAs, caller); // 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, bool marshalling = false) { // Fixed-size array if (typeInfo.IsArray) return GenerateCSharpNativeToManaged(buildData, typeInfo, caller, marshalling); // 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.MarshalAs != null) return GenerateCSharpManagedToNativeType(buildData, apiType.MarshalAs, caller, marshalling); 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, marshalling); } 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})"; case "Array": case "Span": case "DataContainer": if (typeInfo.GenericArgs != null) { // Convert array that uses different type for marshalling var arrayTypeInfo = typeInfo.GenericArgs[0]; var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); if (arrayApiType != null && arrayApiType.MarshalAs != null) return $"{{0}}.ConvertArray(x => ({GenerateCSharpNativeToManaged(buildData, arrayApiType.MarshalAs, caller)})x)"; } return string.Empty; 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.Name); } // 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 { var apiType = FindApiTypeInfo(buildData, functionInfo.ReturnType, caller); if (apiType != null && apiType.MarshalAs != null) returnValueType = GenerateCSharpNativeToManaged(buildData, apiType.MarshalAs, caller, true); else returnValueType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType, caller, true); } #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 (returnValueType == "Version") returnMarshalType = "MarshalUsing(typeof(VersionMarshaller))"; else if (functionInfo.ReturnType.Type == "Variant") // object 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))"; } 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.IsArrayOrSpan || returnNativeType == "Array") { var elementApiType = functionInfo.ReturnType.GenericArgs?.Count > 0 ? FindApiTypeInfo(buildData, functionInfo.ReturnType.GenericArgs[0], caller) : null; string unmanagedType = ""; //if (functionInfo.ReturnType.GenericArgs != null && functionInfo.ReturnType.GenericArgs[0].IsPod(buildData, caller)) var genericType = functionInfo.ReturnType.GenericArgs?[0].Type; if (functionInfo.ReturnType.Type == "BytesContainer") genericType = "byte"; if (functionInfo.ReturnType.IsObjectRef || (elementApiType?.IsScriptingObject ?? false)) genericType = null; switch (genericType) { case "bool": unmanagedType = "U1"; break; case "byte": unmanagedType = "U1"; break; case "sbyte": unmanagedType = "I1"; break; case "char": unmanagedType = "I2"; break; case "short": case "int16": unmanagedType = "I2"; break; case "ushort": case "uint16": unmanagedType = "U2"; break; case "int": case "int32": unmanagedType = "I4"; break; case "uint": case "uint32": unmanagedType = "U4"; break; case "long": case "int64": unmanagedType = "I8"; break; case "ulong": case "uint64": unmanagedType = "U8"; break; case "float": unmanagedType = "R4"; break; case "double": unmanagedType = "R8"; break; case "Float2": case "Double2": case "Vector2": case "Float3": case "Double3": case "Vector3": case "Float4": case "Double4": case "Vector4": case "Color": case "Color32": case "Guid": unmanagedType = "Any"; // FIXME break; case null: case "": case "String": case "StringAnsi": case "StringView": unmanagedType = null; break; default: if (elementApiType != null && (elementApiType.IsClass || !elementApiType.IsPod)) unmanagedType = null; else unmanagedType = "Any"; //Log.Warning($"unknown type: '{parameterInfo.Type.GenericArgs[0].Type}'"); break; } if (!string.IsNullOrEmpty(unmanagedType)) { string arraySubType = ""; if (unmanagedType != "Any") arraySubType = $"ArraySubType = UnmanagedType.{unmanagedType}, "; returnMarshalType = $"MarshalAs(UnmanagedType.LPArray, {arraySubType}SizeParamIndex = {(!functionInfo.IsStatic ? 1 : 0) + functionInfo.Parameters.Count + (functionInfo.Glue.CustomParameters.FindIndex(x => x.Name == $"__returnCount"))})"; } else if (genericType == "String" || genericType == "StringAnsi" || genericType == "StringView") returnMarshalType = $"/*marsh2c*/MarshalUsing(typeof(FlaxEngine.Interop.BoxedArrayMarshaller), CountElementName = \"__returnCount\")"; else if (elementApiType?.IsValueType ?? true) returnMarshalType = $"/*marsh2a*/MarshalUsing(typeof(FlaxEngine.Interop.BoxedArrayMarshaller<,>), CountElementName = \"__returnCount\")"; else returnMarshalType = $"/*marsh2b*/MarshalUsing(typeof(FlaxEngine.Interop.NativeArrayMarshaller<,>), CountElementName = \"__returnCount\")"; } else if (functionInfo.ReturnType.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer" || functionInfo.ReturnType.Type == "BytesContainer" || returnNativeType == "Array") { //[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 8)] returnMarshalType = "/*marsh3*/MarshalUsing(typeof(FlaxEngine.Interop.NativeArrayMarshaller<,>), CountElementName = nameof(__returnCount))"; } else if (functionInfo.ReturnType.Type == "Dictionary") returnMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)"; else if (returnValueType == "byte[]") returnMarshalType = "/*marsh4*/MarshalUsing(typeof(FlaxEngine.Interop.NativeArrayMarshaller<,>), 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, true); #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 == "Version") parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.VersionMarshaller))"; 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.IsArrayOrSpan || nativeType == "Array") { var elementApiType = parameterInfo.Type.GenericArgs?.Count > 0 ? FindApiTypeInfo(buildData, parameterInfo.Type.GenericArgs[0], caller) : null; string unmanagedType = ""; var genericType = parameterInfo.Type.GenericArgs?[0].Type; if (parameterInfo.Type.Type == "BytesContainer") genericType = "byte"; if (parameterInfo.Type.IsObjectRef || (elementApiType?.IsScriptingObject ?? false)) genericType = null; switch (genericType) { case "bool": unmanagedType = "U1"; break; case "byte": unmanagedType = "U1"; break; case "sbyte": unmanagedType = "I1"; break; case "char": unmanagedType = "I2"; break; case "short": case "int16": unmanagedType = "I2"; break; case "ushort": case "uint16": unmanagedType = "U2"; break; case "int": case "int32": unmanagedType = "I4"; break; case "uint": case "uint32": unmanagedType = "U4"; break; case "long": case "int64": unmanagedType = "I8"; break; case "ulong": case "uint64": unmanagedType = "U8"; break; case "float": unmanagedType = "R4"; break; case "double": unmanagedType = "R8"; break; case "Float2": case "Double2": case "Vector2": case "Float3": case "Double3": case "Vector3": case "Float4": case "Double4": case "Vector4": case "Color": case "Color32": case "Guid": unmanagedType = "Any"; // FIXME break; case null: case "": case "String": case "StringAnsi": case "StringView": unmanagedType = null; break; case "AntiRollBar": unmanagedType = "Any"; // FIXME: This looks like a POD-type but isn't one? break; default: if (elementApiType != null && (elementApiType.IsClass || !elementApiType.IsPod)) unmanagedType = null; else if (elementApiType == null) unmanagedType = "Any"; else unmanagedType = "Any"; //Log.Warning($"unknown type: '{parameterInfo.Type.GenericArgs[0].Type}'"); break; } if (!string.IsNullOrEmpty(unmanagedType)) { string arraySubType = ""; if (unmanagedType != "Any") arraySubType = $"ArraySubType = UnmanagedType.{unmanagedType}, "; parameterMarshalType = $"MarshalAs(UnmanagedType.LPArray, {arraySubType}SizeParamIndex = {(!functionInfo.IsStatic ? 1 : 0) + functionInfo.Parameters.Count + (functionInfo.Glue.CustomParameters.FindIndex(x => x.Name == $"__{parameterInfo.Name}Count"))})"; } else if (genericType == "String" || genericType == "StringAnsi" || genericType == "StringView") parameterMarshalType = $"/*marsh5c*/MarshalUsing(typeof(FlaxEngine.Interop.NativeArrayMarshaller), CountElementName = \"__{parameterInfo.Name}Count\")"; else if (elementApiType.IsValueType) { var nativeName = parameterInfo.Type.GetFullNameNative(buildData, caller); var nativeInternalName = GenerateCppManagedWrapperName(elementApiType); var marshallerName = (!string.IsNullOrEmpty(elementApiType?.Namespace) ? $"{elementApiType?.Namespace}.Interop." : "") + $"{elementApiType.Name}Marshaller"; //parameterMarshalType = $"/*marsh5a pod:{elementApiType.IsPod}*/MarshalUsing(typeof(FlaxEngine.Interop.NativeArrayMarshaller<,>), CountElementName = \"__{parameterInfo.Name}Count\")"; if (/*nativeName != nativeInternalName && */elementApiType.MarshalAs != null) parameterMarshalType = $"/*marsh5a1 pod:{elementApiType.IsPod}*/MarshalUsing(typeof(FlaxEngine.Interop.NativeArrayMarshaller<,>), CountElementName = \"__{parameterInfo.Name}Count\")"; else parameterMarshalType = $"/*marsh5a2 pod:{elementApiType.IsPod}*/MarshalUsing(typeof(FlaxEngine.Interop.NativeArrayMarshaller<{elementApiType.Name},{marshallerName}.{elementApiType.Name}Internal>), CountElementName = \"__{parameterInfo.Name}Count\")"; //parameterMarshalType += $"] [MarshalUsing(typeof({marshallerName}), ElementIndirectionDepth = 1)"; // [MarshalUsing(typeof(ExampleMarshaller), ElementIndirectionDepth = 1)] } else parameterMarshalType = $"/*marsh5b*/MarshalUsing(typeof(FlaxEngine.Interop.BoxedArrayMarshaller<,>), CountElementName = \"__{parameterInfo.Name}Count\")"; if (!parameterInfo.IsOut && !parameterInfo.IsRef) parameterMarshalType += ", In"; // The usage of 'LibraryImportAttribute' does not follow recommendations. It is recommended to use explicit '[In]' and '[Out]' attributes on array parameters. //if (elementApiType?.MarshalAs != null) // parameterMarshalType += $"] [MarshalUsing(typeof({elementApiType.MarshalAs}), ElementIndirectionDepth = 1)"; } 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)"; else if (nativeType.EndsWith("[]")) { parameterMarshalType = $"/*marsh6*/MarshalUsing(typeof(FlaxEngine.Interop.NativeArrayMarshaller<,>))"; } if (!string.IsNullOrEmpty(parameterMarshalType)) contents.Append($"[{parameterMarshalType}] "); #endif if (parameterInfo.IsOut) contents.Append("out "); else if (parameterInfo.IsRef || UsePassByReference(buildData, parameterInfo.Type, caller)) { if (parameterInfo.IsConst || parameterInfo.Type.IsConst) contents.Append("in "); else 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, true); #if USE_NETCORE string parameterMarshalType = ""; if (parameterInfo.IsOut && parameterInfo.DefaultValue == "var __resultAsRef") { // TODO: make this code shared with MarshalUsing selection from the above if (parameterInfo.Type.IsArrayOrSpan) parameterMarshalType = $"/*marsh1*/MarshalUsing(typeof(FlaxEngine.Interop.NativeArrayMarshaller<,>), CountElementName = \"{parameterInfo.Name}Count\")"; else if (parameterInfo.Type.Type == "Dictionary") parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)"; else if (parameterInfo.Type.Type == "CultureInfo") parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.CultureInfoMarshaller))"; else if (parameterInfo.Type.Type == "Version") parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.VersionMarshaller))"; } 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)) { if (parameterInfo.IsConst || parameterInfo.Type.IsConst) contents.Append("in "); else contents.Append("/*xcvg1*/ 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)) { if (isSetter || parameterInfo.Type.IsConst) contents.Append("in "); else { //CanUpdate(BehaviorUpdateContext if (parameterInfo.Name == "context" && functionInfo.Name == "CanUpdate") separator = separator; contents.Append("/*xcvg2*/ 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)) { if (parameterInfo.IsConst ||parameterInfo.Type.IsConst) contents.Append("in "); else contents.Append("ref "); } // Pass value contents.Append(parameterInfo.DefaultValue); } else { // Convert value contents.Append(string.Format(convertFunc, parameterInfo.DefaultValue)); } } contents.Append(')'); if ((functionInfo.ReturnType.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer") && functionInfo.ReturnType.GenericArgs != null) { // Convert array that uses different type for marshalling var arrayTypeInfo = functionInfo.ReturnType.GenericArgs[0]; var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); if (arrayApiType != null && arrayApiType.MarshalAs != null) contents.Append($".ConvertArray(x => ({GenerateCSharpNativeToManaged(buildData, arrayTypeInfo, caller)})x)"); } 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(); } // Check if array has fixed allocation and add in MaxCount Collection attribute if a Collection attribute does not already exist. if (defaultValueType != null && (string.IsNullOrEmpty(attributes) || !attributes.Contains("Collection", StringComparison.Ordinal))) { // Array or Span or DataContainer #if USE_NETCORE if ((defaultValueType.Type == "Array" || defaultValueType.Type == "Span" || defaultValueType.Type == "DataContainer" || defaultValueType.Type == "MonoArray" || defaultValueType.Type == "MArray") && defaultValueType.GenericArgs != null) #else if ((defaultValueType.Type == "Array" || defaultValueType.Type == "Span" || defaultValueType.Type == "DataContainer") && defaultValueType.GenericArgs != null) #endif { if (defaultValueType.GenericArgs.Count > 1) { var allocationArg = defaultValueType.GenericArgs[1]; if (allocationArg.Type.Contains("FixedAllocation", StringComparison.Ordinal) && allocationArg.GenericArgs.Count > 0) { if (int.TryParse(allocationArg.GenericArgs[0].ToString(), out int allocation)) { contents.Append(indent).Append($"[Collection(MaxCount={allocation.ToString()})]").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, null, 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.DeprecatedMessage, 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 "; if (access == AccessLevel.Internal) return "internal "; return "public "; } private static void GenerateCSharpClass(BuildData buildData, StringBuilder contents, string indent, ClassInfo classInfo) { var useUnmanaged = classInfo.IsStatic || classInfo.IsScriptingObject; contents.AppendLine(); // Namespace begin string interopNamespace = ""; if (!string.IsNullOrEmpty(classInfo.Namespace)) { interopNamespace = $"{classInfo.Namespace}.Interop"; contents.AppendLine($"namespace {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 = ""; string marshallerFullName = ""; if (!classInfo.IsStatic) { marshallerName = classInfo.Name + "Marshaller"; #if MARSHALLER_FULL_NAME marshallerFullName = !string.IsNullOrEmpty(interopNamespace) ? $"{interopNamespace}.{marshallerName}" : marshallerName; #else if (!string.IsNullOrEmpty(interopNamespace)) CSharpUsedNamespaces.Add(interopNamespace); marshallerFullName = marshallerName; #endif contents.Append(indent).AppendLine($"[NativeMarshalling(typeof({marshallerFullName}))]"); } #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(); if (buildData.Configuration != TargetConfiguration.Release) contents.Append(indent).Append("[System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)]").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) { if (parameterInfo.IsConst || parameterInfo.Type.IsConst) contents.Append("in "); else contents.Append("/*faffaf1*/ 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("/*faffaf2*/ 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)) { if (!string.IsNullOrEmpty(interopNamespace)) { contents.AppendLine("}"); contents.AppendLine($"namespace {interopNamespace}"); contents.AppendLine("{"); } contents.AppendLine(); contents.AppendLine(string.Join(Environment.NewLine + indent, (indent + $$""" /// /// Marshaller for type . /// #if FLAX_EDITOR [HideInEditor] #endif [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 #pragma warning disable 618 #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) => managed != null ? ManagedHandle.ToIntPtr(managed, GCHandleType.Weak) : IntPtr.Zero; public static void Free(IntPtr 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) => managed != null ? ManagedHandle.ToIntPtr(managed/*, GCHandleType.Weak*/) : IntPtr.Zero; public static void Free(IntPtr unmanaged) => ManagedHandleMarshaller.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 618 #pragma warning restore 1591 } """).Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries))); } #endif // Namespace end if (!string.IsNullOrEmpty(classInfo.Namespace)) contents.AppendLine("}"); } private static void GenerateCSharpStructure(BuildData buildData, StringBuilder contents, string indent, StructureInfo structureInfo) { contents.AppendLine(); #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"; #if MARSHALLER_FULL_NAME string marshallerFullName = !string.IsNullOrEmpty(interopNamespace) ? $"{interopNamespace}.{marshallerName}" : marshallerName; #else if (!string.IsNullOrEmpty(interopNamespace)) CSharpUsedNamespaces.Add(interopNamespace); string marshallerFullName = marshallerName; #endif structNativeMarshaling = $"[NativeMarshalling(typeof({marshallerFullName}))]"; 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) 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) 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(); structContents.AppendLine(); foreach (var fieldInfo in structureInfo.Fields) { if (fieldInfo.IsStatic || fieldInfo.IsConstexpr) continue; var marshalType = fieldInfo.Type; var apiType = FindApiTypeInfo(buildData, marshalType, structureInfo); if (apiType != null && apiType.MarshalAs != null) marshalType = apiType.MarshalAs; bool internalType = apiType is StructureInfo fieldStructureInfo && UseCustomMarshalling(buildData, fieldStructureInfo, structureInfo); string type, originalType; if (marshalType.IsArray && (fieldInfo.NoArray || structureInfo.IsPod)) { // Fixed-size array that needs to be inlined into structure instead of passing it as managed array marshalType.IsArray = false; originalType = type = GenerateCSharpNativeToManaged(buildData, marshalType, structureInfo); marshalType.IsArray = true; } else originalType = type = GenerateCSharpNativeToManaged(buildData, marshalType, structureInfo); structContents.Append(structIndent).Append("public "); string internalTypeMarshaller = ""; if (marshalType.IsArray && (fieldInfo.NoArray || structureInfo.IsPod)) { #if USE_NETCORE if (GenerateCSharpUseFixedBuffer(originalType)) { // Use fixed statement with primitive types of buffers structContents.Append($"fixed {originalType} {fieldInfo.Name}0[{marshalType.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}) * {marshalType.ArraySize}ul);"); toNativeContent.AppendLine($"FlaxEngine.Utils.MemoryCopy(new IntPtr(unmanaged.{fieldInfo.Name}0), new IntPtr(managed.{fieldInfo.Name}0), sizeof({originalType}) * {marshalType.ArraySize}ul);"); } else #endif { // Padding in structs for fixed-size array structContents.Append(type).Append(' ').Append(fieldInfo.Name).Append("0;").AppendLine(); for (int i = 1; i < marshalType.ArraySize; i++) { 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 if (fieldInfo.Access == AccessLevel.Public || fieldInfo.Access == AccessLevel.Internal) { for (int i = 0; i < marshalType.ArraySize; i++) { toManagedContent.AppendLine($"managed.{fieldInfo.Name}{i} = unmanaged.{fieldInfo.Name}{i};"); toNativeContent.AppendLine($"unmanaged.{fieldInfo.Name}{i} = managed.{fieldInfo.Name}{i};"); } } else { throw new NotImplementedException("TODO: generate utility method to copy private/protected array data items"); } } continue; } else { if (marshalType.IsObjectRef || marshalType.Type == "Dictionary") type = "IntPtr"; else if (marshalType.IsPtr && !originalType.EndsWith("*")) type = "IntPtr"; else if (marshalType.IsArrayOrSpan) { apiType = FindApiTypeInfo(buildData, marshalType.GenericArgs[0], structureInfo); internalType = apiType is StructureInfo elementStructureInfo && UseCustomMarshalling(buildData, elementStructureInfo, structureInfo); string originalElementType = originalType.Substring(0, originalType.Length - 2); if (internalType) { 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"; type = $"NativeArray<{internalElementType}>"; } else if (marshalType.GenericArgs[0].IsObjectRef) type = "NativeArray"; else type = $"NativeArray<{originalElementType}>"; } else if (marshalType.Type == "Version") type = "IntPtr"; else if (type == "string") type = "IntPtr"; else if (type == "bool") type = "byte"; else if (marshalType.Type == "Variant") type = "IntPtr"; else if (internalType) { internalTypeMarshaller = type + "Marshaller"; type = $"{internalTypeMarshaller}.{type}Internal"; } //else if (type == "Guid") // type = "GuidNative"; structContents.Append($"{type} {fieldInfo.Name};").Append(type != originalType ? $" // {originalType}" : "").AppendLine(); } // Generate struct constructor/getter and deconstructor/setter function var managedField = $"managed.{fieldInfo.Name}"; var unmanagedField = $"unmanaged.{fieldInfo.Name}"; toManagedContent.Append($"{managedField} = "); toNativeContent.Append($"{unmanagedField} = "); if (marshalType.IsObjectRef) { var managedType = GenerateCSharpNativeToManaged(buildData, marshalType.GenericArgs[0], structureInfo); toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{managedType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}/*, GCHandleType.Weak*/) : IntPtr.Zero; // plaa 1"); 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 (marshalType.Type == "ScriptingObject") { toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}/*, GCHandleType.Weak*/) : IntPtr.Zero; // plaa 2"); 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 (marshalType.IsPtr && originalType != "IntPtr" && !originalType.EndsWith("*")) { toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}/*, GCHandleType.Weak*/) : IntPtr.Zero; // plaa 3"); 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 (marshalType.Type == "Dictionary") { toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); toNativeContent.AppendLine($"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 (marshalType.IsArrayOrSpan) { string originalElementType = originalType.Substring(0, originalType.Length - 2); if (internalType) { // Marshal blittable array elements back to original non-blittable elements 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($"{unmanagedField} != IntPtr.Zero ? NativeInterop.ConvertArray({unmanagedField}.AsSpan(), {originalElementTypeMarshaller}.ToManaged) : null;"); toNativeContent.AppendLine($"{managedField}?.Length > 0 ? NativeInterop.ConvertArrayNative({managedField}.AsSpan(), {originalElementTypeMarshaller}.ToNative) : new NativeArray<{internalElementType}>();"); freeContents.AppendLine($"if ({unmanagedField} != IntPtr.Zero) {{ foreach (var value in {unmanagedField}.AsReadOnlySpan()) {{ {originalElementTypeMarshaller}.Free(value); }} {unmanagedField}.Free(); }}"); freeContents2.AppendLine($"if ({unmanagedField} != IntPtr.Zero) {{ foreach (var value in {unmanagedField}.AsReadOnlySpan()) {{ {originalElementTypeMarshaller}.NativeToManaged.Free(value); }} {unmanagedField}.Free(); }}"); } else if (marshalType.GenericArgs[0].IsObjectRef) { // Array elements passed as GCHandles toManagedContent.AppendLine($"{unmanagedField} != IntPtr.Zero ? NativeInterop.GCHandleArrayToManagedArray<{originalElementType}>({unmanagedField}) : null;"); toNativeContent.AppendLine($"{managedField}?.Length > 0 ? NativeInterop.ManagedArrayToGCHandleWrappedArray(managed.{fieldInfo.Name}) : new NativeArray();"); freeContents.AppendLine($"if ({unmanagedField} != IntPtr.Zero) {{ foreach (var ptr in {unmanagedField}.AsReadOnlySpan()) {{ if (ptr != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(ptr).Free(); }} }} {unmanagedField}.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.AppendLine($"{unmanagedField} != IntPtr.Zero ? {unmanagedField}.AsReadOnlySpan().ToArray() : null;"); toNativeContent.AppendLine($"{managedField}?.Length > 0 ? new NativeArray<{originalElementType}>({managedField}.AsSpan()) : new NativeArray<{originalElementType}>();"); freeContents.AppendLine($"if ({unmanagedField} != IntPtr.Zero) {{ {unmanagedField}.Free(); }}"); freeContents2.AppendLine($"if ({unmanagedField} != IntPtr.Zero) {{ {unmanagedField}.Free(); }}"); } } else if (marshalType.Type == "Version") { toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); toNativeContent.AppendLine($"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.AppendLine($"ManagedString.ToManaged(unmanaged.{fieldInfo.Name});"); toNativeContent.AppendLine($"ManagedString.ToNative(managed.{fieldInfo.Name});"); freeContents.AppendLine($"ManagedString.Free(unmanaged.{fieldInfo.Name});"); freeContents2.AppendLine($"ManagedString.Free(unmanaged.{fieldInfo.Name});"); } else if (originalType == "bool") { toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != 0;"); toNativeContent.AppendLine($"managed.{fieldInfo.Name} ? (byte)1 : (byte)0;"); } else if (marshalType.Type == "Variant") { // Variant passed as boxed object handle toManagedContent.AppendLine($"ManagedHandleMarshaller.NativeToManaged.ConvertToManaged(unmanaged.{fieldInfo.Name});"); toNativeContent.AppendLine($"ManagedHandleMarshaller.NativeToManaged.ConvertToUnmanaged(managed.{fieldInfo.Name});"); } else if (internalType) { toManagedContent.AppendLine($"{internalTypeMarshaller}.ToManaged(unmanaged.{fieldInfo.Name});"); toNativeContent.AppendLine($"{internalTypeMarshaller}.ToNative(managed.{fieldInfo.Name});"); freeContents.AppendLine($"{internalTypeMarshaller}.Free(unmanaged.{fieldInfo.Name});"); freeContents2.AppendLine($"{internalTypeMarshaller}.NativeToManaged.Free(unmanaged.{fieldInfo.Name});"); } /*else if (originalType == "Guid") { toManagedContent.Append("(Guid)unmanaged.").Append(fieldInfo.Name); toNativeContent.Append("(GuidNative)managed.").Append(fieldInfo.Name); }*/ else { toManagedContent.Append("unmanaged.").Append(fieldInfo.Name).AppendLine(";"); toNativeContent.Append("managed.").Append(fieldInfo.Name).AppendLine(";"); } } // Native struct end structIndent = structIndent.Substring(0, structIndent.Length - 4); structContents.AppendLine(structIndent + "}").AppendLine(); toNativeContent.Append("return unmanaged;"); toManagedContent.Append("return managed;"); } string hideInEditorAttribute = (buildData.Target != null & buildData.Target.IsEditor) ? "[HideInEditor]" : ""; string marshalManagedType = structureInfo.Name; string marshalNativeType = $"{structureInfo.Name}Internal"; contents.AppendLine(string.Join(Environment.NewLine + indent, (indent + $$""" /// /// Marshaller for type . /// {{hideInEditorAttribute}} [CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.ManagedToUnmanagedIn, typeof({{marshallerFullName}}.ManagedToNative))] [CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.UnmanagedToManagedOut, typeof({{marshallerFullName}}.ManagedToNative))] [CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.ElementIn, typeof({{marshallerFullName}}.ManagedToNative))] [CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.ManagedToUnmanagedOut, typeof({{marshallerFullName}}.NativeToManaged))] [CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.UnmanagedToManagedIn, typeof({{marshallerFullName}}.NativeToManaged))] [CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.ElementOut, typeof({{marshallerFullName}}.NativeToManaged))] [CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.ManagedToUnmanagedRef, typeof({{marshallerFullName}}.Bidirectional))] [CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.UnmanagedToManagedRef, typeof({{marshallerFullName}}.Bidirectional))] [CustomMarshaller(typeof({{marshalManagedType}}), MarshalMode.ElementRef, typeof({{marshallerFullName}}))] {{GenerateCSharpAccessLevel(structureInfo.Access)}}static unsafe class {{marshallerName}} { #pragma warning disable 1591 #pragma warning disable 618 {{structContents.Replace("\n", Environment.NewLine + " ").ToString().TrimEnd()}} {{hideInEditorAttribute}} public static class NativeToManaged { public static {{marshalManagedType}} ConvertToManaged({{marshalNativeType}} unmanaged) => {{marshallerFullName}}.ToManaged(unmanaged); public static {{marshalNativeType}} ConvertToUnmanaged({{marshalManagedType}} managed) => {{marshallerFullName}}.ToNative(managed); public static void Free({{marshalNativeType}} unmanaged) { {{freeContents2.Replace("\n", Environment.NewLine + " ").ToString().TrimEnd()}} } } {{hideInEditorAttribute}} public static class ManagedToNative { public static {{marshalManagedType}} ConvertToManaged({{marshalNativeType}} unmanaged) => {{marshallerFullName}}.ToManaged(unmanaged); public static {{marshalNativeType}} ConvertToUnmanaged({{marshalManagedType}} managed) => {{marshallerFullName}}.ToNative(managed); public static void Free({{marshalNativeType}} unmanaged) => {{marshallerFullName}}.Free(unmanaged); } {{hideInEditorAttribute}} public ref struct Bidirectional { {{marshalManagedType}} managed; {{marshalNativeType}} unmanaged; public void FromManaged({{marshalManagedType}} managed) => this.managed = managed; public {{marshalNativeType}} ToUnmanaged() { unmanaged = {{marshallerFullName}}.ToNative(managed); return unmanaged; } public void FromUnmanaged({{marshalNativeType}} unmanaged) { if (!unmanaged.Equals(this.unmanaged)) {{marshallerName}}.Free(this.unmanaged); // Release temporary handles before replacing them with permanent handles this.unmanaged = unmanaged; } public {{marshalManagedType}} ToManaged() { managed = {{marshallerFullName}}.ToManaged(unmanaged); return managed; } public void Free() { NativeToManaged.Free(unmanaged); } } internal static {{marshalManagedType}} ConvertToManaged({{marshalNativeType}} unmanaged) => ToManaged(unmanaged); internal static {{marshalNativeType}} ConvertToUnmanaged({{marshalManagedType}} managed) => ToNative(managed); internal static void Free({{marshalNativeType}} unmanaged) { {{freeContents.Replace("\n", Environment.NewLine + " ").ToString().TrimEnd()}} } internal static {{marshalManagedType}} ToManaged({{marshalNativeType}} unmanaged) { {{toManagedContent.Replace("\n", Environment.NewLine + " ").ToString().TrimEnd()}} } internal static {{marshalNativeType}} ToNative({{marshalManagedType}} managed) { {{toNativeContent.Replace("\n", Environment.NewLine + " ").ToString().TrimEnd()}} } #pragma warning restore 618 #pragma warning restore 1591 } """).Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries))); PutStringBuilder(toManagedContent); PutStringBuilder(toNativeContent); 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); // 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 if (GenerateCSharpUseFixedBuffer(managedType)) { // Use fixed statement with primitive types of buffers if (managedType == "char") managedType = "short"; // char's are not blittable, store as short instead contents.Append($"fixed {managedType} {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}];").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(); 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.AppendLine($"namespace {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); string managedType = string.Empty; if (enumInfo.UnderlyingType != null) { managedType = GenerateCSharpNativeToManaged(buildData, enumInfo.UnderlyingType, enumInfo); contents.Append(" : ").Append(managedType); } contents.AppendLine(); contents.Append(indent + "{"); indent += " "; // Entries bool usedMax = false; 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)) { if (usedMax) usedMax = false; string value; if (enumInfo.UnderlyingType != null) { value = GenerateCSharpDefaultValueNativeToManaged(buildData, entryInfo.Value, enumInfo, enumInfo.UnderlyingType, false, managedType); if (value.Contains($"{managedType}.MaxValue", StringComparison.Ordinal)) usedMax = true; } else { value = entryInfo.Value; } contents.Append(" = ").Append(value.Trim()); } // Handle case of next value after Max value being zero if a value is not defined. else if (string.IsNullOrEmpty(entryInfo.Value) && usedMax) { contents.Append(" = ").Append('0'); usedMax = false; } 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(); string interopNamespace = ""; if (!string.IsNullOrEmpty(interfaceInfo.Namespace)) { interopNamespace = $"{interfaceInfo.Namespace}.Interop"; contents.AppendLine($"namespace {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("/*faffaf3*/ 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 { 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($"public static class {marshallerName}"); contents.Append(indent).AppendLine("{"); contents.AppendLine("#pragma warning disable 1591"); contents.AppendLine("#pragma warning disable 618"); 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 618"); 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); } } }