Files
FlaxEngine/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs

2435 lines
131 KiB
C#

// Copyright (c) 2012-2024 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<string> CSharpUsedNamespaces = new HashSet<string>();
private static readonly List<string> CSharpUsedNamespacesSorted = new List<string>();
private static readonly List<string> CSharpAdditionalCode = new List<string>();
private static readonly Dictionary<string, string> CSharpAdditionalCodeCache = new Dictionary<string, string>();
#if USE_NETCORE
private static readonly TypeInfo CSharpEventBindReturn = new TypeInfo("void");
private static readonly List<FunctionInfo.ParameterInfo> CSharpEventBindParams = new List<FunctionInfo.ParameterInfo>() { new FunctionInfo.ParameterInfo() { Name = "bind", Type = new TypeInfo("bool") } };
#endif
public static event Action<BuildData, ApiTypeInfo, StringBuilder, string> GenerateCSharpTypeInternals;
internal static readonly Dictionary<string, string> CSharpNativeToManagedBasicTypes = new Dictionary<string, string>()
{
// 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<string, string> CSharpNativeToManagedDefault = new Dictionary<string, string>()
{
// 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<string>();
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<int>) 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("/// <summary>Function delegate.</summary>");
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 (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.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer" || functionInfo.ReturnType.Type == "BytesContainer" || returnNativeType == "Array")
returnMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = nameof(__returnCount))";
else if (functionInfo.ReturnType.Type == "Dictionary")
returnMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)";
else if (returnValueType == "byte[]")
returnMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = \"__returnCount\")";
else if (returnValueType == "bool[]")
{
// Boolean arrays does not support custom marshalling for some unknown reason
returnMarshalType = $"MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = {functionInfo.Parameters.Count + (functionInfo.Glue.CustomParameters?.Count ?? 0)})";
}
#endif
#if !USE_NETCORE
contents.AppendLine().Append(indent).Append("[MethodImpl(MethodImplOptions.InternalCall)]");
contents.AppendLine().Append(indent).Append("internal static partial ");
#else
if (string.IsNullOrEmpty(functionInfo.Glue.LibraryEntryPoint))
throw new Exception($"Function {caller.FullNameNative}::{functionInfo.Name} has missing entry point for library import.");
contents.AppendLine().Append(indent).Append($"[LibraryImport(\"{caller.ParentModule.Module.BinaryModuleName}\", EntryPoint = \"{functionInfo.Glue.LibraryEntryPoint}\", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.Interop.StringMarshaller))]");
if (!string.IsNullOrEmpty(returnMarshalType))
contents.AppendLine().Append(indent).Append($"[return: {returnMarshalType}]");
contents.AppendLine().Append(indent).Append("internal static partial ");
#endif
contents.Append(returnValueType).Append(" Internal_").Append(functionInfo.UniqueName).Append('(');
var separator = false;
if (!functionInfo.IsStatic)
{
contents.Append("IntPtr __obj");
separator = true;
}
foreach (var parameterInfo in functionInfo.Parameters)
{
if (separator)
contents.Append(", ");
separator = true;
var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller, 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 == "Variant") // object
parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.ManagedHandleMarshaller))";
else if (parameterInfo.Type.Type == "MonoArray" || parameterInfo.Type.Type == "MArray")
parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemArrayMarshaller))";
else if (nativeType == "object[]")
parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemObjectArrayMarshaller))";
else if (parameterInfo.Type.Type == "Array" && parameterInfo.Type.GenericArgs.Count > 0 && parameterInfo.Type.GenericArgs[0].Type == "bool")
parameterMarshalType = $"MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = {(!functionInfo.IsStatic ? 1 : 0) + functionInfo.Parameters.Count + (functionInfo.Glue.CustomParameters.FindIndex(x => x.Name == $"__{parameterInfo.Name}Count"))})";
else if (parameterInfo.Type.Type == "Array" || parameterInfo.Type.Type == "Span" || parameterInfo.Type.Type == "DataContainer" || parameterInfo.Type.Type == "BytesContainer" || nativeType == "Array")
{
parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = \"__{parameterInfo.Name}Count\")";
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.
}
else if (parameterInfo.Type.Type == "Dictionary")
parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)";
else if (nativeType == "bool")
parameterMarshalType = "MarshalAs(UnmanagedType.U1)";
else if (nativeType == "char")
parameterMarshalType = "MarshalAs(UnmanagedType.I2)";
if (!string.IsNullOrEmpty(parameterMarshalType))
contents.Append($"[{parameterMarshalType}] ");
#endif
if (parameterInfo.IsOut)
contents.Append("out ");
else if (parameterInfo.IsRef || UsePassByReference(buildData, parameterInfo.Type, caller))
contents.Append("ref ");
// Out parameters that need additional converting will be converted at the native side (eg. object reference)
if (parameterInfo.IsOut && !string.IsNullOrEmpty(GenerateCSharpManagedToNativeConverter(buildData, parameterInfo.Type, caller)))
nativeType = parameterInfo.Type.Type;
contents.Append(nativeType);
contents.Append(' ');
contents.Append(parameterInfo.Name);
}
var customParametersCount = functionInfo.Glue.CustomParameters?.Count ?? 0;
for (var i = 0; i < customParametersCount; i++)
{
var parameterInfo = functionInfo.Glue.CustomParameters[i];
if (separator)
contents.Append(", ");
separator = true;
var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller, true);
#if USE_NETCORE
string parameterMarshalType = "";
if (parameterInfo.IsOut && parameterInfo.DefaultValue == "var __resultAsRef")
{
if (parameterInfo.Type.Type == "Array" || parameterInfo.Type.Type == "Span" || parameterInfo.Type.Type == "DataContainer" || parameterInfo.Type.Type == "BytesContainer")
parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = \"{parameterInfo.Name}Count\")";
else if (parameterInfo.Type.Type == "Dictionary")
parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)";
}
if (nativeType == "System.Type")
parameterMarshalType = "MarshalUsing(typeof(FlaxEngine.Interop.SystemTypeMarshaller))";
if (!string.IsNullOrEmpty(parameterMarshalType))
contents.Append($"[{parameterMarshalType}] ");
#endif
if (parameterInfo.IsOut)
contents.Append("out ");
else if (parameterInfo.IsRef || UsePassByReference(buildData, parameterInfo.Type, caller))
contents.Append("ref ");
contents.Append(nativeType);
contents.Append(' ');
contents.Append(parameterInfo.Name);
}
contents.Append(')').Append(';').AppendLine();
}
private static void GenerateCSharpWrapperFunctionCall(BuildData buildData, StringBuilder contents, ApiTypeInfo caller, FunctionInfo functionInfo, bool isSetter = false)
{
#if USE_NETCORE
for (var i = 0; i < functionInfo.Parameters.Count; i++)
{
var parameterInfo = functionInfo.Parameters[i];
if (parameterInfo.Type.IsArray || parameterInfo.Type.Type == "Array" || parameterInfo.Type.Type == "Span" || parameterInfo.Type.Type == "BytesContainer" || parameterInfo.Type.Type == "DataContainer" || parameterInfo.Type.Type == "BitArray")
{
if (!parameterInfo.IsOut)
contents.Append($"var __{parameterInfo.Name}Count = {(isSetter ? "value" : parameterInfo.Name)}?.Length ?? 0; ");
}
}
#endif
if (functionInfo.Glue.UseReferenceForResult)
{
}
else if (!functionInfo.ReturnType.IsVoid)
{
contents.Append("return ");
}
contents.Append("Internal_").Append(functionInfo.UniqueName).Append('(');
// Pass parameters
var separator = false;
if (!functionInfo.IsStatic)
{
contents.Append("__unmanagedPtr");
separator = true;
}
for (var i = 0; i < functionInfo.Parameters.Count; i++)
{
var parameterInfo = functionInfo.Parameters[i];
if (separator)
contents.Append(", ");
separator = true;
if (parameterInfo.IsOut)
contents.Append("out ");
else if (parameterInfo.IsRef || UsePassByReference(buildData, parameterInfo.Type, caller))
contents.Append("ref ");
var convertFunc = GenerateCSharpManagedToNativeConverter(buildData, parameterInfo.Type, caller);
var paramName = isSetter ? "value" : parameterInfo.Name;
if (string.IsNullOrWhiteSpace(convertFunc) || parameterInfo.IsOut)
{
// Pass value
contents.Append(paramName);
}
else
{
if (parameterInfo.IsRef)
throw new Exception($"Cannot use Ref meta on parameter {parameterInfo} in function {functionInfo.Name} in {caller}. Use API_PARAM(Out) if you want to pass it as a result reference.");
// Convert value
contents.Append(string.Format(convertFunc, paramName));
}
}
// Pass additional parameters
var customParametersCount = functionInfo.Glue.CustomParameters?.Count ?? 0;
for (var i = 0; i < customParametersCount; i++)
{
var parameterInfo = functionInfo.Glue.CustomParameters[i];
if (separator)
contents.Append(", ");
separator = true;
var convertFunc = GenerateCSharpManagedToNativeConverter(buildData, parameterInfo.Type, caller);
if (string.IsNullOrWhiteSpace(convertFunc) || parameterInfo.IsOut)
{
if (parameterInfo.IsOut)
contents.Append("out ");
else if (parameterInfo.IsRef || UsePassByReference(buildData, parameterInfo.Type, caller))
contents.Append("ref ");
// Pass value
contents.Append(parameterInfo.DefaultValue);
}
else
{
// Convert value
contents.Append(string.Format(convertFunc, parameterInfo.DefaultValue));
}
}
contents.Append(')');
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("/// <returns>") || c.Contains("<param name=")))
continue;
contents.Append(indent).Append(c.Replace("::", ".")).AppendLine();
}
}
private static void GenerateCSharpAttributes(BuildData buildData, StringBuilder contents, string indent, ApiTypeInfo apiTypeInfo, string attributes = null, string[] comment = null, bool canUseTooltip = false, bool useUnmanaged = false, string defaultValue = null, string deprecatedMessage = null, TypeInfo defaultValueType = null)
{
#if AUTO_DOC_TOOLTIPS
var writeTooltip = true;
#endif
var writeDefaultValue = true;
if (!string.IsNullOrEmpty(attributes))
{
// Write attributes
contents.Append(indent).Append('[').Append(attributes).Append(']').AppendLine();
#if AUTO_DOC_TOOLTIPS
writeTooltip = !attributes.Contains("Tooltip(") && !attributes.Contains("HideInEditor");
#endif
writeDefaultValue = !attributes.Contains("DefaultValue(");
}
if (useUnmanaged)
{
// Write attribute for C++ calling code
contents.Append(indent).AppendLine("[Unmanaged]");
// Skip boilerplate code when using debugger
//contents.Append(indent).AppendLine("[System.Diagnostics.DebuggerStepThrough]");
}
if (deprecatedMessage != null || apiTypeInfo.IsDeprecated)
{
// Deprecated type
if (!string.IsNullOrEmpty(apiTypeInfo.DeprecatedMessage))
contents.Append(indent).AppendLine($"[Obsolete(\"{apiTypeInfo.DeprecatedMessage}\")]");
else if (!string.IsNullOrEmpty(deprecatedMessage))
contents.Append(indent).AppendLine($"[Obsolete(\"{deprecatedMessage}\")]");
else
contents.Append(indent).AppendLine("[Obsolete]");
}
#if AUTO_DOC_TOOLTIPS
if (canUseTooltip &&
writeTooltip &&
buildData.Configuration != TargetConfiguration.Release &&
comment != null &&
comment.Length >= 3 &&
comment[0] == "/// <summary>")
{
// Write documentation comment as tooltip
string tooltip = null;
for (int i = 1; i < comment.Length && comment[i] != "/// </summary>"; 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("/// <summary>");
contents.Append(indent).Append("/// Initializes a new instance of the <see cref=\"").Append(classInfo.Name).AppendLine("\"/>.");
contents.Append(indent).AppendLine("/// </summary>");
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($"/// <summary>The delegate for event {eventInfo.Name}.</summary>").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)
contents.Append("ref ");
else if (parameterInfo.IsThis)
contents.Append("this ");
else if (parameterInfo.IsParams)
contents.Append("params ");
contents.Append(managedType);
contents.Append(' ');
contents.Append(parameterInfo.Name);
var defaultValue = GenerateCSharpDefaultValueNativeToManaged(buildData, parameterInfo.DefaultValue, classInfo, parameterInfo.Type, false, managedType);
if (!string.IsNullOrEmpty(defaultValue))
contents.Append(" = ").Append(defaultValue);
}
contents.Append(')').AppendLine().AppendLine(indent + "{");
indent += " ";
contents.Append(indent);
GenerateCSharpWrapperFunctionCall(buildData, contents, classInfo, functionInfo);
indent = indent.Substring(0, indent.Length - 4);
contents.AppendLine();
contents.AppendLine(indent + "}");
}
GenerateCSharpWrapperFunction(buildData, contents, indent, classInfo, functionInfo);
}
// Interface implementation
if (hasInterface)
{
foreach (var interfaceInfo in classInfo.Interfaces)
{
if (interfaceInfo.Access != AccessLevel.Public)
continue;
foreach (var functionInfo in interfaceInfo.Functions)
{
if (!classInfo.IsScriptingObject)
throw new Exception($"Class {classInfo.Name} cannot implement interface {interfaceInfo.Name} because it requires ScriptingObject as a base class.");
contents.AppendLine();
if (functionInfo.Comment != null && functionInfo.Comment.Length != 0)
contents.Append(indent).AppendLine("/// <inheritdoc />");
GenerateCSharpAttributes(buildData, contents, indent, classInfo, functionInfo.Attributes, null, false, useUnmanaged);
contents.Append(indent).Append(GenerateCSharpAccessLevel(functionInfo.Access));
if (functionInfo.IsVirtual && !classInfo.IsSealed)
contents.Append("virtual ");
var returnValueType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType, classInfo);
contents.Append(returnValueType).Append(' ').Append(functionInfo.Name).Append('(');
for (var i = 0; i < functionInfo.Parameters.Count; i++)
{
var parameterInfo = functionInfo.Parameters[i];
if (i != 0)
contents.Append(", ");
if (!string.IsNullOrEmpty(parameterInfo.Attributes))
contents.Append('[').Append(parameterInfo.Attributes).Append(']').Append(' ');
var managedType = GenerateCSharpNativeToManaged(buildData, parameterInfo.Type, classInfo);
if (parameterInfo.IsOut)
contents.Append("out ");
else if (parameterInfo.IsRef)
contents.Append("ref ");
else if (parameterInfo.IsThis)
contents.Append("this ");
else if (parameterInfo.IsParams)
contents.Append("params ");
contents.Append(managedType);
contents.Append(' ');
contents.Append(parameterInfo.Name);
var defaultValue = GenerateCSharpDefaultValueNativeToManaged(buildData, parameterInfo.DefaultValue, classInfo, parameterInfo.Type, false, managedType);
if (!string.IsNullOrEmpty(defaultValue))
contents.Append(" = ").Append(defaultValue);
}
contents.Append(')').AppendLine().AppendLine(indent + "{");
indent += " ";
contents.Append(indent);
GenerateCSharpWrapperFunctionCall(buildData, contents, classInfo, functionInfo);
indent = indent.Substring(0, indent.Length - 4);
contents.AppendLine();
contents.AppendLine(indent + "}");
GenerateCSharpWrapperFunction(buildData, contents, indent, classInfo, functionInfo);
}
}
}
GenerateCSharpManagedTypeInternals(buildData, classInfo, contents, indent);
// Abstract wrapper (to ensure C# class can be created for Visual Scripts, see NativeInterop.NewObject)
if (classInfo.IsAbstract)
contents.AppendLine().Append(indent).Append("[Unmanaged] [HideInEditor] private sealed class AbstractWrapper : ").Append(classInfo.Name).AppendLine(" { }");
// Nested types
foreach (var apiTypeInfo in classInfo.Children)
{
GenerateCSharpType(buildData, contents, indent, apiTypeInfo);
}
// Class end
indent = indent.Substring(0, indent.Length - 4);
contents.AppendLine(indent + "}");
#if USE_NETCORE
if (!string.IsNullOrEmpty(marshallerName))
{
if (!string.IsNullOrEmpty(interopNamespace))
{
contents.AppendLine("}");
contents.AppendLine($"namespace {interopNamespace}");
contents.AppendLine("{");
}
contents.AppendLine();
contents.AppendLine(string.Join("\n" + indent, (indent + $$"""
/// <summary>
/// Marshaller for type <see cref="{{classInfo.Name}}"/>.
/// </summary>
#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) {}
}
#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' })));
}
#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;
string type, originalType;
if (fieldInfo.Type.IsArray && (fieldInfo.NoArray || structureInfo.IsPod))
{
// Fixed-size array that needs to be inlined into structure instead of passing it as managed array
fieldInfo.Type.IsArray = false;
originalType = type = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, structureInfo);
fieldInfo.Type.IsArray = true;
}
else
originalType = type = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, structureInfo);
structContents.Append(structIndent).Append("public ");
var apiType = FindApiTypeInfo(buildData, fieldInfo.Type, structureInfo);
bool internalType = apiType is StructureInfo fieldStructureInfo && UseCustomMarshalling(buildData, fieldStructureInfo, structureInfo);
string internalTypeMarshaller = "";
if (fieldInfo.Type.IsArray && (fieldInfo.NoArray || structureInfo.IsPod))
{
#if USE_NETCORE
if (GenerateCSharpUseFixedBuffer(originalType))
{
// Use fixed statement with primitive types of buffers
structContents.Append($"fixed {originalType} {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}];").AppendLine();
// Copy fixed-size array
toManagedContent.AppendLine($"FlaxEngine.Utils.MemoryCopy(new IntPtr(managed.{fieldInfo.Name}0), new IntPtr(unmanaged.{fieldInfo.Name}0), sizeof({originalType}) * {fieldInfo.Type.ArraySize}ul);");
toNativeContent.AppendLine($"FlaxEngine.Utils.MemoryCopy(new IntPtr(unmanaged.{fieldInfo.Name}0), new IntPtr(managed.{fieldInfo.Name}0), sizeof({originalType}) * {fieldInfo.Type.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 < fieldInfo.Type.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 < fieldInfo.Type.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 (fieldInfo.Type.IsObjectRef || fieldInfo.Type.Type == "Dictionary")
type = "IntPtr";
else if (fieldInfo.Type.IsPtr && !originalType.EndsWith("*"))
type = "IntPtr";
else if (fieldInfo.Type.Type == "Array" || fieldInfo.Type.Type == "Span" || fieldInfo.Type.Type == "DataContainer" || fieldInfo.Type.Type == "BytesContainer")
{
type = "IntPtr";
apiType = FindApiTypeInfo(buildData, fieldInfo.Type.GenericArgs[0], structureInfo);
internalType = apiType is StructureInfo elementStructureInfo && UseCustomMarshalling(buildData, elementStructureInfo, structureInfo);
}
else if (fieldInfo.Type.Type == "Version")
type = "IntPtr";
else if (type == "string")
type = "IntPtr";
else if (type == "bool")
type = "byte";
else if (fieldInfo.Type.Type == "Variant")
type = "IntPtr";
else if (internalType)
{
internalTypeMarshaller = type + "Marshaller";
type = $"{internalTypeMarshaller}.{type}Internal";
}
//else if (type == "Guid")
// type = "GuidNative";
structContents.Append($"{type} {fieldInfo.Name};").Append(type == "IntPtr" ? $" // {originalType}" : "").AppendLine();
}
// Generate struct constructor/getter and deconstructor/setter function
toManagedContent.Append("managed.").Append(fieldInfo.Name).Append(" = ");
toNativeContent.Append("unmanaged.").Append(fieldInfo.Name).Append(" = ");
if (fieldInfo.Type.IsObjectRef)
{
var managedType = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type.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;");
freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}");
// Permanent ScriptingObject handle is passed from native side, do not release it
//freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}");
}
else if (fieldInfo.Type.Type == "ScriptingObject")
{
toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<FlaxEngine.Object>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;");
toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero;");
freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}");
// Permanent ScriptingObject handle is passed from native side, do not release it
//freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}");
}
else if (fieldInfo.Type.IsPtr && originalType != "IntPtr" && !originalType.EndsWith("*"))
{
toManagedContent.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;");
freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}");
// Permanent ScriptingObject handle is passed from native side, do not release it
//freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}");
}
else if (fieldInfo.Type.Type == "Dictionary")
{
toManagedContent.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 (fieldInfo.Type.Type == "Array" || fieldInfo.Type.Type == "Span" || fieldInfo.Type.Type == "DataContainer" || fieldInfo.Type.Type == "BytesContainer")
{
string originalElementType = originalType.Substring(0, originalType.Length - 2);
if (internalType)
{
// Marshal blittable array elements back to original non-blittable elements
string originalElementTypeMarshaller = (!string.IsNullOrEmpty(apiType?.Namespace) ? $"{apiType?.Namespace}.Interop." : "") + $"{originalElementType}Marshaller";
string originalElementTypeName = originalElementType.Substring(originalElementType.LastIndexOf('.') + 1); // Strip namespace
string internalElementType = $"{originalElementTypeMarshaller}.{originalElementTypeName}Internal";
toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.ConvertArray((Unsafe.As<ManagedArray>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target)).ToSpan<{internalElementType}>(), {originalElementTypeMarshaller}.ToManaged) : null;");
toNativeContent.AppendLine($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(NativeInterop.ConvertArray(managed.{fieldInfo.Name}, {originalElementTypeMarshaller}.ToNative)), GCHandleType.Weak) : IntPtr.Zero;");
freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = (Unsafe.As<ManagedArray>(handle.Target)).ToSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} (Unsafe.As<ManagedArray>(handle.Target)).Free(); handle.Free(); }}");
freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = (Unsafe.As<ManagedArray>(handle.Target)).ToSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.NativeToManaged.Free(value); }} (Unsafe.As<ManagedArray>(handle.Target)).Free(); handle.Free(); }}");
}
else if (fieldInfo.Type.GenericArgs[0].IsObjectRef)
{
// Array elements passed as GCHandles
toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.GCHandleArrayToManagedArray<{originalElementType}>(Unsafe.As<ManagedArray>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target)) : null;");
toNativeContent.AppendLine($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(NativeInterop.ManagedArrayToGCHandleWrappedArray(managed.{fieldInfo.Name}), GCHandleType.Weak) : IntPtr.Zero;");
freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<IntPtr> ptrs = (Unsafe.As<ManagedArray>(handle.Target)).ToSpan<IntPtr>(); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(ptr).Free(); }} }} (Unsafe.As<ManagedArray>(handle.Target)).Free(); handle.Free(); }}");
// Permanent ScriptingObject handle is passed from native side, do not release it
//freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<IntPtr> ptrs = (Unsafe.As<ManagedArray>(handle.Target)).ToSpan<IntPtr>(); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(ptr).Free(); }} }} (Unsafe.As<ManagedArray>(handle.Target)).Free(); handle.Free(); }}");
}
else
{
// Blittable array elements
toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? (Unsafe.As<ManagedArray>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target)).ToArray<{originalElementType}>() : null;");
toNativeContent.AppendLine($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(managed.{fieldInfo.Name}), GCHandleType.Weak) : IntPtr.Zero;");
freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); (Unsafe.As<ManagedArray>(handle.Target)).Free(); handle.Free(); }}");
freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); (Unsafe.As<ManagedArray>(handle.Target)).Free(); handle.Free(); }}");
}
}
else if (fieldInfo.Type.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 (fieldInfo.Type.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;");
}
contents.AppendLine(string.Join("\n" + indent, (indent + $$"""
/// <summary>
/// Marshaller for type <see cref="{{structureInfo.Name}}"/>.
/// </summary>
{{InsertHideInEditorSection()}}
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ManagedToUnmanagedIn, typeof({{marshallerFullName}}.ManagedToNative))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.UnmanagedToManagedOut, typeof({{marshallerFullName}}.ManagedToNative))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ElementIn, typeof({{marshallerFullName}}.ManagedToNative))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ManagedToUnmanagedOut, typeof({{marshallerFullName}}.NativeToManaged))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.UnmanagedToManagedIn, typeof({{marshallerFullName}}.NativeToManaged))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ElementOut, typeof({{marshallerFullName}}.NativeToManaged))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ManagedToUnmanagedRef, typeof({{marshallerFullName}}.Bidirectional))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.UnmanagedToManagedRef, typeof({{marshallerFullName}}.Bidirectional))]
[CustomMarshaller(typeof({{structureInfo.Name}}), MarshalMode.ElementRef, typeof({{marshallerFullName}}))]
{{GenerateCSharpAccessLevel(structureInfo.Access)}}static unsafe class {{marshallerName}}
{
#pragma warning disable 1591
#pragma warning disable 618
{{structContents.Replace("\n", "\n" + " ").ToString().TrimEnd()}}
{{InsertHideInEditorSection()}}
public static class NativeToManaged
{
public static {{structureInfo.Name}} ConvertToManaged({{structureInfo.Name}}Internal unmanaged) => {{marshallerFullName}}.ToManaged(unmanaged);
public static {{structureInfo.Name}}Internal ConvertToUnmanaged({{structureInfo.Name}} managed) => {{marshallerFullName}}.ToNative(managed);
public static void Free({{structureInfo.Name}}Internal unmanaged)
{
{{freeContents2.Replace("\n", "\n" + " ").ToString().TrimEnd()}}
}
}
{{InsertHideInEditorSection()}}
public static class ManagedToNative
{
public static {{structureInfo.Name}} ConvertToManaged({{structureInfo.Name}}Internal unmanaged) => {{marshallerFullName}}.ToManaged(unmanaged);
public static {{structureInfo.Name}}Internal ConvertToUnmanaged({{structureInfo.Name}} managed) => {{marshallerFullName}}.ToNative(managed);
public static void Free({{structureInfo.Name}}Internal unmanaged) => {{marshallerFullName}}.Free(unmanaged);
}
{{InsertHideInEditorSection()}}
public struct Bidirectional
{
{{structureInfo.Name}} managed;
{{structureInfo.Name}}Internal unmanaged;
public void FromManaged({{structureInfo.Name}} managed) => this.managed = managed;
public {{structureInfo.Name}}Internal ToUnmanaged() { unmanaged = {{marshallerFullName}}.ToNative(managed); return unmanaged; }
public void FromUnmanaged({{structureInfo.Name}}Internal unmanaged) => this.unmanaged = unmanaged;
public {{structureInfo.Name}} ToManaged() { managed = {{marshallerFullName}}.ToManaged(unmanaged); return managed; }
public void Free() => NativeToManaged.Free(unmanaged);
}
internal static {{structureInfo.Name}} ConvertToManaged({{structureInfo.Name}}Internal unmanaged) => ToManaged(unmanaged);
internal static {{structureInfo.Name}}Internal ConvertToUnmanaged({{structureInfo.Name}} managed) => ToNative(managed);
internal static void Free({{structureInfo.Name}}Internal unmanaged)
{
{{freeContents.Replace("\n", "\n" + " ").ToString().TrimEnd()}}
}
internal static {{structureInfo.Name}} ToManaged({{structureInfo.Name}}Internal unmanaged)
{
{{toManagedContent.Replace("\n", "\n" + " ").ToString().TrimEnd()}}
}
internal static {{structureInfo.Name}}Internal ToNative({{structureInfo.Name}} managed)
{
{{toNativeContent.Replace("\n", "\n" + " ").ToString().TrimEnd()}}
}
#pragma warning restore 618
#pragma warning restore 1591
}
""").Split(new char[] { '\n' })));
string InsertHideInEditorSection()
{
return (buildData.Target != null & buildData.Target.IsEditor) ? $$"""
[HideInEditor]
""" : "";
}
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("/// <summary>");
contents.Append(indent).AppendLine($"/// The default <see cref=\"{structureInfo.Name}\"/>.");
contents.Append(indent).AppendLine("/// </summary>");
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);
}
// 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("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("/// <summary>");
contents.Append(indent).AppendLine($"/// Marshaller for type <see cref=\"{interfaceInfo.Name}\"/>.");
contents.Append(indent).AppendLine("/// </summary>");
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<string, Module> 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);
}
}
}