wip 2 no leaks

This commit is contained in:
2025-12-21 17:45:31 +02:00
parent 5d45b9ea1c
commit 0973363c64
10 changed files with 666 additions and 58 deletions

View File

@@ -531,14 +531,25 @@ namespace Flax.Build.Bindings
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);
#if USE_NETCORE
if (arrayApiType?.IsPod ?? false)
{
if (functionInfo == null)
{
// Storage type should be wrapped
type = $"NativeArray<{arrayApiType.FullNameNative}>";
return $"MUtils::ToNativeArrayWrapper<{arrayApiType.FullNameNative}>({{0}})/*sahho*/";
}
else
{
//type = arrayApiType.FullNameNative + "*/*wahho*/";
type = $"NativeArray<{arrayApiType.FullNameNative}>/*tahho*/";
//if (arrayTypeInfo.Type == "bool")
// return "MUtils::ToBoolArray({0})/*bahho*/";
return "MUtils::ToNativeArrayWrapper({0})/*nahhoa7eatra*/";
}
}
#endif
type = "MArray*";
if (arrayApiType != null && arrayApiType.MarshalAs != null)
@@ -554,12 +565,14 @@ namespace Flax.Build.Bindings
return "MUtils::ToArray({0}, " + GenerateCppGetMClass(buildData, arrayTypeInfo, caller, functionInfo) + ")";
}
#if !USE_NETCORE
// Span
if (typeInfo.Type == "Span" && typeInfo.GenericArgs != null)
{
type = "MonoArray*";
return "MUtils::Span({0}, " + GenerateCppGetMClass(buildData, typeInfo.GenericArgs[0], caller, functionInfo) + ")";
}
#endif
// BytesContainer
if (typeInfo.Type == "BytesContainer" && typeInfo.GenericArgs == null)
@@ -590,7 +603,7 @@ namespace Flax.Build.Bindings
{
CppIncludeFiles.Add("Engine/Scripting/Internal/ManagedBitArray.h");
#if USE_NETCORE
// Boolean arrays does not support custom marshalling for some unknown reason
// Special case for copying bits to bytes
type = "bool*";
return "MUtils::ToBoolArray({0})";
#else
@@ -742,6 +755,7 @@ namespace Flax.Build.Bindings
// Array
if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null)
{
needLocalVariable = true;
var arrayTypeInfo = typeInfo.GenericArgs[0];
var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller);
if (arrayApiType != null && arrayApiType.MarshalAs != null)
@@ -761,10 +775,15 @@ namespace Flax.Build.Bindings
genericArgs += ", " + typeInfo.GenericArgs[1];
result = $"Array<{genericArgs}>({result})";
}
else if (arrayApiType?.IsPod ?? false)
{
type = arrayApiType.FullNameNative + '*';
result = $"Array<{genericArgs}>(({arrayApiType.FullNameNative}*){{0}}, {{1}})/*xviox1 {arrayTypeInfo.Type}*/";
}
else if (arrayApiType?.Name == "bool")
{
type = "bool*";
result = "Array<bool>({0}, {1})";
result = "Array<bool>({0}, {1})/*xviox2*/\"";
}
return result;
}
@@ -775,11 +794,20 @@ namespace Flax.Build.Bindings
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)
var arrayApiType = FindApiTypeInfo(buildData, typeInfo.GenericArgs[0], caller);
if (typeInfo.GenericArgs[0].IsPtr && arrayApiType != null && arrayApiType.IsScriptingObject)
{
// TODO: array needs to be freed
return "MUtils::ToSpan<" + typeInfo.GenericArgs[0] + ">(" + "MUtils::ToArray<" + typeInfo.GenericArgs[0] + ">({0}))";
}
else if (arrayApiType?.IsPod ?? false)
{
type = arrayApiType.FullNameNative + '*';
var nativeName = arrayApiType.FullNameNative;
if (typeInfo.GenericArgs[0].IsConst)
nativeName = "const " + nativeName;
return $"Span<{nativeName}>({{0}}, {{1}})/*spantag */";
}
return "MUtils::ToSpan<" + typeInfo.GenericArgs[0] + ">({0})";
}
@@ -1250,7 +1278,11 @@ namespace Flax.Build.Bindings
if (CppParamsThatNeedConversion[i] && (!FindApiTypeInfo(buildData, parameterInfo.Type, caller)?.IsStruct ?? false))
{
name = '*' + name;
countParamName = '*' + countParamName;
countParamName = '*' + countParamName + "/*plop*/";
}
else if (parameterInfo.Type.IsRef)
{
countParamName = '*' + countParamName + "/*plipa*/";
}
string param = string.Empty;
@@ -1348,6 +1380,25 @@ namespace Flax.Build.Bindings
contents.Append(';');
contents.AppendLine();
#if false
for (var i = 0; i < functionInfo.Parameters.Count; i++)
{
var parameterInfo = functionInfo.Parameters[i];
if (CppParamsThatNeedConversion[i] || CppParamsThatNeedLocalVariable[i])
{
var apiType = FindApiTypeInfo(buildData, parameterInfo.Type, caller);
if (parameterInfo.Type.Type == "Array")
{
contents.Append(indent).AppendLine($"/* converthing: {parameterInfo.Name} Type == {parameterInfo.Type.Type} ({parameterInfo.Type.ToString()}) */");
contents.Append(indent).AppendLine($"/* converthing2: {parameterInfo.Name} IsArray == {parameterInfo.Type.IsArray} */");
contents.Append(indent).AppendLine($"/* converthing3: {parameterInfo.Name} apitype == {apiType?.Name ?? ""} */");
//contents.Append(indent).AppendLine($"MUtils::FreeManagedArray<{parameterInfo.Type.GenericArgs[0].ToString(false)}>({parameterInfo.Name}Temp);");
}
}
}
#endif
// Convert special parameters back to managed world
if (!useInlinedReturn)
{
@@ -1359,6 +1410,7 @@ namespace Flax.Build.Bindings
if (CppParamsThatNeedConversion[i])
{
var value = string.Format(CppParamsThatNeedConversionWrappers[i], parameterInfo.Name + "Temp");
var elementType = parameterInfo.Type.GenericArgs != null ? FindApiTypeInfo(buildData, parameterInfo.Type.GenericArgs[0], caller) : null;
// MObject* parameters returned by reference need write barrier for GC
if (parameterInfo.IsOut)
@@ -1368,14 +1420,30 @@ namespace Flax.Build.Bindings
{
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")
{
if (elementType?.IsPod ?? false)
{
contents.Append(indent).Append($"// isgigs1a").AppendLine();
contents.Append(indent).Append($"*{parameterInfo.Name} = ({parameterInfo.Type.GenericArgs[0]}*)MCore::GC::AllocateMemory(sizeof({parameterInfo.Type.GenericArgs[0]}) * {parameterInfo.Name}Temp.Count());").AppendLine();
contents.Append(indent).Append($"Platform::MemoryCopy(*{parameterInfo.Name}, {parameterInfo.Name}Temp.Get(), sizeof({parameterInfo.Type.GenericArgs[0]}) * {parameterInfo.Name}Temp.Count());").AppendLine();
}
else
{
contents.Append(indent).Append($"// isgigs1b").AppendLine();
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}.Count();", parameterInfo.Name, parameterInfo.Name + "Temp").AppendLine();
}
else
#endif
{
contents.Append(indent).Append($"// isgigs2").AppendLine();
contents.Append(indent).AppendFormat("MCore::GC::WriteRef({0}, (MObject*){1});", parameterInfo.Name, value).AppendLine();
}
continue;
}
if (apiType.IsStruct && !apiType.IsPod)
@@ -1391,22 +1459,30 @@ namespace Flax.Build.Bindings
// BytesContainer
if (parameterInfo.Type.Type == "BytesContainer" && parameterInfo.Type.GenericArgs == null)
{
contents.Append(indent).Append($"// hshsf").AppendLine();
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;
//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'");
else
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();
else if (parameterInfo.Type.Type == "Array" && (elementType?.IsPod ?? false))
contents.Append(indent).AppendFormat("*{0} = {1}.data;", parameterInfo.Name, value).AppendLine();
else
contents.Append(indent).AppendFormat("*{0} = {1};", parameterInfo.Name, value).AppendLine();
#if USE_NETCORE
if (parameterInfo.Type.Type == "Array")
if (parameterInfo.Type.Type == "Array" || (parameterInfo.Type.Type == "BytesContainer" && parameterInfo.Type.GenericArgs == null))
{
contents.Append(indent).Append("// hshshj").AppendLine();
// 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();
if (parameterInfo.Type.Type is "Span" or "BytesContainer")
contents.Append(indent).AppendFormat("*__{0}Count = {1}.Length();", parameterInfo.Name, parameterInfo.Name + "Temp").AppendLine();
else
contents.Append(indent).AppendFormat("*__{0}Count = {1}.Count();", parameterInfo.Name, parameterInfo.Name + "Temp").AppendLine();
}
#endif
}
@@ -1721,6 +1797,52 @@ namespace Flax.Build.Bindings
contents.Append($" MCore::GCHandle::Free(__param{i}_handle);").AppendLine();
}
}
else if (paramValue.Contains("MUtils::ToArray(")) // FIXME
{
var genericType = parameterInfo.Type.GenericArgs[0].ToString(false);
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::FreeManagedArray<{genericType}>(__param_{parameterInfo.Name}); //fgfgh3").AppendLine();
if (useThunk)
{
// Release the original handle
contents.Append($" if (__param_orig_{parameterInfo.Name} != __param_{ parameterInfo.Name})").AppendLine();
contents.Append($" MUtils::FreeManagedArray<{genericType}>(__param_orig_{parameterInfo.Name}); //fgfgh4").AppendLine();
}
}
else if (apiType != null && !apiType.IsInBuild)
{
// int: ispod, isvaluetype
// vector: ispod, isvaluetype, isstruct
// guid: ispod, isvaluetype, isstruct, isinbuild
contents.Append($" MUtils::FreeManagedArray<{genericType}>((MArray*)params[{i}]); //fgfgh5").AppendLine();
//contents.Append($" auto __param{i}_handle = *(MGCHandle*)&params[{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::FreeManagedArray<{genericType}>((MArray*)params[{i}]); //fgfgh6").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*)&params[{i}]; //fgfgh7").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;");
@@ -3108,7 +3230,9 @@ namespace Flax.Build.Bindings
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(" auto boxed = MCore::Object::Box((void*)&managed, klass);").AppendLine();
header.Append(" ::FreeManaged(managed);").AppendLine();
header.Append(" return boxed;").AppendLine();
header.Append(" }").AppendLine();
header.AppendFormat(" DLLEXPORT USED void Unbox({0}& result, MObject* data)", fullName).AppendLine();
@@ -3125,8 +3249,8 @@ namespace Flax.Build.Bindings
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(" auto managed = ToManaged(data[i]);").AppendLine();
header.Append(" MCore::GC::WriteValue(&resultPtr[i], &managed, 1, klass);").AppendLine();
header.Append(" }").AppendLine();
header.Append(" }").AppendLine();
@@ -3134,14 +3258,32 @@ namespace Flax.Build.Bindings
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(" result[i] = ToNative(dataPtr[i]);").AppendLine();
header.Append(" }").AppendLine();
header.AppendFormat(" DLLEXPORT USED void FreeManaged(MObject* data)", fullName).AppendLine();
header.AppendFormat(" DLLEXPORT USED void FreeManaged(MObject* data)").AppendLine();
header.Append(" {").AppendLine();
if (false) // FIXME: unboxing allocates temporary handles which FreeManaged does not free...
{
header.AppendFormat(" auto managed = reinterpret_cast<{0}*>(MCore::Object::Unbox(data));", wrapperName).AppendLine();
header.Append(" ::FreeManaged(*managed);").AppendLine();
}
header.Append(" MCore::GCHandle::Free(*(MGCHandle*)&data);").AppendLine();
header.Append(" }").AppendLine();
header.AppendFormat(" DLLEXPORT USED void FreeManagedArray(MArray* array)").AppendLine();
header.Append(" {").AppendLine();
header.AppendFormat(" {0}* resultPtr = ({0}*)MCore::Array::GetAddress(array);", wrapperName).AppendLine();
header.Append(" const auto length = MCore::Array::GetLength(array);").AppendLine();
header.Append(" for (int32 i = 0; i < length; ++i)").AppendLine();
header.Append(" {").AppendLine();
header.AppendFormat(" ::FreeManaged(*reinterpret_cast<{0}*>(&resultPtr[i]));", wrapperName).AppendLine();
header.Append(" }").AppendLine();
//header.Append(" PLATFORM_DEBUG_BREAK; // FIXME 1").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(" MCore::Array::Free(array);").AppendLine();
header.Append(" MCore::GCHandle::Free(*(MGCHandle*)&array);").AppendLine();
header.Append(" }").AppendLine();
header.Append('}').Append(';').AppendLine();
@@ -3191,9 +3333,11 @@ namespace Flax.Build.Bindings
continue;
CppNonPodTypesConvertingGeneration = true;
var wrapper = GenerateCppWrapperManagedToNative(buildData, fieldInfo.Type, apiType, out _, out _, null, out _);
var wrapper = GenerateCppWrapperManagedToNative(buildData, fieldInfo.Type, apiType, out var fieldTypeName, out var fieldType, null, out _);
CppNonPodTypesConvertingGeneration = false;
var arrayCountName = $"value.{fieldInfo.Name}.length";
if (fieldInfo.Type.IsArray)
{
// Fixed-size array needs to unbox every item manually
@@ -3206,7 +3350,7 @@ namespace Flax.Build.Bindings
}
else
{
wrapper = string.Format(wrapper.Remove(wrapper.Length - 6), string.Format("value.{0}", fieldInfo.Name));
wrapper = string.Format(wrapper.Remove(wrapper.Length - 6), string.Format("value.{0}", fieldInfo.Name), arrayCountName);
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();
@@ -3217,7 +3361,22 @@ namespace Flax.Build.Bindings
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();
{
var arrayElementType = fieldInfo.Type.GenericArgs != null ? FindApiTypeInfo(buildData, fieldInfo.Type.GenericArgs[0], apiType) : null;
var ispod = (arrayElementType?.IsPod.ToString() ?? "null");
header.Append($" // {fieldInfo.Type.Type}, {fieldType?.FullNameNative}, {fieldType?.Name}, {fieldTypeName}, {ispod}").AppendLine();
if (fieldInfo.Type.IsArrayOrSpan && (arrayElementType?.IsPod ?? false))
{
header.AppendFormat(" result.{0} = {1}; /*kaek1 */", fieldInfo.Name, string.Format(wrapper, $"value.{fieldInfo.Name}.data", $"value.{fieldInfo.Name}.length")).AppendLine();
}
else
{
header.AppendFormat(" result.{0} = {1}; /*kaek2 */", fieldInfo.Name, string.Format(wrapper, $"value.{fieldInfo.Name}", $"value.{fieldInfo.Name}.lengthnot")).AppendLine();
}
//var fieldType = FindApiTypeInfo(buildData, fieldInfo.Type, apiType);
//header.AppendFormat(" result.{0} = {1}; /*kaek2*/", fieldInfo.Name, string.Format(wrapper, $"value.{fieldInfo.Name}")).AppendLine();
}
/*if (wrapper.Contains("::ToNative(")) // FIXME
{
header.Append($" auto __param{fieldInfo.Name}_handle = *(MGCHandle*)&value.{fieldInfo.Name};").AppendLine();
@@ -3242,15 +3401,85 @@ namespace Flax.Build.Bindings
var wrapper = GenerateCppWrapperManagedToNative(buildData, fieldInfo.Type, apiType, out var managedType, out var fieldApiType, null, out _);
CppNonPodTypesConvertingGeneration = false;
if (wrapper.Contains("::ToNative(")) // FIXME fieldApiType.IsScriptingObject
if (fieldInfo.Type.IsArray)
{
//header.Append($" fixed_array_type = fixed_array_type; // {fieldInfo.Name} ({managedType}) is fixed array").AppendLine();
}
else if (string.IsNullOrEmpty(wrapper))
{
if (managedType == "bool")
{
}
else
{
}
}
else if (fieldInfo.Type.Type == "String" || managedType == "MString*")
{
if (fieldInfo.Type.Type != "String")
{
}
if (managedType != "MString*")
{
}
header.Append($" MCore::String::Free(value.{fieldInfo.Name});").AppendLine();
}
else if (fieldInfo.Type.Type == "Span")
{
}
else if (fieldInfo.Type.Type == "Array")
{
if (managedType == "MArray*")
{
}
else if (managedType == "Char*" || managedType == "char*")
{
}
else
{
//header.Append($" unhandled_type = unhandled_type; // {fieldInfo.Name} ({managedType}) should be cleaned? // asdfggg56").AppendLine();
}
}
else if (fieldApiType == null)
{
header.Append($" weird_type = weird_type; // {fieldInfo.Name} ({managedType}) is a weird one // okdfiodftg").AppendLine();
}
else if (fieldApiType.IsScriptingObject)
{
header.Append($" // {fieldInfo.Name} is a permanent handle // asdf2").AppendLine();
}
else if (!fieldApiType.IsPod) // FIXMEb
{
if (fieldApiType.IsClass)
{
}
else
{
header.Append($" ::FreeManaged(value.{fieldInfo.Name}); // asdfggg588").AppendLine();
//header.Append($" unhandled_type = unhandled_type; // {fieldInfo.Name} ({managedType}) should be cleaned? // asdfggg588").AppendLine();
}
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();
}
else if (fieldApiType.IsPod)
{
}
else
{
}
}
if (classInfo != null)
{
@@ -3337,9 +3566,27 @@ namespace Flax.Build.Bindings
header.AppendFormat(" Unbox(result[i], dataPtr[i]);", fullName).AppendLine();
header.Append(" }").AppendLine();
header.AppendFormat(" DLLEXPORT USED void FreeManaged(MObject* data) // honk1", fullName).AppendLine();
header.AppendFormat(" DLLEXPORT USED void FreeManaged(MObject* data)").AppendLine();
header.Append(" {").AppendLine();
//header.Append(" ::FreeManaged(reinterpret_cast<OnlineUserManaged*>(MCore::Object::Unbox(data)));").AppendLine();
header.Append(" PLATFORM_DEBUG_BREAK; // FIXME 2b").AppendLine();
header.Append(" MCore::GCHandle::Free(*(MGCHandle*)&data);").AppendLine();
header.Append(" }").AppendLine();
header.AppendFormat(" DLLEXPORT USED void FreeManagedArray(MArray* array)").AppendLine();
header.Append(" {").AppendLine();
header.Append(" PLATFORM_DEBUG_BREAK; // FIXME 2").AppendLine();
header.Append(" MObject** resultPtr = (MObject**)MCore::Array::GetAddress(array);").AppendLine();
header.Append(" const auto length = MCore::Array::GetLength(array);").AppendLine();
header.Append(" for (int32 i = 0; i < length; ++i)").AppendLine();
header.Append(" {").AppendLine();
header.Append(" FreeManaged(resultPtr[i]);").AppendLine();
//header.AppendFormat(" MCore::GCHandle::Free(*(MGCHandle*)&resultPtr[i]);", wrapperName).AppendLine();
header.Append(" }").AppendLine();
//header.Append(" PLATFORM_DEBUG_BREAK; // FIXME 1").AppendLine();
//header.AppendFormat(" auto managed = MCore::Object::Unbox(data);", wrapperName).AppendLine();
//header.AppendFormat(" ::FreeManaged(*reinterpret_cast<{0}*>(managed));", wrapperName).AppendLine();
header.Append(" MCore::Array::Free(array);").AppendLine();
header.Append(" MCore::GCHandle::Free(*(MGCHandle*)&array);").AppendLine();
header.Append(" }").AppendLine();
header.Append('}').Append(';').AppendLine();