Files
FlaxEngine/Source/Engine/Scripting/Runtime/DotNet.cpp
2023-11-26 15:09:46 +01:00

2225 lines
71 KiB
C++

// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "Engine/Scripting/Types.h"
#if USE_NETCORE
#pragma warning(default : 4297)
#include "Engine/Core/Log.h"
#include "Engine/Core/Types/DateTime.h"
#include "Engine/Core/Types/TimeSpan.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Platform/Platform.h"
#include "Engine/Platform/File.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Scripting/Internal/InternalCalls.h"
#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MDomain.h"
#include "Engine/Scripting/ManagedCLR/MEvent.h"
#include "Engine/Scripting/ManagedCLR/MField.h"
#include "Engine/Scripting/ManagedCLR/MMethod.h"
#include "Engine/Scripting/ManagedCLR/MProperty.h"
#include "Engine/Scripting/ManagedCLR/MException.h"
#include "Engine/Scripting/ManagedCLR/MUtils.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/BinaryModule.h"
#include "Engine/Engine/Globals.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Debug/Exceptions/CLRInnerException.h"
#if DOTNET_HOST_CORECLR
#include <nethost.h>
#include <coreclr_delegates.h>
#include <hostfxr.h>
#elif DOTNET_HOST_MONO
#include "Engine/Engine/CommandLine.h"
#include "Engine/Utilities/StringConverter.h"
#include <mono/jit/jit.h>
#include <mono/jit/mono-private-unstable.h>
#include <mono/utils/mono-logger.h>
#include <mono/metadata/assembly.h>
#include <mono/metadata/appdomain.h>
#include <mono/metadata/class.h>
#include <mono/metadata/metadata.h>
#include <mono/metadata/threads.h>
#include <mono/metadata/reflection.h>
#include <mono/metadata/mono-gc.h>
#include <mono/metadata/mono-private-unstable.h>
typedef char char_t;
#define DOTNET_HOST_MONO_DEBUG 0
#ifdef USE_MONO_AOT_MODULE
void* MonoAotModuleHandle = nullptr;
#endif
MonoDomain* MonoDomainHandle = nullptr;
#else
#error "Unknown .NET runtime host."
#endif
#if PLATFORM_WINDOWS
#include <combaseapi.h>
#undef SetEnvironmentVariable
#undef GetEnvironmentVariable
#undef LoadLibrary
#undef LoadImage
#endif
#if defined(_WIN32)
#define CORECLR_DELEGATE_CALLTYPE __stdcall
#define FLAX_CORECLR_STRING String
#define FLAX_CORECLR_TEXT(x) TEXT(x)
#else
#define CORECLR_DELEGATE_CALLTYPE
#define FLAX_CORECLR_STRING StringAnsi
#define FLAX_CORECLR_TEXT(x) x
#endif
// System.Reflection.TypeAttributes
enum class MTypeAttributes : uint32
{
VisibilityMask = 0x00000007,
NotPublic = 0x00000000,
Public = 0x00000001,
NestedPublic = 0x00000002,
NestedPrivate = 0x00000003,
NestedFamily = 0x00000004,
NestedAssembly = 0x00000005,
NestedFamANDAssem = 0x00000006,
NestedFamORAssem = 0x00000007,
LayoutMask = 0x00000018,
AutoLayout = 0x00000000,
SequentialLayout = 0x00000008,
ExplicitLayout = 0x00000010,
ClassSemanticsMask = 0x00000020,
Class = 0x00000000,
Interface = 0x00000020,
Abstract = 0x00000080,
Sealed = 0x00000100,
SpecialName = 0x00000400,
Import = 0x00001000,
Serializable = 0x00002000,
WindowsRuntime = 0x00004000,
StringFormatMask = 0x00030000,
AnsiClass = 0x00000000,
UnicodeClass = 0x00010000,
AutoClass = 0x00020000,
CustomFormatClass = 0x00030000,
CustomFormatMask = 0x00C00000,
BeforeFieldInit = 0x00100000,
RTSpecialName = 0x00000800,
HasSecurity = 0x00040000,
ReservedMask = 0x00040800,
};
// System.Reflection.MethodAttributes
enum class MMethodAttributes : uint32
{
MemberAccessMask = 0x0007,
PrivateScope = 0x0000,
Private = 0x0001,
FamANDAssem = 0x0002,
Assembly = 0x0003,
Family = 0x0004,
FamORAssem = 0x0005,
Public = 0x0006,
Static = 0x0010,
Final = 0x0020,
Virtual = 0x0040,
HideBySig = 0x0080,
CheckAccessOnOverride = 0x0200,
VtableLayoutMask = 0x0100,
ReuseSlot = 0x0000,
NewSlot = 0x0100,
Abstract = 0x0400,
SpecialName = 0x0800,
PinvokeImpl = 0x2000,
UnmanagedExport = 0x0008,
RTSpecialName = 0x1000,
HasSecurity = 0x4000,
RequireSecObject = 0x8000,
ReservedMask = 0xd000,
};
// System.Reflection.FieldAttributes
enum class MFieldAttributes : uint32
{
FieldAccessMask = 0x0007,
PrivateScope = 0x0000,
Private = 0x0001,
FamANDAssem = 0x0002,
Assembly = 0x0003,
Family = 0x0004,
FamORAssem = 0x0005,
Public = 0x0006,
Static = 0x0010,
InitOnly = 0x0020,
Literal = 0x0040,
NotSerialized = 0x0080,
SpecialName = 0x0200,
PinvokeImpl = 0x2000,
RTSpecialName = 0x0400,
HasFieldMarshal = 0x1000,
HasDefault = 0x8000,
HasFieldRVA = 0x0100,
ReservedMask = 0x9500,
};
DECLARE_ENUM_OPERATORS(MTypeAttributes);
DECLARE_ENUM_OPERATORS(MMethodAttributes);
DECLARE_ENUM_OPERATORS(MFieldAttributes);
// Multiple AppDomains are superseded by AssemblyLoadContext in .NET
extern MDomain* MRootDomain;
extern MDomain* MActiveDomain;
extern Array<MDomain*, FixedAllocation<4>> MDomains;
Dictionary<String, void*> CachedFunctions;
Dictionary<void*, MClass*> CachedClassHandles;
Dictionary<void*, MAssembly*> CachedAssemblyHandles;
/// <summary>
/// Returns the function pointer to the managed static method in NativeInterop class.
/// </summary>
void* GetStaticMethodPointer(const String& methodName);
/// <summary>
/// Calls the managed static method with given parameters.
/// </summary>
template<typename RetType, typename... Args>
FORCE_INLINE RetType CallStaticMethod(void* methodPtr, Args... args)
{
#if DOTNET_HOST_MONO
ASSERT_LOW_LAYER(mono_domain_get()); // Ensure that Mono runtime has been attached to this thread
#endif
typedef RetType (CORECLR_DELEGATE_CALLTYPE* fun)(Args...);
return ((fun)methodPtr)(args...);
}
void RegisterNativeLibrary(const char* moduleName, const Char* modulePath)
{
static void* RegisterNativeLibraryPtr = GetStaticMethodPointer(TEXT("RegisterNativeLibrary"));
CallStaticMethod<void, const char*, const Char*>(RegisterNativeLibraryPtr, moduleName, modulePath);
}
bool InitHostfxr();
void ShutdownHostfxr();
MAssembly* GetAssembly(void* assemblyHandle);
MClass* GetClass(MType* typeHandle);
MClass* GetOrCreateClass(MType* typeHandle);
MType* GetObjectType(MObject* obj);
void* GetCustomAttribute(const MClass* klass, const MClass* attributeClass);
// Structures used to pass information from runtime, must match with the structures in managed side
struct NativeClassDefinitions
{
void* typeHandle;
MClass* nativePointer;
const char* name;
const char* fullname;
const char* namespace_;
MTypeAttributes typeAttributes;
};
struct NativeMethodDefinitions
{
const char* name;
int numParameters;
void* handle;
MMethodAttributes methodAttributes;
};
struct NativeFieldDefinitions
{
const char* name;
void* fieldHandle;
void* fieldType;
int fieldOffset;
MFieldAttributes fieldAttributes;
};
struct NativePropertyDefinitions
{
const char* name;
void* getterHandle;
void* setterHandle;
MMethodAttributes getterAttributes;
MMethodAttributes setterAttributes;
};
MDomain* MCore::CreateDomain(const StringAnsi& domainName)
{
return nullptr;
}
void MCore::UnloadDomain(const StringAnsi& domainName)
{
}
bool MCore::LoadEngine()
{
PROFILE_CPU();
// Initialize hostfxr
if (InitHostfxr())
return true;
// Prepare managed side
CallStaticMethod<void>(GetStaticMethodPointer(TEXT("Init")));
#ifdef MCORE_MAIN_MODULE_NAME
// MCORE_MAIN_MODULE_NAME define is injected by Scripting.Build.cs on platforms that use separate shared library for engine symbols
::String flaxLibraryPath(Platform::GetMainDirectory() / TEXT(MACRO_TO_STR(MCORE_MAIN_MODULE_NAME)));
#else
::String flaxLibraryPath(Platform::GetExecutableFilePath());
#endif
#if PLATFORM_MAC
// On some platforms all native binaries are side-by-side with the app in a different folder
if (!FileSystem::FileExists(flaxLibraryPath))
{
flaxLibraryPath = ::String(StringUtils::GetDirectoryName(Platform::GetExecutableFilePath())) / StringUtils::GetFileName(flaxLibraryPath);
}
#endif
if (!FileSystem::FileExists(flaxLibraryPath))
{
LOG(Error, "Flax Engine native library file is missing ({0})", flaxLibraryPath);
}
RegisterNativeLibrary("FlaxEngine", flaxLibraryPath.Get());
MRootDomain = New<MDomain>("Root");
MDomains.Add(MRootDomain);
void* GetRuntimeInformationPtr = GetStaticMethodPointer(TEXT("GetRuntimeInformation"));
char* buildInfo = CallStaticMethod<char*>(GetRuntimeInformationPtr);
LOG(Info, ".NET runtime version: {0}", ::String(buildInfo));
MCore::GC::FreeMemory(buildInfo);
return false;
}
void MCore::UnloadEngine()
{
if (!MRootDomain)
return;
PROFILE_CPU();
CallStaticMethod<void>(GetStaticMethodPointer(TEXT("Exit")));
MDomains.ClearDelete();
MRootDomain = nullptr;
ShutdownHostfxr();
}
#if USE_EDITOR
void MCore::ReloadScriptingAssemblyLoadContext()
{
// Clear any cached class attributes (see https://github.com/FlaxEngine/FlaxEngine/issues/1108)
for (auto e : CachedClassHandles)
e.Value->_attributes.Clear();
static void* ReloadScriptingAssemblyLoadContextPtr = GetStaticMethodPointer(TEXT("ReloadScriptingAssemblyLoadContext"));
CallStaticMethod<void>(ReloadScriptingAssemblyLoadContextPtr);
}
#endif
MObject* MCore::Object::Box(void* value, const MClass* klass)
{
static void* BoxValuePtr = GetStaticMethodPointer(TEXT("BoxValue"));
return (MObject*)CallStaticMethod<void*, void*, void*>(BoxValuePtr, klass->_handle, value);
}
void* MCore::Object::Unbox(MObject* obj)
{
static void* UnboxValuePtr = GetStaticMethodPointer(TEXT("UnboxValue"));
return CallStaticMethod<void*, void*>(UnboxValuePtr, obj);
}
MObject* MCore::Object::New(const MClass* klass)
{
static void* NewObjectPtr = GetStaticMethodPointer(TEXT("NewObject"));
return (MObject*)CallStaticMethod<void*, void*>(NewObjectPtr, klass->_handle);
}
void MCore::Object::Init(MObject* obj)
{
static void* ObjectInitPtr = GetStaticMethodPointer(TEXT("ObjectInit"));
CallStaticMethod<void, void*>(ObjectInitPtr, obj);
}
MClass* MCore::Object::GetClass(MObject* obj)
{
ASSERT(obj);
static void* GetObjectClassPtr = GetStaticMethodPointer(TEXT("GetObjectClass"));
return (MClass*)CallStaticMethod<MClass*, void*>(GetObjectClassPtr, obj);
}
MString* MCore::Object::ToString(MObject* obj)
{
static void* GetObjectStringPtr = GetStaticMethodPointer(TEXT("GetObjectString"));
return (MString*)CallStaticMethod<void*, void*>(GetObjectStringPtr, obj);
}
int32 MCore::Object::GetHashCode(MObject* obj)
{
static void* GetObjectStringPtr = GetStaticMethodPointer(TEXT("GetObjectHashCode"));
return CallStaticMethod<int32, void*>(GetObjectStringPtr, obj);
}
MString* MCore::String::GetEmpty(MDomain* domain)
{
static void* GetStringEmptyPtr = GetStaticMethodPointer(TEXT("GetStringEmpty"));
return (MString*)CallStaticMethod<void*>(GetStringEmptyPtr);
}
MString* MCore::String::New(const char* str, int32 length, MDomain* domain)
{
static void* NewStringUTF8Ptr = GetStaticMethodPointer(TEXT("NewStringUTF8"));
return (MString*)CallStaticMethod<void*, const char*, int>(NewStringUTF8Ptr, str, length);
}
MString* MCore::String::New(const Char* str, int32 length, MDomain* domain)
{
static void* NewStringUTF16Ptr = GetStaticMethodPointer(TEXT("NewStringUTF16"));
return (MString*)CallStaticMethod<void*, const Char*, int>(NewStringUTF16Ptr, str, length);
}
StringView MCore::String::GetChars(MString* obj)
{
int32 length = 0;
static void* GetStringPointerPtr = GetStaticMethodPointer(TEXT("GetStringPointer"));
const Char* chars = CallStaticMethod<const Char*, void*, int*>(GetStringPointerPtr, obj, &length);
return StringView(chars, length);
}
MArray* MCore::Array::New(const MClass* elementKlass, int32 length)
{
static void* NewArrayPtr = GetStaticMethodPointer(TEXT("NewArray"));
return (MArray*)CallStaticMethod<void*, void*, long long>(NewArrayPtr, elementKlass->_handle, length);
}
MClass* MCore::Array::GetClass(MClass* elementKlass)
{
static void* GetArrayLengthPtr = GetStaticMethodPointer(TEXT("GetArrayTypeFromElementType"));
MType* typeHandle = (MType*)CallStaticMethod<void*, void*>(GetArrayLengthPtr, elementKlass->_handle);
return GetOrCreateClass(typeHandle);
}
int32 MCore::Array::GetLength(const MArray* obj)
{
static void* GetArrayLengthPtr = GetStaticMethodPointer(TEXT("GetArrayLength"));
return CallStaticMethod<int, void*>(GetArrayLengthPtr, (void*)obj);
}
void* MCore::Array::GetAddress(const MArray* obj)
{
static void* GetArrayPointerPtr = GetStaticMethodPointer(TEXT("GetArrayPointer"));
return CallStaticMethod<void*, void*>(GetArrayPointerPtr, (void*)obj);
}
MArray* MCore::Array::Unbox(MObject* obj)
{
static void* GetArrayPtr = GetStaticMethodPointer(TEXT("GetArray"));
return (MArray*)CallStaticMethod<void*, void*>(GetArrayPtr, (void*)obj);
}
MGCHandle MCore::GCHandle::New(MObject* obj, bool pinned)
{
ASSERT(obj);
static void* NewGCHandlePtr = GetStaticMethodPointer(TEXT("NewGCHandle"));
return (MGCHandle)CallStaticMethod<void*, void*, bool>(NewGCHandlePtr, obj, pinned);
}
MGCHandle MCore::GCHandle::NewWeak(MObject* obj, bool trackResurrection)
{
ASSERT(obj);
static void* NewGCHandleWeakPtr = GetStaticMethodPointer(TEXT("NewGCHandleWeak"));
return (MGCHandle)CallStaticMethod<void*, void*, bool>(NewGCHandleWeakPtr, obj, trackResurrection);
}
MObject* MCore::GCHandle::GetTarget(const MGCHandle& handle)
{
return (MObject*)(void*)handle;
}
void MCore::GCHandle::Free(const MGCHandle& handle)
{
static void* FreeGCHandlePtr = GetStaticMethodPointer(TEXT("FreeGCHandle"));
CallStaticMethod<void, void*>(FreeGCHandlePtr, (void*)handle);
}
void MCore::GC::Collect()
{
PROFILE_CPU();
static void* GCCollectPtr = GetStaticMethodPointer(TEXT("GCCollect"));
CallStaticMethod<void, int, int, bool, bool>(GCCollectPtr, MaxGeneration(), (int)MGCCollectionMode::Default, true, false);
}
void MCore::GC::Collect(int32 generation)
{
PROFILE_CPU();
static void* GCCollectPtr = GetStaticMethodPointer(TEXT("GCCollect"));
CallStaticMethod<void, int, int, bool, bool>(GCCollectPtr, generation, (int)MGCCollectionMode::Default, true, false);
}
void MCore::GC::Collect(int32 generation, MGCCollectionMode collectionMode, bool blocking, bool compacting)
{
PROFILE_CPU();
static void* GCCollectPtr = GetStaticMethodPointer(TEXT("GCCollect"));
CallStaticMethod<void, int, int, bool, bool>(GCCollectPtr, generation, (int)collectionMode, blocking, compacting);
}
int32 MCore::GC::MaxGeneration()
{
static int32 maxGeneration = CallStaticMethod<int32>(GetStaticMethodPointer(TEXT("GCMaxGeneration")));
return maxGeneration;
}
void MCore::GC::WaitForPendingFinalizers()
{
PROFILE_CPU();
static void* GCWaitForPendingFinalizersPtr = GetStaticMethodPointer(TEXT("GCWaitForPendingFinalizers"));
CallStaticMethod<void>(GCWaitForPendingFinalizersPtr);
}
void MCore::GC::WriteRef(void* ptr, MObject* ref)
{
*(void**)ptr = ref;
}
void MCore::GC::WriteValue(void* dst, void* src, int32 count, const MClass* klass)
{
const int32 size = klass->GetInstanceSize();
memcpy(dst, src, count * size);
}
void MCore::GC::WriteArrayRef(MArray* dst, MObject* ref, int32 index)
{
static void* WriteArrayReferencePtr = GetStaticMethodPointer(TEXT("WriteArrayReference"));
CallStaticMethod<void, void*, void*, int32>(WriteArrayReferencePtr, dst, ref, index);
}
void MCore::GC::WriteArrayRef(MArray* dst, Span<MObject*> refs)
{
static void* WriteArrayReferencesPtr = GetStaticMethodPointer(TEXT("WriteArrayReferences"));
CallStaticMethod<void, void*, void*, int32>(WriteArrayReferencesPtr, dst, refs.Get(), refs.Length());
}
void* MCore::GC::AllocateMemory(int32 size, bool coTaskMem)
{
static void* AllocMemoryPtr = GetStaticMethodPointer(TEXT("AllocMemory"));
return CallStaticMethod<void*, int, bool>(AllocMemoryPtr, size, coTaskMem);
}
void MCore::GC::FreeMemory(void* ptr, bool coTaskMem)
{
if (!ptr)
return;
static void* FreeMemoryPtr = GetStaticMethodPointer(TEXT("FreeMemory"));
CallStaticMethod<void, void*, bool>(FreeMemoryPtr, ptr, coTaskMem);
}
void MCore::Thread::Attach()
{
#if DOTNET_HOST_MONO
if (!IsInMainThread() && !mono_domain_get())
{
mono_thread_attach(MonoDomainHandle);
}
#endif
}
void MCore::Thread::Exit()
{
}
bool MCore::Thread::IsAttached()
{
return true;
}
void MCore::Exception::Throw(MObject* exception)
{
static void* RaiseExceptionPtr = GetStaticMethodPointer(TEXT("RaiseException"));
CallStaticMethod<void*, void*>(RaiseExceptionPtr, exception);
}
MObject* MCore::Exception::GetNullReference()
{
static void* GetNullReferenceExceptionPtr = GetStaticMethodPointer(TEXT("GetNullReferenceException"));
return (MObject*)CallStaticMethod<void*>(GetNullReferenceExceptionPtr);
}
MObject* MCore::Exception::Get(const char* msg)
{
static void* GetExceptionPtr = GetStaticMethodPointer(TEXT("GetException"));
return (MObject*)CallStaticMethod<void*, const char*>(GetExceptionPtr, msg);
}
MObject* MCore::Exception::GetArgument(const char* arg, const char* msg)
{
static void* GetArgumentExceptionPtr = GetStaticMethodPointer(TEXT("GetArgumentException"));
return (MObject*)CallStaticMethod<void*>(GetArgumentExceptionPtr);
}
MObject* MCore::Exception::GetArgumentNull(const char* arg)
{
static void* GetArgumentNullExceptionPtr = GetStaticMethodPointer(TEXT("GetArgumentNullException"));
return (MObject*)CallStaticMethod<void*>(GetArgumentNullExceptionPtr);
}
MObject* MCore::Exception::GetArgumentOutOfRange(const char* arg)
{
static void* GetArgumentOutOfRangeExceptionPtr = GetStaticMethodPointer(TEXT("GetArgumentOutOfRangeException"));
return (MObject*)CallStaticMethod<void*>(GetArgumentOutOfRangeExceptionPtr);
}
MObject* MCore::Exception::GetNotSupported(const char* msg)
{
static void* GetNotSupportedExceptionPtr = GetStaticMethodPointer(TEXT("GetNotSupportedException"));
return (MObject*)CallStaticMethod<void*>(GetNotSupportedExceptionPtr);
}
::String MCore::Type::ToString(MType* type)
{
MClass* klass = GetOrCreateClass(type);
return ::String(klass->GetFullName());
}
MClass* MCore::Type::GetClass(MType* type)
{
static void* GetTypeClassPtr = GetStaticMethodPointer(TEXT("GetTypeClass"));
return CallStaticMethod<MClass*, void*>(GetTypeClassPtr, type);
}
MType* MCore::Type::GetElementType(MType* type)
{
static void* GetElementClassPtr = GetStaticMethodPointer(TEXT("GetElementClass"));
return (MType*)CallStaticMethod<void*, void*>(GetElementClassPtr, type);
}
int32 MCore::Type::GetSize(MType* type)
{
return GetOrCreateClass(type)->GetInstanceSize();
}
MTypes MCore::Type::GetType(MType* type)
{
MClass* klass = GetOrCreateClass(type);
if (klass->_types == 0)
{
static void* GetTypeMTypesEnumPtr = GetStaticMethodPointer(TEXT("GetTypeMTypesEnum"));
klass->_types = CallStaticMethod<uint32, void*>(GetTypeMTypesEnumPtr, klass->_handle);
}
return (MTypes)klass->_types;
}
bool MCore::Type::IsPointer(MType* type)
{
static void* GetTypeIsPointerPtr = GetStaticMethodPointer(TEXT("GetTypeIsPointer"));
return CallStaticMethod<bool, void*>(GetTypeIsPointerPtr, type);
}
bool MCore::Type::IsReference(MType* type)
{
static void* GetTypeIsReferencePtr = GetStaticMethodPointer(TEXT("GetTypeIsReference"));
return CallStaticMethod<bool, void*>(GetTypeIsReferencePtr, type);
}
void MCore::ScriptingObject::SetInternalValues(MClass* klass, MObject* object, void* unmanagedPtr, const Guid* id)
{
#if PLATFORM_DESKTOP && !USE_MONO_AOT
static void* ScriptingObjectSetInternalValuesPtr = GetStaticMethodPointer(TEXT("ScriptingObjectSetInternalValues"));
CallStaticMethod<void, MObject*, void*, const Guid*>(ScriptingObjectSetInternalValuesPtr, object, unmanagedPtr, id);
#else
const MField* monoUnmanagedPtrField = klass->GetField("__unmanagedPtr");
if (monoUnmanagedPtrField)
monoUnmanagedPtrField->SetValue(object, &unmanagedPtr);
const MField* monoIdField = klass->GetField("__internalId");
if (id != nullptr && monoIdField)
monoIdField->SetValue(object, (void*)id);
#endif
}
MObject* MCore::ScriptingObject::CreateScriptingObject(MClass* klass, void* unmanagedPtr, const Guid* id)
{
#if PLATFORM_DESKTOP && !USE_MONO_AOT
static void* ScriptingObjectSetInternalValuesPtr = GetStaticMethodPointer(TEXT("ScriptingObjectCreate"));
return CallStaticMethod<MObject*, void*, void*, const Guid*>(ScriptingObjectSetInternalValuesPtr, klass->_handle, unmanagedPtr, id);
#else
MObject* object = MCore::Object::New(klass);
if (object)
{
MCore::ScriptingObject::SetInternalValues(klass, object, unmanagedPtr, id);
MCore::Object::Init(object);
}
return object;
#endif
}
const MAssembly::ClassesDictionary& MAssembly::GetClasses() const
{
if (_hasCachedClasses || !IsLoaded())
return _classes;
PROFILE_CPU();
const auto startTime = DateTime::NowUTC();
#if TRACY_ENABLE
ZoneText(*_name, _name.Length());
#endif
ScopeLock lock(BinaryModule::Locker);
if (_hasCachedClasses)
return _classes;
ASSERT(_classes.IsEmpty());
NativeClassDefinitions* managedClasses;
int classCount;
static void* GetManagedClassesPtr = GetStaticMethodPointer(TEXT("GetManagedClasses"));
CallStaticMethod<void, void*, NativeClassDefinitions**, int*>(GetManagedClassesPtr, _handle, &managedClasses, &classCount);
_classes.EnsureCapacity(classCount);
for (int32 i = 0; i < classCount; i++)
{
NativeClassDefinitions& managedClass = managedClasses[i];
// Create class object
MClass* klass = New<MClass>(this, managedClass.typeHandle, managedClass.name, managedClass.fullname, managedClass.namespace_, managedClass.typeAttributes);
_classes.Add(klass->GetFullName(), klass);
managedClass.nativePointer = klass;
MCore::GC::FreeMemory((void*)managedClasses[i].name);
MCore::GC::FreeMemory((void*)managedClasses[i].fullname);
MCore::GC::FreeMemory((void*)managedClasses[i].namespace_);
}
static void* RegisterManagedClassNativePointersPtr = GetStaticMethodPointer(TEXT("RegisterManagedClassNativePointers"));
CallStaticMethod<void, NativeClassDefinitions**, int>(RegisterManagedClassNativePointersPtr, &managedClasses, classCount);
MCore::GC::FreeMemory(managedClasses);
const auto endTime = DateTime::NowUTC();
LOG(Info, "Caching classes for assembly {0} took {1}ms", String(_name), (int32)(endTime - startTime).GetTotalMilliseconds());
#if 0
for (auto i = _classes.Begin(); i.IsNotEnd(); ++i)
LOG(Info, "Class: {0}", String(i->Value->GetFullName()));
#endif
_hasCachedClasses = true;
return _classes;
}
void GetAssemblyName(void* assemblyHandle, StringAnsi& name, StringAnsi& fullname)
{
static void* GetAssemblyNamePtr = GetStaticMethodPointer(TEXT("GetAssemblyName"));
const char* name_;
const char* fullname_;
CallStaticMethod<void, void*, const char**, const char**>(GetAssemblyNamePtr, assemblyHandle, &name_, &fullname_);
name = name_;
fullname = fullname_;
MCore::GC::FreeMemory((void*)name_);
MCore::GC::FreeMemory((void*)fullname_);
}
DEFINE_INTERNAL_CALL(void) NativeInterop_CreateClass(NativeClassDefinitions* managedClass, void* assemblyHandle)
{
ScopeLock lock(BinaryModule::Locker);
MAssembly* assembly = GetAssembly(assemblyHandle);
if (assembly == nullptr)
{
StringAnsi assemblyName;
StringAnsi assemblyFullName;
GetAssemblyName(assemblyHandle, assemblyName, assemblyFullName);
assembly = New<MAssembly>(nullptr, assemblyName, assemblyFullName, assemblyHandle);
CachedAssemblyHandles.Add(assemblyHandle, assembly);
}
MClass* klass = New<MClass>(assembly, managedClass->typeHandle, managedClass->name, managedClass->fullname, managedClass->namespace_, managedClass->typeAttributes);
if (assembly != nullptr)
{
auto& classes = const_cast<MAssembly::ClassesDictionary&>(assembly->GetClasses());
MClass* oldKlass;
if (classes.TryGet(klass->GetFullName(), oldKlass))
{
LOG(Warning, "Class '{0}' was already added to assembly '{1}'", String(klass->GetFullName()), String(assembly->GetName()));
Delete(klass);
klass = oldKlass;
}
else
{
classes.Add(klass->GetFullName(), klass);
}
}
managedClass->nativePointer = klass;
}
bool MAssembly::LoadCorlib()
{
if (IsLoaded())
return false;
PROFILE_CPU();
#if TRACY_ENABLE
const StringAnsiView name("Corlib");
ZoneText(*name, name.Length());
#endif
// Ensure to be unloaded
Unload();
// Start
const auto startTime = DateTime::NowUTC();
OnLoading();
// Load
{
static void* GetAssemblyByNamePtr = GetStaticMethodPointer(TEXT("GetAssemblyByName"));
_handle = CallStaticMethod<void*, const char*>(GetAssemblyByNamePtr, "System.Private.CoreLib");
GetAssemblyName(_handle, _name, _fullname);
}
if (_handle == nullptr)
{
OnLoadFailed();
return true;
}
_hasCachedClasses = false;
CachedAssemblyHandles.Add(_handle, this);
// End
OnLoaded(startTime);
return false;
}
bool MAssembly::LoadImage(const String& assemblyPath, const StringView& nativePath)
{
// TODO: Use new hostfxr delegate load_assembly_bytes? (.NET 8+)
// Open .Net assembly
static void* LoadAssemblyImagePtr = GetStaticMethodPointer(TEXT("LoadAssemblyImage"));
_handle = CallStaticMethod<void*, const Char*>(LoadAssemblyImagePtr, assemblyPath.Get());
if (_handle == nullptr)
{
Log::CLRInnerException(TEXT(".NET assembly image is invalid at ") + assemblyPath);
return true;
}
GetAssemblyName(_handle, _name, _fullname);
CachedAssemblyHandles.Add(_handle, this);
// Provide new path of hot-reloaded native library path for managed DllImport
if (nativePath.HasChars())
{
StringAnsi nativeName = _name.EndsWith(".CSharp") ? StringAnsi(_name.Get(), _name.Length() - 7) : StringAnsi(_name);
RegisterNativeLibrary(nativeName.Get(), nativePath.Get());
}
#if USE_EDITOR
// Register the editor module location for Assembly resolver
else
{
RegisterNativeLibrary(_name.Get(), assemblyPath.Get());
}
#endif
_hasCachedClasses = false;
_assemblyPath = assemblyPath;
return false;
}
bool MAssembly::UnloadImage(bool isReloading)
{
if (_handle && isReloading)
{
LOG(Info, "Unloading managed assembly \'{0}\' (is reloading)", String(_name));
static void* CloseAssemblyPtr = GetStaticMethodPointer(TEXT("CloseAssembly"));
CallStaticMethod<void, const void*>(CloseAssemblyPtr, _handle);
CachedAssemblyHandles.Remove(_handle);
_handle = nullptr;
}
return false;
}
MClass::MClass(const MAssembly* parentAssembly, void* handle, const char* name, const char* fullname, const char* namespace_, MTypeAttributes attributes)
: _handle(handle)
, _name(name)
, _namespace_(namespace_)
, _assembly(parentAssembly)
, _fullname(fullname)
, _hasCachedProperties(false)
, _hasCachedFields(false)
, _hasCachedMethods(false)
, _hasCachedAttributes(false)
, _hasCachedEvents(false)
, _hasCachedInterfaces(false)
{
ASSERT(handle != nullptr);
switch (attributes & MTypeAttributes::VisibilityMask)
{
case MTypeAttributes::NotPublic:
case MTypeAttributes::NestedPrivate:
_visibility = MVisibility::Private;
break;
case MTypeAttributes::Public:
case MTypeAttributes::NestedPublic:
_visibility = MVisibility::Public;
break;
case MTypeAttributes::NestedFamily:
case MTypeAttributes::NestedAssembly:
_visibility = MVisibility::Internal;
break;
case MTypeAttributes::NestedFamORAssem:
_visibility = MVisibility::ProtectedInternal;
break;
case MTypeAttributes::NestedFamANDAssem:
_visibility = MVisibility::PrivateProtected;
break;
default:
CRASH;
}
const MTypeAttributes staticClassFlags = MTypeAttributes::Abstract | MTypeAttributes::Sealed;
_isStatic = (attributes & staticClassFlags) == staticClassFlags;
_isSealed = !_isStatic && (attributes & MTypeAttributes::Sealed) == MTypeAttributes::Sealed;
_isAbstract = !_isStatic && (attributes & MTypeAttributes::Abstract) == MTypeAttributes::Abstract;
_isInterface = (attributes & MTypeAttributes::ClassSemanticsMask) == MTypeAttributes::Interface;
// TODO: pass type info from C# side at once (pack into flags with attributes)
static void* TypeIsValueTypePtr = GetStaticMethodPointer(TEXT("TypeIsValueType"));
_isValueType = CallStaticMethod<bool, void*>(TypeIsValueTypePtr, handle);
static void* TypeIsEnumPtr = GetStaticMethodPointer(TEXT("TypeIsEnum"));
_isEnum = CallStaticMethod<bool, void*>(TypeIsEnumPtr, handle);
CachedClassHandles[handle] = this;
}
bool MAssembly::ResolveMissingFile(String& assemblyPath) const
{
#if DOTNET_HOST_MONO
// Fallback to AOT-ed assembly location
assemblyPath = Globals::BinariesFolder / TEXT("Dotnet") / StringUtils::GetFileName(assemblyPath);
return !FileSystem::FileExists(assemblyPath);
#endif
return true;
}
MClass::~MClass()
{
_methods.ClearDelete();
_fields.ClearDelete();
_properties.ClearDelete();
_events.ClearDelete();
CachedClassHandles.Remove(_handle);
}
StringAnsiView MClass::GetName() const
{
return _name;
}
StringAnsiView MClass::GetNamespace() const
{
return _namespace_;
}
MType* MClass::GetType() const
{
return (MType*)_handle;
}
MClass* MClass::GetBaseClass() const
{
static void* GetClassParentPtr = GetStaticMethodPointer(TEXT("GetClassParent"));
return CallStaticMethod<MClass*, void*>(GetClassParentPtr, _handle);
}
bool MClass::IsSubClassOf(const MClass* klass, bool checkInterfaces) const
{
static void* TypeIsSubclassOfPtr = GetStaticMethodPointer(TEXT("TypeIsSubclassOf"));
return klass && CallStaticMethod<bool, void*, void*, bool>(TypeIsSubclassOfPtr, _handle, klass->_handle, checkInterfaces);
}
bool MClass::HasInterface(const MClass* klass) const
{
static void* TypeIsAssignableFrom = GetStaticMethodPointer(TEXT("TypeIsAssignableFrom"));
return klass && CallStaticMethod<bool, void*, void*>(TypeIsAssignableFrom, klass->_handle, _handle);
}
bool MClass::IsInstanceOfType(MObject* object) const
{
if (object == nullptr)
return false;
MClass* objectClass = MCore::Object::GetClass(object);
return IsSubClassOf(objectClass, false);
}
uint32 MClass::GetInstanceSize() const
{
if (_size != 0)
return _size;
static void* NativeSizeOfPtr = GetStaticMethodPointer(TEXT("NativeSizeOf"));
_size = CallStaticMethod<int, void*>(NativeSizeOfPtr, _handle);
return _size;
}
MClass* MClass::GetElementClass() const
{
static void* GetElementClassPtr = GetStaticMethodPointer(TEXT("GetElementClass"));
return CallStaticMethod<MClass*, void*>(GetElementClassPtr, _handle);
}
MMethod* MClass::GetMethod(const char* name, int32 numParams) const
{
GetMethods();
for (int32 i = 0; i < _methods.Count(); i++)
{
if (_methods[i]->GetParametersCount() == numParams && _methods[i]->GetName() == name)
return _methods[i];
}
return nullptr;
}
const Array<MMethod*>& MClass::GetMethods() const
{
if (_hasCachedMethods)
return _methods;
ScopeLock lock(BinaryModule::Locker);
if (_hasCachedMethods)
return _methods;
NativeMethodDefinitions* methods;
int methodsCount;
static void* GetClassMethodsPtr = GetStaticMethodPointer(TEXT("GetClassMethods"));
CallStaticMethod<void, void*, NativeMethodDefinitions**, int*>(GetClassMethodsPtr, _handle, &methods, &methodsCount);
for (int32 i = 0; i < methodsCount; i++)
{
NativeMethodDefinitions& definition = methods[i];
MMethod* method = New<MMethod>(const_cast<MClass*>(this), StringAnsi(definition.name), definition.handle, definition.numParameters, definition.methodAttributes);
_methods.Add(method);
MCore::GC::FreeMemory((void*)definition.name);
}
MCore::GC::FreeMemory(methods);
_hasCachedMethods = true;
return _methods;
}
MField* MClass::GetField(const char* name) const
{
GetFields();
for (int32 i = 0; i < _fields.Count(); i++)
{
if (_fields[i]->GetName() == name)
return _fields[i];
}
return nullptr;
}
const Array<MField*>& MClass::GetFields() const
{
if (_hasCachedFields)
return _fields;
ScopeLock lock(BinaryModule::Locker);
if (_hasCachedFields)
return _fields;
NativeFieldDefinitions* fields;
int numFields;
static void* GetClassFieldsPtr = GetStaticMethodPointer(TEXT("GetClassFields"));
CallStaticMethod<void, void*, NativeFieldDefinitions**, int*>(GetClassFieldsPtr, _handle, &fields, &numFields);
for (int32 i = 0; i < numFields; i++)
{
NativeFieldDefinitions& definition = fields[i];
MField* field = New<MField>(const_cast<MClass*>(this), definition.fieldHandle, definition.name, definition.fieldType, definition.fieldOffset, definition.fieldAttributes);
_fields.Add(field);
MCore::GC::FreeMemory((void*)definition.name);
}
MCore::GC::FreeMemory(fields);
_hasCachedFields = true;
return _fields;
}
const Array<MEvent*>& MClass::GetEvents() const
{
if (_hasCachedEvents)
return _events;
// TODO: implement MEvent in .NET
_hasCachedEvents = true;
return _events;
}
MProperty* MClass::GetProperty(const char* name) const
{
GetProperties();
for (int32 i = 0; i < _properties.Count(); i++)
{
if (_properties[i]->GetName() == name)
return _properties[i];
}
return nullptr;
}
const Array<MProperty*>& MClass::GetProperties() const
{
if (_hasCachedProperties)
return _properties;
ScopeLock lock(BinaryModule::Locker);
if (_hasCachedProperties)
return _properties;
NativePropertyDefinitions* foundProperties;
int numProperties;
static void* GetClassPropertiesPtr = GetStaticMethodPointer(TEXT("GetClassProperties"));
CallStaticMethod<void, void*, NativePropertyDefinitions**, int*>(GetClassPropertiesPtr, _handle, &foundProperties, &numProperties);
for (int i = 0; i < numProperties; i++)
{
const NativePropertyDefinitions& definition = foundProperties[i];
MProperty* property = New<MProperty>(const_cast<MClass*>(this), definition.name, definition.getterHandle, definition.setterHandle, definition.getterAttributes, definition.setterAttributes);
_properties.Add(property);
MCore::GC::FreeMemory((void*)definition.name);
}
MCore::GC::FreeMemory(foundProperties);
_hasCachedProperties = true;
return _properties;
}
const Array<MClass*>& MClass::GetInterfaces() const
{
if (_hasCachedInterfaces)
return _interfaces;
ScopeLock lock(BinaryModule::Locker);
if (_hasCachedInterfaces)
return _interfaces;
MType** foundInterfaceTypes;
int numInterfaces;
static void* GetClassInterfacesPtr = GetStaticMethodPointer(TEXT("GetClassInterfaces"));
CallStaticMethod<void, void*, MType***, int*>(GetClassInterfacesPtr, _handle, &foundInterfaceTypes, &numInterfaces);
for (int32 i = 0; i < numInterfaces; i++)
{
MClass* interfaceClass = GetOrCreateClass(foundInterfaceTypes[i]);
_interfaces.Add(interfaceClass);
}
MCore::GC::FreeMemory(foundInterfaceTypes);
_hasCachedInterfaces = true;
return _interfaces;
}
bool MClass::HasAttribute(const MClass* monoClass) const
{
return GetCustomAttribute(this, monoClass) != nullptr;
}
bool MClass::HasAttribute() const
{
return !GetAttributes().IsEmpty();
}
MObject* MClass::GetAttribute(const MClass* monoClass) const
{
return (MObject*)GetCustomAttribute(this, monoClass);
}
const Array<MObject*>& MClass::GetAttributes() const
{
if (_hasCachedAttributes)
return _attributes;
ScopeLock lock(BinaryModule::Locker);
if (_hasCachedAttributes)
return _attributes;
MObject** attributes;
int numAttributes;
static void* GetClassAttributesPtr = GetStaticMethodPointer(TEXT("GetClassAttributes"));
CallStaticMethod<void, void*, MObject***, int*>(GetClassAttributesPtr, _handle, &attributes, &numAttributes);
_attributes.Set(attributes, numAttributes);
MCore::GC::FreeMemory(attributes);
_hasCachedAttributes = true;
return _attributes;
}
bool MDomain::SetCurrentDomain(bool force)
{
MActiveDomain = this;
return true;
}
void MDomain::Dispatch() const
{
}
MEvent::MEvent(MClass* parentClass, void* handle, const char* name)
: _handle(handle)
, _addMethod(nullptr)
, _removeMethod(nullptr)
, _parentClass(parentClass)
, _name(name)
, _hasCachedAttributes(false)
, _hasAddMonoMethod(true)
, _hasRemoveMonoMethod(true)
{
}
MMethod* MEvent::GetAddMethod() const
{
return nullptr; // TODO: implement MEvent in .NET
}
MMethod* MEvent::GetRemoveMethod() const
{
return nullptr; // TODO: implement MEvent in .NET
}
bool MEvent::HasAttribute(MClass* monoClass) const
{
return false; // TODO: implement MEvent in .NET
}
bool MEvent::HasAttribute() const
{
return false; // TODO: implement MEvent in .NET
}
MObject* MEvent::GetAttribute(MClass* monoClass) const
{
return nullptr; // TODO: implement MEvent in .NET
}
const Array<MObject*>& MEvent::GetAttributes() const
{
if (_hasCachedAttributes)
return _attributes;
_hasCachedAttributes = true;
// TODO: implement MEvent in .NET
return _attributes;
}
MException::MException(MObject* exception)
: InnerException(nullptr)
{
ASSERT(exception);
MClass* exceptionClass = MCore::Object::GetClass(exception);
MProperty* exceptionMsgProp = exceptionClass->GetProperty("Message");
MMethod* exceptionMsgGetter = exceptionMsgProp->GetGetMethod();
MString* exceptionMsg = (MString*)exceptionMsgGetter->Invoke(exception, nullptr, nullptr);
Message = MUtils::ToString(exceptionMsg);
MProperty* exceptionStackProp = exceptionClass->GetProperty("StackTrace");
MMethod* exceptionStackGetter = exceptionStackProp->GetGetMethod();
MString* exceptionStackTrace = (MString*)exceptionStackGetter->Invoke(exception, nullptr, nullptr);
StackTrace = MUtils::ToString(exceptionStackTrace);
MProperty* innerExceptionProp = exceptionClass->GetProperty("InnerException");
MMethod* innerExceptionGetter = innerExceptionProp->GetGetMethod();
MObject* innerException = (MObject*)innerExceptionGetter->Invoke(exception, nullptr, nullptr);
if (innerException)
InnerException = New<MException>(innerException);
}
MException::~MException()
{
if (InnerException)
Delete(InnerException);
}
MField::MField(MClass* parentClass, void* handle, const char* name, void* type, int fieldOffset, MFieldAttributes attributes)
: _handle(handle)
, _type(type)
, _fieldOffset(fieldOffset)
, _parentClass(parentClass)
, _name(name)
, _hasCachedAttributes(false)
{
switch (attributes & MFieldAttributes::FieldAccessMask)
{
case MFieldAttributes::Private:
_visibility = MVisibility::Private;
break;
case MFieldAttributes::FamANDAssem:
_visibility = MVisibility::PrivateProtected;
break;
case MFieldAttributes::Assembly:
_visibility = MVisibility::Internal;
break;
case MFieldAttributes::Family:
_visibility = MVisibility::Protected;
break;
case MFieldAttributes::FamORAssem:
_visibility = MVisibility::ProtectedInternal;
break;
case MFieldAttributes::Public:
_visibility = MVisibility::Public;
break;
default:
CRASH;
}
_isStatic = (attributes & MFieldAttributes::Static) == MFieldAttributes::Static;
}
MType* MField::GetType() const
{
return (MType*)_type;
}
int32 MField::GetOffset() const
{
return _fieldOffset;
}
void MField::GetValue(MObject* instance, void* result) const
{
static void* FieldGetValuePtr = GetStaticMethodPointer(TEXT("FieldGetValue"));
CallStaticMethod<void, void*, void*, void*>(FieldGetValuePtr, instance, _handle, result);
}
void MField::GetValueReference(MObject* instance, void* result) const
{
static void* FieldGetValueReferencePtr = GetStaticMethodPointer(TEXT("FieldGetValueReference"));
CallStaticMethod<void, void*, void*, int, void*>(FieldGetValueReferencePtr, instance, _handle, _fieldOffset, result);
}
MObject* MField::GetValueBoxed(MObject* instance) const
{
static void* FieldGetValueBoxedPtr = GetStaticMethodPointer(TEXT("FieldGetValueBoxed"));
return CallStaticMethod<MObject*, void*, void*>(FieldGetValueBoxedPtr, instance, _handle);
}
void MField::SetValue(MObject* instance, void* value) const
{
static void* FieldSetValuePtr = GetStaticMethodPointer(TEXT("FieldSetValue"));
CallStaticMethod<void, void*, void*, void*>(FieldSetValuePtr, instance, _handle, value);
}
bool MField::HasAttribute(MClass* monoClass) const
{
// TODO: implement MField attributes in .NET
return false;
}
bool MField::HasAttribute() const
{
// TODO: implement MField attributes in .NET
return false;
}
MObject* MField::GetAttribute(MClass* monoClass) const
{
// TODO: implement MField attributes in .NET
return nullptr;
}
const Array<MObject*>& MField::GetAttributes() const
{
if (_hasCachedAttributes)
return _attributes;
_hasCachedAttributes = true;
// TODO: implement MField attributes in .NET
return _attributes;
}
MMethod::MMethod(MClass* parentClass, StringAnsi&& name, void* handle, int32 paramsCount, MMethodAttributes attributes)
: _handle(handle)
, _paramsCount(paramsCount)
, _parentClass(parentClass)
, _name(MoveTemp(name))
, _hasCachedAttributes(false)
, _hasCachedSignature(false)
{
switch (attributes & MMethodAttributes::MemberAccessMask)
{
case MMethodAttributes::Private:
_visibility = MVisibility::Private;
break;
case MMethodAttributes::FamANDAssem:
_visibility = MVisibility::PrivateProtected;
break;
case MMethodAttributes::Assembly:
_visibility = MVisibility::Internal;
break;
case MMethodAttributes::Family:
_visibility = MVisibility::Protected;
break;
case MMethodAttributes::FamORAssem:
_visibility = MVisibility::ProtectedInternal;
break;
case MMethodAttributes::Public:
_visibility = MVisibility::Public;
break;
default:
CRASH;
}
_isStatic = (attributes & MMethodAttributes::Static) == MMethodAttributes::Static;
#if COMPILE_WITH_PROFILER
const StringAnsi& className = parentClass->GetFullName();
ProfilerName.Resize(className.Length() + 2 + _name.Length());
Platform::MemoryCopy(ProfilerName.Get(), className.Get(), className.Length());
ProfilerName.Get()[className.Length()] = ':';
ProfilerName.Get()[className.Length() + 1] = ':';
Platform::MemoryCopy(ProfilerName.Get() + className.Length() + 2, _name.Get(), _name.Length());
ProfilerData.name = ProfilerName.Get();
ProfilerData.function = _name.Get();
ProfilerData.file = nullptr;
ProfilerData.line = 0;
ProfilerData.color = 0;
#endif
}
void MMethod::CacheSignature() const
{
ScopeLock lock(BinaryModule::Locker);
if (_hasCachedSignature)
return;
static void* GetMethodReturnTypePtr = GetStaticMethodPointer(TEXT("GetMethodReturnType"));
static void* GetMethodParameterTypesPtr = GetStaticMethodPointer(TEXT("GetMethodParameterTypes"));
_returnType = CallStaticMethod<void*, void*>(GetMethodReturnTypePtr, _handle);
if (_paramsCount != 0)
{
void** parameterTypeHandles;
CallStaticMethod<void, void*, void***>(GetMethodParameterTypesPtr, _handle, &parameterTypeHandles);
_parameterTypes.Set(parameterTypeHandles, _paramsCount);
MCore::GC::FreeMemory(parameterTypeHandles);
}
_hasCachedSignature = true;
}
MObject* MMethod::Invoke(void* instance, void** params, MObject** exception) const
{
PROFILE_CPU_SRC_LOC(ProfilerData);
static void* InvokeMethodPtr = GetStaticMethodPointer(TEXT("InvokeMethod"));
return (MObject*)CallStaticMethod<void*, void*, void*, void*, void*>(InvokeMethodPtr, instance, _handle, params, exception);
}
MObject* MMethod::InvokeVirtual(MObject* instance, void** params, MObject** exception) const
{
return Invoke(instance, params, exception);
}
#if !USE_MONO_AOT
void* MMethod::GetThunk()
{
if (!_cachedThunk)
{
static void* GetThunkPtr = GetStaticMethodPointer(TEXT("GetThunk"));
_cachedThunk = CallStaticMethod<void*, void*>(GetThunkPtr, _handle);
#if !BUILD_RELEASE
if (!_cachedThunk)
LOG(Error, "Failed to get C# method thunk for {0}::{1}", String(_parentClass->GetFullName()), String(_name));
#endif
}
return _cachedThunk;
}
#endif
MMethod* MMethod::InflateGeneric() const
{
// This seams to be unused on .NET (Mono required inflating generic class of the script)
return const_cast<MMethod*>(this);
}
MType* MMethod::GetReturnType() const
{
if (!_hasCachedSignature)
CacheSignature();
return (MType*)_returnType;
}
int32 MMethod::GetParametersCount() const
{
return _paramsCount;
}
MType* MMethod::GetParameterType(int32 paramIdx) const
{
if (!_hasCachedSignature)
CacheSignature();
ASSERT_LOW_LAYER(paramIdx >= 0 && paramIdx < _paramsCount);
return (MType*)_parameterTypes.Get()[paramIdx];
}
bool MMethod::GetParameterIsOut(int32 paramIdx) const
{
if (!_hasCachedSignature)
CacheSignature();
ASSERT_LOW_LAYER(paramIdx >= 0 && paramIdx < _paramsCount);
// TODO: cache GetParameterIsOut maybe?
static void* GetMethodParameterIsOutPtr = GetStaticMethodPointer(TEXT("GetMethodParameterIsOut"));
return CallStaticMethod<bool, void*, int>(GetMethodParameterIsOutPtr, _handle, paramIdx);
}
bool MMethod::HasAttribute(MClass* monoClass) const
{
// TODO: implement MMethod attributes in .NET
return false;
}
bool MMethod::HasAttribute() const
{
// TODO: implement MMethod attributes in .NET
return false;
}
MObject* MMethod::GetAttribute(MClass* monoClass) const
{
// TODO: implement MMethod attributes in .NET
return nullptr;
}
const Array<MObject*>& MMethod::GetAttributes() const
{
if (_hasCachedAttributes)
return _attributes;
_hasCachedAttributes = true;
// TODO: implement MMethod attributes in .NET
return _attributes;
}
MProperty::MProperty(MClass* parentClass, const char* name, void* getterHandle, void* setterHandle, MMethodAttributes getterAttributes, MMethodAttributes setterAttributes)
: _parentClass(parentClass)
, _name(name)
, _hasCachedAttributes(false)
{
_hasGetMethod = getterHandle != nullptr;
if (_hasGetMethod)
_getMethod = New<MMethod>(parentClass, StringAnsi("get_" + _name), getterHandle, 1, getterAttributes);
else
_getMethod = nullptr;
_hasSetMethod = setterHandle != nullptr;
if (_hasSetMethod)
_setMethod = New<MMethod>(parentClass, StringAnsi("set_" + _name), setterHandle, 1, setterAttributes);
else
_setMethod = nullptr;
}
MProperty::~MProperty()
{
if (_getMethod)
Delete(_getMethod);
if (_setMethod)
Delete(_setMethod);
}
MMethod* MProperty::GetGetMethod() const
{
return _getMethod;
}
MMethod* MProperty::GetSetMethod() const
{
return _setMethod;
}
MObject* MProperty::GetValue(MObject* instance, MObject** exception) const
{
CHECK_RETURN(_getMethod, nullptr);
return _getMethod->Invoke(instance, nullptr, exception);
}
void MProperty::SetValue(MObject* instance, void* value, MObject** exception) const
{
CHECK(_setMethod);
void* params[1];
params[0] = value;
_setMethod->Invoke(instance, params, exception);
}
bool MProperty::HasAttribute(MClass* monoClass) const
{
// TODO: implement MProperty attributes in .NET
return false;
}
bool MProperty::HasAttribute() const
{
// TODO: implement MProperty attributes in .NET
return false;
}
MObject* MProperty::GetAttribute(MClass* monoClass) const
{
// TODO: implement MProperty attributes in .NET
return nullptr;
}
const Array<MObject*>& MProperty::GetAttributes() const
{
if (_hasCachedAttributes)
return _attributes;
_hasCachedAttributes = true;
// TODO: implement MProperty attributes in .NET
return _attributes;
}
MAssembly* GetAssembly(void* assemblyHandle)
{
ScopeLock lock(BinaryModule::Locker);
MAssembly* assembly;
if (CachedAssemblyHandles.TryGet(assemblyHandle, assembly))
return assembly;
return nullptr;
}
MClass* GetClass(MType* typeHandle)
{
ScopeLock lock(BinaryModule::Locker);
MClass* klass = nullptr;
CachedClassHandles.TryGet(typeHandle, klass);
return nullptr;
}
MClass* GetOrCreateClass(MType* typeHandle)
{
if (!typeHandle)
return nullptr;
ScopeLock lock(BinaryModule::Locker);
MClass* klass;
if (!CachedClassHandles.TryGet(typeHandle, klass))
{
NativeClassDefinitions classInfo;
void* assemblyHandle;
static void* GetManagedClassFromTypePtr = GetStaticMethodPointer(TEXT("GetManagedClassFromType"));
CallStaticMethod<void, void*, void*>(GetManagedClassFromTypePtr, typeHandle, &classInfo, &assemblyHandle);
MAssembly* assembly = GetAssembly(assemblyHandle);
klass = New<MClass>(assembly, classInfo.typeHandle, classInfo.name, classInfo.fullname, classInfo.namespace_, classInfo.typeAttributes);
if (assembly != nullptr)
{
auto& classes = const_cast<MAssembly::ClassesDictionary&>(assembly->GetClasses());
if (classes.ContainsKey(klass->GetFullName()))
{
LOG(Warning, "Class '{0}' was already added to assembly '{1}'", String(klass->GetFullName()), String(assembly->GetName()));
}
classes[klass->GetFullName()] = klass;
}
if (typeHandle != classInfo.typeHandle)
CallStaticMethod<void, void*, void*>(GetManagedClassFromTypePtr, typeHandle, &classInfo);
MCore::GC::FreeMemory((void*)classInfo.name);
MCore::GC::FreeMemory((void*)classInfo.fullname);
MCore::GC::FreeMemory((void*)classInfo.namespace_);
}
ASSERT(klass != nullptr);
return klass;
}
MType* GetObjectType(MObject* obj)
{
static void* GetObjectTypePtr = GetStaticMethodPointer(TEXT("GetObjectType"));
void* typeHandle = CallStaticMethod<void*, void*>(GetObjectTypePtr, obj);
return (MType*)typeHandle;
}
void* GetCustomAttribute(const MClass* klass, const MClass* attributeClass)
{
const Array<MObject*>& attributes = klass->GetAttributes();
for (MObject* attr : attributes)
{
MClass* attrClass = MCore::Object::GetClass(attr);
if (attrClass == attributeClass)
return attr;
}
return nullptr;
}
#if DOTNET_HOST_CORECLR
const char_t* NativeInteropTypeName = FLAX_CORECLR_TEXT("FlaxEngine.Interop.NativeInterop, FlaxEngine.CSharp");
hostfxr_initialize_for_runtime_config_fn hostfxr_initialize_for_runtime_config;
hostfxr_initialize_for_dotnet_command_line_fn hostfxr_initialize_for_dotnet_command_line;
hostfxr_get_runtime_delegate_fn hostfxr_get_runtime_delegate;
hostfxr_close_fn hostfxr_close;
load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer;
get_function_pointer_fn get_function_pointer;
hostfxr_set_error_writer_fn hostfxr_set_error_writer;
hostfxr_get_dotnet_environment_info_result_fn hostfxr_get_dotnet_environment_info_result;
hostfxr_run_app_fn hostfxr_run_app;
bool InitHostfxr()
{
const ::String csharpLibraryPath = Globals::BinariesFolder / TEXT("FlaxEngine.CSharp.dll");
const ::String csharpRuntimeConfigPath = Globals::BinariesFolder / TEXT("FlaxEngine.CSharp.runtimeconfig.json");
if (!FileSystem::FileExists(csharpLibraryPath))
LOG(Fatal, "Failed to initialize .NET runtime, missing file: {0}", csharpLibraryPath);
if (!FileSystem::FileExists(csharpRuntimeConfigPath))
LOG(Fatal, "Failed to initialize .NET runtime, missing file: {0}", csharpRuntimeConfigPath);
const FLAX_CORECLR_STRING& libraryPath = FLAX_CORECLR_STRING(csharpLibraryPath);
// Get path to hostfxr library
get_hostfxr_parameters get_hostfxr_params;
get_hostfxr_params.size = sizeof(hostfxr_initialize_parameters);
get_hostfxr_params.assembly_path = libraryPath.Get();
#if PLATFORM_MAC
::String macOSDotnetRoot = TEXT("/usr/local/share/dotnet");
#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(_M_X64)
// When emulating x64 on arm
const ::String dotnetRootEmulated = macOSDotnetRoot / TEXT("x64");
if (FileSystem::FileExists(dotnetRootEmulated / TEXT("dotnet"))) {
macOSDotnetRoot = dotnetRootEmulated;
}
#endif
const FLAX_CORECLR_STRING& finalDotnetRootPath = FLAX_CORECLR_STRING(macOSDotnetRoot);
get_hostfxr_params.dotnet_root = finalDotnetRootPath.Get();
#else
get_hostfxr_params.dotnet_root = nullptr;
#endif
FLAX_CORECLR_STRING dotnetRoot;
String dotnetRootEnvVar;
if (!Platform::GetEnvironmentVariable(TEXT("DOTNET_ROOT"), dotnetRootEnvVar) && FileSystem::DirectoryExists(dotnetRootEnvVar))
{
dotnetRoot = FLAX_CORECLR_STRING(dotnetRootEnvVar);
get_hostfxr_params.dotnet_root = dotnetRoot.Get();
}
#if !USE_EDITOR
const String bundledDotnetPath = Globals::ProjectFolder / TEXT("Dotnet");
if (FileSystem::DirectoryExists(bundledDotnetPath))
{
dotnetRoot = FLAX_CORECLR_STRING(bundledDotnetPath);
#if PLATFORM_WINDOWS_FAMILY
dotnetRoot.Replace('/', '\\');
#endif
get_hostfxr_params.dotnet_root = dotnetRoot.Get();
}
#endif
char_t hostfxrPath[1024];
size_t hostfxrPathSize = sizeof(hostfxrPath) / sizeof(char_t);
int rc = get_hostfxr_path(hostfxrPath, &hostfxrPathSize, &get_hostfxr_params);
if (rc != 0)
{
LOG(Error, "Failed to find hostfxr: {0:x} ({1})", (unsigned int)rc, String(get_hostfxr_params.dotnet_root));
// Warn user about missing .Net
#if PLATFORM_DESKTOP
Platform::OpenUrl(TEXT("https://dotnet.microsoft.com/en-us/download/dotnet/8.0"));
#endif
#if USE_EDITOR
LOG(Fatal, "Missing .NET 8 or later SDK installation required to run Flax Editor.");
#else
LOG(Fatal, "Missing .NET 8 or later Runtime installation required to run this application.");
#endif
return true;
}
String path(hostfxrPath);
LOG(Info, "Found hostfxr in {0}", path);
// Get API from hostfxr library
void* hostfxr = Platform::LoadLibrary(path.Get());
if (hostfxr == nullptr)
{
if (FileSystem::FileExists(path))
LOG(Fatal, "Failed to load hostfxr library, possible platform/architecture mismatch with the library. See log for more information. ({0})", path);
else
LOG(Fatal, "Failed to load hostfxr library ({0})", path);
return true;
}
hostfxr_initialize_for_runtime_config = (hostfxr_initialize_for_runtime_config_fn)Platform::GetProcAddress(hostfxr, "hostfxr_initialize_for_runtime_config");
hostfxr_initialize_for_dotnet_command_line = (hostfxr_initialize_for_dotnet_command_line_fn)Platform::GetProcAddress(hostfxr, "hostfxr_initialize_for_dotnet_command_line");
hostfxr_get_runtime_delegate = (hostfxr_get_runtime_delegate_fn)Platform::GetProcAddress(hostfxr, "hostfxr_get_runtime_delegate");
hostfxr_close = (hostfxr_close_fn)Platform::GetProcAddress(hostfxr, "hostfxr_close");
hostfxr_set_error_writer = (hostfxr_set_error_writer_fn)Platform::GetProcAddress(hostfxr, "hostfxr_set_error_writer");
hostfxr_get_dotnet_environment_info_result = (hostfxr_get_dotnet_environment_info_result_fn)Platform::GetProcAddress(hostfxr, "hostfxr_get_dotnet_environment_info_result");
hostfxr_run_app = (hostfxr_run_app_fn)Platform::GetProcAddress(hostfxr, "hostfxr_run_app");
if (!hostfxr_get_runtime_delegate || !hostfxr_run_app)
{
LOG(Fatal, "Failed to setup hostfxr API ({0})", path);
return true;
}
// TODO: Implement support for picking RC/beta updates of .NET runtime
// Uncomment for enabling support for upcoming .NET major release candidates
#if 0
String dotnetRollForwardPr;
if (Platform::GetEnvironmentVariable(TEXT("DOTNET_ROLL_FORWARD_TO_PRERELEASE"), dotnetRollForwardPr))
Platform::SetEnvironmentVariable(TEXT("DOTNET_ROLL_FORWARD_TO_PRERELEASE"), TEXT("1"));
#endif
// Initialize hosting component
const char_t* argv[1] = { libraryPath.Get() };
hostfxr_initialize_parameters init_params;
init_params.size = sizeof(hostfxr_initialize_parameters);
init_params.host_path = libraryPath.Get();
path = String(StringUtils::GetDirectoryName(path)) / TEXT("/../../../");
StringUtils::PathRemoveRelativeParts(path);
dotnetRoot = FLAX_CORECLR_STRING(path);
init_params.dotnet_root = dotnetRoot.Get();
hostfxr_handle handle = nullptr;
rc = hostfxr_initialize_for_dotnet_command_line(ARRAY_COUNT(argv), argv, &init_params, &handle);
if (rc != 0 || handle == nullptr)
{
hostfxr_close(handle);
if (rc == 0x80008096) // FrameworkMissingFailure
{
String platformStr;
switch (PLATFORM_TYPE)
{
case PlatformType::Windows:
case PlatformType::UWP:
platformStr = PLATFORM_64BITS ? "Windows x64" : "Windows x86";
break;
case PlatformType::Linux:
platformStr = PLATFORM_ARCH_ARM64 ? "Linux Arm64" : PLATFORM_ARCH_ARM ? "Linux Arm32" : PLATFORM_64BITS ? "Linux x64" : "Linux x86";
break;
case PlatformType::Mac:
platformStr = PLATFORM_ARCH_ARM || PLATFORM_ARCH_ARM64 ? "macOS Arm64" : PLATFORM_64BITS ? "macOS x64" : "macOS x86";
break;
default:;
platformStr = "";
}
LOG(Fatal, "Failed to resolve compatible .NET runtime version in '{0}'. Make sure the correct platform version for runtime is installed ({1})", platformStr, String(init_params.dotnet_root));
}
else
LOG(Fatal, "Failed to initialize hostfxr: {0:x} ({1})", (unsigned int)rc, String(init_params.dotnet_root));
return true;
}
void* pget_function_pointer = nullptr;
rc = hostfxr_get_runtime_delegate(handle, hdt_get_function_pointer, &pget_function_pointer);
if (rc != 0 || pget_function_pointer == nullptr)
{
hostfxr_close(handle);
LOG(Fatal, "Failed to get runtime delegate hdt_get_function_pointer: 0x{0:x}", (unsigned int)rc);
return true;
}
hostfxr_close(handle);
get_function_pointer = (get_function_pointer_fn)pget_function_pointer;
return false;
}
void ShutdownHostfxr()
{
}
void* GetStaticMethodPointer(const String& methodName)
{
void* fun;
if (CachedFunctions.TryGet(methodName, fun))
return fun;
PROFILE_CPU();
const int rc = get_function_pointer(NativeInteropTypeName, FLAX_CORECLR_STRING(methodName).Get(), UNMANAGEDCALLERSONLY_METHOD, nullptr, nullptr, &fun);
if (rc != 0)
LOG(Fatal, "Failed to get unmanaged function pointer for method '{0}': 0x{1:x}", methodName, (unsigned int)rc);
CachedFunctions.Add(methodName, fun);
return fun;
}
#elif DOTNET_HOST_MONO
void OnLogCallback(const char* logDomain, const char* logLevel, const char* message, mono_bool fatal, void* userData)
{
String currentDomain(logDomain);
String msg(message);
msg.Replace('\n', ' ');
static const char* monoErrorLevels[] =
{
nullptr,
"error",
"critical",
"warning",
"message",
"info",
"debug"
};
uint32 errorLevel = 0;
if (logLevel != nullptr)
{
for (uint32 i = 1; i < 7; i++)
{
if (strcmp(monoErrorLevels[i], logLevel) == 0)
{
errorLevel = i;
break;
}
}
}
if (currentDomain.IsEmpty())
{
auto domain = MCore::GetActiveDomain();
if (domain != nullptr)
{
currentDomain = domain->GetName().Get();
}
else
{
currentDomain = "null";
}
}
#if 0
// Print C# stack trace (crash may be caused by the managed code)
if (mono_domain_get() && Assemblies::FlaxEngine.Assembly->IsLoaded())
{
const auto managedStackTrace = DebugLog::GetStackTrace();
if (managedStackTrace.HasChars())
{
LOG(Warning, "Managed stack trace:");
LOG_STR(Warning, managedStackTrace);
}
}
#endif
if (errorLevel == 0)
{
Log::CLRInnerException(String::Format(TEXT("Message: {0} | Domain: {1}"), msg, currentDomain)).SetLevel(LogType::Error);
}
else if (errorLevel <= 2)
{
Log::CLRInnerException(String::Format(TEXT("Message: {0} | Domain: {1}"), msg, currentDomain)).SetLevel(LogType::Error);
}
else if (errorLevel <= 3)
{
LOG(Warning, "Message: {0} | Domain: {1}", msg, currentDomain);
}
else
{
LOG(Info, "Message: {0} | Domain: {1}", msg, currentDomain);
}
}
void OnPrintCallback(const char* string, mono_bool isStdout)
{
LOG_STR(Warning, String(string));
}
void OnPrintErrorCallback(const char* string, mono_bool isStdout)
{
// HACK: ignore this message
if (string && Platform::MemoryCompare(string, "debugger-agent: Unable to listen on ", 36) == 0)
return;
LOG_STR(Error, String(string));
}
static MonoAssembly* OnMonoAssemblyLoad(const char* aname)
{
// Find assembly file
const String name(aname);
#if DOTNET_HOST_MONO_DEBUG
LOG(Info, "Loading C# assembly {0}", name);
#endif
String fileName = name;
if (!name.EndsWith(TEXT(".dll")) && !name.EndsWith(TEXT(".exe")))
fileName += TEXT(".dll");
String path = fileName;
if (!FileSystem::FileExists(path))
{
path = Globals::ProjectFolder / String(TEXT("/Dotnet/shared/Microsoft.NETCore.App/")) / fileName;
if (!FileSystem::FileExists(path))
{
path = Globals::ProjectFolder / String(TEXT("/Dotnet/")) / fileName;
}
}
// Load assembly
#if DOTNET_HOST_MONO_DEBUG
LOG(Info, "Loading C# assembly from path = {0}, exist = {1}", path, FileSystem::FileExists(path));
#endif
MonoAssembly* assembly = nullptr;
if (FileSystem::FileExists(path))
{
StringAnsi pathAnsi(path);
assembly = mono_assembly_open(pathAnsi.Get(), nullptr);
}
if (!assembly)
{
LOG(Error, "Failed to load C# assembly {0}", path);
}
return assembly;
}
static MonoAssembly* OnMonoAssemblyPreloadHook(MonoAssemblyName* aname, char** assemblies_path, void* user_data)
{
return OnMonoAssemblyLoad(mono_assembly_name_get_name(aname));
}
#if 0
static unsigned char* OnMonoLoadAOT(MonoAssembly* assembly, int size, void* user_data, void** out_handle)
{
MonoAssemblyName* assemblyName = mono_assembly_get_name(assembly);
const char* assemblyNameStr = mono_assembly_name_get_name(assemblyName);
#if DOTNET_HOST_MONO_DEBUG
LOG(Info, "Loading AOT data for C# assembly {0}", String(assemblyNameStr));
#endif
return nullptr;
}
static void OnMonoFreeAOT(MonoAssembly* assembly, int size, void* user_data, void* handle)
{
#if DOTNET_HOST_MONO_DEBUG
MonoAssemblyName* assemblyName = mono_assembly_get_name(assembly);
const char* assemblyNameStr = mono_assembly_name_get_name(assemblyName);
LOG(Info, "Free AOT data for C# assembly {0}", String(assemblyNameStr));
#endif
}
#endif
#if PLATFORM_IOS
#include "Engine/Engine/Globals.h"
#include <mono/utils/mono-dl-fallback.h>
#include <dlfcn.h>
static void* OnMonoDlFallbackLoad(const char* name, int flags, char** err, void* user_data)
{
const String fileName = StringUtils::GetFileName(String(name));
#if DOTNET_HOST_MONO_DEBUG
LOG(Info, "Loading dynamic library {0}", fileName);
#endif
int dlFlags = 0;
if (flags & MONO_DL_GLOBAL && !(flags & MONO_DL_LOCAL))
dlFlags |= RTLD_GLOBAL;
else
dlFlags |= RTLD_LOCAL;
if (flags & MONO_DL_LAZY)
dlFlags |= RTLD_LAZY;
else
dlFlags |= RTLD_NOW;
void* result = dlopen(name, dlFlags);
if (!result)
{
// Try Frameworks location on iOS
String path = Globals::ProjectFolder / TEXT("Frameworks") / fileName;
if (!path.EndsWith(TEXT(".dylib")))
path += TEXT(".dylib");
result = dlopen(StringAsANSI<>(*path).Get(), dlFlags);
if (!result)
{
LOG(Error, "Failed to load dynamic libary {0}", String(name));
}
}
return result;
}
static void* OnMonoDlFallbackSymbol(void* handle, const char* name, char** err, void* user_data)
{
return dlsym(handle, name);
}
static void* OnMonoDlFallbackClose(void* handle, void* user_data)
{
dlclose(handle);
return 0;
}
#endif
bool InitHostfxr()
{
#if DOTNET_HOST_MONO_DEBUG
// Enable detailed Mono logging
Platform::SetEnvironmentVariable(TEXT("MONO_LOG_LEVEL"), TEXT("debug"));
Platform::SetEnvironmentVariable(TEXT("MONO_LOG_MASK"), TEXT("all"));
//Platform::SetEnvironmentVariable(TEXT("MONO_GC_DEBUG"), TEXT("6:gc-log.txt,check-remset-consistency,nursery-canaries"));
#endif
// Adjust GC threads suspending mode to not block attached native threads (eg. Job System)
Platform::SetEnvironmentVariable(TEXT("MONO_THREADS_SUSPEND"), TEXT("preemptive"));
#if defined(USE_MONO_AOT_MODE)
// Enable AOT mode (per-platform)
mono_jit_set_aot_mode(USE_MONO_AOT_MODE);
#endif
// Platform-specific setup
#if PLATFORM_IOS
setenv("MONO_AOT_MODE", "aot", 1);
setenv("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "1", 1);
#endif
#ifdef USE_MONO_AOT_MODULE
// Load AOT module
const DateTime aotModuleLoadStartTime = DateTime::Now();
LOG(Info, "Loading Mono AOT module...");
void* libAotModule = Platform::LoadLibrary(TEXT(USE_MONO_AOT_MODULE));
if (libAotModule == nullptr)
{
LOG(Error, "Failed to laod Mono AOT module (" TEXT(USE_MONO_AOT_MODULE) ")");
return true;
}
MonoAotModuleHandle = libAotModule;
void* getModulesPtr = Platform::GetProcAddress(libAotModule, "GetMonoModules");
if (getModulesPtr == nullptr)
{
LOG(Error, "Failed to get Mono AOT modules getter.");
return true;
}
typedef int (*GetMonoModulesFunc)(void** buffer, int bufferSize);
const auto getModules = (GetMonoModulesFunc)getModulesPtr;
const int32 moduelsCount = getModules(nullptr, 0);
void** modules = (void**)Allocator::Allocate(moduelsCount * sizeof(void*));
getModules(modules, moduelsCount);
for (int32 i = 0; i < moduelsCount; i++)
{
mono_aot_register_module((void**)modules[i]);
}
Allocator::Free(modules);
LOG(Info, "Mono AOT module loaded in {0}ms", (int32)(DateTime::Now() - aotModuleLoadStartTime).GetTotalMilliseconds());
#endif
// Setup debugger
{
int32 debuggerLogLevel = 0;
if (CommandLine::Options.MonoLog.IsTrue() || DOTNET_HOST_MONO_DEBUG)
{
LOG(Info, "Using detailed Mono logging");
mono_trace_set_level_string("debug");
debuggerLogLevel = 10;
}
else
{
mono_trace_set_level_string("warning");
}
#if MONO_DEBUG_ENABLE && !PLATFORM_SWITCH
StringAnsi debuggerIp = "127.0.0.1";
uint16 debuggerPort = 41000 + Platform::GetCurrentProcessId() % 1000;
if (CommandLine::Options.DebuggerAddress.HasValue())
{
const auto& address = CommandLine::Options.DebuggerAddress.GetValue();
const int32 splitIndex = address.Find(':');
if (splitIndex == INVALID_INDEX)
{
debuggerIp = address.ToStringAnsi();
}
else
{
debuggerIp = address.Left(splitIndex).ToStringAnsi();
StringUtils::Parse(address.Right(address.Length() - splitIndex - 1).Get(), &debuggerPort);
}
}
char buffer[150];
sprintf(buffer, "--debugger-agent=transport=dt_socket,address=%s:%d,embedding=1,server=y,suspend=%s,loglevel=%d", debuggerIp.Get(), debuggerPort, CommandLine::Options.WaitForDebugger ? "y,timeout=5000" : "n", debuggerLogLevel);
const char* options[] = {
"--soft-breakpoints",
//"--optimize=float32",
buffer
};
mono_jit_parse_options(ARRAY_COUNT(options), (char**)options);
mono_debug_init(MONO_DEBUG_FORMAT_MONO, 0);
LOG(Info, "Mono debugger server at {0}:{1}", ::String(debuggerIp), debuggerPort);
#endif
}
// Connect to mono engine callback system
mono_trace_set_log_handler(OnLogCallback, nullptr);
mono_trace_set_print_handler(OnPrintCallback);
mono_trace_set_printerr_handler(OnPrintErrorCallback);
// Initialize Mono VM
StringAnsi baseDirectory(Globals::ProjectFolder);
const char* appctxKeys[] =
{
"RUNTIME_IDENTIFIER",
"APP_CONTEXT_BASE_DIRECTORY",
};
const char* appctxValues[] =
{
MACRO_TO_STR(DOTNET_HOST_RUNTIME_IDENTIFIER),
baseDirectory.Get(),
};
static_assert(ARRAY_COUNT(appctxKeys) == ARRAY_COUNT(appctxValues), "Invalid appctx setup");
monovm_initialize(ARRAY_COUNT(appctxKeys), appctxKeys, appctxValues);
mono_install_assembly_preload_hook(OnMonoAssemblyPreloadHook, nullptr);
#if 0
mono_install_load_aot_data_hook(OnMonoLoadAOT, OnMonoFreeAOT, nullptr);
#endif
#if PLATFORM_IOS
mono_dl_fallback_register(OnMonoDlFallbackLoad, OnMonoDlFallbackSymbol, OnMonoDlFallbackClose, nullptr);
#endif
// Init managed runtime
#if PLATFORM_ANDROID || PLATFORM_IOS
const char* monoVersion = "mobile";
#else
const char* monoVersion = ""; // ignored
#endif
MonoDomainHandle = mono_jit_init_version("Flax", monoVersion);
if (!MonoDomainHandle)
{
LOG(Fatal, "Failed to initialize Mono.");
return true;
}
mono_gc_init_finalizer_thread();
// Log info
char* buildInfo = mono_get_runtime_build_info();
LOG(Info, "Mono runtime version: {0}", String(buildInfo));
mono_free(buildInfo);
return false;
}
void ShutdownHostfxr()
{
mono_jit_cleanup(MonoDomainHandle);
MonoDomainHandle = nullptr;
#ifdef USE_MONO_AOT_MODULE
Platform::FreeLibrary(MonoAotModuleHandle);
#endif
}
void* GetStaticMethodPointer(const String& methodName)
{
void* fun;
if (CachedFunctions.TryGet(methodName, fun))
return fun;
PROFILE_CPU();
static MonoClass* nativeInteropClass = nullptr;
if (!nativeInteropClass)
{
const char* assemblyName = "FlaxEngine.CSharp";
const char* className = "FlaxEngine.Interop.NativeInterop";
MonoAssembly* flaxEngineAssembly = OnMonoAssemblyLoad(assemblyName);
ASSERT(flaxEngineAssembly);
MonoType* interopTyp = mono_reflection_type_from_name((char*)className, mono_assembly_get_image(flaxEngineAssembly));
ASSERT(interopTyp);
nativeInteropClass = mono_class_from_mono_type(interopTyp);
ASSERT(nativeInteropClass);
}
const StringAsUTF8<40> methodNameAnsi(methodName.Get(), methodName.Length());
MonoMethod* method = mono_class_get_method_from_name(nativeInteropClass, methodNameAnsi.Get(), -1);
ASSERT(method);
MonoError error;
mono_error_init(&error);
fun = mono_method_get_unmanaged_callers_only_ftnptr(method, &error);
if (fun == nullptr)
{
const unsigned short errorCode = mono_error_get_error_code(&error);
const char* errorMessage = mono_error_get_message(&error);
LOG(Fatal, "Failed to get unmanaged function pointer for method '{0}': 0x{1:x}, {2}", methodName, errorCode, String(errorMessage));
}
mono_error_cleanup(&error);
CachedFunctions.Add(methodName, fun);
return fun;
}
#endif
#endif