Refactor CoreCLR runtime into explicit dotnet api instead of mocking mono api

Required by platforms that will use mono under the hood for .Net 7
New `USE_CSHARP` define for C# ability
Engine doesn't use `mono_*` apis directly but via MCore/MClass/MMethod/ apis
This commit is contained in:
Wojtek Figat
2023-03-27 17:29:42 +02:00
parent eed2cdfe04
commit 510fc443e8
111 changed files with 17048 additions and 8765 deletions

View File

@@ -7,15 +7,14 @@
#include "Engine/Profiler/ProfilerCPU.h"
#include "ManagedCLR/MAssembly.h"
#include "ManagedCLR/MClass.h"
#include "ManagedCLR/MType.h"
#include "ManagedCLR/MMethod.h"
#include "ManagedCLR/MField.h"
#include "ManagedCLR/MUtils.h"
#include "ManagedCLR/MException.h"
#include "FlaxEngine.Gen.h"
#include "MException.h"
#include "Scripting.h"
#include "Events.h"
#include "StdTypesContainer.h"
#include "Internal/StdTypesContainer.h"
Dictionary<Pair<ScriptingTypeHandle, StringView>, void(*)(ScriptingObject*, void*, bool)> ScriptingEvents::EventsTable;
Delegate<ScriptingObject*, Span<Variant>, ScriptingTypeHandle, StringView> ScriptingEvents::Event;
@@ -25,7 +24,7 @@ ManagedBinaryModule* GetBinaryModuleCorlib()
#if COMPILE_WITHOUT_CSHARP
return nullptr;
#else
static ManagedBinaryModule assembly("corlib", MAssemblyOptions(false)); // Don't precache all corlib classes
static ManagedBinaryModule assembly("corlib");
return &assembly;
#endif
}
@@ -54,12 +53,12 @@ const ScriptingType& ScriptingTypeHandle::GetType() const
return Module->Types[TypeIndex];
}
#if USE_MONO
#if USE_CSHARP
MonoClass* ScriptingTypeHandle::GetMonoClass() const
MClass* ScriptingTypeHandle::GetClass() const
{
ASSERT_LOW_LAYER(Module && Module->Types[TypeIndex].ManagedClass);
return Module->Types[TypeIndex].ManagedClass->GetNative();
return Module->Types[TypeIndex].ManagedClass;
}
#endif
@@ -695,8 +694,8 @@ void BinaryModule::Destroy(bool isReloading)
GetModules().RemoveKeepOrder(this);
}
ManagedBinaryModule::ManagedBinaryModule(const StringAnsiView& name, const MAssemblyOptions& options)
: ManagedBinaryModule(New<MAssembly>(nullptr, name, options))
ManagedBinaryModule::ManagedBinaryModule(const StringAnsiView& name)
: ManagedBinaryModule(New<MAssembly>(nullptr, name))
{
}
@@ -799,18 +798,18 @@ namespace
return nullptr;
}
bool VariantTypeEquals(const VariantType& type, MonoType* monoType)
bool VariantTypeEquals(const VariantType& type, MType* mType)
{
MonoClass* monoClass = mono_class_from_mono_type(monoType);
if (MUtils::GetClass(type) != monoClass)
MClass* mClass = MCore::Type::GetClass(mType);
if (MUtils::GetClass(type) != mClass)
{
// Hack for Vector2/3/4 which alias with Float2/3/4 or Double2/3/4 (depending on USE_LARGE_WORLDS)
const auto& stdTypes = *StdTypesContainer::Instance();
if (monoClass == stdTypes.Vector2Class->GetNative() && (type.Type == VariantType::Float2 || type.Type == VariantType::Double2))
if (mClass == stdTypes.Vector2Class && (type.Type == VariantType::Float2 || type.Type == VariantType::Double2))
return true;
if (monoClass == stdTypes.Vector3Class->GetNative() && (type.Type == VariantType::Float3 || type.Type == VariantType::Double3))
if (mClass == stdTypes.Vector3Class && (type.Type == VariantType::Float3 || type.Type == VariantType::Double3))
return true;
if (monoClass == stdTypes.Vector4Class->GetNative() && (type.Type == VariantType::Float4 || type.Type == VariantType::Double4))
if (mClass == stdTypes.Vector4Class && (type.Type == VariantType::Float4 || type.Type == VariantType::Double4))
return true;
return false;
@@ -828,64 +827,54 @@ MMethod* ManagedBinaryModule::FindMethod(MClass* mclass, const ScriptingTypeMeth
const auto& methods = mclass->GetMethods();
for (MMethod* method : methods)
{
#if USE_MONO
MonoMethodSignature* sig = mono_method_signature(method->GetNative());
/*if (method->IsStatic() != signature.IsStatic ||
method->GetName() != signature.Name ||
(int32)mono_signature_get_param_count(sig) != signature.Params.Count())
continue;*/
#if USE_CSHARP
if (method->IsStatic() != signature.IsStatic)
continue;
if (method->GetName() != signature.Name)
continue;
if ((int32)mono_signature_get_param_count(sig) != signature.Params.Count())
if (method->GetParametersCount() != signature.Params.Count())
continue;
void* sigParams = nullptr;
MonoType* type = mono_signature_get_params(sig, &sigParams);
bool isValid = true;
int paramIdx = 0;
while (type != nullptr)
for (int32 paramIdx = 0; paramIdx < signature.Params.Count(); paramIdx++)
{
auto& param = signature.Params[paramIdx];
if (param.IsOut != (mono_signature_param_is_out(sig, paramIdx) != 0) ||
MType* type = method->GetParameterType(paramIdx);
if (param.IsOut != method->GetParameterIsOut(paramIdx) ||
!VariantTypeEquals(param.Type, type))
{
auto asdf = VariantTypeEquals(param.Type, type);
isValid = false;
break;
}
type = mono_signature_get_params(sig, &sigParams);
paramIdx++;
}
if (isValid && VariantTypeEquals(signature.ReturnType, mono_signature_get_return_type(sig)))
if (isValid && VariantTypeEquals(signature.ReturnType, method->GetReturnType()))
return method;
#endif
}
return nullptr;
}
#if USE_MONO
#if USE_CSHARP
ManagedBinaryModule* ManagedBinaryModule::FindModule(MonoClass* klass)
ManagedBinaryModule* ManagedBinaryModule::FindModule(const MClass* klass)
{
// TODO: consider caching lookup table MonoImage* -> ManagedBinaryModule*
ManagedBinaryModule* module = nullptr;
MonoImage* mImage = mono_class_get_image(klass);
auto& modules = BinaryModule::GetModules();
for (auto e : modules)
if (klass && klass->GetAssembly())
{
auto managedModule = dynamic_cast<ManagedBinaryModule*>(e);
if (managedModule && managedModule->Assembly->GetMonoImage() == mImage)
auto& modules = BinaryModule::GetModules();
for (auto e : modules)
{
module = managedModule;
break;
auto managedModule = dynamic_cast<ManagedBinaryModule*>(e);
if (managedModule && managedModule->Assembly == klass->GetAssembly())
{
module = managedModule;
break;
}
}
}
return module;
}
ScriptingTypeHandle ManagedBinaryModule::FindType(MonoClass* klass)
ScriptingTypeHandle ManagedBinaryModule::FindType(const MClass* klass)
{
auto typeModule = FindModule(klass);
if (typeModule)
@@ -924,7 +913,7 @@ void ManagedBinaryModule::OnLoaded(MAssembly* assembly)
ASSERT(type.ManagedClass == nullptr);
// Cache class
const MString typeName(type.Fullname.Get(), type.Fullname.Length());
const StringAnsi typeName(type.Fullname.Get(), type.Fullname.Length());
classes.TryGet(typeName, type.ManagedClass);
if (type.ManagedClass == nullptr)
{
@@ -933,7 +922,7 @@ void ManagedBinaryModule::OnLoaded(MAssembly* assembly)
}
// Cache klass -> type index lookup
MonoClass* klass = type.ManagedClass->GetNative();
MClass* klass = type.ManagedClass;
#if !BUILD_RELEASE
if (ClassToTypeIndex.ContainsKey(klass))
{
@@ -1004,26 +993,14 @@ void ManagedBinaryModule::InitType(MClass* mclass)
{
#if !COMPILE_WITHOUT_CSHARP
// Skip if already initialized
const MString& typeName = mclass->GetFullName();
const StringAnsi& typeName = mclass->GetFullName();
if (TypeNameToTypeIndex.ContainsKey(typeName))
return;
// Find first native base C++ class of this C# class
MClass* baseClass = nullptr;
MonoClass* baseKlass = mono_class_get_parent(mclass->GetNative());
MonoImage* baseKlassImage = mono_class_get_image(baseKlass);
MClass* baseClass = mclass->GetBaseClass();
ScriptingTypeHandle baseType;
auto& modules = GetModules();
for (int32 i = 0; i < modules.Count(); i++)
{
auto e = dynamic_cast<ManagedBinaryModule*>(modules[i]);
if (e && e->Assembly->GetMonoImage() == baseKlassImage)
{
baseType.Module = e;
baseClass = e->Assembly->GetClass(baseKlass);
break;
}
}
baseType.Module = FindModule(baseClass);
if (!baseClass)
{
LOG(Error, "Missing base class for managed class {0} from assembly {1}.", String(typeName), Assembly->ToString());
@@ -1058,13 +1035,12 @@ void ManagedBinaryModule::InitType(MClass* mclass)
_managedMemoryBlocks.Add(typeNameData);
// Initialize scripting interfaces implemented in C#
MonoClass* interfaceKlass;
void* interfaceIt = nullptr;
int32 interfacesCount = 0;
MonoClass* klass = mclass->GetNative();
while ((interfaceKlass = mono_class_get_interfaces(klass, &interfaceIt)))
MClass* klass = mclass;
const Array<MClass*>& interfaceClasses = klass->GetInterfaces();
for (const MClass* interfaceClass : interfaceClasses)
{
const ScriptingTypeHandle interfaceType = FindType(interfaceKlass);
const ScriptingTypeHandle interfaceType = FindType(interfaceClass);
if (interfaceType)
interfacesCount++;
}
@@ -1073,10 +1049,9 @@ void ManagedBinaryModule::InitType(MClass* mclass)
{
interfaces = (ScriptingType::InterfaceImplementation*)Allocator::Allocate((interfacesCount + 1) * sizeof(ScriptingType::InterfaceImplementation));
interfacesCount = 0;
interfaceIt = nullptr;
while ((interfaceKlass = mono_class_get_interfaces(klass, &interfaceIt)))
for (const MClass* interfaceClass : interfaceClasses)
{
const ScriptingTypeHandle interfaceTypeHandle = FindType(interfaceKlass);
const ScriptingTypeHandle interfaceTypeHandle = FindType(interfaceClass);
if (!interfaceTypeHandle)
continue;
auto& interface = interfaces[interfacesCount++];
@@ -1100,7 +1075,7 @@ void ManagedBinaryModule::InitType(MClass* mclass)
auto& type = Types[typeIndex];
type.ManagedClass = mclass;
// Register Mono class
// Register C# class
ASSERT(!ClassToTypeIndex.ContainsKey(klass));
ClassToTypeIndex[klass] = typeIndex;
@@ -1125,11 +1100,9 @@ void ManagedBinaryModule::InitType(MClass* mclass)
// Special case if method was found but the base class uses generic arguments
if (method && baseClass->IsGeneric())
{
// TODO: encapsulate it into MClass to support inflated methods
auto parentClass = mono_class_get_parent(mclass->GetNative());
auto parentMethod = mono_class_get_method_from_name(parentClass, referenceMethod->GetName().Get(), 0);
auto inflatedMethod = mono_class_inflate_generic_method(parentMethod, nullptr);
method = New<MMethod>(inflatedMethod, baseClass);
MClass* parentClass = mclass->GetBaseClass();
MMethod* parentMethod = parentClass->GetMethod(referenceMethod->GetName().Get(), 0);
method = parentMethod->InflateGeneric();
}
baseClass = baseClass->GetBaseClass();
@@ -1153,7 +1126,7 @@ void ManagedBinaryModule::OnUnloading(MAssembly* assembly)
for (int32 i = _firstManagedTypeIndex; i < Types.Count(); i++)
{
const ScriptingType& type = Types[i];
const MString typeName(type.Fullname.Get(), type.Fullname.Length());
const StringAnsi typeName(type.Fullname.Get(), type.Fullname.Length());
TypeNameToTypeIndex.Remove(typeName);
}
}
@@ -1211,10 +1184,9 @@ void* ManagedBinaryModule::FindMethod(const ScriptingTypeHandle& typeHandle, con
bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Span<Variant> paramValues, Variant& result)
{
#if USE_MONO
#if USE_CSHARP
const auto mMethod = (MMethod*)method;
MonoMethodSignature* signature = mono_method_signature(mMethod->GetNative());
const int32 parametersCount = mono_signature_get_param_count(signature);
const int32 parametersCount = mMethod->GetParametersCount();;
if (paramValues.Length() != parametersCount)
{
LOG(Error, "Failed to call method '{0}.{1}' (args count: {2}) with invalid parameters amount ({3})", String(mMethod->GetParentClass()->GetFullName()), String(mMethod->GetName()), parametersCount, paramValues.Length());
@@ -1227,10 +1199,10 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp
if (!mMethod->IsStatic())
{
// Box instance into C# object
MonoObject* instanceObject = MUtils::BoxVariant(instance);
MObject* instanceObject = MUtils::BoxVariant(instance);
// Validate instance
if (!instanceObject || !mono_class_is_subclass_of(mono_object_get_class(instanceObject), mMethod->GetParentClass()->GetNative(), withInterfaces))
if (!instanceObject || !MCore::Object::GetClass(instanceObject)->IsSubClassOf(mMethod->GetParentClass(), withInterfaces))
{
if (!instanceObject)
LOG(Error, "Failed to call method '{0}.{1}' (args count: {2}) without object instance", String(mMethod->GetParentClass()->GetFullName()), String(mMethod->GetName()), parametersCount);
@@ -1240,37 +1212,32 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp
}
// For value-types instance is the actual boxed object data, not te object itself
mInstance = mono_class_is_valuetype(mono_object_get_class(instanceObject)) ? mono_object_unbox(instanceObject) : instanceObject;
mInstance = MCore::Object::GetClass(instanceObject)->IsValueType() ? MCore::Object::Unbox(instanceObject) : instanceObject;
}
// Marshal parameters
void** params = (void**)alloca(parametersCount * sizeof(void*));
bool failed = false;
bool hasOutParams = false;
void* sigParams = nullptr;
MonoType* type = mono_signature_get_params(signature, &sigParams);
for (int paramIdx = 0; type != nullptr;)
for (int32 paramIdx = 0; paramIdx < parametersCount; paramIdx++)
{
auto& paramValue = paramValues[paramIdx];
const bool isOut = mono_signature_param_is_out(signature, paramIdx) != 0;
Variant& paramValue = paramValues[paramIdx];
const bool isOut = mMethod->GetParameterIsOut(paramIdx);
hasOutParams |= isOut;
// Marshal parameter for managed method
MType paramType(type);
MType* paramType = mMethod->GetParameterType(paramIdx);
params[paramIdx] = MUtils::VariantToManagedArgPtr(paramValue, paramType, failed);
if (failed)
{
LOG(Error, "Failed to marshal parameter {5}:{4} of method '{0}.{1}' (args count: {2}), value type: {6}, value: {3}", String(mMethod->GetParentClass()->GetFullName()), String(mMethod->GetName()), parametersCount, paramValue, paramType.ToString(), paramIdx, paramValue.Type);
LOG(Error, "Failed to marshal parameter {5}:{4} of method '{0}.{1}' (args count: {2}), value type: {6}, value: {3}", String(mMethod->GetParentClass()->GetFullName()), String(mMethod->GetName()), parametersCount, paramValue, MCore::Type::ToString(paramType), paramIdx, paramValue.Type);
return true;
}
type = mono_signature_get_params(signature, &sigParams);
paramIdx++;
}
// Invoke the method
MObject* exception = nullptr;
MonoObject* resultObject = withInterfaces ? mMethod->InvokeVirtual((MonoObject*)mInstance, params, &exception) : mMethod->Invoke(mInstance, params, &exception);
MObject* resultObject = withInterfaces ? mMethod->InvokeVirtual((MObject*)mInstance, params, &exception) : mMethod->Invoke(mInstance, params, &exception);
if (exception)
{
MException ex(exception);
@@ -1303,18 +1270,17 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp
{
for (int32 paramIdx = 0; paramIdx < parametersCount; paramIdx++)
{
const bool isOut = mono_signature_param_is_out(signature, paramIdx) != 0;
if (isOut)
if (mMethod->GetParameterIsOut(paramIdx))
{
auto& paramValue = paramValues[paramIdx];
auto param = params[paramIdx];
switch (paramValue.Type.Type)
{
case VariantType::String:
paramValue.SetString(MUtils::ToString((MonoString*)param));
paramValue.SetString(MUtils::ToString((MString*)param));
break;
case VariantType::Object:
paramValue = MUtils::UnboxVariant((MonoObject*)param);
paramValue = MUtils::UnboxVariant((MObject*)param);
break;
case VariantType::Structure:
{
@@ -1322,7 +1288,8 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp
if (paramTypeHandle)
{
auto& valueType = paramTypeHandle.GetType();
valueType.Struct.Unbox(paramValue.AsBlob.Data, (MonoObject*)((byte*)param - sizeof(MonoObject)));
MISSING_CODE("TODO: reimplement unpacking managed structure out parameter from C# call");
//valueType.Struct.Unbox(paramValue.AsBlob.Data, (MObject*)((byte*)param - sizeof(MonoObject))); // TODO: fix this for dotnet7
}
break;
}
@@ -1339,21 +1306,18 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp
void ManagedBinaryModule::GetMethodSignature(void* method, ScriptingTypeMethodSignature& signature)
{
#if USE_MONO
#if USE_CSHARP
const auto mMethod = (MMethod*)method;
signature.Name = mMethod->GetName();
signature.IsStatic = mMethod->IsStatic();
MonoMethodSignature* sig = mono_method_signature(mMethod->GetNative());
signature.ReturnType = MoveTemp(MUtils::UnboxVariantType(mono_signature_get_return_type(sig)));
void* signatureParams = nullptr;
mono_signature_get_params(sig, &signatureParams);
const int32 paramsCount = (int32)mono_signature_get_param_count(sig);
signature.ReturnType = MoveTemp(MUtils::UnboxVariantType(mMethod->GetReturnType()));
const int32 paramsCount = mMethod->GetParametersCount();
signature.Params.Resize(paramsCount);
for (int32 paramIdx = 0; paramIdx < paramsCount; paramIdx++)
{
auto& param = signature.Params[paramIdx];
param.Type = MoveTemp(MUtils::UnboxVariantType(((MonoType**)signatureParams)[paramIdx]));
param.IsOut = mono_signature_param_is_out(sig, paramIdx) != 0;
param.Type = MoveTemp(MUtils::UnboxVariantType(mMethod->GetParameterType(paramIdx)));
param.IsOut = mMethod->GetParameterIsOut(paramIdx);
}
#endif
}
@@ -1366,28 +1330,28 @@ void* ManagedBinaryModule::FindField(const ScriptingTypeHandle& typeHandle, cons
void ManagedBinaryModule::GetFieldSignature(void* field, ScriptingTypeFieldSignature& fieldSignature)
{
#if USE_MONO
#if USE_CSHARP
const auto mField = (MField*)field;
fieldSignature.Name = mField->GetName();
fieldSignature.ValueType = MoveTemp(MUtils::UnboxVariantType(mField->GetType().GetNative()));
fieldSignature.ValueType = MoveTemp(MUtils::UnboxVariantType(mField->GetType()));
fieldSignature.IsStatic = mField->IsStatic();
#endif
}
bool ManagedBinaryModule::GetFieldValue(void* field, const Variant& instance, Variant& result)
{
#if USE_MONO
#if USE_CSHARP
const auto mField = (MField*)field;
// Get instance object
MonoObject* instanceObject = nullptr;
MObject* instanceObject = nullptr;
if (!mField->IsStatic())
{
// Box instance into C# object
instanceObject = MUtils::BoxVariant(instance);
// Validate instance
if (!instanceObject || !mono_class_is_subclass_of(mono_object_get_class(instanceObject), mField->GetParentClass()->GetNative(), false))
if (!instanceObject || !MCore::Object::GetClass(instanceObject)->IsSubClassOf(mField->GetParentClass()))
{
if (!instanceObject)
LOG(Error, "Failed to get field '{0}.{1}' without object instance", String(mField->GetParentClass()->GetFullName()), String(mField->GetName()));
@@ -1398,7 +1362,7 @@ bool ManagedBinaryModule::GetFieldValue(void* field, const Variant& instance, Va
}
// Get the value
MonoObject* resultObject = mField->GetValueBoxed(instanceObject);
MObject* resultObject = mField->GetValueBoxed(instanceObject);
result = MUtils::UnboxVariant(resultObject);
return false;
#else
@@ -1408,18 +1372,18 @@ bool ManagedBinaryModule::GetFieldValue(void* field, const Variant& instance, Va
bool ManagedBinaryModule::SetFieldValue(void* field, const Variant& instance, Variant& value)
{
#if USE_MONO
#if USE_CSHARP
const auto mField = (MField*)field;
// Get instance object
MonoObject* instanceObject = nullptr;
MObject* instanceObject = nullptr;
if (!mField->IsStatic())
{
// Box instance into C# object
instanceObject = MUtils::BoxVariant(instance);
// Validate instance
if (!instanceObject || !mono_class_is_subclass_of(mono_object_get_class(instanceObject), mField->GetParentClass()->GetNative(), false))
if (!instanceObject || !MCore::Object::GetClass(instanceObject)->IsSubClassOf(mField->GetParentClass()))
{
if (!instanceObject)
LOG(Error, "Failed to set field '{0}.{1}' without object instance", String(mField->GetParentClass()->GetFullName()), String(mField->GetName()));
@@ -1446,8 +1410,8 @@ void ManagedBinaryModule::Destroy(bool isReloading)
Assembly->Unload(isReloading);
}
NativeBinaryModule::NativeBinaryModule(const StringAnsiView& name, const MAssemblyOptions& options)
: NativeBinaryModule(New<MAssembly>(nullptr, name, options))
NativeBinaryModule::NativeBinaryModule(const StringAnsiView& name)
: NativeBinaryModule(New<MAssembly>(nullptr, name))
{
}