3444 lines
186 KiB
C#
3444 lines
186 KiB
C#
// Copyright (c) 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<KeyValuePair<string, string>> CppInternalCalls = new List<KeyValuePair<string, string>>();
|
|
public static readonly List<ApiTypeInfo> CppUsedNonPodTypes = new List<ApiTypeInfo>();
|
|
private static readonly List<ApiTypeInfo> CppUsedNonPodTypesList = new List<ApiTypeInfo>();
|
|
public static readonly HashSet<FileInfo> CppReferencesFiles = new HashSet<FileInfo>();
|
|
private static readonly List<FieldInfo> CppAutoSerializeFields = new List<FieldInfo>();
|
|
private static readonly List<PropertyInfo> CppAutoSerializeProperties = new List<PropertyInfo>();
|
|
public static readonly HashSet<string> CppIncludeFiles = new HashSet<string>();
|
|
private static readonly List<string> CppIncludeFilesList = new List<string>();
|
|
private static readonly Dictionary<string, TypeInfo> CppVariantToTypes = new Dictionary<string, TypeInfo>();
|
|
private static readonly Dictionary<string, TypeInfo> CppVariantFromTypes = new Dictionary<string, TypeInfo>();
|
|
private static bool CppNonPodTypesConvertingGeneration = false;
|
|
private static StringBuilder CppContentsEnd;
|
|
|
|
public class ScriptingLangInfo
|
|
{
|
|
public bool Enabled;
|
|
public string VirtualWrapperMethodsPostfix;
|
|
}
|
|
|
|
public static readonly List<ScriptingLangInfo> ScriptingLangInfos = new List<ScriptingLangInfo>
|
|
{
|
|
new ScriptingLangInfo
|
|
{
|
|
Enabled = true,
|
|
VirtualWrapperMethodsPostfix = "_ManagedWrapper", // C#
|
|
},
|
|
};
|
|
|
|
public static event Action<BuildData, IGrouping<string, Module>, StringBuilder> GenerateCppBinaryModuleHeader;
|
|
public static event Action<BuildData, IGrouping<string, Module>, StringBuilder> GenerateCppBinaryModuleSource;
|
|
public static event Action<BuildData, ModuleInfo, StringBuilder> GenerateCppModuleSource;
|
|
public static event Action<BuildData, ApiTypeInfo, StringBuilder> GenerateCppTypeInternalsStatics;
|
|
public static event Action<BuildData, ApiTypeInfo, StringBuilder> GenerateCppTypeInternals;
|
|
public static event Action<BuildData, ApiTypeInfo, StringBuilder> GenerateCppTypeInitRuntime;
|
|
public static event Action<BuildData, VirtualClassInfo, FunctionInfo, int, int, StringBuilder> GenerateCppScriptWrapperFunction;
|
|
|
|
private static readonly List<string> CppInBuildVariantStructures = new List<string>
|
|
{
|
|
"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_orig_{paramName} = {result};").AppendLine();
|
|
contents.Append($" auto __param_{paramName} = __param_orig_{paramName};").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.Type == "SoftTypeReference")
|
|
return $"Variant::Typename(StringAnsiView({value}))";
|
|
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.Type == "SoftTypeReference")
|
|
return $"(StringAnsiView){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))
|
|
{
|
|
var name = apiType.FullNameNative;
|
|
if (typeInfo.GenericArgs != null)
|
|
{
|
|
name += '<';
|
|
for (var i = 0; i < typeInfo.GenericArgs.Count; i++)
|
|
{
|
|
if (i != 0)
|
|
name += ", ";
|
|
name += typeInfo.GenericArgs[i];
|
|
}
|
|
name += '>';
|
|
}
|
|
if (typeInfo.IsPtr)
|
|
return $"({name}*){value}.AsBlob.Data";
|
|
else
|
|
return $"*({name}*){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<TypeInfo> { 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<TypeInfo> { 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)
|
|
{
|
|
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})";
|
|
}
|
|
else if (apiType.IsScriptingObject && typeInfo.IsRef && !typeInfo.IsPtr)
|
|
{
|
|
// Scripting Object is passed as pointer from C# but C++ uses reference
|
|
type = typeInfo.ToString(false) + '*';
|
|
return $"InternalGetReference({{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<string, string>(functionInfo.UniqueName, classStructInfo.Name + "::" + functionInfo.Name));
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (functionInfo.UniqueName == "SetView" && callerName == "SceneRenderTaskInternal")
|
|
callerName = callerName;
|
|
if (functionInfo.UniqueName == "SetDriveControl")
|
|
callerName = callerName;
|
|
|
|
// 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<FunctionInfo.ParameterInfo>(),
|
|
};
|
|
var returnType = functionInfo.ReturnType;
|
|
var returnApiType = FindApiTypeInfo(buildData, returnType, caller);
|
|
if (returnApiType != null && returnApiType.MarshalAs != null)
|
|
returnType = returnApiType.MarshalAs;
|
|
|
|
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 (returnType.Type == "Array" || returnType.Type == "Span" || returnType.Type == "DataContainer" || returnType.Type == "BitArray" || 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<string, string>(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,
|
|
IsConst = true,
|
|
});
|
|
}
|
|
#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 (!returnType.IsVoid)
|
|
{
|
|
if (useInlinedReturn)
|
|
callBegin += "return ";
|
|
else
|
|
callBegin += "auto __result = ";
|
|
}
|
|
|
|
#if USE_NETCORE
|
|
string callReturnCount = "";
|
|
if (returnTypeIsContainer)
|
|
{
|
|
// Array marshallers need to know amount of items written in the buffer
|
|
callReturnCount = indent;
|
|
if (returnType.Type == "Span" || 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 if (parameterInfo.Type.IsRef && !parameterInfo.Type.IsConst)
|
|
{
|
|
// Non-const lvalue reference parameters needs to be passed via temporary value
|
|
contents.Append(indent).AppendFormat("auto {0}Temp = {1};", parameterInfo.Name, param).AppendLine();
|
|
callParams += parameterInfo.Name;
|
|
callParams += "Temp";
|
|
}
|
|
else
|
|
{
|
|
callParams += param;
|
|
}
|
|
}
|
|
|
|
#if USE_NETCORE
|
|
if (!string.IsNullOrEmpty(callReturnCount))
|
|
{
|
|
var tempVar = returnTypeIsContainer && returnType != functionInfo.ReturnType ? $"{returnType} __callTemp = " : "const auto& __callTemp = ";
|
|
contents.Append(indent).Append(tempVar).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")
|
|
{
|
|
// Array marshallers need to know amount of items written in the buffer
|
|
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();
|
|
|
|
// Array marshallers need to know amount of items written in the buffer
|
|
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();
|
|
#if USE_NETCORE
|
|
if (parameterInfo.Type.Type == "Array")
|
|
{
|
|
// Array marshallers need to know amount of items written in the buffer
|
|
contents.Append(indent).AppendFormat("*__{0}Count = {1}.Count();", parameterInfo.Name, parameterInfo.Name + "Temp").AppendLine();
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!useInlinedReturn && !functionInfo.Glue.UseReferenceForResult && !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_orig_{parameterInfo.Name} = {paramValue};").AppendLine();
|
|
contents.Append($" auto __param_{parameterInfo.Name} = __param_orig_{parameterInfo.Name};").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();
|
|
|
|
// Release temporary GCHandles
|
|
/*if (passAsParamPtr)
|
|
{
|
|
//contents.Append($" if (params[{i}] != (void*)&__param_{parameterInfo.Name})").AppendLine();
|
|
contents.Append($" {{").AppendLine();
|
|
contents.Append($" auto __param{i}_handle = *(MGCHandle*)¶ms[{i}];").AppendLine();
|
|
contents.Append($" MCore::GCHandle::Free(__param{i}_handle);").AppendLine();
|
|
contents.Append($" }}").AppendLine();
|
|
}
|
|
else
|
|
{
|
|
//contents.Append($" if (params[{i}] != (void*)&__param_{parameterInfo.Name})").AppendLine();
|
|
contents.Append($" {{").AppendLine();
|
|
contents.Append($" auto __param{i}_handle = *(MGCHandle*)¶ms[{i}];").AppendLine();
|
|
contents.Append($" MCore::GCHandle::Free(__param{i}_handle);").AppendLine();
|
|
contents.Append($" }}").AppendLine();
|
|
}*/
|
|
}
|
|
}
|
|
}
|
|
|
|
// Release GCHandles of boxed values
|
|
for (var i = 0; i < functionInfo.Parameters.Count; i++)
|
|
{
|
|
var parameterInfo = functionInfo.Parameters[i];
|
|
var paramValue = GenerateCppWrapperNativeToBox(buildData, parameterInfo.Type, classInfo, out var apiType, parameterInfo.Name);
|
|
var paramIsRef = parameterInfo.IsRef || parameterInfo.IsOut;
|
|
|
|
if (paramValue.Contains("MUtils::Box<")) // FIXME
|
|
{
|
|
if (paramIsRef)
|
|
{
|
|
//contents.Append($" MUtils::FreeManaged<{parameterInfo.Type.ToString(false)}>((MObject*)__param_{parameterInfo.Name});").AppendLine();
|
|
/*if (useThunk)
|
|
{
|
|
// Release the original handle
|
|
contents.Append($" if (__param_orig_{parameterInfo.Name} != __param_{parameterInfo.Name}) //asdf1a").AppendLine();
|
|
contents.Append($" MUtils::FreeManaged<{parameterInfo.Type.ToString(false)}>((MObject*)__param_{parameterInfo.Name});").AppendLine();
|
|
}*/
|
|
contents.Append($" MUtils::FreeManaged<{parameterInfo.Type.Type}>((MObject*)__param_{parameterInfo.Name});").AppendLine();
|
|
if (useThunk)
|
|
{
|
|
// Release the original handle
|
|
contents.Append($" if (__param_orig_{parameterInfo.Name} != __param_{ parameterInfo.Name})").AppendLine();
|
|
contents.Append($" MUtils::FreeManaged<{parameterInfo.Type.Type}>((MObject*)__param_orig_{parameterInfo.Name});").AppendLine();
|
|
}
|
|
}
|
|
else if (apiType != null && !apiType.IsInBuild)
|
|
{
|
|
// int: ispod, isvaluetype
|
|
// vector: ispod, isvaluetype, isstruct
|
|
// guid: ispod, isvaluetype, isstruct, isinbuild
|
|
contents.Append($" MUtils::FreeManaged<{parameterInfo.Type.Type}>((MObject*)params[{i}]); //asdf1b").AppendLine();
|
|
//contents.Append($" auto __param{i}_handle = *(MGCHandle*)¶ms[{i}]; // asdf1b").AppendLine();
|
|
//contents.Append($" ASSERT((((unsigned long long)__param{i}_handle & 0xC000000000000000) >> 62) == 0);").AppendLine();
|
|
//contents.Append($" MCore::GCHandle::Free(__param{i}_handle);").AppendLine();
|
|
}
|
|
else if (apiType != null && !apiType.IsValueType)
|
|
{
|
|
if (parameterInfo.Type.Type.ToLower().Contains("string"))
|
|
apiType = apiType;
|
|
contents.Append($" MUtils::FreeManaged<{parameterInfo.Type.Type}>((MObject*)params[{i}]); //asdf1d").AppendLine();
|
|
}
|
|
else //if (apiType != null)
|
|
{
|
|
if (parameterInfo.Type.Type.ToLower().Contains("string"))
|
|
apiType = apiType;
|
|
//contents.Append($" //asdf1c {parameterInfo.Type.Type}").AppendLine();
|
|
contents.Append($" auto __param{i}_handle = *(MGCHandle*)¶ms[{i}]; // asdf1c").AppendLine();
|
|
//contents.Append($" ASSERT((((unsigned long long)__param{i}_handle & 0xC000000000000000) >> 62) == 0);").AppendLine();
|
|
contents.Append($" MCore::GCHandle::Free(__param{i}_handle);").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";
|
|
if (memberType.IsPtr)
|
|
{
|
|
var t = FindApiTypeInfo(buildData, memberType, caller);
|
|
if (t.IsScriptingObject)
|
|
return "_OBJ";
|
|
}
|
|
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<class Texture>)
|
|
GenerateCppAddFileReference(buildData, caller, typeInfo, apiTypeInfo);
|
|
|
|
// TODO: find a better way to reference other include files for types that have separate serialization header
|
|
if (typeInfo.Type.EndsWith("Curve") && typeInfo.GenericArgs != null)
|
|
CppIncludeFilesList.Add("Engine/Animations/CurveSerialization.h");
|
|
|
|
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, ClassStructInfo typeInfo, string typeNameNative, string typeNameInternal)
|
|
{
|
|
var interfacesPtr = "nullptr";
|
|
var interfaces = typeInfo.Interfaces;
|
|
if (interfaces != null)
|
|
{
|
|
var virtualTypeInfo = typeInfo as VirtualClassInfo;
|
|
interfacesPtr = typeNameInternal + "_Interfaces";
|
|
contents.Append("static const ScriptingType::InterfaceImplementation ").Append(interfacesPtr).AppendLine("[] = {");
|
|
for (int i = 0; i < interfaces.Count; i++)
|
|
{
|
|
var interfaceInfo = interfaces[i];
|
|
var scriptVTableOffset = virtualTypeInfo?.GetScriptVTableOffset(interfaceInfo) ?? 0;
|
|
contents.AppendLine($" {{ &{interfaceInfo.NativeName}::TypeInitializer, (int16)VTABLE_OFFSET({typeNameNative}, {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();
|
|
contents.Append(" static MMethod* method = nullptr;").AppendLine();
|
|
contents.AppendFormat(" if (!MCore::Ready) {{ MCore::OnManagedEventAfterShutdown(\"{0}.{1}\"); return; }}", classTypeNameManaged, eventInfo.Name).AppendLine();
|
|
contents.AppendFormat(" if (!method) {{ method = {1}::TypeInitializer.GetClass()->GetMethod(\"Internal_{0}_Invoke\", {2}); CHECK(method); }}", eventInfo.Name, classTypeNameNative, paramsCount).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();
|
|
|
|
// Release temporary GCHandles
|
|
if (CppParamsThatNeedConversion[i])
|
|
{
|
|
// Release the original handle
|
|
contents.Append($" if (&__param_orig_arg{i} != &__param_arg{i})").AppendLine();
|
|
contents.Append($" FreeManaged(({managedType})__param_arg{i});").AppendLine();
|
|
}
|
|
/*contents.Append($" FreeManaged(({managedType})__param_arg{i}); //hmm1b").AppendLine();
|
|
if (CppParamsThatNeedConversion[i])
|
|
{
|
|
// Release the original handle
|
|
contents.Append($" if (&__param_orig_arg{i} != &__param_arg{i})").AppendLine();
|
|
contents.Append($" FreeManaged(({managedType})__param_orig_arg{i});").AppendLine();
|
|
}*/
|
|
|
|
paramType.IsRef = true;
|
|
}
|
|
}
|
|
contents.Append(" }").AppendLine().AppendLine();
|
|
|
|
// C# event wrapper binding method (binds/unbinds C# wrapper to C++ delegate)
|
|
CppInternalCalls.Add(new KeyValuePair<string, string>(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<void(");
|
|
for (var i = 0; i < paramsCount; i++)
|
|
{
|
|
if (i != 0)
|
|
contents.Append(", ");
|
|
contents.Append(eventInfo.Type.GenericArgs[i].GetFullNameNative(buildData, classInfo));
|
|
}
|
|
contents.Append(")> 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<Variant>(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<void(");
|
|
for (var i = 0; i < paramsCount; i++)
|
|
{
|
|
if (i != 0)
|
|
contents.Append(", ");
|
|
contents.Append(eventInfo.Type.GenericArgs[i].GetFullNameNative(buildData, classInfo));
|
|
}
|
|
contents.Append(")> 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<ScriptingTypeHandle, StringView>({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, classTypeNameInternal);
|
|
|
|
// 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)
|
|
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).Append(");").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<FunctionInfo.ParameterInfo>(),
|
|
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<FunctionInfo.ParameterInfo>
|
|
{
|
|
new FunctionInfo.ParameterInfo
|
|
{
|
|
Type = fieldInfo.Type,
|
|
Name = "value",
|
|
IsConst = true,
|
|
},
|
|
},
|
|
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<const Array<Variant, HeapAllocation>*>(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();
|
|
|
|
// Interfaces
|
|
var interfacesTable = GenerateCppInterfaceInheritanceTable(buildData, contents, moduleInfo, structureInfo, structureTypeNameNative, structureTypeNameInternal);
|
|
|
|
// Type initializer
|
|
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, ");
|
|
if (structureInfo.BaseType != null)
|
|
contents.Append($"&{structureInfo.BaseType.FullNameNative}::TypeInitializer");
|
|
else
|
|
contents.Append("nullptr");
|
|
contents.Append(", ").Append(interfacesTable).Append(");").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<Variant>(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<const Array<Variant, HeapAllocation>*>(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<Array<Variant, HeapAllocation>*>(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<Array<Variant, HeapAllocation>*>(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<FieldInfo> 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 FreeManaged(const {0}& value);", wrapperName).AppendLine();
|
|
header.AppendLine("}");
|
|
|
|
// Generate MConverter for a structure
|
|
header.Append("template<>").AppendLine();
|
|
header.AppendFormat("struct MConverter<{0}>", fullName).AppendLine();
|
|
header.Append('{').AppendLine();
|
|
|
|
header.AppendFormat(" DLLEXPORT USED 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(" DLLEXPORT USED 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.AppendFormat(" auto managed = *reinterpret_cast<{0}*>(MCore::Object::Unbox(data));", wrapperName).AppendLine();
|
|
header.AppendFormat(" result = ToNative(managed);", wrapperName).AppendLine();
|
|
header.AppendFormat(" ::FreeManaged(managed);", wrapperName).AppendLine();
|
|
header.Append(" }").AppendLine();
|
|
|
|
header.AppendFormat(" DLLEXPORT USED 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(" DLLEXPORT USED 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.AppendFormat(" DLLEXPORT USED void FreeManaged(MObject* data)", fullName).AppendLine();
|
|
header.Append(" {").AppendLine();
|
|
//header.AppendFormat(" auto managed = MCore::Object::Unbox(data);", wrapperName).AppendLine();
|
|
//header.AppendFormat(" ::FreeManaged(*reinterpret_cast<{0}*>(managed));", wrapperName).AppendLine();
|
|
header.AppendFormat(" MCore::GCHandle::Free(*(MGCHandle*)&data);", wrapperName).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 (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();
|
|
/*if (wrapper.Contains("::ToNative(")) // FIXME
|
|
{
|
|
header.Append($" auto __param{fieldInfo.Name}_handle = *(MGCHandle*)&value.{fieldInfo.Name};").AppendLine();
|
|
header.Append($" ASSERT((((unsigned long long)__param{fieldInfo.Name}_handle & 0xC000000000000000) >> 62) == 0); // asdf3").AppendLine();
|
|
header.Append($" MCore::GCHandle::Free(__param{fieldInfo.Name}_handle);").AppendLine();
|
|
}*/
|
|
}
|
|
header.Append(" return result;").AppendLine();
|
|
header.Append('}').AppendLine();
|
|
|
|
// Generate release function for temporary managed handles
|
|
header.AppendLine();
|
|
header.AppendFormat("void FreeManaged(const {0}& value)", wrapperName).AppendLine();
|
|
header.Append('{').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 managedType, out var fieldApiType, null, out _);
|
|
CppNonPodTypesConvertingGeneration = false;
|
|
|
|
if (wrapper.Contains("::ToNative(")) // FIXME fieldApiType.IsScriptingObject
|
|
{
|
|
header.Append($" auto __param{fieldInfo.Name}_handle = *(MGCHandle*)&value.{fieldInfo.Name};").AppendLine();
|
|
//header.Append($" ASSERT((((unsigned long long)__param{fieldInfo.Name}_handle & 0xC000000000000000) >> 62) == 0); // asdf3").AppendLine();
|
|
header.Append($" if ((((unsigned long long)__param{fieldInfo.Name}_handle & 0xC000000000000000) >> 62) != 0) // asdf3").AppendLine();
|
|
header.Append($" __param{fieldInfo.Name}_handle = __param{fieldInfo.Name}_handle;").AppendLine();
|
|
|
|
header.Append($" MCore::GCHandle::Free(__param{fieldInfo.Name}_handle);").AppendLine();
|
|
}
|
|
}
|
|
if (classInfo != null)
|
|
{
|
|
header.Append($" auto __value_handle = *(MGCHandle*)&value;").AppendLine();
|
|
header.Append($" ASSERT((((unsigned long long)__value_handle & 0xC000000000000000) >> 62) == 0); // asdf4").AppendLine();
|
|
header.Append($" MCore::GCHandle::Free(__value_handle);").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(" DLLEXPORT USED 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(" DLLEXPORT USED 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(" DLLEXPORT USED 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(" DLLEXPORT USED 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(" DLLEXPORT USED 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(" DLLEXPORT USED 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.AppendFormat(" DLLEXPORT USED void FreeManaged(MObject* data) // honk1", fullName).AppendLine();
|
|
header.Append(" {").AppendLine();
|
|
//header.Append(" ::FreeManaged(reinterpret_cast<OnlineUserManaged*>(MCore::Object::Unbox(data)));").AppendLine();
|
|
header.Append(" }").AppendLine();
|
|
|
|
header.Append('}').Append(';').AppendLine();
|
|
}
|
|
}
|
|
|
|
contents.Insert(headerPos, header.ToString());
|
|
|
|
// Includes
|
|
header.Clear();
|
|
CppReferencesFiles.Remove(null);
|
|
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"); // Include serialization header as the last one to properly handle specialization of custom types serialization
|
|
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<string, Module> 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);
|
|
}
|
|
}
|
|
}
|