// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. 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 bool[] CppParamsThatNeedLocalVariable = new bool[64]; private static readonly bool[] CppParamsThatNeedConversion = new bool[64]; private static readonly string[] CppParamsThatNeedConversionWrappers = new string[64]; private static readonly string[] CppParamsThatNeedConversionTypes = new string[64]; private static readonly string[] CppParamsWrappersCache = new string[64]; public static readonly List> CppInternalCalls = new List>(); public static readonly List CppUsedNonPodTypes = new List(); private static readonly List CppUsedNonPodTypesList = new List(); public static readonly HashSet CppReferencesFiles = new HashSet(); private static readonly List CppAutoSerializeFields = new List(); private static readonly List CppAutoSerializeProperties = new List(); public static readonly HashSet CppIncludeFiles = new HashSet(); private static readonly List CppIncludeFilesList = new List(); private static readonly Dictionary CppVariantToTypes = new Dictionary(); private static readonly Dictionary CppVariantFromTypes = new Dictionary(); private static bool CppNonPodTypesConvertingGeneration = false; private static StringBuilder CppContentsEnd; public class ScriptingLangInfo { public bool Enabled; public string VirtualWrapperMethodsPostfix; } public static readonly List ScriptingLangInfos = new List { new ScriptingLangInfo { Enabled = true, VirtualWrapperMethodsPostfix = "_ManagedWrapper", // C# }, }; public static event Action, StringBuilder> GenerateCppBinaryModuleHeader; public static event Action, StringBuilder> GenerateCppBinaryModuleSource; public static event Action GenerateCppModuleSource; public static event Action GenerateCppTypeInternalsStatics; public static event Action GenerateCppTypeInternals; public static event Action GenerateCppTypeInitRuntime; public static event Action GenerateCppScriptWrapperFunction; private static readonly List CppInBuildVariantStructures = new List { "Vector2", "Vector3", "Vector4", "Float2", "Float3", "Float4", "Double2", "Double2", "Double3", "Int2", "Int3", "Int4", "Color", "Quaternion", "Guid", "BoundingSphere", "BoundingBox", "Transform", "Matrix", "Ray", "Rectangle", }; private static bool GenerateCppIsTemplateInstantiationType(ApiTypeInfo typeInfo) { return typeInfo.Instigator != null && typeInfo.Instigator.TypeInfo is ClassStructInfo classStructInfo && classStructInfo.IsTemplate; } private static string GenerateCppWrapperNativeToVariantMethodName(TypeInfo typeInfo) { var sb = GetStringBuilder(); sb.Append(typeInfo.Type.Replace("::", "_")); if (typeInfo.IsPtr) sb.Append("Ptr"); if (typeInfo.GenericArgs != null) { foreach (var arg in typeInfo.GenericArgs) { sb.Append(arg.Type.Replace("::", "_")); if (arg.IsPtr) sb.Append("Ptr"); } } var result = sb.ToString(); PutStringBuilder(sb); return result; } private static string GenerateCppWrapperNativeToManagedParam(BuildData buildData, StringBuilder contents, TypeInfo paramType, string paramName, ApiTypeInfo caller, bool isRef, out bool useLocalVar) { useLocalVar = false; var nativeToManaged = GenerateCppWrapperNativeToManaged(buildData, paramType, caller, out var managedTypeAsNative, null); string result; if (!string.IsNullOrEmpty(nativeToManaged)) { result = string.Format(nativeToManaged, paramName); if (managedTypeAsNative[managedTypeAsNative.Length - 1] == '*' && !isRef) { // Pass pointer value } else { // Pass as pointer to local variable converted for managed runtime if (paramType.IsPtr) result = string.Format(nativeToManaged, '*' + paramName); contents.Append($" auto __param_{paramName} = {result};").AppendLine(); result = $"&__param_{paramName}"; useLocalVar = true; } } else { result = paramName; if (paramType.IsRef && !paramType.IsConst && !isRef) { // Pass reference as a pointer result = '&' + result; } else if (paramType.IsPtr || managedTypeAsNative[managedTypeAsNative.Length - 1] == '*') { // Pass pointer value } else { // Pass as pointer to value result = '&' + result; } } return $"(void*){result}"; } private static void GenerateCppAddFileReference(BuildData buildData, ApiTypeInfo caller, TypeInfo typeInfo, ApiTypeInfo apiType) { CppReferencesFiles.Add(apiType?.File); if (typeInfo.GenericArgs != null) { for (int i = 0; i < typeInfo.GenericArgs.Count; i++) { var g = typeInfo.GenericArgs[i]; GenerateCppAddFileReference(buildData, caller, g, FindApiTypeInfo(buildData, g, caller)); } } } public static string GenerateCppWrapperNativeToVariant(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, string value) { if (typeInfo.Type == "Variant") return value; if (typeInfo.Type == "String") return $"Variant(StringView({value}))"; if (typeInfo.Type == "StringAnsi") return $"Variant(StringAnsiView({value}))"; if (typeInfo.IsObjectRef) return $"Variant({value}.Get())"; if (typeInfo.IsArray) { var wrapperName = GenerateCppWrapperNativeToVariantMethodName(typeInfo); CppVariantFromTypes[wrapperName] = typeInfo; return $"VariantFrom{GenerateCppWrapperNativeToVariantMethodName(typeInfo)}Array((const {typeInfo}*){value}, {typeInfo.ArraySize})"; } if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) { var wrapperName = GenerateCppWrapperNativeToVariantMethodName(typeInfo.GenericArgs[0]); CppVariantFromTypes[wrapperName] = typeInfo; return $"VariantFrom{wrapperName}Array((const {typeInfo.GenericArgs[0]}*){value}.Get(), {value}.Count())"; } if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null) { var wrapperName = GenerateCppWrapperNativeToVariantMethodName(typeInfo.GenericArgs[0]) + GenerateCppWrapperNativeToVariantMethodName(typeInfo.GenericArgs[1]); CppVariantFromTypes[wrapperName] = typeInfo; return $"VariantFrom{wrapperName}Dictionary({value})"; } if (typeInfo.Type == "Span" && typeInfo.GenericArgs != null) { return "Variant()"; // TODO: Span to Variant converting (use utility method the same way as for arrays) } var apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null) { CppReferencesFiles.Add(apiType.File); if (apiType.IsStruct && !apiType.IsPod && !CppUsedNonPodTypes.Contains(apiType)) CppUsedNonPodTypes.Add(apiType); if (apiType.IsEnum) return $"Variant::Enum(VariantType(VariantType::Enum, StringAnsiView(\"{apiType.FullNameManaged}\", {apiType.FullNameManaged.Length})), {value})"; if (apiType.IsStruct && !CppInBuildVariantStructures.Contains(apiType.Name)) if (typeInfo.IsPtr) return $"Variant::Structure(VariantType(VariantType::Structure, StringAnsiView(\"{apiType.FullNameManaged}\", {apiType.FullNameManaged.Length})), *{value})"; else return $"Variant::Structure(VariantType(VariantType::Structure, StringAnsiView(\"{apiType.FullNameManaged}\", {apiType.FullNameManaged.Length})), {value})"; } if (typeInfo.IsPtr && typeInfo.IsConst) return $"Variant(({typeInfo.Type}*){value})"; return $"Variant({value})"; } public static string GenerateCppWrapperVariantToNative(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, string value) { if (typeInfo.Type == "Variant") return value; if (typeInfo.Type == "String") return $"(StringView){value}"; if (typeInfo.Type == "StringAnsi") return $"(StringAnsiView){value}"; if (typeInfo.IsPtr && typeInfo.IsConst && typeInfo.Type == "Char") return $"((StringView){value}).GetText()"; // (StringView)Variant, if not empty, is guaranteed to point to a null-terminated buffer. if (typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "SoftObjectReference") return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((ScriptingObject*){value})"; if (typeInfo.IsObjectRef) return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((Asset*){value})"; if (typeInfo.IsArray) throw new Exception($"Not supported type to convert from the Variant to fixed-size array '{typeInfo}[{typeInfo.ArraySize}]'."); if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) { var wrapperName = GenerateCppWrapperNativeToVariantMethodName(typeInfo); CppVariantToTypes[wrapperName] = typeInfo; return $"MoveTemp(VariantTo{wrapperName}({value}))"; } if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null) { var wrapperName = GenerateCppWrapperNativeToVariantMethodName(typeInfo); CppVariantToTypes[wrapperName] = typeInfo; return $"MoveTemp(VariantTo{wrapperName}({value}))"; } if (typeInfo.Type == "Span" && typeInfo.GenericArgs != null) { return $"{typeInfo}()"; // Cannot be implemented since Variant stores array of Variants thus cannot get linear span of data } var apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null) { CppReferencesFiles.Add(apiType.File); if (apiType.IsStruct && !apiType.IsPod && !CppUsedNonPodTypes.Contains(apiType)) CppUsedNonPodTypes.Add(apiType); if (apiType.IsEnum) return $"({apiType.FullNameNative})(uint64){value}"; if (apiType.IsScriptingObject) return $"ScriptingObject::Cast<{typeInfo.Type}>((ScriptingObject*){value})"; if (apiType.IsStruct && !CppInBuildVariantStructures.Contains(apiType.Name)) if (typeInfo.IsPtr) return $"({apiType.FullNameNative}*){value}.AsBlob.Data"; else return $"*({apiType.FullNameNative}*){value}.AsBlob.Data"; } if (typeInfo.IsPtr) return $"({typeInfo})(void*){value}"; return $"({typeInfo}){value}"; } public static void GenerateCppVirtualWrapperCallBaseMethod(BuildData buildData, StringBuilder contents, VirtualClassInfo classInfo, FunctionInfo functionInfo, string scriptVTableBase, string scriptVTableOffset) { contents.AppendLine(" // Prevent stack overflow by calling native base method"); if (buildData.Toolchain?.Compiler == TargetCompiler.Clang) { // Clang compiler // TODO: secure VTableFunctionInjector with mutex (even at cost of performance) contents.AppendLine($" {functionInfo.UniqueName}_Signature funcPtr = &{classInfo.NativeName}::{functionInfo.Name};"); contents.AppendLine($" VTableFunctionInjector vtableInjector(object, *(void**)&funcPtr, {scriptVTableBase}[{scriptVTableOffset} + 2]); // TODO: this is not thread-safe"); if (classInfo is InterfaceInfo) { contents.Append($" return (({classInfo.NativeName}*)(void*)object)->{functionInfo.Name}("); } else { contents.Append(" return (object->*funcPtr)("); } } else { // MSVC or other compiler contents.Append($" return (this->**({functionInfo.UniqueName}_Internal_Signature*)&{scriptVTableBase}[{scriptVTableOffset} + 2])("); } bool separator = false; for (var i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; if (separator) contents.Append(", "); separator = true; contents.Append(parameterInfo.Name); } contents.AppendLine(");"); } private static string GenerateCppGetMClass(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, FunctionInfo functionInfo) { // Optimal path for in-build types var managedType = GenerateCSharpNativeToManaged(buildData, typeInfo, caller, true); switch (managedType) { // In-built types (cached by the engine on startup) case "bool": return "MCore::TypeCache::Boolean"; case "sbyte": return "MCore::TypeCache::SByte"; case "byte": return "MCore::TypeCache::Byte"; case "short": return "MCore::TypeCache::Int16"; case "ushort": return "MCore::TypeCache::UInt16"; case "int": return "MCore::TypeCache::Int32"; case "uint": return "MCore::TypeCache::UInt32"; case "long": return "MCore::TypeCache::Int64"; case "ulong": return "MCore::TypeCache::UInt64"; case "float": return "MCore::TypeCache::Single"; case "double": return "MCore::TypeCache::Double"; case "string": return "MCore::TypeCache::String"; case "object": return "MCore::TypeCache::Object"; case "void": return "MCore::TypeCache::Void"; case "char": return "MCore::TypeCache::Char"; case "IntPtr": return "MCore::TypeCache::IntPtr"; case "UIntPtr": return "MCore::TypeCache::UIntPtr"; // Vector2/3/4 have custom type in C# (due to lack of typename using in older C#) case "Vector2": return "Scripting::FindClass(\"FlaxEngine.Vector2\")"; case "Vector3": return "Scripting::FindClass(\"FlaxEngine.Vector3\")"; case "Vector4": return "Scripting::FindClass(\"FlaxEngine.Vector4\")"; } // Find API type var apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null) { CppReferencesFiles.Add(apiType.File); if (apiType.IsStruct && !apiType.IsPod && !CppUsedNonPodTypes.Contains(apiType)) CppUsedNonPodTypes.Add(apiType); if (!apiType.SkipGeneration && !apiType.IsEnum) { // Use declared type initializer CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MClass.h"); return $"{apiType.FullNameNative}::TypeInitializer.GetClass()"; } } // Pass it from C# in glue parameter if used inside the wrapper function if (functionInfo != null) { var customParam = new FunctionInfo.ParameterInfo { Name = "resultArrayItemType" + functionInfo.Glue.CustomParameters.Count, DefaultValue = "typeof(" + managedType + ')', Type = new TypeInfo { Type = "MTypeObject", IsPtr = true, }, }; functionInfo.Glue.CustomParameters.Add(customParam); return "MUtils::GetClass(" + customParam.Name + ")"; } // Find namespace for this type to build a fullname if (apiType != null) { var e = apiType.Parent; while (!(e is FileInfo)) { e = e.Parent; } if (e is FileInfo fileInfo && !managedType.StartsWith(fileInfo.Namespace)) { managedType = fileInfo.Namespace + '.' + managedType.Replace(".", "+"); } } // Use runtime lookup from fullname of the C# class return "Scripting::FindClass(\"" + managedType + "\")"; } private static string GenerateCppGetNativeType(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, FunctionInfo functionInfo) { CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MClass.h"); // Optimal path for in-build types var managedType = GenerateCSharpNativeToManaged(buildData, typeInfo, caller, true); switch (managedType) { case "bool": case "sbyte": case "byte": case "short": case "ushort": case "int": case "uint": case "long": case "ulong": case "float": case "double": case "string": case "object": case "void": case "char": case "IntPtr": case "UIntPtr": return $"{GenerateCppGetMClass(buildData, typeInfo, caller, null)}->GetType()"; } // Find API type var apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null) { CppReferencesFiles.Add(apiType.File); if (apiType.IsStruct && !apiType.IsPod && !CppUsedNonPodTypes.Contains(apiType)) CppUsedNonPodTypes.Add(apiType); if (!apiType.SkipGeneration && !apiType.IsEnum) { // Use declared type initializer return $"{apiType.FullNameNative}::TypeInitializer.GetClass()->GetType()"; } } // Pass it from C# in glue parameter if used inside the wrapper function if (functionInfo != null) { var customParam = new FunctionInfo.ParameterInfo { Name = "resultArrayItemType" + functionInfo.Glue.CustomParameters.Count, DefaultValue = "typeof(" + managedType + ')', Type = new TypeInfo { Type = "MTypeObject", IsPtr = true, }, }; functionInfo.Glue.CustomParameters.Add(customParam); return "INTERNAL_TYPE_OBJECT_GET(" + customParam.Name + ')'; } // Convert MClass* into MType* return $"{GenerateCppGetMClass(buildData, typeInfo, caller, null)}->GetType()"; } private static string GenerateCppManagedWrapperName(ApiTypeInfo type) { var result = type.NativeName + "Managed"; while (type.Parent is ClassStructInfo) { result = type.Parent.NativeName + '_' + result; type = type.Parent; } return result; } private static string GenerateCppWrapperNativeToManaged(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, out string type, FunctionInfo functionInfo) { // Use dynamic array as wrapper container for fixed-size native arrays if (typeInfo.IsArray) { var arrayType = new TypeInfo { Type = "Array", GenericArgs = new List { new TypeInfo(typeInfo) { IsArray = false } } }; var result = GenerateCppWrapperNativeToManaged(buildData, arrayType, caller, out type, functionInfo); return result.Replace("{0}", $"Span<{typeInfo.Type}>({{0}}, {typeInfo.ArraySize})"); } // Special case for bit-fields if (typeInfo.IsBitField) { if (typeInfo.BitSize == 1) { type = "bool"; return "{0} != 0"; } throw new NotImplementedException("TODO: support bit-fields with more than 1 bit."); } switch (typeInfo.Type) { case "String": case "StringView": case "StringAnsi": case "StringAnsiView": type = "MString*"; return "MUtils::ToString({0})"; case "Variant": type = "MObject*"; return "MUtils::BoxVariant({0})"; case "VariantType": type = "MTypeObject*"; return "MUtils::BoxVariantType({0})"; case "ScriptingTypeHandle": type = "MTypeObject*"; return "MUtils::BoxScriptingTypeHandle({0})"; case "ScriptingObject": case "ManagedScriptingObject": case "PersistentScriptingObject": type = "MObject*"; return "ScriptingObject::ToManaged((ScriptingObject*){0})"; case "MClass": type = "MTypeObject*"; return "MUtils::GetType({0})"; case "CultureInfo": type = "void*"; return "MUtils::ToManaged({0})"; case "Version": type = "MObject*"; return "MUtils::ToManaged({0})"; default: // Object reference property if (typeInfo.IsObjectRef) { type = "MObject*"; return "{0}.GetManagedInstance()"; } // Array or DataContainer if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && typeInfo.GenericArgs != null) { var arrayTypeInfo = typeInfo.GenericArgs[0]; #if USE_NETCORE // Boolean arrays does not support custom marshalling for some unknown reason if (arrayTypeInfo.Type == "bool") { type = "bool*"; return "MUtils::ToBoolArray({0})"; } var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); #endif type = "MArray*"; if (arrayApiType != null && arrayApiType.MarshalAs != null) { // Convert array that uses different type for marshalling if (arrayApiType != null && arrayApiType.MarshalAs != null) arrayTypeInfo = arrayApiType.MarshalAs; // Convert array that uses different type for marshalling var genericArgs = arrayApiType.MarshalAs.GetFullNameNative(buildData, caller); if (typeInfo.GenericArgs.Count != 1) genericArgs += ", " + typeInfo.GenericArgs[1]; return "MUtils::ToArray(Array<" + genericArgs + ">({0}), " + GenerateCppGetMClass(buildData, arrayTypeInfo, caller, functionInfo) + ")"; } return "MUtils::ToArray({0}, " + GenerateCppGetMClass(buildData, arrayTypeInfo, caller, functionInfo) + ")"; } // Span if (typeInfo.Type == "Span" && typeInfo.GenericArgs != null) { type = "MonoArray*"; return "MUtils::Span({0}, " + GenerateCppGetMClass(buildData, typeInfo.GenericArgs[0], caller, functionInfo) + ")"; } // BytesContainer if (typeInfo.Type == "BytesContainer" && typeInfo.GenericArgs == null) { type = "MArray*"; return "MUtils::ToArray({0})"; } // Dictionary if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null) { CppIncludeFiles.Add("Engine/Scripting/Internal/ManagedDictionary.h"); type = "MObject*"; var keyClass = GenerateCppGetNativeType(buildData, typeInfo.GenericArgs[0], caller, functionInfo); var valueClass = GenerateCppGetNativeType(buildData, typeInfo.GenericArgs[1], caller, functionInfo); return "ManagedDictionary::ToManaged({0}, " + keyClass + ", " + valueClass + ")"; } // HashSet if (typeInfo.Type == "HashSet" && typeInfo.GenericArgs != null) { // TODO: automatic converting managed-native for HashSet throw new NotImplementedException("TODO: converting native HashSet to managed"); } // BitArray if (typeInfo.Type == "BitArray" && typeInfo.GenericArgs != null) { CppIncludeFiles.Add("Engine/Scripting/Internal/ManagedBitArray.h"); #if USE_NETCORE // Boolean arrays does not support custom marshalling for some unknown reason type = "bool*"; return "MUtils::ToBoolArray({0})"; #else type = "MObject*"; return "ManagedBitArray::ToManaged({0})"; #endif } // Function if (typeInfo.Type == "Function" && typeInfo.GenericArgs != null) { // TODO: automatic converting managed-native for Function throw new NotImplementedException("TODO: converting native Function to managed"); } var apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null) { CppReferencesFiles.Add(apiType.File); if (apiType.MarshalAs != null) return GenerateCppWrapperNativeToManaged(buildData, apiType.MarshalAs, caller, out type, functionInfo); // Scripting Object if (apiType.IsScriptingObject) { type = "MObject*"; return "ScriptingObject::ToManaged((ScriptingObject*){0})"; } // interface if (apiType.IsInterface) { type = "MObject*"; return "ScriptingObject::ToManaged(ScriptingObject::FromInterface({0}, " + apiType.NativeName + "::TypeInitializer))"; } // Non-POD structure passed as value (eg. it contains string or array inside) if (apiType.IsStruct && !apiType.IsPod) { // Use wrapper structure that represents the memory layout of the managed data if (!CppUsedNonPodTypes.Contains(apiType)) CppUsedNonPodTypes.Add(apiType); type = GenerateCppManagedWrapperName(apiType); if (functionInfo != null) type += '*'; return "ToManaged({0})"; } // Managed class if (apiType.IsClass) { // Use wrapper that box the data into managed object if (!CppUsedNonPodTypes.Contains(apiType)) CppUsedNonPodTypes.Add(apiType); type = "MObject*"; return "MConverter<" + apiType.Name + ">::Box({0})"; } // Nested type (namespace prefix is required) if (!(apiType.Parent is FileInfo)) { type = apiType.FullNameNative; return string.Empty; } } type = typeInfo.ToString(); return string.Empty; } } private static string GenerateCppWrapperManagedToNative(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, out string type, out ApiTypeInfo apiType, FunctionInfo functionInfo, out bool needLocalVariable) { needLocalVariable = false; // Register any API types usage apiType = FindApiTypeInfo(buildData, typeInfo, caller); GenerateCppAddFileReference(buildData, caller, typeInfo, apiType); // Use dynamic array as wrapper container for fixed-size native arrays if (typeInfo.IsArray) { var arrayType = new TypeInfo { Type = "Array", GenericArgs = new List { new TypeInfo(typeInfo) { IsArray = false } } }; var result = GenerateCppWrapperManagedToNative(buildData, arrayType, caller, out type, out _, functionInfo, out needLocalVariable); return result + ".Get()"; } // Special case for bit-fields if (typeInfo.IsBitField) { if (typeInfo.BitSize == 1) { type = "bool"; return "{0} ? 1 : 0"; } throw new NotImplementedException("TODO: support bit-fields with more than 1 bit."); } switch (typeInfo.Type) { case "String": type = "MString*"; return "String(MUtils::ToString({0}))"; case "StringView": type = "MString*"; return "MUtils::ToString({0})"; case "StringAnsi": case "StringAnsiView": type = "MString*"; return "MUtils::ToStringAnsi({0})"; case "Variant": type = "MObject*"; return "MUtils::UnboxVariant({0})"; case "VariantType": type = "MTypeObject*"; return "MUtils::UnboxVariantType({0})"; case "ScriptingTypeHandle": type = "MTypeObject*"; return "MUtils::UnboxScriptingTypeHandle({0})"; case "CultureInfo": type = "void*"; return "MUtils::ToNative({0})"; case "Version": type = "MObject*"; return "MUtils::ToNative({0})"; default: // Object reference property if (typeInfo.IsObjectRef) { // For non-pod types converting only, other API converts managed to unmanaged object in C# wrapper code) if (CppNonPodTypesConvertingGeneration) { if (typeInfo.Type == "AssetReference") { type = "MObject*"; return "(" + typeInfo.GenericArgs[0].Type + "*)ScriptingObject::ToNative({0})"; } else { type = "MObject*"; return "(" + typeInfo.GenericArgs[0].Type + "*)ScriptingObject::ToNative({0})"; } } type = typeInfo.GenericArgs[0].Type + '*'; return string.Empty; } // MClass if (typeInfo.Type == "MClass" && typeInfo.GenericArgs == null) { type = "MTypeObject*"; return "MUtils::GetClass({0})"; } // Array if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) { var arrayTypeInfo = typeInfo.GenericArgs[0]; var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); if (arrayApiType != null && arrayApiType.MarshalAs != null) arrayTypeInfo = arrayApiType.MarshalAs; var genericArgs = arrayTypeInfo.GetFullNameNative(buildData, caller); if (typeInfo.GenericArgs.Count != 1) genericArgs += ", " + typeInfo.GenericArgs[1]; type = "MArray*"; var result = "MUtils::ToArray<" + genericArgs + ">({0})"; if (arrayApiType != null && arrayApiType.MarshalAs != null) { // Convert array that uses different type for marshalling genericArgs = typeInfo.GenericArgs[0].GetFullNameNative(buildData, caller); if (typeInfo.GenericArgs.Count != 1) genericArgs += ", " + typeInfo.GenericArgs[1]; result = $"Array<{genericArgs}>({result})"; } return result; } // Span or DataContainer if ((typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && typeInfo.GenericArgs != null) { type = "MArray*"; // Scripting Objects pointers has to be converted from managed object pointer into native object pointer to use Array converted for this var t = FindApiTypeInfo(buildData, typeInfo.GenericArgs[0], caller); if (typeInfo.GenericArgs[0].IsPtr && t != null && t.IsScriptingObject) { return "MUtils::ToSpan<" + typeInfo.GenericArgs[0] + ">(" + "MUtils::ToArray<" + typeInfo.GenericArgs[0] + ">({0}))"; } return "MUtils::ToSpan<" + typeInfo.GenericArgs[0] + ">({0})"; } // Dictionary if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null) { CppIncludeFiles.Add("Engine/Scripting/Internal/ManagedDictionary.h"); type = "MObject*"; return string.Format("ManagedDictionary::ToNative<{0}, {1}>({{0}})", typeInfo.GenericArgs[0], typeInfo.GenericArgs[1]); } // HashSet if (typeInfo.Type == "HashSet" && typeInfo.GenericArgs != null) { // TODO: automatic converting managed-native for HashSet throw new NotImplementedException("TODO: converting managed HashSet to native"); } // BitArray if (typeInfo.Type == "BitArray" && typeInfo.GenericArgs != null) { // TODO: automatic converting managed-native for BitArray throw new NotImplementedException("TODO: converting managed BitArray to native"); } // BytesContainer if (typeInfo.Type == "BytesContainer" && typeInfo.GenericArgs == null) { needLocalVariable = true; type = "MArray*"; return "MUtils::LinkArray({0})"; } // Function if (typeInfo.Type == "Function" && typeInfo.GenericArgs != null) { var args = string.Empty; if (typeInfo.GenericArgs.Count > 1) { args += typeInfo.GenericArgs[1].GetFullNameNative(buildData, caller); for (int i = 2; i < typeInfo.GenericArgs.Count; i++) args += ", " + typeInfo.GenericArgs[i].GetFullNameNative(buildData, caller); } var T = $"Function<{typeInfo.GenericArgs[0].GetFullNameNative(buildData, caller)}({args})>"; type = T + "::Signature"; return T + "({0})"; } if (apiType != null) { if (apiType.MarshalAs != null) return GenerateCppWrapperManagedToNative(buildData, apiType.MarshalAs, caller, out type, out apiType, functionInfo, out needLocalVariable); // Scripting Object (for non-pod types converting only, other API converts managed to unmanaged object in C# wrapper code) if (CppNonPodTypesConvertingGeneration && apiType.IsScriptingObject && typeInfo.IsPtr) { type = typeInfo.ToString(); return "(" + type + ")ScriptingObject::ToNative({0})"; } // Non-POD structure passed as value (eg. it contains string or array inside) if (apiType.IsStruct && !apiType.IsPod) { // Use wrapper structure that represents the memory layout of the managed data needLocalVariable = true; if (!CppUsedNonPodTypes.Contains(apiType)) CppUsedNonPodTypes.Add(apiType); type = GenerateCppManagedWrapperName(apiType); if (functionInfo != null) return "ToNative(*{0})"; return "ToNative({0})"; } // Managed class if (apiType.IsClass && !apiType.IsScriptingObject) { // Use wrapper that unboxes the data into managed object if (!CppUsedNonPodTypes.Contains(apiType)) CppUsedNonPodTypes.Add(apiType); type = "MObject*"; return "MConverter<" + apiType.Name + ">::Unbox({0})"; } // Scripting Object if (functionInfo == null && apiType.IsScriptingObject) { // Inside bindings function the managed runtime passes raw unamanged pointer type = "MObject*"; return "(" + typeInfo.Type + "*)ScriptingObject::ToNative({0})"; } // Nested type (namespace prefix is required) if (!(apiType.Parent is FileInfo)) { type = apiType.FullNameNative; return string.Empty; } } if (UsePassByReference(buildData, typeInfo, caller)) { type = typeInfo.Type; if (typeInfo.GenericArgs != null) { type += '<'; for (var i = 0; i < typeInfo.GenericArgs.Count; i++) { if (i != 0) type += ", "; type += typeInfo.GenericArgs[i]; } type += '>'; } return string.Empty; } type = typeInfo.ToString(); return string.Empty; } } private static string GenerateCppWrapperNativeToBox(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, out ApiTypeInfo apiType, string value) { // Optimize passing scripting objects apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null && apiType.IsScriptingObject) return $"ScriptingObject::ToManaged((ScriptingObject*){value})"; // Array or Span or DataContainer if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && typeInfo.GenericArgs != null && typeInfo.GenericArgs.Count >= 1) return $"MUtils::ToArray({value}, {GenerateCppGetMClass(buildData, typeInfo.GenericArgs[0], caller, null)})"; // BytesContainer if (typeInfo.Type == "BytesContainer" && typeInfo.GenericArgs == null) return "MUtils::ToArray({0})"; // Construct native typename for MUtils template argument var nativeType = new StringBuilder(64); nativeType.Append(typeInfo.Type); if (typeInfo.GenericArgs != null) { nativeType.Append('<'); for (var j = 0; j < typeInfo.GenericArgs.Count; j++) { if (j != 0) nativeType.Append(", "); nativeType.Append(typeInfo.GenericArgs[j]); } nativeType.Append('>'); } if (typeInfo.IsPtr) nativeType.Append('*'); // Use MUtils to box the value return $"MUtils::Box<{nativeType}>({value}, {GenerateCppGetMClass(buildData, typeInfo, caller, null)})"; } private static bool GenerateCppWrapperFunctionImplicitBinding(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller) { if (typeInfo.IsVoid) return true; if (typeInfo.IsPtr || typeInfo.IsRef || typeInfo.IsArray || typeInfo.IsBitField || (typeInfo.GenericArgs != null && typeInfo.GenericArgs.Count != 0)) return false; if (CSharpNativeToManagedBasicTypes.ContainsKey(typeInfo.Type) || CSharpNativeToManagedBasicTypes.ContainsValue(typeInfo.Type)) return true; var apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null) { if (apiType.IsEnum) return true; } return false; } private static bool GenerateCppWrapperFunctionImplicitBinding(BuildData buildData, FunctionInfo functionInfo, ApiTypeInfo caller) { if (!functionInfo.IsStatic || functionInfo.Access != AccessLevel.Public || (functionInfo.Glue.CustomParameters != null && functionInfo.Glue.CustomParameters.Count != 0)) return false; if (!GenerateCppWrapperFunctionImplicitBinding(buildData, functionInfo.ReturnType, caller)) return false; for (int i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; if (parameterInfo.IsOut || parameterInfo.IsRef || !GenerateCppWrapperFunctionImplicitBinding(buildData, parameterInfo.Type, caller)) return false; } return true; } private static void GenerateCppWrapperFunction(BuildData buildData, StringBuilder contents, ApiTypeInfo caller, string callerName, FunctionInfo functionInfo, string callFormat = "{0}({1})") { #if !USE_NETCORE // Optimize static function wrappers that match C# internal call ABI exactly // Use it for Engine-internally only because in games this makes it problematic to use the same function name but with different signature that is not visible to scripting if (CurrentModule.Module is EngineModule && callFormat == "{0}({1})" && GenerateCppWrapperFunctionImplicitBinding(buildData, functionInfo, caller)) { // Ensure the function name is unique within a class/structure if (caller is ClassStructInfo classStructInfo && classStructInfo.Functions.All(f => f.Name != functionInfo.Name || f == functionInfo)) { // Use native method binding directly (no generated wrapper) CppInternalCalls.Add(new KeyValuePair(functionInfo.UniqueName, classStructInfo.Name + "::" + functionInfo.Name)); return; } } #endif // Setup function binding glue to ensure that wrapper method signature matches for C++ and C# functionInfo.Glue = new FunctionInfo.GlueInfo { UseReferenceForResult = UsePassByReference(buildData, functionInfo.ReturnType, caller), CustomParameters = new List(), }; bool returnTypeIsContainer = false; var returnValueConvert = GenerateCppWrapperNativeToManaged(buildData, functionInfo.ReturnType, caller, out var returnValueType, functionInfo); if (functionInfo.Glue.UseReferenceForResult) { returnValueType = "void"; functionInfo.Glue.CustomParameters.Add(new FunctionInfo.ParameterInfo { Name = "__resultAsRef", DefaultValue = "var __resultAsRef", Type = new TypeInfo { Type = functionInfo.ReturnType.Type, GenericArgs = functionInfo.ReturnType.GenericArgs, }, IsOut = true, }); } #if USE_NETCORE else if (functionInfo.ReturnType.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer" || functionInfo.ReturnType.Type == "BitArray" || functionInfo.ReturnType.Type == "BytesContainer") { returnTypeIsContainer = true; functionInfo.Glue.CustomParameters.Add(new FunctionInfo.ParameterInfo { Name = "__returnCount", DefaultValue = "var __returnCount", Type = new TypeInfo("int"), IsOut = true, }); } #endif var prevIndent = " "; var indent = " "; contents.Append(prevIndent); bool useSeparateImpl = false; // True if separate function declaration from implementation bool useLibraryExportInPlainC = false; // True if generate separate wrapper for library imports that uses plain-C style binding (without C++ name mangling) #if USE_NETCORE string libraryEntryPoint; if (buildData.Toolchain?.Compiler == TargetCompiler.MSVC) { libraryEntryPoint = $"{caller.FullNameManaged}::Internal_{functionInfo.UniqueName}"; // MSVC allows to override exported symbol name } else { // For simple functions we can bind it directly (CppNameMangling is incomplete for complex cases) // TODO: improve this to use wrapper method directly from P/Invoke if possible /*if (functionInfo.Parameters.Count + functionInfo.Parameters.Count == 0) { useSeparateImpl = true; // DLLEXPORT doesn't properly export function thus separate implementation from declaration libraryEntryPoint = CppNameMangling.MangleFunctionName(buildData, functionInfo.Name, callerName, functionInfo.ReturnType, functionInfo.IsStatic ? null : new TypeInfo(caller.Name) { IsPtr = true }, functionInfo.Parameters, functionInfo.Glue.CustomParameters); } else*/ { useLibraryExportInPlainC = true; libraryEntryPoint = $"{callerName}_{functionInfo.UniqueName}"; } } if (useLibraryExportInPlainC) contents.AppendFormat("static {0} {1}(", returnValueType, functionInfo.UniqueName); else contents.AppendFormat("DLLEXPORT static {0} {1}(", returnValueType, functionInfo.UniqueName); functionInfo.Glue.LibraryEntryPoint = libraryEntryPoint; #else contents.AppendFormat("static {0} {1}(", returnValueType, functionInfo.UniqueName); #endif CppInternalCalls.Add(new KeyValuePair(functionInfo.UniqueName, functionInfo.UniqueName)); var separator = false; var signatureStart = contents.Length; if (!functionInfo.IsStatic) { contents.Append(caller.Name).Append("* __obj"); separator = true; } var useInlinedReturn = true; for (var i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; if (separator) contents.Append(", "); separator = true; CppParamsThatNeedConversion[i] = false; CppParamsWrappersCache[i] = GenerateCppWrapperManagedToNative(buildData, parameterInfo.Type, caller, out var managedType, out var apiType, functionInfo, out CppParamsThatNeedLocalVariable[i]); // Out parameters that need additional converting will be converted at the native side (eg. object reference) var isOutWithManagedConverter = parameterInfo.IsOut && !string.IsNullOrEmpty(GenerateCSharpManagedToNativeConverter(buildData, parameterInfo.Type, caller)); if (isOutWithManagedConverter) managedType = "MObject*"; contents.Append(managedType); if (parameterInfo.IsRef || parameterInfo.IsOut || UsePassByReference(buildData, parameterInfo.Type, caller)) contents.Append('*'); contents.Append(' '); contents.Append(parameterInfo.Name); // Special case for output result parameters that needs additional converting from native to managed format (such as non-POD structures, output arrays, etc.) var isRefOut = parameterInfo.IsRef && parameterInfo.Type.IsRef && !parameterInfo.Type.IsConst; if (parameterInfo.IsOut || isRefOut) { bool convertOutputParameter = false; if (apiType != null) { // Non-POD structure passed as value (eg. it contains string or array inside) if (apiType.IsStruct && !apiType.IsPod) { convertOutputParameter = true; } // Arrays, Scripting Objects, Dictionaries and other types that need to be converted into managed format if used as output parameter else if (!apiType.IsPod) { convertOutputParameter = true; } else if (apiType.Name == "Variant") { convertOutputParameter = true; } } // BytesContainer else if (parameterInfo.Type.Type == "BytesContainer" && parameterInfo.Type.GenericArgs == null) { convertOutputParameter = true; } if (convertOutputParameter) { useInlinedReturn = false; CppParamsThatNeedConversion[i] = true; CppParamsThatNeedConversionWrappers[i] = GenerateCppWrapperNativeToManaged(buildData, parameterInfo.Type, caller, out CppParamsThatNeedConversionTypes[i], functionInfo); } } #if USE_NETCORE if (parameterInfo.Type.IsArray || parameterInfo.Type.Type == "Array" || parameterInfo.Type.Type == "Span" || parameterInfo.Type.Type == "BytesContainer" || parameterInfo.Type.Type == "DataContainer" || parameterInfo.Type.Type == "BitArray") { // We need additional output parameters for array sizes var name = $"__{parameterInfo.Name}Count"; functionInfo.Glue.CustomParameters.Add(new FunctionInfo.ParameterInfo { Name = name, DefaultValue = parameterInfo.IsOut ? "int _" : name, Type = new TypeInfo { Type = "int" }, IsOut = parameterInfo.IsOut, IsRef = isRefOut || parameterInfo.Type.IsRef, }); } #endif } for (var i = 0; i < functionInfo.Glue.CustomParameters.Count; i++) { var parameterInfo = functionInfo.Glue.CustomParameters[i]; if (separator) contents.Append(", "); separator = true; GenerateCppWrapperManagedToNative(buildData, parameterInfo.Type, caller, out var managedType, out _, functionInfo, out _); contents.Append(managedType); if (parameterInfo.IsRef || parameterInfo.IsOut || UsePassByReference(buildData, parameterInfo.Type, caller)) contents.Append('*'); contents.Append(' '); contents.Append(parameterInfo.Name); } contents.Append(')'); var signatureEnd = contents.Length; if (useSeparateImpl) { // Write declaration only, function definition wil be put in the end of the file CppContentsEnd.AppendFormat("{0} {2}::{1}(", returnValueType, functionInfo.UniqueName, callerName); CppContentsEnd.Append(contents.ToString(signatureStart, signatureEnd - signatureStart)); contents.Append(';').AppendLine(); contents = CppContentsEnd; prevIndent = null; indent = " "; } contents.AppendLine(); contents.Append(prevIndent).AppendLine("{"); #if USE_NETCORE if (buildData.Toolchain?.Compiler == TargetCompiler.MSVC && !useLibraryExportInPlainC) contents.Append(indent).AppendLine($"MSVC_FUNC_EXPORT(\"{libraryEntryPoint}\")"); // Export generated function binding under the C# name #endif if (!functionInfo.IsStatic) contents.Append(indent).AppendLine("if (__obj == nullptr) DebugLog::ThrowNullReference();"); string callBegin = indent; if (functionInfo.Glue.UseReferenceForResult) { callBegin += "*__resultAsRef = "; } else if (!functionInfo.ReturnType.IsVoid) { if (useInlinedReturn) callBegin += "return "; else callBegin += "auto __result = "; } #if USE_NETCORE string callReturnCount = ""; if (returnTypeIsContainer) { callReturnCount = indent; if (functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "BytesContainer") callReturnCount += "*__returnCount = {0}.Length();"; else callReturnCount += "*__returnCount = {0}.Count();"; } #endif string call; if (functionInfo.IsStatic) { // Call native static method string nativeType = caller.Tags != null && caller.Tags.ContainsKey("NativeInvokeUseName") ? caller.Name : caller.NativeName; if (caller.Parent != null && !(caller.Parent is FileInfo)) nativeType = caller.Parent.FullNameNative + "::" + nativeType; call = $"{nativeType}::{functionInfo.Name}"; } else { // Call native member method call = $"__obj->{functionInfo.Name}"; } string callParams = string.Empty; separator = false; for (var i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; if (separator) callParams += ", "; separator = true; var name = parameterInfo.Name; if (CppParamsThatNeedConversion[i] && (!FindApiTypeInfo(buildData, parameterInfo.Type, caller)?.IsStruct ?? false)) name = '*' + name; string param = string.Empty; if (string.IsNullOrWhiteSpace(CppParamsWrappersCache[i])) { // Pass value if (UsePassByReference(buildData, parameterInfo.Type, caller)) param += '*'; param += name; } else { // Convert value param += string.Format(CppParamsWrappersCache[i], name); } // Special case for output result parameters that needs additional converting from native to managed format (such as non-POD structures or output array parameter) if (CppParamsThatNeedConversion[i]) { var apiType = FindApiTypeInfo(buildData, parameterInfo.Type, caller); if (apiType != null) { if (parameterInfo.IsOut) contents.Append(indent).AppendFormat("{1} {0}Temp;", parameterInfo.Name, parameterInfo.Type.GetFullNameNative(buildData, caller, false)).AppendLine(); else contents.Append(indent).AppendFormat("auto {0}Temp = {1};", parameterInfo.Name, param).AppendLine(); if (parameterInfo.Type.IsPtr && !parameterInfo.Type.IsRef) callParams += "&"; callParams += parameterInfo.Name; callParams += "Temp"; } // BytesContainer else if (parameterInfo.Type.Type == "BytesContainer" && parameterInfo.Type.GenericArgs == null) { contents.Append(indent).AppendFormat("BytesContainer {0}Temp;", parameterInfo.Name).AppendLine(); callParams += parameterInfo.Name; callParams += "Temp"; } } // Special case for parameter that cannot be passed directly to the function from the wrapper method input parameter (eg. MArray* converted into BytesContainer uses as BytesContainer&) else if (CppParamsThatNeedLocalVariable[i]) { contents.Append(indent).AppendFormat("auto {0}Temp = {1};", parameterInfo.Name, param).AppendLine(); if (parameterInfo.Type.IsPtr) callParams += "&"; callParams += parameterInfo.Name; callParams += "Temp"; } // Instruct for more optimized value move operation else if (parameterInfo.Type.IsMoveRef) { callParams += $"MoveTemp({param})"; } else { callParams += param; } } #if USE_NETCORE if (!string.IsNullOrEmpty(callReturnCount)) { contents.Append(indent).Append("const auto& __callTemp = ").Append(string.Format(callFormat, call, callParams)).Append(";").AppendLine(); call = "__callTemp"; contents.Append(string.Format(callReturnCount, call)); contents.AppendLine(); contents.Append(callBegin); } else #endif { contents.Append(callBegin); call = string.Format(callFormat, call, callParams); } if (!string.IsNullOrEmpty(returnValueConvert)) { contents.AppendFormat(returnValueConvert, call); } else { contents.Append(call); } contents.Append(';'); contents.AppendLine(); // Convert special parameters back to managed world if (!useInlinedReturn) { for (var i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; // Special case for output result parameters that needs additional converting from native to managed format (such as non-POD structures or output array parameter) if (CppParamsThatNeedConversion[i]) { var value = string.Format(CppParamsThatNeedConversionWrappers[i], parameterInfo.Name + "Temp"); // MObject* parameters returned by reference need write barrier for GC if (parameterInfo.IsOut) { var apiType = FindApiTypeInfo(buildData, parameterInfo.Type, caller); if (apiType != null) { if (apiType.IsClass) { contents.Append(indent).AppendFormat("MCore::GC::WriteRef({0}, (MObject*){1});", parameterInfo.Name, value).AppendLine(); #if USE_NETCORE if (parameterInfo.Type.Type == "Array") { contents.Append(indent).AppendFormat("*__{0}Count = {1}.Count();", parameterInfo.Name, parameterInfo.Name + "Temp").AppendLine(); } #endif continue; } if (apiType.IsStruct && !apiType.IsPod) { // Structure that has reference to managed objects requries copy relevant for GC barriers (on Mono) CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MClass.h"); contents.Append(indent).AppendFormat("{{ auto __temp = {1}; MCore::GC::WriteValue({0}, &__temp, 1, {2}::TypeInitializer.GetClass()); }}", parameterInfo.Name, value, apiType.FullNameNative).AppendLine(); continue; } } else { // BytesContainer if (parameterInfo.Type.Type == "BytesContainer" && parameterInfo.Type.GenericArgs == null) { contents.Append(indent).AppendFormat("MCore::GC::WriteRef({0}, (MObject*){1});", parameterInfo.Name, value).AppendLine(); contents.Append(indent).AppendFormat("*__{0}Count = {1}.Length();", parameterInfo.Name, parameterInfo.Name + "Temp").AppendLine(); continue; } throw new Exception($"Unsupported type of parameter '{parameterInfo}' in method '{functionInfo}' to be passed using 'out'"); } } contents.Append(indent).AppendFormat("*{0} = {1};", parameterInfo.Name, value).AppendLine(); } } } // Free special native resources bool needsCleanup = false; for (var i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; if (/*CppParamsThatNeedConversion[i] ||*/ CppParamsThatNeedLocalVariable[i]) { needsCleanup = true; //break; //if (parameterInfo.Type.Type == "AssetReference") var apiType = FindApiTypeInfo(buildData, parameterInfo.Type, caller); if (apiType != null && apiType.IsStruct && !apiType.IsPod) contents.Append(indent).AppendFormat("FreeNative({0}Temp);", parameterInfo.Name).AppendLine(); } } if (needsCleanup) { //contents.Append(indent).AppendFormat("FreeNative({0});", parameterInfo.Name, value).AppendLine(); } if (!useInlinedReturn && !functionInfo.Glue.UseReferenceForResult && !functionInfo.ReturnType.IsVoid) { contents.Append(indent).Append("return __result;").AppendLine(); } contents.Append(prevIndent).AppendLine("}"); contents.AppendLine(); if (useLibraryExportInPlainC) { // Write simple wrapper to bind internal method CppContentsEnd.AppendFormat("DEFINE_INTERNAL_CALL({0}) {1}(", returnValueType, libraryEntryPoint); var sig = contents.ToString(signatureStart, signatureEnd - signatureStart); CppContentsEnd.Append(sig).AppendLine(); CppContentsEnd.AppendLine("{"); CppContentsEnd.Append($" return {callerName}::{functionInfo.UniqueName}("); while (sig.Contains("Function<")) { // Hack for template args int start = sig.IndexOf("Function<"); int end = sig.IndexOf(">::Signature"); sig = sig.Substring(0, start) + sig.Substring(end + 3); } var args = sig.Split(',', StringSplitOptions.TrimEntries); for (int i = 0; i < args.Length; i++) { var arg = args[i]; if (i + 1 == args.Length) arg = arg.Substring(0, arg.Length - 1); if (arg.Length == 0) continue; var lastSpace = arg.LastIndexOf(' '); if (lastSpace != -1) arg = arg.Substring(lastSpace + 1); if (i != 0) CppContentsEnd.Append(", "); CppContentsEnd.Append(arg); } CppContentsEnd.AppendLine(");"); CppContentsEnd.AppendLine("}").AppendLine(); } } public static void GenerateCppReturn(BuildData buildData, StringBuilder contents, string indent, TypeInfo type) { contents.Append(indent); if (type.IsVoid) { contents.AppendLine("return;"); return; } if (type.IsPtr) { contents.AppendLine("return nullptr;"); return; } contents.AppendLine($"{type} __return {{}};"); contents.Append(indent).AppendLine("return __return;"); } private static void GenerateCppManagedWrapperFunction(BuildData buildData, StringBuilder contents, VirtualClassInfo classInfo, FunctionInfo functionInfo, int scriptVTableSize, int scriptVTableIndex) { if (!EngineConfiguration.WithCSharp(buildData.TargetOptions)) return; contents.AppendFormat(" {0} {1}_ManagedWrapper(", functionInfo.ReturnType, functionInfo.UniqueName); var separator = false; for (var i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; if (separator) contents.Append(", "); separator = true; contents.Append(parameterInfo.Type); contents.Append(' '); contents.Append(parameterInfo.Name); } CppIncludeFiles.Add("Engine/Profiler/ProfilerCPU.h"); contents.Append(')'); contents.AppendLine(); contents.AppendLine(" {"); // Get object string scriptVTableOffset; if (classInfo.IsInterface) { contents.AppendLine($" auto object = ScriptingObject::FromInterface(this, {classInfo.NativeName}::TypeInitializer);"); contents.AppendLine(" if (object == nullptr)"); contents.AppendLine(" {"); contents.AppendLine($" LOG(Error, \"Failed to cast interface {{0}} to scripting object\", TEXT(\"{classInfo.Name}\"));"); GenerateCppReturn(buildData, contents, " ", functionInfo.ReturnType); contents.AppendLine(" }"); contents.AppendLine($" const int32 scriptVTableOffset = {scriptVTableIndex} + object->GetType().GetInterface({classInfo.NativeName}::TypeInitializer)->ScriptVTableOffset;"); scriptVTableOffset = "scriptVTableOffset"; } else { contents.AppendLine($" auto object = ({classInfo.NativeName}*)this;"); scriptVTableOffset = scriptVTableIndex.ToString(); } contents.AppendLine(" static THREADLOCAL void* WrapperCallInstance = nullptr;"); // Base method call contents.AppendLine(" ScriptingTypeHandle managedTypeHandle = object->GetTypeHandle();"); contents.AppendLine(" const ScriptingType* managedTypePtr = &managedTypeHandle.GetType();"); contents.AppendLine(" while (managedTypePtr->Script.Spawn != &ManagedBinaryModule::ManagedObjectSpawn)"); contents.AppendLine(" {"); contents.AppendLine(" managedTypeHandle = managedTypePtr->GetBaseType();"); contents.AppendLine(" managedTypePtr = &managedTypeHandle.GetType();"); contents.AppendLine(" }"); contents.AppendLine(" if (WrapperCallInstance == object)"); contents.AppendLine(" {"); GenerateCppVirtualWrapperCallBaseMethod(buildData, contents, classInfo, functionInfo, "managedTypePtr->Script.ScriptVTableBase", scriptVTableOffset); contents.AppendLine(" }"); contents.AppendLine(" auto scriptVTable = (MMethod**)managedTypePtr->Script.ScriptVTable;"); contents.AppendLine($" ASSERT(scriptVTable && scriptVTable[{scriptVTableOffset}]);"); contents.AppendLine($" auto method = scriptVTable[{scriptVTableOffset}];"); contents.AppendLine(" MObject* exception = nullptr;"); contents.AppendLine(" auto prevWrapperCallInstance = WrapperCallInstance;"); contents.AppendLine(" WrapperCallInstance = object;"); if (functionInfo.Parameters.Count == 0) contents.AppendLine(" void** params = nullptr;"); else contents.AppendLine($" void* params[{functionInfo.Parameters.Count}];"); // If platform supports JITed code execution then use method thunk, otherwise fallback to generic runtime invoke var returnType = functionInfo.ReturnType; var useThunk = buildData.Platform.HasDynamicCodeExecutionSupport && Configuration.AOTMode == DotNetAOTModes.None; if (useThunk) { //contents.AppendLine($" PROFILE_CPU_NAMED(\"{classInfo.FullNameManaged}::{functionInfo.Name}\");"); contents.AppendLine(" PROFILE_CPU_SRC_LOC(method->ProfilerData);"); // Convert parameters into managed format as boxed values var thunkParams = string.Empty; var thunkCall = string.Empty; if (functionInfo.Parameters.Count != 0) { separator = functionInfo.Parameters.Count != 0; for (var i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; var paramIsRef = parameterInfo.IsRef || parameterInfo.IsOut; var paramValue = GenerateCppWrapperNativeToBox(buildData, parameterInfo.Type, classInfo, out var apiType, parameterInfo.Name); var useLocalVar = false; if (paramIsRef) { // Pass as pointer to value when using ref/out parameter contents.Append($" auto __param_{parameterInfo.Name} = {paramValue};").AppendLine(); paramValue = $"&__param_{parameterInfo.Name}"; useLocalVar = true; } CppParamsThatNeedLocalVariable[i] = useLocalVar; if (separator) thunkParams += ", "; if (separator) thunkCall += ", "; separator = true; thunkParams += "void*"; contents.Append($" params[{i}] = {paramValue};").AppendLine(); thunkCall += $"params[{i}]"; } } // Invoke method thunk if (returnType.IsVoid) { contents.AppendLine($" typedef void (*Thunk)(void* instance{thunkParams}, MObject** exception);"); contents.AppendLine(" const auto thunk = (Thunk)method->GetThunk();"); contents.AppendLine($" thunk(object->GetOrCreateManagedInstance(){thunkCall}, &exception);"); } else { contents.AppendLine($" typedef MObject* (*Thunk)(void* instance{thunkParams}, MObject** exception);"); contents.AppendLine(" const auto thunk = (Thunk)method->GetThunk();"); contents.AppendLine($" MObject* __result = thunk(object->GetOrCreateManagedInstance(){thunkCall}, &exception);"); } // Convert parameter values back from managed to native (could be modified there) for (var i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; var paramIsRef = parameterInfo.IsRef || parameterInfo.IsOut; if (paramIsRef && !parameterInfo.Type.IsConst) { // Unbox from MObject* contents.Append($" {parameterInfo.Name} = MUtils::Unbox<{parameterInfo.Type.ToString(false)}>(*(MObject**)params[{i}]);").AppendLine(); } } } else { // Convert parameters into managed format as pointers to value if (functionInfo.Parameters.Count != 0) { for (var i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; var paramIsRef = parameterInfo.IsRef || parameterInfo.IsOut; var paramValue = GenerateCppWrapperNativeToManagedParam(buildData, contents, parameterInfo.Type, parameterInfo.Name, classInfo, paramIsRef, out CppParamsThatNeedLocalVariable[i]); contents.Append($" params[{i}] = {paramValue};").AppendLine(); } } // Invoke method contents.AppendLine(" MObject* __result = method->Invoke(object->GetOrCreateManagedInstance(), params, &exception);"); // Convert parameter values back from managed to native (could be modified there) for (var i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; if ((parameterInfo.IsRef || parameterInfo.IsOut) && !parameterInfo.Type.IsConst) { // Direct value convert var managedToNative = GenerateCppWrapperManagedToNative(buildData, parameterInfo.Type, classInfo, out var managedType, out var apiType, null, out _); var passAsParamPtr = managedType.EndsWith("*"); var useLocalVarPointer = CppParamsThatNeedConversion[i] && !apiType.IsValueType; var paramValue = useLocalVarPointer ? $"*({managedType}{(passAsParamPtr ? "" : "*")}*)params[{i}]" : $"({managedType}{(passAsParamPtr ? "" : "*")})params[{i}]"; if (!string.IsNullOrEmpty(managedToNative)) { if (!passAsParamPtr) paramValue = '*' + paramValue; paramValue = string.Format(managedToNative, paramValue); } else if (!passAsParamPtr) paramValue = '*' + paramValue; contents.Append($" {parameterInfo.Name} = {paramValue};").AppendLine(); } } } contents.AppendLine(" WrapperCallInstance = prevWrapperCallInstance;"); contents.AppendLine(" if (exception)"); contents.AppendLine(" DebugLog::LogException(exception);"); // Unpack returned value if (!returnType.IsVoid) { if (returnType.IsRef) throw new NotSupportedException($"Passing return value by reference is not supported for virtual API methods. Used on method '{functionInfo}'."); if (useThunk) { // Thunk might return value within pointer (eg. as int or boolean) switch (returnType.Type) { case "bool": contents.AppendLine(" return __result != 0;"); break; case "int8": case "int16": case "int32": case "int64": contents.AppendLine($" return ({returnType.Type})(intptr)__result;"); break; case "uint8": case "uint16": case "uint32": case "uint64": contents.AppendLine($" return ({returnType.Type})(uintptr)__result;"); break; default: contents.AppendLine($" return MUtils::Unbox<{returnType}>(__result);"); break; } } else { // Runtime invoke always returns boxed value as MObject* contents.AppendLine($" return MUtils::Unbox<{returnType}>(__result);"); } } contents.AppendLine(" }"); contents.AppendLine(); } private static string GenerateCppScriptVTable(BuildData buildData, StringBuilder contents, VirtualClassInfo classInfo) { var scriptVTableSize = classInfo.GetScriptVTableSize(out var scriptVTableOffset); if (scriptVTableSize == 0) return "nullptr, nullptr"; var scriptVTableIndex = scriptVTableOffset; foreach (var functionInfo in classInfo.Functions) { if (!functionInfo.IsVirtual) continue; GenerateCppManagedWrapperFunction(buildData, contents, classInfo, functionInfo, scriptVTableSize, scriptVTableIndex); GenerateCppScriptWrapperFunction?.Invoke(buildData, classInfo, functionInfo, scriptVTableSize, scriptVTableIndex, contents); scriptVTableIndex++; } CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MMethod.h"); CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MClass.h"); foreach (var functionInfo in classInfo.Functions) { if (!functionInfo.IsVirtual) continue; var thunkParams = string.Empty; var separator = false; for (var i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; if (separator) thunkParams += ", "; separator = true; thunkParams += parameterInfo.Type; } var t = functionInfo.IsConst ? " const" : string.Empty; contents.AppendLine($" typedef {functionInfo.ReturnType} ({classInfo.NativeName}::*{functionInfo.UniqueName}_Signature)({thunkParams}){t};"); if (buildData.Toolchain?.Compiler != TargetCompiler.Clang) { // MSVC or other compiler contents.AppendLine($" typedef {functionInfo.ReturnType} ({classInfo.NativeName}Internal::*{functionInfo.UniqueName}_Internal_Signature)({thunkParams}){t};"); } } contents.AppendLine(""); contents.AppendLine(" static void SetupScriptVTable(MClass* mclass, void**& scriptVTable, void**& scriptVTableBase)"); contents.AppendLine(" {"); if (classInfo.IsInterface) { contents.AppendLine(" ASSERT(scriptVTable);"); } else { contents.AppendLine(" if (!scriptVTable)"); contents.AppendLine(" {"); contents.AppendLine($" scriptVTable = (void**)Platform::Allocate(sizeof(void*) * {scriptVTableSize + 1}, 16);"); contents.AppendLine($" Platform::MemoryClear(scriptVTable, sizeof(void*) * {scriptVTableSize + 1});"); contents.AppendLine($" scriptVTableBase = (void**)Platform::Allocate(sizeof(void*) * {scriptVTableSize + 2}, 16);"); contents.AppendLine(" }"); } scriptVTableIndex = scriptVTableOffset; foreach (var functionInfo in classInfo.Functions) { if (!functionInfo.IsVirtual) continue; contents.AppendLine($" scriptVTable[{scriptVTableIndex++}] = mclass->GetMethod(\"{functionInfo.Name}\", {functionInfo.Parameters.Count});"); } contents.AppendLine(" }"); contents.AppendLine(""); contents.AppendLine(" static void SetupScriptObjectVTable(void** scriptVTable, void** scriptVTableBase, void** vtable, int32 entriesCount, int32 wrapperIndex)"); contents.AppendLine(" {"); scriptVTableIndex = scriptVTableOffset; foreach (var functionInfo in classInfo.Functions) { if (!functionInfo.IsVirtual) continue; contents.AppendLine($" if (scriptVTable[{scriptVTableIndex}])"); contents.AppendLine(" {"); contents.AppendLine($" {functionInfo.UniqueName}_Signature funcPtr = &{classInfo.NativeName}::{functionInfo.Name};"); contents.AppendLine(" const int32 vtableIndex = GetVTableIndex(vtable, entriesCount, *(void**)&funcPtr);"); contents.AppendLine(" if (vtableIndex >= 0 && vtableIndex < entriesCount)"); contents.AppendLine(" {"); contents.AppendLine($" scriptVTableBase[{scriptVTableIndex} + 2] = vtable[vtableIndex];"); for (int i = 0, count = 0; i < ScriptingLangInfos.Count; i++) { var langInfo = ScriptingLangInfos[i]; if (!langInfo.Enabled) continue; if (count == 0) contents.AppendLine(" if (wrapperIndex == 0)"); else contents.AppendLine($" else if (wrapperIndex == {count})"); contents.AppendLine(" {"); contents.AppendLine($" auto thunkPtr = &{classInfo.NativeName}Internal::{functionInfo.UniqueName}{langInfo.VirtualWrapperMethodsPostfix};"); contents.AppendLine(" vtable[vtableIndex] = *(void**)&thunkPtr;"); contents.AppendLine(" }"); count++; } contents.AppendLine(" }"); contents.AppendLine(" else"); contents.AppendLine(" {"); contents.AppendLine($" LOG(Error, \"Failed to find the vtable entry for method {{0}} in class {{1}}\", TEXT(\"{functionInfo.Name}\"), TEXT(\"{classInfo.Name}\"));"); contents.AppendLine(" }"); contents.AppendLine(" }"); scriptVTableIndex++; } // Native interfaces override in managed code requires vtables hacking which requires additional inject on Clang-platforms if (buildData.Toolchain?.Compiler == TargetCompiler.Clang && classInfo.IsClass && classInfo.Interfaces != null) { // Override vtable entries of interface methods (for each virtual function in each interface) foreach (var interfaceInfo in classInfo.Interfaces) { if (interfaceInfo.Access == AccessLevel.Private) continue; foreach (var functionInfo in interfaceInfo.Functions) { if (!functionInfo.IsVirtual) continue; contents.AppendLine(" {"); { var thunkParams = string.Empty; var separator = false; for (var i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; if (separator) thunkParams += ", "; separator = true; thunkParams += parameterInfo.Type; } var t = functionInfo.IsConst ? " const" : string.Empty; contents.AppendLine($" typedef {functionInfo.ReturnType} ({classInfo.NativeName}::*{functionInfo.UniqueName}_Signature)({thunkParams}){t};"); } contents.AppendLine($" {functionInfo.UniqueName}_Signature funcPtr = &{classInfo.NativeName}::{functionInfo.Name};"); contents.AppendLine(" const int32 vtableIndex = GetVTableIndex(vtable, entriesCount, *(void**)&funcPtr);"); contents.AppendLine(" if (vtableIndex >= 0 && vtableIndex < entriesCount)"); contents.AppendLine(" {"); contents.AppendLine($" extern void {interfaceInfo.NativeName}Internal_{functionInfo.UniqueName}_VTableOverride(void*& vtableEntry, int32 wrapperIndex);"); contents.AppendLine($" {interfaceInfo.NativeName}Internal_{functionInfo.UniqueName}_VTableOverride(vtable[vtableIndex], wrapperIndex);"); contents.AppendLine(" }"); contents.AppendLine(" else"); contents.AppendLine(" {"); contents.AppendLine($" LOG(Error, \"Failed to find the vtable entry for method {{0}} in class {{1}}\", TEXT(\"{functionInfo.Name}\"), TEXT(\"{classInfo.Name}\"));"); contents.AppendLine(" }"); contents.AppendLine(" }"); } } } contents.AppendLine(" }"); contents.AppendLine(""); return $"&{classInfo.NativeName}Internal::SetupScriptVTable, &{classInfo.NativeName}Internal::SetupScriptObjectVTable"; } private static string GenerateCppAutoSerializationDefineType(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, ApiTypeInfo caller, TypeInfo memberType, MemberInfo member) { if (memberType.IsBitField) return "_BIT"; return string.Empty; } private static bool GenerateCppAutoSerializationSkip(BuildData buildData, ApiTypeInfo caller, MemberInfo memberInfo, TypeInfo typeInfo) { if (memberInfo.IsStatic || memberInfo.IsConstexpr) return true; if (memberInfo.Access != AccessLevel.Public && !memberInfo.HasAttribute("Serialize")) return true; if (memberInfo.HasAttribute("NoSerialize") || memberInfo.HasAttribute("NonSerialized")) return true; var apiTypeInfo = FindApiTypeInfo(buildData, typeInfo, caller); if (apiTypeInfo != null && apiTypeInfo.IsInterface) return true; // Add includes to properly compile bindings (eg. SoftObjectReference) GenerateCppAddFileReference(buildData, caller, typeInfo, apiTypeInfo); return false; } private static void GenerateCppAutoSerialization(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, ApiTypeInfo typeInfo, string typeNameNative) { var classInfo = typeInfo as ClassInfo; var structureInfo = typeInfo as StructureInfo; var baseType = classInfo?.BaseType ?? structureInfo?.BaseType; if (classInfo != null && classInfo.IsBaseTypeHidden) baseType = null; if (baseType != null && (baseType.Name == "PersistentScriptingObject" || baseType.Name == "ScriptingObject" || baseType.Name == "ManagedScriptingObject")) baseType = null; CppAutoSerializeFields.Clear(); CppAutoSerializeProperties.Clear(); CppIncludeFiles.Add("Engine/Serialization/Serialization.h"); contents.AppendLine(); contents.Append($"void {typeNameNative}::Serialize(SerializeStream& stream, const void* otherObj)").AppendLine(); contents.Append('{').AppendLine(); if (baseType != null) contents.Append($" {baseType.FullNameNative}::Serialize(stream, otherObj);").AppendLine(); contents.Append($" SERIALIZE_GET_OTHER_OBJ({typeNameNative});").AppendLine(); if (classInfo != null) { foreach (var fieldInfo in classInfo.Fields) { if (GenerateCppAutoSerializationSkip(buildData, typeInfo, fieldInfo, fieldInfo.Type)) continue; var typeHint = GenerateCppAutoSerializationDefineType(buildData, contents, moduleInfo, typeInfo, fieldInfo.Type, fieldInfo); contents.Append($" SERIALIZE{typeHint}({fieldInfo.Name});").AppendLine(); CppAutoSerializeFields.Add(fieldInfo); } foreach (var propertyInfo in classInfo.Properties) { if (propertyInfo.Getter == null || propertyInfo.Setter == null) continue; if (GenerateCppAutoSerializationSkip(buildData, typeInfo, propertyInfo, propertyInfo.Type)) continue; CppAutoSerializeProperties.Add(propertyInfo); // Skip writing deprecated properties (read-only deserialization to allow reading old data) if (propertyInfo.HasAttribute("Obsolete")) continue; contents.Append(" {"); contents.Append(" const auto"); if (propertyInfo.Getter.ReturnType.IsConstRef) contents.Append('&'); contents.Append($" value = {propertyInfo.Getter.Name}();"); contents.Append(" if (other) { const auto"); if (propertyInfo.Getter.ReturnType.IsConstRef) contents.Append('&'); contents.Append($" otherValue = other->{propertyInfo.Getter.Name}();"); contents.Append(" if (Serialization::ShouldSerialize(value, &otherValue)) { "); contents.Append($"stream.JKEY(\"{propertyInfo.Name}\"); Serialization::Serialize(stream, value, &otherValue);"); contents.Append(" } } else if (Serialization::ShouldSerialize(value, nullptr)) { "); contents.Append($"stream.JKEY(\"{propertyInfo.Name}\"); Serialization::Serialize(stream, value, nullptr);"); contents.Append(" }"); contents.Append('}').AppendLine(); } } else if (structureInfo != null) { foreach (var fieldInfo in structureInfo.Fields) { if (GenerateCppAutoSerializationSkip(buildData, typeInfo, fieldInfo, fieldInfo.Type)) continue; var typeHint = GenerateCppAutoSerializationDefineType(buildData, contents, moduleInfo, typeInfo, fieldInfo.Type, fieldInfo); contents.Append($" SERIALIZE{typeHint}({fieldInfo.Name});").AppendLine(); CppAutoSerializeFields.Add(fieldInfo); } } contents.Append('}').AppendLine(); contents.AppendLine(); contents.Append($"void {typeNameNative}::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)").AppendLine(); contents.Append('{').AppendLine(); if (baseType != null) contents.Append($" {baseType.FullNameNative}::Deserialize(stream, modifier);").AppendLine(); foreach (var fieldInfo in CppAutoSerializeFields) { var typeHint = GenerateCppAutoSerializationDefineType(buildData, contents, moduleInfo, typeInfo, fieldInfo.Type, fieldInfo); contents.Append($" DESERIALIZE{typeHint}({fieldInfo.Name});").AppendLine(); } foreach (var propertyInfo in CppAutoSerializeProperties) { contents.AppendLine(" {"); contents.AppendLine($" const auto e = SERIALIZE_FIND_MEMBER(stream, \"{propertyInfo.Name}\");"); contents.AppendLine(" if (e != stream.MemberEnd()) {"); contents.AppendLine($" auto p = {propertyInfo.Getter.Name}();"); contents.AppendLine(" Serialization::Deserialize(e->value, p, modifier);"); contents.AppendLine($" {propertyInfo.Setter.Name}(p);"); contents.AppendLine(" }"); contents.AppendLine(" }"); } contents.Append('}').AppendLine(); } private static string GenerateCppInterfaceInheritanceTable(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, VirtualClassInfo typeInfo, string typeNameNative) { var interfacesPtr = "nullptr"; var interfaces = typeInfo.Interfaces; if (interfaces != null) { interfacesPtr = typeNameNative + "_Interfaces"; contents.Append("static const ScriptingType::InterfaceImplementation ").Append(interfacesPtr).AppendLine("[] = {"); for (int i = 0; i < interfaces.Count; i++) { var interfaceInfo = interfaces[i]; var scriptVTableOffset = typeInfo.GetScriptVTableOffset(interfaceInfo); contents.AppendLine($" {{ &{interfaceInfo.NativeName}::TypeInitializer, (int16)VTABLE_OFFSET({typeInfo.NativeName}, {interfaceInfo.NativeName}), {scriptVTableOffset}, true }},"); } contents.AppendLine(" { nullptr, 0 },"); contents.AppendLine("};"); } return interfacesPtr; } private static void GenerateCppClass(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, ClassInfo classInfo) { var classTypeNameNative = classInfo.FullNameNative; var classTypeNameManaged = classInfo.FullNameManaged; var classTypeNameManagedInternalCall = classTypeNameManaged.Replace('+', '/'); var classTypeNameInternal = classInfo.FullNameNativeInternal; var internalTypeName = classTypeNameInternal + "Internal"; var useScripting = classInfo.IsStatic || classInfo.IsScriptingObject; var useCSharp = EngineConfiguration.WithCSharp(buildData.TargetOptions); var hasInterface = classInfo.Interfaces != null && classInfo.Interfaces.Any(x => x.Access == AccessLevel.Public); CppInternalCalls.Clear(); if (classInfo.IsAutoSerialization) GenerateCppAutoSerialization(buildData, contents, moduleInfo, classInfo, classTypeNameNative); GenerateCppTypeInternalsStatics?.Invoke(buildData, classInfo, contents); contents.AppendLine(); contents.Append("class ").Append(internalTypeName).AppendLine(); contents.Append('{').AppendLine(); contents.AppendLine("public:"); // Events foreach (var eventInfo in classInfo.Events) { if (!useScripting || eventInfo.IsHidden) continue; var paramsCount = eventInfo.Type.GenericArgs?.Count ?? 0; CppIncludeFiles.Add("Engine/Profiler/ProfilerCPU.h"); var bindPrefix = eventInfo.IsStatic ? classTypeNameNative + "::" : "__obj->"; if (useCSharp) { // C# event invoking wrapper (calls C# event from C++ delegate) CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MClass.h"); CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MEvent.h"); CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MClass.h"); contents.Append(" "); if (eventInfo.IsStatic) contents.Append("static "); contents.AppendFormat("void {0}_ManagedWrapper(", eventInfo.Name); for (var i = 0; i < paramsCount; i++) { if (i != 0) contents.Append(", "); contents.Append(eventInfo.Type.GenericArgs[i].GetFullNameNative(buildData, classInfo)).Append(" arg" + i); } contents.Append(')').AppendLine(); contents.Append(" {").AppendLine(); if (buildData.Target.IsEditor && false) contents.Append(" MMethod* method = nullptr;").AppendLine(); // TODO: find a better way to cache event method in editor and handle C# hot-reload else contents.Append(" static MMethod* method = nullptr;").AppendLine(); contents.Append(" if (!method)").AppendLine(); contents.AppendFormat(" method = {1}::TypeInitializer.GetClass()->GetMethod(\"Internal_{0}_Invoke\", {2});", eventInfo.Name, classTypeNameNative, paramsCount).AppendLine(); contents.Append(" CHECK(method);").AppendLine(); contents.Append(" MObject* exception = nullptr;").AppendLine(); if (paramsCount == 0) contents.AppendLine(" void** params = nullptr;"); else contents.AppendLine($" void* params[{paramsCount}];"); for (var i = 0; i < paramsCount; i++) { var paramType = eventInfo.Type.GenericArgs[i]; var paramName = "arg" + i; var paramIsRef = paramType.IsRef && !paramType.IsConst; var paramValue = GenerateCppWrapperNativeToManagedParam(buildData, contents, paramType, paramName, classInfo, paramIsRef, out CppParamsThatNeedConversion[i]); contents.Append($" params[{i}] = {paramValue};").AppendLine(); } if (eventInfo.IsStatic) contents.AppendLine(" MObject* instance = nullptr;"); else contents.AppendLine($" MObject* instance = (({classTypeNameNative}*)this)->GetManagedInstance();"); contents.Append(" method->Invoke(instance, params, &exception);").AppendLine(); contents.Append(" if (exception)").AppendLine(); contents.Append(" DebugLog::LogException(exception);").AppendLine(); for (var i = 0; i < paramsCount; i++) { var paramType = eventInfo.Type.GenericArgs[i]; if (paramType.IsRef && !paramType.IsConst) { // Convert value back from managed to native (could be modified there) paramType.IsRef = false; var managedToNative = GenerateCppWrapperManagedToNative(buildData, paramType, classInfo, out var managedType, out var apiType, null, out _); var passAsParamPtr = managedType.EndsWith("*"); var useLocalVarPointer = CppParamsThatNeedConversion[i] && !apiType.IsValueType; var paramValue = useLocalVarPointer ? $"*({managedType}{(passAsParamPtr ? "" : "*")}*)params[{i}]" : $"({managedType}{(passAsParamPtr ? "" : "*")})params[{i}]"; if (!string.IsNullOrEmpty(managedToNative)) { if (!passAsParamPtr) paramValue = '*' + paramValue; paramValue = string.Format(managedToNative, paramValue); } else if (!passAsParamPtr) paramValue = '*' + paramValue; contents.Append($" arg{i} = {paramValue};").AppendLine(); paramType.IsRef = true; } } contents.Append(" }").AppendLine().AppendLine(); // C# event wrapper binding method (binds/unbinds C# wrapper to C++ delegate) CppInternalCalls.Add(new KeyValuePair(eventInfo.Name + "_Bind", eventInfo.Name + "_ManagedBind")); bool useSeparateImpl = false; // True if separate function declaration from implementation contents.AppendFormat(" DLLEXPORT static void {0}_ManagedBind(", eventInfo.Name); var signatureStart = contents.Length; if (buildData.Toolchain?.Compiler == TargetCompiler.Clang) useSeparateImpl = true; // DLLEXPORT doesn't properly export function thus separate implementation from declaration if (!eventInfo.IsStatic) contents.AppendFormat("{0}* __obj, ", classTypeNameNative); contents.Append("bool bind)"); var contentsPrev = contents; var indent = " "; if (useSeparateImpl) { // Write declaration only, function definition wil be put in the end of the file CppContentsEnd.AppendFormat("void {1}::{0}_ManagedBind(", eventInfo.Name, internalTypeName); var sig = contents.ToString(signatureStart, contents.Length - signatureStart); CppContentsEnd.Append(contents.ToString(signatureStart, contents.Length - signatureStart)); contents.Append(';').AppendLine(); contents = CppContentsEnd; indent = null; } contents.AppendLine().Append(indent).Append('{').AppendLine(); if (buildData.Toolchain?.Compiler == TargetCompiler.MSVC) contents.Append(indent).AppendLine($" MSVC_FUNC_EXPORT(\"{classTypeNameManaged}::Internal_{eventInfo.Name}_Bind\")"); // Export generated function binding under the C# name if (!eventInfo.IsStatic) contents.Append(indent).Append(" if (__obj == nullptr) return;").AppendLine(); contents.Append(indent).Append(" Function f;").AppendLine(); if (eventInfo.IsStatic) contents.Append(indent).AppendFormat(" f.Bind<{0}_ManagedWrapper>();", eventInfo.Name).AppendLine(); else contents.Append(indent).AppendFormat(" f.Bind<{1}, &{1}::{0}_ManagedWrapper>(({1}*)__obj);", eventInfo.Name, internalTypeName).AppendLine(); contents.Append(indent).Append(" if (bind)").AppendLine(); contents.Append(indent).AppendFormat(" {0}{1}.Bind(f);", bindPrefix, eventInfo.Name).AppendLine(); contents.Append(indent).Append(" else").AppendLine(); contents.Append(indent).AppendFormat(" {0}{1}.Unbind(f);", bindPrefix, eventInfo.Name).AppendLine(); contents.Append(indent).Append('}').AppendLine().AppendLine(); if (useSeparateImpl) contents = contentsPrev; } // Generic scripting event invoking wrapper (calls scripting code from C++ delegate) CppIncludeFiles.Add("Engine/Scripting/Events.h"); contents.Append(" "); if (eventInfo.IsStatic) contents.Append("static "); contents.AppendFormat("void {0}_Wrapper(", eventInfo.Name); for (var i = 0; i < paramsCount; i++) { if (i != 0) contents.Append(", "); contents.Append(eventInfo.Type.GenericArgs[i].GetFullNameNative(buildData, classInfo)).Append(" arg" + i); } contents.Append(')').AppendLine(); contents.Append(" {").AppendLine(); if (paramsCount == 0) contents.AppendLine(" Variant* params = nullptr;"); else contents.AppendLine($" Variant params[{paramsCount}];"); for (var i = 0; i < paramsCount; i++) { var paramType = eventInfo.Type.GenericArgs[i]; var paramName = "arg" + i; var paramValue = GenerateCppWrapperNativeToVariant(buildData, paramType, classInfo, paramName); contents.Append($" params[{i}] = {paramValue};").AppendLine(); } contents.AppendLine($" ScriptingEvents::Event({(eventInfo.IsStatic ? "nullptr" : "(ScriptingObject*)this")}, Span(params, {paramsCount}), {classTypeNameNative}::TypeInitializer, StringView(TEXT(\"{eventInfo.Name}\"), {eventInfo.Name.Length}));"); contents.Append(" }").AppendLine().AppendLine(); // Scripting event wrapper binding method (binds/unbinds generic wrapper to C++ delegate) contents.AppendFormat(" static void {0}_Bind(", eventInfo.Name); contents.AppendFormat("{0}* __obj, void* instance, bool bind)", classTypeNameNative).AppendLine(); contents.Append(" {").AppendLine(); contents.Append(" Function f;").AppendLine(); if (eventInfo.IsStatic) contents.AppendFormat(" f.Bind<{0}_Wrapper>();", eventInfo.Name).AppendLine(); else contents.AppendFormat(" f.Bind<{1}, &{1}::{0}_Wrapper>(({1}*)instance);", eventInfo.Name, internalTypeName).AppendLine(); contents.Append(" if (bind)").AppendLine(); contents.AppendFormat(" {0}{1}.Bind(f);", bindPrefix, eventInfo.Name).AppendLine(); contents.Append(" else").AppendLine(); contents.AppendFormat(" {0}{1}.Unbind(f);", bindPrefix, eventInfo.Name).AppendLine(); contents.Append(" }").AppendLine().AppendLine(); } // Fields foreach (var fieldInfo in classInfo.Fields) { if (!useScripting || !useCSharp || fieldInfo.IsHidden || fieldInfo.IsConstexpr) continue; if (fieldInfo.Getter != null) GenerateCppWrapperFunction(buildData, contents, classInfo, internalTypeName, fieldInfo.Getter, "{0}"); if (fieldInfo.Setter != null) { var callFormat = "{0} = {1}"; var type = fieldInfo.Setter.Parameters[0].Type; if (type.IsArray) callFormat = $"auto __tmp = {{1}}; for (int32 i = 0; i < {type.ArraySize}; i++) {{0}}[i] = __tmp[i]"; GenerateCppWrapperFunction(buildData, contents, classInfo, internalTypeName, fieldInfo.Setter, callFormat); } } // Properties foreach (var propertyInfo in classInfo.Properties) { if (!useScripting || !useCSharp || propertyInfo.IsHidden) continue; if (propertyInfo.Getter != null) GenerateCppWrapperFunction(buildData, contents, classInfo, internalTypeName, propertyInfo.Getter); if (propertyInfo.Setter != null) GenerateCppWrapperFunction(buildData, contents, classInfo, internalTypeName, propertyInfo.Setter); } // Functions foreach (var functionInfo in classInfo.Functions) { if (!useCSharp || functionInfo.IsHidden) continue; if (!useScripting) throw new Exception($"Not supported function {functionInfo.Name} inside non-static and non-scripting class type {classInfo.Name}."); GenerateCppWrapperFunction(buildData, contents, classInfo, internalTypeName, functionInfo); } // Interface implementation if (hasInterface && useCSharp) { 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."); GenerateCppWrapperFunction(buildData, contents, classInfo, internalTypeName, functionInfo); } } } GenerateCppTypeInternals?.Invoke(buildData, classInfo, contents); // Virtual methods overrides var setupScriptVTable = GenerateCppScriptVTable(buildData, contents, classInfo); // Runtime initialization (internal methods binding) contents.AppendLine(" static void InitRuntime()"); contents.AppendLine(" {"); if (useScripting) { foreach (var eventInfo in classInfo.Events) { // Register scripting event binder contents.AppendLine($" ScriptingEvents::EventsTable[Pair({classTypeNameNative}::TypeInitializer, StringView(TEXT(\"{eventInfo.Name}\"), {eventInfo.Name.Length}))] = (void(*)(ScriptingObject*, void*, bool)){classTypeNameInternal}Internal::{eventInfo.Name}_Bind;"); } } if (useScripting && useCSharp) { #if !USE_NETCORE foreach (var e in CppInternalCalls) { contents.AppendLine($" ADD_INTERNAL_CALL(\"{classTypeNameManagedInternalCall}::Internal_{e.Key}\", &{e.Value});"); } #endif } GenerateCppTypeInitRuntime?.Invoke(buildData, classInfo, contents); contents.AppendLine(" }").AppendLine(); if (!useScripting && !classInfo.IsAbstract) { // Constructor contents.AppendLine(" static void Ctor(void* ptr)"); contents.AppendLine(" {"); contents.AppendLine($" new(ptr){classTypeNameNative}();"); contents.AppendLine(" }").AppendLine(); // Destructor contents.AppendLine(" static void Dtor(void* ptr)"); contents.AppendLine(" {"); contents.AppendLine($" (({classTypeNameNative}*)ptr)->~{classInfo.NativeName}();"); contents.AppendLine(" }").AppendLine(); } contents.Append('}').Append(';').AppendLine(); contents.AppendLine(); // Interfaces var interfacesTable = GenerateCppInterfaceInheritanceTable(buildData, contents, moduleInfo, classInfo, classTypeNameNative); // Type initializer if (GenerateCppIsTemplateInstantiationType(classInfo)) contents.Append("template<> "); contents.Append($"ScriptingTypeInitializer {classTypeNameNative}::TypeInitializer((BinaryModule*)GetBinaryModule{moduleInfo.Name}(), "); contents.Append($"StringAnsiView(\"{classTypeNameManaged}\", {classTypeNameManaged.Length}), "); contents.Append($"sizeof({classTypeNameNative}), "); contents.Append($"&{classTypeNameInternal}Internal::InitRuntime, "); if (useScripting) { if (classInfo.IsStatic || classInfo.NoSpawn) contents.Append("&ScriptingType::DefaultSpawn, "); else contents.Append($"(ScriptingType::SpawnHandler)&{classTypeNameNative}::Spawn, "); if (classInfo.BaseType != null && useScripting) contents.Append($"&{classInfo.BaseType.FullNameNative}::TypeInitializer, "); else contents.Append("nullptr, "); contents.Append(setupScriptVTable); } else { if (classInfo.IsAbstract) contents.Append("nullptr, nullptr, "); else contents.Append($"&{classTypeNameInternal}Internal::Ctor, &{classTypeNameInternal}Internal::Dtor, "); if (classInfo.BaseType != null) contents.Append($"&{classInfo.BaseType.FullNameNative}::TypeInitializer"); else contents.Append("nullptr"); } contents.Append(", ").Append(interfacesTable); contents.Append(");"); contents.AppendLine(); // Nested types foreach (var apiTypeInfo in classInfo.Children) { GenerateCppType(buildData, contents, moduleInfo, apiTypeInfo); } } private static void GenerateCppStruct(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, StructureInfo structureInfo) { var structureTypeNameNative = structureInfo.FullNameNative; var structureTypeNameManaged = structureInfo.FullNameManaged; var structureTypeNameManagedInternalCall = structureTypeNameManaged.Replace('+', '/'); var structureTypeNameInternal = structureInfo.FullNameNativeInternal; var internalTypeName = structureTypeNameInternal + "Internal"; var useCSharp = EngineConfiguration.WithCSharp(buildData.TargetOptions); CppInternalCalls.Clear(); if (structureInfo.IsAutoSerialization) GenerateCppAutoSerialization(buildData, contents, moduleInfo, structureInfo, structureTypeNameNative); GenerateCppTypeInternalsStatics?.Invoke(buildData, structureInfo, contents); contents.AppendLine(); contents.AppendFormat("class {0}Internal", structureTypeNameInternal).AppendLine(); contents.Append('{').AppendLine(); contents.AppendLine("public:"); // Fields foreach (var fieldInfo in structureInfo.Fields) { if (fieldInfo.IsConstexpr) continue; // Static fields are using C++ static value accessed via getter function binding if (fieldInfo.IsStatic) { fieldInfo.Getter = new FunctionInfo { Access = fieldInfo.Access, IsStatic = true, Parameters = new List(), ReturnType = fieldInfo.Type, Name = fieldInfo.Name, UniqueName = "Get" + fieldInfo.Name, }; if (!fieldInfo.IsReadOnly) { fieldInfo.Setter = new FunctionInfo { Access = fieldInfo.Access, IsStatic = true, Parameters = new List { new FunctionInfo.ParameterInfo { Type = fieldInfo.Type, Name = "value", }, }, ReturnType = new TypeInfo { Type = "void", }, Name = fieldInfo.Name, UniqueName = "Set" + fieldInfo.Name, }; } } if (fieldInfo.Getter != null) GenerateCppWrapperFunction(buildData, contents, structureInfo, internalTypeName, fieldInfo.Getter, "{0}"); if (fieldInfo.Setter != null) GenerateCppWrapperFunction(buildData, contents, structureInfo, internalTypeName, fieldInfo.Setter, "{0} = {1}"); } // Functions foreach (var functionInfo in structureInfo.Functions) { // TODO: add support for API functions in structures throw new NotImplementedException($"TODO: add support for API functions in structures (function {functionInfo} in structure {structureInfo.Name})"); //GenerateCppWrapperFunction(buildData, contents, structureInfo, structureTypeNameInternal, functionInfo); } GenerateCppTypeInternals?.Invoke(buildData, structureInfo, contents); contents.AppendLine(" static void InitRuntime()"); contents.AppendLine(" {"); if (useCSharp) { #if !USE_NETCORE foreach (var e in CppInternalCalls) { contents.AppendLine($" ADD_INTERNAL_CALL(\"{structureTypeNameManagedInternalCall}::Internal_{e.Key}\", &{e.Value});"); } #endif } GenerateCppTypeInitRuntime?.Invoke(buildData, structureInfo, contents); contents.AppendLine(" }").AppendLine(); // Constructor for structures contents.AppendLine(" static void Ctor(void* ptr)"); contents.AppendLine(" {"); contents.AppendLine($" new(ptr){structureTypeNameNative}();"); contents.AppendLine(" }").AppendLine(); // Destructor for structures contents.AppendLine(" static void Dtor(void* ptr)"); contents.AppendLine(" {"); contents.AppendLine($" (({structureTypeNameNative}*)ptr)->~{structureInfo.NativeName}();"); contents.AppendLine(" }").AppendLine(); // Copy operator for structures contents.AppendLine(" static void Copy(void* dst, void* src)"); contents.AppendLine(" {"); contents.AppendLine($" *({structureTypeNameNative}*)dst = *({structureTypeNameNative}*)src;"); contents.AppendLine(" }").AppendLine(); if (useCSharp) { // Boxing structures from native data to managed object CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MClass.h"); if (!structureInfo.IsPod && !CppUsedNonPodTypes.Contains(structureInfo)) CppUsedNonPodTypes.Add(structureInfo); contents.AppendLine(" static MObject* Box(void* ptr)"); contents.AppendLine(" {"); if (structureInfo.MarshalAs != null) contents.AppendLine($" MISSING_CODE(\"Boxing native type {structureInfo.Name} as {structureInfo.MarshalAs}\"); return nullptr;"); // TODO: impl this else if (structureInfo.IsPod) contents.AppendLine($" return MCore::Object::Box(ptr, {structureTypeNameNative}::TypeInitializer.GetClass());"); else contents.AppendLine($" return MUtils::Box(*({structureTypeNameNative}*)ptr, {structureTypeNameNative}::TypeInitializer.GetClass());"); contents.AppendLine(" }").AppendLine(); // Unboxing structures from managed object to native data contents.AppendLine(" static void Unbox(void* ptr, MObject* managed)"); contents.AppendLine(" {"); if (structureInfo.MarshalAs != null) contents.AppendLine($" MISSING_CODE(\"Boxing native type {structureInfo.Name} as {structureInfo.MarshalAs}\");"); // TODO: impl this else if (structureInfo.IsPod) contents.AppendLine($" Platform::MemoryCopy(ptr, MCore::Object::Unbox(managed), sizeof({structureTypeNameNative}));"); else contents.AppendLine($" *({structureTypeNameNative}*)ptr = ToNative(*({GenerateCppManagedWrapperName(structureInfo)}*)MCore::Object::Unbox(managed));"); contents.AppendLine(" }").AppendLine(); } else { contents.AppendLine(" static MObject* Box(void* ptr)"); contents.AppendLine(" {"); contents.AppendLine(" return nullptr;"); contents.AppendLine(" }").AppendLine(); contents.AppendLine(" static void Unbox(void* ptr, MObject* managed)"); contents.AppendLine(" {"); contents.AppendLine(" }").AppendLine(); } // Getter for structure field contents.AppendLine(" static void GetField(void* ptr, const String& name, Variant& value)"); contents.AppendLine(" {"); for (int i = 0, count = 0; i < structureInfo.Fields.Count; i++) { var fieldInfo = structureInfo.Fields[i]; if (fieldInfo.IsReadOnly || fieldInfo.IsStatic || fieldInfo.IsConstexpr || fieldInfo.Access == AccessLevel.Private) continue; if (count == 0) contents.AppendLine($" if (name == TEXT(\"{fieldInfo.Name}\"))"); else contents.AppendLine($" else if (name == TEXT(\"{fieldInfo.Name}\"))"); contents.AppendLine($" value = {GenerateCppWrapperNativeToVariant(buildData, fieldInfo.Type, structureInfo, $"(({structureTypeNameNative}*)ptr)->{fieldInfo.Name}")};"); count++; } contents.AppendLine(" }").AppendLine(); // Setter for structure field contents.AppendLine(" static void SetField(void* ptr, const String& name, const Variant& value)"); contents.AppendLine(" {"); for (int i = 0, count = 0; i < structureInfo.Fields.Count; i++) { var fieldInfo = structureInfo.Fields[i]; if (fieldInfo.IsReadOnly || fieldInfo.IsStatic || fieldInfo.IsConstexpr || fieldInfo.Access == AccessLevel.Private) continue; if (count == 0) contents.AppendLine($" if (name == TEXT(\"{fieldInfo.Name}\"))"); else contents.AppendLine($" else if (name == TEXT(\"{fieldInfo.Name}\"))"); if (fieldInfo.Type.IsArray) { // Fixed-size array need a special converting to unpack from Variant contents.AppendLine(" {"); contents.AppendLine(" CHECK(value.Type.Type == VariantType::Array);"); contents.AppendLine(" auto* array = reinterpret_cast*>(value.AsData);"); contents.AppendLine($" for (int32 i = 0; i < {fieldInfo.Type.ArraySize} && i < array->Count(); i++)"); contents.AppendLine($" (({structureTypeNameNative}*)ptr)->{fieldInfo.Name}[i] = *({fieldInfo.Type}*)array->At(i).AsBlob.Data;"); contents.AppendLine(" }"); } else contents.AppendLine($" (({structureTypeNameNative}*)ptr)->{fieldInfo.Name} = {GenerateCppWrapperVariantToNative(buildData, fieldInfo.Type, structureInfo, "value")};"); count++; } contents.AppendLine(" }"); contents.Append('}').Append(';').AppendLine(); contents.AppendLine(); if (GenerateCppIsTemplateInstantiationType(structureInfo)) contents.Append("template<> "); contents.Append($"ScriptingTypeInitializer {structureTypeNameNative}::TypeInitializer((BinaryModule*)GetBinaryModule{moduleInfo.Name}(), "); contents.Append($"StringAnsiView(\"{structureTypeNameManaged}\", {structureTypeNameManaged.Length}), "); contents.Append($"sizeof({structureTypeNameNative}), "); contents.Append($"&{structureTypeNameInternal}Internal::InitRuntime, "); contents.Append($"&{structureTypeNameInternal}Internal::Ctor, &{structureTypeNameInternal}Internal::Dtor, &{structureTypeNameInternal}Internal::Copy, &{structureTypeNameInternal}Internal::Box, &{structureTypeNameInternal}Internal::Unbox, &{structureTypeNameInternal}Internal::GetField, &{structureTypeNameInternal}Internal::SetField);").AppendLine(); // Nested types foreach (var apiTypeInfo in structureInfo.Children) { GenerateCppType(buildData, contents, moduleInfo, apiTypeInfo); } } private static void GenerateCppEnum(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, EnumInfo enumInfo) { var enumTypeNameNative = enumInfo.FullNameNative; var enumTypeNameManaged = enumInfo.FullNameManaged; var enumTypeNameInternal = enumInfo.FullNameNativeInternal; contents.AppendLine(); contents.AppendFormat("class {0}Internal", enumTypeNameInternal).AppendLine(); contents.Append('{').AppendLine(); contents.AppendLine("public:"); contents.AppendLine(" static ScriptingType::EnumItem Items[];"); contents.Append('}').Append(';').AppendLine(); contents.AppendLine(); // Items contents.AppendFormat("ScriptingType::EnumItem {0}Internal::Items[] = {{", enumTypeNameInternal).AppendLine(); foreach (var entry in enumInfo.Entries) contents.AppendFormat(" {{ (uint64){0}::{1}, \"{1}\" }},", enumTypeNameNative, entry.Name).AppendLine(); contents.AppendLine(" { 0, nullptr }"); contents.AppendLine("};").AppendLine(); contents.Append($"ScriptingTypeInitializer {enumTypeNameInternal}_TypeInitializer((BinaryModule*)GetBinaryModule{moduleInfo.Name}(), "); contents.Append($"StringAnsiView(\"{enumTypeNameManaged}\", {enumTypeNameManaged.Length}), "); contents.Append($"sizeof({enumTypeNameNative}), "); contents.Append($"{enumTypeNameInternal}Internal::Items);").AppendLine(); contents.AppendLine($"template<> {moduleInfo.Name.ToUpperInvariant()}_API ScriptingTypeHandle StaticType<{enumTypeNameNative}>() {{ return {enumTypeNameInternal}_TypeInitializer; }}"); } private static void GenerateCppInterface(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, InterfaceInfo interfaceInfo) { var interfaceTypeNameNative = interfaceInfo.FullNameNative; var interfaceTypeNameManaged = interfaceInfo.FullNameManaged; var interfaceTypeNameInternal = interfaceInfo.FullNameNativeInternal; GenerateCppTypeInternalsStatics?.Invoke(buildData, interfaceInfo, contents); // Wrapper interface implement to invoke scripting if inherited in C# or VS contents.AppendLine(); contents.AppendFormat("class {0}Wrapper : public ", interfaceTypeNameInternal).Append(interfaceTypeNameNative).AppendLine(); contents.Append('{').AppendLine(); contents.AppendLine("public:"); contents.AppendLine(" ScriptingObject* Object;"); foreach (var functionInfo in interfaceInfo.Functions) { if (!functionInfo.IsVirtual) continue; contents.AppendFormat(" {0} {1}(", functionInfo.ReturnType, functionInfo.Name); var separator = false; for (var i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; if (separator) contents.Append(", "); separator = true; contents.Append(parameterInfo.Type).Append(' ').Append(parameterInfo.Name); } contents.Append(')'); if (functionInfo.IsConst) contents.Append(" const"); contents.Append(" override").AppendLine(); contents.AppendLine(" {"); // TODO: try to use ScriptVTable for interfaces implementation in scripting to call proper function instead of manually check at runtime if (functionInfo.Parameters.Count != 0) { contents.AppendLine($" Variant parameters[{functionInfo.Parameters.Count}];"); for (var i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; contents.AppendLine($" parameters[{i}] = {GenerateCppWrapperNativeToVariant(buildData, parameterInfo.Type, interfaceInfo, parameterInfo.Name)};"); } } else { contents.AppendLine(" Variant* parameters = nullptr;"); } contents.AppendLine(" auto typeHandle = Object->GetTypeHandle();"); contents.AppendLine(" while (typeHandle)"); contents.AppendLine(" {"); contents.AppendLine($" auto method = typeHandle.Module->FindMethod(typeHandle, StringAnsiView(\"{functionInfo.Name}\", {functionInfo.Name.Length}), {functionInfo.Parameters.Count});"); contents.AppendLine(" if (method)"); contents.AppendLine(" {"); contents.AppendLine(" Variant __result;"); contents.AppendLine($" typeHandle.Module->InvokeMethod(method, Object, Span(parameters, {functionInfo.Parameters.Count}), __result);"); // Convert parameter values back from scripting to native (could be modified there) for (var i = 0; i < functionInfo.Parameters.Count; i++) { var parameterInfo = functionInfo.Parameters[i]; var paramIsRef = parameterInfo.IsRef || parameterInfo.IsOut; if (paramIsRef && !parameterInfo.Type.IsConst) { contents.AppendLine($" {parameterInfo.Name} = {GenerateCppWrapperVariantToNative(buildData, parameterInfo.Type, interfaceInfo, $"parameters[{i}]")};"); } } if (functionInfo.ReturnType.IsVoid) contents.AppendLine(" return;"); else contents.AppendLine($" return {GenerateCppWrapperVariantToNative(buildData, functionInfo.ReturnType, interfaceInfo, "__result")};"); contents.AppendLine(" }"); contents.AppendLine(" typeHandle = typeHandle.GetType().GetBaseType();"); contents.AppendLine(" }"); GenerateCppReturn(buildData, contents, " ", functionInfo.ReturnType); contents.AppendLine(" }"); } if (interfaceInfo.Name == "ISerializable") { // TODO: how to handle other interfaces that have some abstract native methods? maybe NativeOnly tag on interface? do it right and remove this hack contents.AppendLine(" void Serialize(SerializeStream& stream, const void* otherObj) override {} void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override {}"); } contents.Append('}').Append(';').AppendLine(); contents.AppendLine(); contents.AppendFormat("class {0}Internal", interfaceTypeNameInternal).AppendLine(); contents.Append('{').AppendLine(); contents.AppendLine("public:"); GenerateCppTypeInternals?.Invoke(buildData, interfaceInfo, contents); // Virtual methods overrides var setupScriptVTable = GenerateCppScriptVTable(buildData, contents, interfaceInfo); // Runtime initialization (internal methods binding) contents.AppendLine(" static void InitRuntime()"); contents.AppendLine(" {"); GenerateCppTypeInitRuntime?.Invoke(buildData, interfaceInfo, contents); contents.AppendLine(" }").AppendLine(); // Interface implementation wrapper accessor for scripting types contents.AppendLine(" static void* GetInterfaceWrapper(ScriptingObject* __obj)"); contents.AppendLine(" {"); contents.AppendLine($" auto wrapper = New<{interfaceTypeNameInternal}Wrapper>();"); contents.AppendLine(" wrapper->Object = __obj;"); contents.AppendLine(" return wrapper;"); contents.AppendLine(" }"); contents.Append('}').Append(';').AppendLine(); contents.AppendLine(); // Native interfaces override in managed code requires vtables hacking which requires additional inject on Clang-platforms if (buildData.Toolchain?.Compiler == TargetCompiler.Clang) { // Generate functions that inject script wrappers into vtable entry foreach (var functionInfo in interfaceInfo.Functions) { if (!functionInfo.IsVirtual) continue; contents.AppendLine($"void {interfaceInfo.NativeName}Internal_{functionInfo.UniqueName}_VTableOverride(void*& vtableEntry, int32 wrapperIndex)"); contents.AppendLine("{"); for (int i = 0, count = 0; i < ScriptingLangInfos.Count; i++) { var langInfo = ScriptingLangInfos[i]; if (!langInfo.Enabled) continue; if (count == 0) contents.AppendLine(" if (wrapperIndex == 0)"); else contents.AppendLine($" else if (wrapperIndex == {count})"); contents.AppendLine(" {"); contents.AppendLine($" auto thunkPtr = &{interfaceInfo.NativeName}Internal::{functionInfo.UniqueName}{langInfo.VirtualWrapperMethodsPostfix};"); contents.AppendLine(" vtableEntry = *(void**)&thunkPtr;"); contents.AppendLine(" }"); count++; } contents.AppendLine("}"); contents.AppendLine(""); } } // Type initializer contents.Append($"ScriptingTypeInitializer {interfaceTypeNameNative}::TypeInitializer((BinaryModule*)GetBinaryModule{moduleInfo.Name}(), "); contents.Append($"StringAnsiView(\"{interfaceTypeNameManaged}\", {interfaceTypeNameManaged.Length}), &{interfaceTypeNameInternal}Internal::InitRuntime,"); contents.Append(setupScriptVTable).Append($", &{interfaceTypeNameInternal}Internal::GetInterfaceWrapper").Append(");"); contents.AppendLine(); // Nested types foreach (var apiTypeInfo in interfaceInfo.Children) { GenerateCppType(buildData, contents, moduleInfo, apiTypeInfo); } } private static void GenerateCppType(BuildData buildData, StringBuilder contents, ModuleInfo moduleInfo, object type) { if (type is ApiTypeInfo apiTypeInfo && apiTypeInfo.SkipGeneration) return; try { if (type is ClassInfo classInfo) GenerateCppClass(buildData, contents, moduleInfo, classInfo); else if (type is StructureInfo structureInfo) GenerateCppStruct(buildData, contents, moduleInfo, structureInfo); else if (type is EnumInfo enumInfo) GenerateCppEnum(buildData, contents, moduleInfo, enumInfo); else if (type is InterfaceInfo interfaceInfo) GenerateCppInterface(buildData, contents, moduleInfo, interfaceInfo); else if (type is InjectCodeInfo injectCodeInfo && string.Equals(injectCodeInfo.Lang, "cpp", StringComparison.OrdinalIgnoreCase)) contents.AppendLine(injectCodeInfo.Code); } catch { Log.Error($"Failed to generate C++ bindings for {type}."); throw; } } private static void GenerateCppCppUsedNonPodTypes(BuildData buildData, ApiTypeInfo apiType) { if (CppUsedNonPodTypesList.Contains(apiType)) return; if (apiType is ClassStructInfo classStructInfo) { if (classStructInfo.MarshalAs != null) return; if (classStructInfo.IsTemplate) throw new Exception($"Cannot use template type '{classStructInfo}' as non-POD type for cross-language bindings."); } if (apiType is StructureInfo structureInfo) { // Check all fields (if one of them is also non-POD structure then we need to generate wrappers for them too) for (var i = 0; i < structureInfo.Fields.Count; i++) { var fieldInfo = structureInfo.Fields[i]; if (fieldInfo.IsStatic || fieldInfo.IsConstexpr) continue; var fieldApiType = FindApiTypeInfo(buildData, fieldInfo.Type, structureInfo); if (fieldApiType != null && fieldApiType.IsStruct && !fieldApiType.IsPod) GenerateCppCppUsedNonPodTypes(buildData, fieldApiType); if (fieldInfo.Type.GenericArgs != null) { foreach (var fieldTypeArg in fieldInfo.Type.GenericArgs) { fieldApiType = FindApiTypeInfo(buildData, fieldTypeArg, structureInfo); if (fieldApiType != null && fieldApiType.IsStruct && !fieldApiType.IsPod) GenerateCppCppUsedNonPodTypes(buildData, fieldApiType); } } } } CppUsedNonPodTypesList.Add(apiType); } private static void GenerateCpp(BuildData buildData, ModuleInfo moduleInfo, ref BindingsResult bindings) { var contents = GetStringBuilder(); CppContentsEnd = GetStringBuilder(); CppUsedNonPodTypes.Clear(); CppReferencesFiles.Clear(); CppIncludeFiles.Clear(); CppIncludeFilesList.Clear(); CppVariantToTypes.Clear(); CppVariantFromTypes.Clear(); CurrentModule = moduleInfo; // Disable C# scripting based on configuration ScriptingLangInfos[0].Enabled = EngineConfiguration.WithCSharp(buildData.TargetOptions); contents.AppendLine("// This code was auto-generated. Do not modify it."); contents.AppendLine(); contents.AppendLine("#include \"Engine/Core/Compiler.h\""); contents.AppendLine("PRAGMA_DISABLE_DEPRECATION_WARNINGS"); // Disable deprecated warnings in generated code contents.AppendLine("#include \"Engine/Scripting/Scripting.h\""); contents.AppendLine("#include \"Engine/Scripting/Internal/InternalCalls.h\""); contents.AppendLine("#include \"Engine/Scripting/ManagedCLR/MUtils.h\""); contents.AppendLine("#include \"Engine/Scripting/ManagedCLR/MCore.h\""); contents.AppendLine($"#include \"{moduleInfo.Name}.Gen.h\""); for (int i = 0; i < moduleInfo.Children.Count; i++) { if (moduleInfo.Children[i] is FileInfo fileInfo) { CppReferencesFiles.Add(fileInfo); } } var headerPos = contents.Length; foreach (var child in moduleInfo.Children) { foreach (var apiTypeInfo in child.Children) { GenerateCppType(buildData, contents, moduleInfo, apiTypeInfo); } } GenerateCppModuleSource?.Invoke(buildData, moduleInfo, contents); { var header = GetStringBuilder(); // Variant converting helper methods foreach (var e in CppVariantToTypes) { var wrapperName = e.Key; var typeInfo = e.Value; var name = typeInfo.ToString(false); header.AppendLine(); header.AppendLine("namespace {"); header.Append($"{name} VariantTo{wrapperName}(const Variant& v)").AppendLine(); header.Append('{').AppendLine(); header.Append($" {name} result;").AppendLine(); if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) { header.Append(" const auto* array = reinterpret_cast*>(v.AsData);").AppendLine(); header.Append(" const int32 length = v.Type.Type == VariantType::Array ? array->Count() : 0;").AppendLine(); header.Append(" result.Resize(length);").AppendLine(); header.Append(" for (int32 i = 0; i < length; i++)").AppendLine(); header.Append($" result[i] = {GenerateCppWrapperVariantToNative(buildData, typeInfo.GenericArgs[0], moduleInfo, "array->At(i)")};").AppendLine(); } else if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null) { header.Append(" const auto* dictionary = v.AsDictionary;").AppendLine(); header.Append(" if (dictionary)").AppendLine(); header.Append(" {").AppendLine(); header.Append(" result.EnsureCapacity(dictionary->Count());").AppendLine(); header.Append(" for (auto& e : *dictionary)").AppendLine(); header.Append($" result.Add({GenerateCppWrapperVariantToNative(buildData, typeInfo.GenericArgs[0], moduleInfo, "e.Key")}, {GenerateCppWrapperVariantToNative(buildData, typeInfo.GenericArgs[1], moduleInfo, "e.Value")});").AppendLine(); header.Append(" }").AppendLine(); } else throw new NotSupportedException($"Invalid type {typeInfo} to unpack from Variant."); header.Append(" return result;").AppendLine(); header.Append('}').AppendLine(); header.AppendLine("}"); } foreach (var e in CppVariantFromTypes) { var wrapperName = e.Key; var typeInfo = e.Value; header.AppendLine(); header.AppendLine("namespace {"); header.Append("Variant VariantFrom"); if (typeInfo.IsArray) { typeInfo.IsArray = false; header.Append($"{wrapperName}Array(const {typeInfo}* v, const int32 length)").AppendLine(); header.Append('{').AppendLine(); header.Append(" Variant result;").AppendLine(); header.Append(" result.SetType(VariantType(VariantType::Array));").AppendLine(); header.Append(" auto* array = reinterpret_cast*>(result.AsData);").AppendLine(); header.Append(" array->Resize(length);").AppendLine(); header.Append(" for (int32 i = 0; i < length; i++)").AppendLine(); header.Append($" array->At(i) = {GenerateCppWrapperNativeToVariant(buildData, typeInfo, moduleInfo, "v[i]")};").AppendLine(); typeInfo.IsArray = true; } else if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) { var valueType = typeInfo.GenericArgs[0]; header.Append($"{wrapperName}Array(const {valueType}* v, const int32 length)").AppendLine(); header.Append('{').AppendLine(); header.Append(" Variant result;").AppendLine(); header.Append(" result.SetType(VariantType(VariantType::Array));").AppendLine(); header.Append(" auto* array = reinterpret_cast*>(result.AsData);").AppendLine(); header.Append(" array->Resize(length);").AppendLine(); header.Append(" for (int32 i = 0; i < length; i++)").AppendLine(); header.Append($" array->At(i) = {GenerateCppWrapperNativeToVariant(buildData, valueType, moduleInfo, "v[i]")};").AppendLine(); } else if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null) { var keyType = typeInfo.GenericArgs[0]; var valueType = typeInfo.GenericArgs[1]; header.Append($"{wrapperName}Dictionary(const Dictionary<{keyType}, {valueType}>& v)").AppendLine(); header.Append('{').AppendLine(); header.Append(" Variant result;").AppendLine(); header.Append(" result.SetType(VariantType(VariantType::Dictionary));").AppendLine(); header.Append(" auto* dictionary = result.AsDictionary;").AppendLine(); header.Append(" dictionary->EnsureCapacity(v.Count());").AppendLine(); header.Append(" for (auto& e : v)").AppendLine(); header.Append($" dictionary->Add({GenerateCppWrapperNativeToVariant(buildData, keyType, moduleInfo, "e.Key")}, {GenerateCppWrapperNativeToVariant(buildData, valueType, moduleInfo, "e.Value")});").AppendLine(); } else throw new NotSupportedException($"Invalid type {typeInfo} to pack to Variant."); header.Append(" return result;").AppendLine(); header.Append('}').AppendLine(); header.AppendLine("}"); } // Non-POD types CppUsedNonPodTypesList.Clear(); if (EngineConfiguration.WithCSharp(buildData.TargetOptions)) { for (int k = 0; k < CppUsedNonPodTypes.Count; k++) GenerateCppCppUsedNonPodTypes(buildData, CppUsedNonPodTypes[k]); } foreach (var apiType in CppUsedNonPodTypesList) { header.AppendLine(); var wrapperName = GenerateCppManagedWrapperName(apiType); var structureInfo = apiType as StructureInfo; var classInfo = apiType as ClassInfo; List fields; if (structureInfo != null) fields = structureInfo.Fields; else if (classInfo != null) fields = classInfo.Fields; else throw new Exception("Not supported Non-POD type " + apiType); CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MClass.h"); // Get the full typename with nested parent prefix var fullName = apiType.FullNameNative; if (structureInfo != null) { // Generate managed type memory layout header.Append("struct ").Append(wrapperName).AppendLine(); header.Append('{').AppendLine(); if (classInfo != null) header.AppendLine(" MObject obj;"); for (var i = 0; i < fields.Count; i++) { var fieldInfo = fields[i]; if (fieldInfo.IsStatic) continue; string type; if (fieldInfo.NoArray && fieldInfo.Type.IsArray) { // Fixed-size array that needs to be inlined into structure instead of passing it as managed array fieldInfo.Type.IsArray = false; CppParamsWrappersCache[i] = GenerateCppWrapperNativeToManaged(buildData, fieldInfo.Type, apiType, out type, null); fieldInfo.Type.IsArray = true; header.AppendFormat(" {0} {1}[{2}];", type, fieldInfo.Name, fieldInfo.Type.ArraySize).AppendLine(); continue; } CppParamsWrappersCache[i] = GenerateCppWrapperNativeToManaged(buildData, fieldInfo.Type, apiType, out type, null); header.AppendFormat(" {0} {1};", type, fieldInfo.Name).AppendLine(); } header.Append('}').Append(';').AppendLine(); // Generate forward declarations of structure converting functions header.AppendLine(); header.AppendLine("namespace {"); header.AppendFormat("{0} ToManaged(const {1}& value);", wrapperName, fullName).AppendLine(); header.AppendFormat("{1} ToNative(const {0}& value);", wrapperName, fullName).AppendLine(); header.AppendFormat("void FreeNative({0}& value);", fullName).AppendLine(); header.AppendLine("}"); // Generate MConverter for a structure header.Append("template<>").AppendLine(); header.AppendFormat("struct MConverter<{0}>", fullName).AppendLine(); header.Append('{').AppendLine(); header.AppendFormat(" MObject* Box(const {0}& data, const MClass* klass)", fullName).AppendLine(); header.Append(" {").AppendLine(); header.Append(" auto managed = ToManaged(data);").AppendLine(); header.Append(" return MCore::Object::Box((void*)&managed, klass);").AppendLine(); header.Append(" }").AppendLine(); header.AppendFormat(" void Unbox({0}& result, MObject* data)", fullName).AppendLine(); header.Append(" {").AppendLine(); header.AppendFormat(" result = ToNative(*reinterpret_cast<{0}*>(MCore::Object::Unbox(data)));", wrapperName).AppendLine(); header.Append(" }").AppendLine(); header.AppendFormat(" void ToManagedArray(MArray* result, const Span<{0}>& data)", fullName).AppendLine(); header.Append(" {").AppendLine(); header.AppendFormat(" MClass* klass = {0}::TypeInitializer.GetClass();", fullName).AppendLine(); header.AppendFormat(" {0}* resultPtr = ({0}*)MCore::Array::GetAddress(result);", wrapperName).AppendLine(); header.Append(" for (int32 i = 0; i < data.Length(); i++)").AppendLine(); header.Append(" {").AppendLine(); header.Append(" auto managed = ToManaged(data[i]);").AppendLine(); header.Append(" MCore::GC::WriteValue(&resultPtr[i], &managed, 1, klass);").AppendLine(); header.Append(" }").AppendLine(); header.Append(" }").AppendLine(); header.AppendFormat(" void ToNativeArray(Span<{0}>& result, const MArray* data)", fullName).AppendLine(); header.Append(" {").AppendLine(); header.AppendFormat(" {0}* dataPtr = ({0}*)MCore::Array::GetAddress(data);", wrapperName).AppendLine(); header.Append(" for (int32 i = 0; i < result.Length(); i++)").AppendLine(); header.Append(" result[i] = ToNative(dataPtr[i]);").AppendLine(); header.Append(" }").AppendLine(); header.Append('}').Append(';').AppendLine(); // Generate converting function native -> managed header.AppendLine(); header.AppendLine("namespace {"); header.AppendFormat("{0} ToManaged(const {1}& value)", wrapperName, fullName).AppendLine(); header.Append('{').AppendLine(); header.AppendFormat(" {0} result;", wrapperName).AppendLine(); for (var i = 0; i < fields.Count; i++) { var fieldInfo = fields[i]; if (fieldInfo.IsStatic || fieldInfo.IsConstexpr) continue; if (fieldInfo.NoArray && fieldInfo.Type.IsArray) { // Fixed-size array needs to unbox every item manually if not using managed array if (fieldInfo.Type.IsPod(buildData, apiType)) header.AppendFormat(" Platform::MemoryCopy(result.{0}, value.{0}, sizeof({2}) * {1});", fieldInfo.Name, fieldInfo.Type.ArraySize, fieldInfo.Type).AppendLine(); else header.AppendFormat(" for (int32 i = 0; i < {0}; i++)", fieldInfo.Type.ArraySize).AppendLine().AppendFormat(" result.{0}[i] = value.{0}[i];", fieldInfo.Name).AppendLine(); continue; } var wrapper = CppParamsWrappersCache[i]; header.AppendFormat(" result.{0} = ", fieldInfo.Name); if (string.IsNullOrEmpty(wrapper)) header.Append("value." + fieldInfo.Name); else header.AppendFormat(wrapper, "value." + fieldInfo.Name); header.Append(';').AppendLine(); } header.Append(" return result;").AppendLine(); header.Append('}').AppendLine(); // Generate converting function managed -> native header.AppendLine(); header.AppendFormat("{1} ToNative(const {0}& value)", wrapperName, fullName).AppendLine(); header.Append('{').AppendLine(); header.AppendFormat(" {0} result;", fullName).AppendLine(); for (var i = 0; i < fields.Count; i++) { var fieldInfo = fields[i]; if (fieldInfo.IsStatic || fieldInfo.IsConstexpr) continue; CppNonPodTypesConvertingGeneration = true; var wrapper = GenerateCppWrapperManagedToNative(buildData, fieldInfo.Type, apiType, out _, out _, null, out _); CppNonPodTypesConvertingGeneration = false; if (fieldInfo.Type.IsArray) { // Fixed-size array needs to unbox every item manually if (fieldInfo.NoArray) { if (fieldInfo.Type.IsPod(buildData, apiType)) header.AppendFormat(" Platform::MemoryCopy(result.{0}, value.{0}, sizeof({2}) * {1});", fieldInfo.Name, fieldInfo.Type.ArraySize, fieldInfo.Type).AppendLine(); else header.AppendFormat(" for (int32 i = 0; i < {0}; i++)", fieldInfo.Type.ArraySize).AppendLine().AppendFormat(" result.{0}[i] = value.{0}[i];", fieldInfo.Name).AppendLine(); } else { wrapper = string.Format(wrapper.Remove(wrapper.Length - 6), string.Format("value.{0}", fieldInfo.Name)); header.AppendFormat(" auto tmp{0} = {1};", fieldInfo.Name, wrapper).AppendLine(); header.AppendFormat(" for (int32 i = 0; i < {0} && i < tmp{1}.Count(); i++)", fieldInfo.Type.ArraySize, fieldInfo.Name).AppendLine(); header.AppendFormat(" result.{0}[i] = tmp{0}[i];", fieldInfo.Name).AppendLine(); } continue; } if (fieldInfo.Type.Type == "AssetReference" && !string.IsNullOrEmpty(wrapper)) header.AppendFormat(" result.{0}.SetUnsafe({1});", fieldInfo.Name, string.Format(wrapper, string.Format("value.{0}", fieldInfo.Name))).AppendLine(); else if (string.IsNullOrEmpty(wrapper)) header.AppendFormat(" result.{0} = value.{0};", fieldInfo.Name).AppendLine(); else header.AppendFormat(" result.{0} = {1};", fieldInfo.Name, string.Format(wrapper, string.Format("value.{0}", fieldInfo.Name))).AppendLine(); } header.Append(" return result;").AppendLine(); header.Append('}').AppendLine(); // Generate cleanup function for native resources header.AppendLine(); header.AppendFormat("void FreeNative({0}& value)", fullName).AppendLine(); header.Append('{').AppendLine(); for (var i = 0; i < fields.Count; i++) { var fieldInfo = fields[i]; if (fieldInfo.IsStatic || fieldInfo.IsConstexpr) continue; if (fieldInfo.Type.Type == "AssetReference") header.AppendFormat(" value.{0}.SetUnsafe(nullptr);", fieldInfo.Name).AppendLine(); } header.Append('}').AppendLine(); header.AppendLine("}"); } else if (classInfo != null) { // Generate MConverter for a class header.Append("template<>").AppendLine(); header.AppendFormat("struct MConverter<{0}>", fullName).AppendLine(); header.Append('{').AppendLine(); header.AppendFormat(" static MObject* Box(const {0}& data, const MClass* klass)", fullName).AppendLine(); header.Append(" {").AppendLine(); header.Append(" MObject* obj = MCore::Object::New(klass);").AppendLine(); for (var i = 0; i < fields.Count; i++) { var fieldInfo = fields[i]; if (fieldInfo.IsStatic || fieldInfo.IsConstexpr) continue; var fieldType = FindApiTypeInfo(buildData, fieldInfo.Type, apiType); if (fieldInfo == null || fieldType.IsValueType) // TODO: support any value type (eg. by boxing) throw new Exception($"Not supported field {fieldInfo.Type} {fieldInfo.Name} in class {classInfo.Name}."); var wrapper = GenerateCppWrapperNativeToManaged(buildData, fieldInfo.Type, apiType, out var type, null); var value = string.IsNullOrEmpty(wrapper) ? "data." + fieldInfo.Name : string.Format(wrapper, "data." + fieldInfo.Name); header.AppendFormat(" klass->GetField(\"{0}\")->SetValue(obj, {1});", fieldInfo.Name, value).AppendLine(); } header.Append(" return obj;").AppendLine(); header.Append(" }").AppendLine(); header.AppendFormat(" static MObject* Box(const {0}& data)", fullName).AppendLine(); header.Append(" {").AppendLine(); header.AppendFormat(" MClass* klass = {0}::TypeInitializer.GetClass();", fullName).AppendLine(); header.Append(" return Box(data, klass);").AppendLine(); header.Append(" }").AppendLine(); header.AppendFormat(" static void Unbox({0}& result, MObject* obj)", fullName).AppendLine(); header.Append(" {").AppendLine(); header.Append(" MClass* klass = MCore::Object::GetClass(obj);").AppendLine(); header.Append(" void* v = nullptr;").AppendLine(); for (var i = 0; i < fields.Count; i++) { var fieldInfo = fields[i]; if (fieldInfo.IsStatic || fieldInfo.IsConstexpr) continue; CppNonPodTypesConvertingGeneration = true; var wrapper = GenerateCppWrapperManagedToNative(buildData, fieldInfo.Type, apiType, out var type, out _, null, out _); CppNonPodTypesConvertingGeneration = false; CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MField.h"); header.AppendFormat(" klass->GetField(\"{0}\")->GetValue(obj, &v);", fieldInfo.Name).AppendLine(); var value = $"({type})v"; header.AppendFormat(" result.{0} = {1};", fieldInfo.Name, string.IsNullOrEmpty(wrapper) ? value : string.Format(wrapper, value)).AppendLine(); } header.Append(" }").AppendLine(); header.AppendFormat(" static {0} Unbox(MObject* data)", fullName).AppendLine(); header.Append(" {").AppendLine(); header.AppendFormat(" {0} result;", fullName).AppendLine(); header.Append(" Unbox(result, data);").AppendLine(); header.Append(" return result;").AppendLine(); header.Append(" }").AppendLine(); header.AppendFormat(" void ToManagedArray(MArray* result, const Span<{0}>& data)", fullName).AppendLine(); header.Append(" {").AppendLine(); header.Append(" for (int32 i = 0; i < data.Length(); i++)").AppendLine(); header.Append(" MCore::GC::WriteArrayRef(result, Box(data[i]), i);").AppendLine(); header.Append(" }").AppendLine(); header.AppendFormat(" void ToNativeArray(Span<{0}>& result, const MArray* data)", fullName).AppendLine(); header.Append(" {").AppendLine(); header.Append(" MObject** dataPtr = (MObject**)MCore::Array::GetAddress(data);").AppendLine(); header.Append(" for (int32 i = 0; i < result.Length(); i++)").AppendLine(); header.AppendFormat(" Unbox(result[i], dataPtr[i]);", fullName).AppendLine(); header.Append(" }").AppendLine(); header.Append('}').Append(';').AppendLine(); } } contents.Insert(headerPos, header.ToString()); // Includes header.Clear(); CppReferencesFiles.Remove(null); CppIncludeFilesList.Clear(); foreach (var fileInfo in CppReferencesFiles) CppIncludeFilesList.Add(fileInfo.Name); CppIncludeFilesList.AddRange(CppIncludeFiles); CppIncludeFilesList.Sort(); if (CppIncludeFilesList.Remove("Engine/Serialization/Serialization.h")) CppIncludeFilesList.Add("Engine/Serialization/Serialization.h"); foreach (var path in CppIncludeFilesList) header.AppendFormat("#include \"{0}\"", path).AppendLine(); contents.Insert(headerPos, header.ToString()); PutStringBuilder(header); } if (CppContentsEnd.Length != 0) { contents.AppendLine().Append(CppContentsEnd); } PutStringBuilder(CppContentsEnd); contents.AppendLine("PRAGMA_ENABLE_DEPRECATION_WARNINGS"); Utilities.WriteFileIfChanged(bindings.GeneratedCppFilePath, contents.ToString()); PutStringBuilder(contents); } private static void GenerateCpp(BuildData buildData, IGrouping binaryModule) { // Skip generating C++ bindings code for C#-only modules if (binaryModule.Any(x => !x.BuildNativeCode)) return; var useCSharp = binaryModule.Any(x => x.BuildCSharp); var contents = GetStringBuilder(); var binaryModuleName = binaryModule.Key; var binaryModuleNameUpper = binaryModuleName.ToUpperInvariant(); var project = Builder.GetModuleProject(binaryModule.First(), buildData); var version = project.Version; // Generate C++ binary module header var binaryModuleHeaderPath = Path.Combine(project.ProjectFolderPath, "Source", binaryModuleName + ".Gen.h"); contents.AppendLine("// This code was auto-generated. Do not modify it."); contents.AppendLine(); contents.AppendLine("#pragma once"); contents.AppendLine(); contents.AppendLine($"#define {binaryModuleNameUpper}_NAME \"{binaryModuleName}\""); if (version.Build <= 0) contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION Version({version.Major}, {version.Minor})"); else if (version.Revision <= 0) contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION Version({version.Major}, {version.Minor}, {version.Build})"); else contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION Version({version.Major}, {version.Minor}, {version.Build}, {version.Revision})"); contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_TEXT \"{version}\""); contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_MAJOR {version.Major}"); contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_MINOR {version.Minor}"); contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_BUILD {version.Build}"); contents.AppendLine($"#define {binaryModuleNameUpper}_VERSION_REVISION {version.Revision}"); contents.AppendLine($"#define {binaryModuleNameUpper}_COMPANY \"{project.Company}\""); contents.AppendLine($"#define {binaryModuleNameUpper}_COPYRIGHT \"{project.Copyright}\""); contents.AppendLine(); contents.AppendLine("class BinaryModule;"); contents.AppendLine($"extern \"C\" {binaryModuleNameUpper}_API BinaryModule* GetBinaryModule{binaryModuleName}();"); GenerateCppBinaryModuleHeader?.Invoke(buildData, binaryModule, contents); Utilities.WriteFileIfChanged(binaryModuleHeaderPath, contents.ToString()); // Generate C++ binary module implementation contents.Clear(); var binaryModuleSourcePath = Path.Combine(project.ProjectFolderPath, "Source", binaryModuleName + ".Gen.cpp"); contents.AppendLine("// This code was auto-generated. Do not modify it."); contents.AppendLine(); contents.AppendLine("#include \"Engine/Scripting/BinaryModule.h\""); contents.AppendLine($"#include \"{binaryModuleName}.Gen.h\""); contents.AppendLine(); contents.AppendLine($"StaticallyLinkedBinaryModuleInitializer StaticallyLinkedBinaryModule{binaryModuleName}(GetBinaryModule{binaryModuleName});"); contents.AppendLine(); contents.AppendLine($"extern \"C\" BinaryModule* GetBinaryModule{binaryModuleName}()"); contents.AppendLine("{"); if (useCSharp) { contents.AppendLine($" static NativeBinaryModule module(\"{binaryModuleName}\");"); } else { contents.AppendLine($" static NativeOnlyBinaryModule module(\"{binaryModuleName}\");"); } contents.AppendLine(" return &module;"); contents.AppendLine("}"); GenerateCppBinaryModuleSource?.Invoke(buildData, binaryModule, contents); Utilities.WriteFileIfChanged(binaryModuleSourcePath, contents.ToString()); PutStringBuilder(contents); } } }