Files
FlaxEngine/Source/Engine/Scripting/BinaryModule.cpp
2021-09-23 13:46:47 +02:00

1106 lines
40 KiB
C++

// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#include "BinaryModule.h"
#include "ScriptingObject.h"
#include "Engine/Core/Log.h"
#include "Engine/Threading/Threading.h"
#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 "FlaxEngine.Gen.h"
#include "MException.h"
#include "Scripting.h"
#include "Events.h"
Dictionary<Pair<ScriptingTypeHandle, StringView>, void(*)(ScriptingObject*, void*, bool)> ScriptingEvents::EventsTable;
Delegate<ScriptingObject*, Span<Variant>, ScriptingTypeHandle, StringView> ScriptingEvents::Event;
ManagedBinaryModule* GetBinaryModuleCorlib()
{
static ManagedBinaryModule assembly("corlib", MAssemblyOptions(false)); // Don't precache all corlib classes
return &assembly;
}
ScriptingTypeHandle::ScriptingTypeHandle(const ScriptingTypeInitializer& initializer)
: Module(initializer.Module)
, TypeIndex(initializer.TypeIndex)
{
}
String ScriptingTypeHandle::ToString(bool withAssembly) const
{
String result = GetType().ToString();
if (withAssembly)
{
result += TEXT("(module ");
result += String(Module->GetName());
result += TEXT(")");
}
return result;
}
const ScriptingType& ScriptingTypeHandle::GetType() const
{
ASSERT_LOW_LAYER(Module);
return Module->Types[TypeIndex];
}
bool ScriptingTypeHandle::IsAssignableFrom(ScriptingTypeHandle c) const
{
while (c)
{
if (c == *this)
return true;
c = c.GetType().GetBaseType();
}
return false;
}
bool ScriptingTypeHandle::operator==(const ScriptingTypeInitializer& other) const
{
return Module == other.Module && TypeIndex == other.TypeIndex;
}
bool ScriptingTypeHandle::operator!=(const ScriptingTypeInitializer& other) const
{
return Module != other.Module || TypeIndex != other.TypeIndex;
}
ScriptingType::ScriptingType()
: ManagedClass(nullptr)
, Module(nullptr)
, InitRuntime(nullptr)
, Fullname(nullptr, 0)
, Type(ScriptingTypes::Script)
, BaseTypePtr(nullptr)
, Interfaces(nullptr)
{
Script.Spawn = nullptr;
Script.VTable = nullptr;
Script.ScriptVTable = nullptr;
Script.ScriptVTableBase = nullptr;
Script.SetupScriptVTable = nullptr;
Script.SetupScriptObjectVTable = nullptr;
Script.DefaultInstance = nullptr;
}
ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, const ScriptingTypeHandle& baseType, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable, const InterfaceImplementation* interfaces)
: ManagedClass(nullptr)
, Module(module)
, InitRuntime(initRuntime)
, Fullname(fullname)
, Type(ScriptingTypes::Script)
, BaseTypeHandle(baseType)
, BaseTypePtr(nullptr)
, Interfaces(interfaces)
, Size(size)
{
Script.Spawn = spawn;
Script.VTable = nullptr;
Script.ScriptVTable = nullptr;
Script.ScriptVTableBase = nullptr;
Script.SetupScriptVTable = setupScriptVTable;
Script.SetupScriptObjectVTable = setupScriptObjectVTable;
Script.DefaultInstance = nullptr;
}
ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, SpawnHandler spawn, ScriptingTypeInitializer* baseType, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable, const InterfaceImplementation* interfaces)
: ManagedClass(nullptr)
, Module(module)
, InitRuntime(initRuntime)
, Fullname(fullname)
, Type(ScriptingTypes::Script)
, BaseTypePtr(baseType)
, Interfaces(interfaces)
, Size(size)
{
Script.Spawn = spawn;
Script.VTable = nullptr;
Script.ScriptVTable = nullptr;
Script.ScriptVTableBase = nullptr;
Script.SetupScriptVTable = setupScriptVTable;
Script.SetupScriptObjectVTable = setupScriptObjectVTable;
Script.DefaultInstance = nullptr;
}
ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces)
: ManagedClass(nullptr)
, Module(module)
, InitRuntime(initRuntime)
, Fullname(fullname)
, Type(ScriptingTypes::Class)
, BaseTypePtr(baseType)
, Interfaces(interfaces)
, Size(size)
{
Class.Ctor = ctor;
Class.Dtor = dtor;
}
ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, Copy copy, Box box, Unbox unbox, GetField getField, SetField setField, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces)
: ManagedClass(nullptr)
, Module(module)
, InitRuntime(initRuntime)
, Fullname(fullname)
, Type(ScriptingTypes::Structure)
, BaseTypePtr(baseType)
, Interfaces(interfaces)
, Size(size)
{
Struct.Ctor = ctor;
Struct.Dtor = dtor;
Struct.Copy = copy;
Struct.Box = box;
Struct.Unbox = unbox;
Struct.GetField = getField;
Struct.SetField = setField;
}
ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, InitRuntimeHandler initRuntime, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces)
: ManagedClass(nullptr)
, Module(module)
, InitRuntime(initRuntime)
, Fullname(fullname)
, Type(ScriptingTypes::Interface)
, BaseTypePtr(baseType)
, Interfaces(interfaces)
, Size(0)
{
}
ScriptingType::ScriptingType(const ScriptingType& other)
: ManagedClass(other.ManagedClass)
, Module(other.Module)
, InitRuntime(other.InitRuntime)
, Fullname(other.Fullname)
, Type(other.Type)
, BaseTypeHandle(other.BaseTypeHandle)
, BaseTypePtr(other.BaseTypePtr)
, Interfaces(other.Interfaces)
, Size(other.Size)
{
switch (other.Type)
{
case ScriptingTypes::Script:
Script.Spawn = other.Script.Spawn;
Script.VTable = nullptr;
Script.ScriptVTable = nullptr;
Script.ScriptVTableBase = nullptr;
Script.SetupScriptVTable = other.Script.SetupScriptVTable;
Script.SetupScriptObjectVTable = other.Script.SetupScriptObjectVTable;
Script.DefaultInstance = nullptr;
break;
case ScriptingTypes::Structure:
Struct.Ctor = other.Struct.Ctor;
Struct.Dtor = other.Struct.Dtor;
Struct.Copy = other.Struct.Copy;
Struct.Box = other.Struct.Box;
Struct.Unbox = other.Struct.Unbox;
Struct.GetField = other.Struct.GetField;
Struct.SetField = other.Struct.SetField;
break;
case ScriptingTypes::Class:
Class.Ctor = other.Class.Ctor;
Class.Dtor = other.Class.Dtor;
break;
case ScriptingTypes::Enum:
break;
case ScriptingTypes::Interface:
break;
default: ;
}
}
ScriptingType::ScriptingType(ScriptingType&& other)
: ManagedClass(other.ManagedClass)
, Module(other.Module)
, InitRuntime(other.InitRuntime)
, Fullname(other.Fullname)
, Type(other.Type)
, BaseTypeHandle(other.BaseTypeHandle)
, BaseTypePtr(other.BaseTypePtr)
, Interfaces(other.Interfaces)
, Size(other.Size)
{
switch (other.Type)
{
case ScriptingTypes::Script:
Script.Spawn = other.Script.Spawn;
Script.VTable = other.Script.VTable;
other.Script.VTable = nullptr;
Script.ScriptVTable = other.Script.ScriptVTable;
other.Script.ScriptVTable = nullptr;
Script.ScriptVTableBase = other.Script.ScriptVTableBase;
other.Script.ScriptVTableBase = nullptr;
Script.SetupScriptVTable = other.Script.SetupScriptVTable;
Script.SetupScriptObjectVTable = other.Script.SetupScriptObjectVTable;
Script.DefaultInstance = other.Script.DefaultInstance;
other.Script.DefaultInstance = nullptr;
break;
case ScriptingTypes::Structure:
Struct.Ctor = other.Struct.Ctor;
Struct.Dtor = other.Struct.Dtor;
Struct.Copy = other.Struct.Copy;
Struct.Box = other.Struct.Box;
Struct.Unbox = other.Struct.Unbox;
Struct.GetField = other.Struct.GetField;
Struct.SetField = other.Struct.SetField;
break;
case ScriptingTypes::Class:
Class.Ctor = other.Class.Ctor;
Class.Dtor = other.Class.Dtor;
break;
case ScriptingTypes::Enum:
break;
case ScriptingTypes::Interface:
break;
default: ;
}
}
ScriptingType::~ScriptingType()
{
switch (Type)
{
case ScriptingTypes::Script:
if (Script.DefaultInstance)
Delete(Script.DefaultInstance);
if (Script.VTable)
Platform::Free((byte*)Script.VTable - GetVTablePrefix());
Platform::Free(Script.ScriptVTable);
Platform::Free(Script.ScriptVTableBase);
break;
case ScriptingTypes::Structure:
break;
case ScriptingTypes::Enum:
break;
case ScriptingTypes::Interface:
break;
default: ;
}
}
ScriptingTypeHandle ScriptingType::GetHandle() const
{
int32 typeIndex;
if (Module && Module->FindScriptingType(Fullname, typeIndex))
{
return ScriptingTypeHandle(Module, typeIndex);
}
return ScriptingTypeHandle();
}
ScriptingObject* ScriptingType::GetDefaultInstance() const
{
ASSERT(Type == ScriptingTypes::Script);
if (!Script.DefaultInstance)
{
const ScriptingObjectSpawnParams params(Guid::New(), GetHandle());
Script.DefaultInstance = Script.Spawn(params);
if (!Script.DefaultInstance)
{
LOG(Error, "Failed to create default instance of type {0}", ToString());
}
}
return Script.DefaultInstance;
}
const ScriptingType::InterfaceImplementation* ScriptingType::GetInterface(const ScriptingTypeInitializer* interfaceType) const
{
const InterfaceImplementation* interfaces = Interfaces;
if (interfaces)
{
while (interfaces->InterfaceType)
{
if (interfaces->InterfaceType == interfaceType)
return interfaces;
interfaces++;
}
}
if (BaseTypeHandle)
{
return BaseTypeHandle.GetType().GetInterface(interfaceType);
}
if (BaseTypePtr)
{
return BaseTypePtr->GetType().GetInterface(interfaceType);
}
return nullptr;
}
String ScriptingType::ToString() const
{
return String(Fullname.Get(), Fullname.Length());
}
ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SpawnHandler spawn, ScriptingTypeInitializer* baseType, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable, const ScriptingType::InterfaceImplementation* interfaces)
: ScriptingTypeHandle(module, module->Types.Count())
{
// Script
module->Types.AddUninitialized();
new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, spawn, baseType, setupScriptVTable, setupScriptObjectVTable, interfaces);
#if BUILD_DEBUG
if (module->TypeNameToTypeIndex.ContainsKey(fullname))
{
LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName()));
}
#endif
module->TypeNameToTypeIndex[fullname] = TypeIndex;
}
ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces)
: ScriptingTypeHandle(module, module->Types.Count())
{
// Class
module->Types.AddUninitialized();
new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, baseType, interfaces);
#if BUILD_DEBUG
if (module->TypeNameToTypeIndex.ContainsKey(fullname))
{
LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName()));
}
#endif
module->TypeNameToTypeIndex[fullname] = TypeIndex;
}
ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces)
: ScriptingTypeHandle(module, module->Types.Count())
{
// Structure
module->Types.AddUninitialized();
new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, copy, box, unbox, getField, setField, baseType, interfaces);
#if BUILD_DEBUG
if (module->TypeNameToTypeIndex.ContainsKey(fullname))
{
LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName()));
}
#endif
module->TypeNameToTypeIndex[fullname] = TypeIndex;
}
ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, ScriptingType::InitRuntimeHandler initRuntime, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces)
: ScriptingTypeHandle(module, module->Types.Count())
{
// Interface
module->Types.AddUninitialized();
new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, initRuntime, baseType, interfaces);
#if BUILD_DEBUG
if (module->TypeNameToTypeIndex.ContainsKey(fullname))
{
LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName()));
}
#endif
module->TypeNameToTypeIndex[fullname] = TypeIndex;
}
BinaryModule::BinaryModulesList& BinaryModule::GetModules()
{
static BinaryModulesList modules;
return modules;
}
BinaryModule* BinaryModule::GetModule(const StringAnsiView& name)
{
BinaryModule* result = nullptr;
auto& modules = GetModules();
for (int32 i = 0; i < modules.Count(); i++)
{
if (modules[i]->GetName() == name)
{
result = modules[i];
break;
}
}
return result;
}
BinaryModule::BinaryModule()
{
// Register
GetModules().Add(this);
}
void* BinaryModule::FindMethod(const ScriptingTypeHandle& typeHandle, const ScriptingTypeMethodSignature& signature)
{
return FindMethod(typeHandle, signature.Name, signature.Params.Count());
}
void BinaryModule::Destroy(bool isReloading)
{
// Unregister
GetModules().RemoveKeepOrder(this);
}
ManagedBinaryModule::ManagedBinaryModule(const StringAnsiView& name, const MAssemblyOptions& options)
: ManagedBinaryModule(New<MAssembly>(nullptr, name, options))
{
}
ManagedBinaryModule::ManagedBinaryModule(MAssembly* assembly)
{
Assembly = assembly;
// Bind for C# assembly events
assembly->Loading.Bind<ManagedBinaryModule, &ManagedBinaryModule::OnLoading>(this);
assembly->Loaded.Bind<ManagedBinaryModule, &ManagedBinaryModule::OnLoaded>(this);
assembly->Unloading.Bind<ManagedBinaryModule, &ManagedBinaryModule::OnUnloading>(this);
if (Assembly->IsLoaded())
{
// Cache stuff if the input assembly has been already loaded
OnLoaded(Assembly);
}
}
ManagedBinaryModule::~ManagedBinaryModule()
{
// Unregister
auto& modules = GetModules();
modules.RemoveKeepOrder(this);
// Cleanup
Delete(Assembly);
}
ManagedBinaryModule* ManagedBinaryModule::GetModule(const MAssembly* assembly)
{
ManagedBinaryModule* result = nullptr;
auto& modules = GetModules();
for (int32 i = 0; i < modules.Count(); i++)
{
auto e = dynamic_cast<ManagedBinaryModule*>(modules[i]);
if (e && e->Assembly == assembly)
{
result = e;
break;
}
}
return result;
}
ScriptingObject* ManagedBinaryModule::ManagedObjectSpawn(const ScriptingObjectSpawnParams& params)
{
// Create native object
ScriptingTypeHandle managedTypeHandle = params.Type;
const ScriptingType* managedTypePtr = &managedTypeHandle.GetType();
while (managedTypePtr->Script.Spawn != &ManagedObjectSpawn)
{
managedTypeHandle = managedTypePtr->GetBaseType();
managedTypePtr = &managedTypeHandle.GetType();
}
ScriptingType& managedType = (ScriptingType&)*managedTypePtr;
ScriptingTypeHandle nativeTypeHandle = managedType.GetBaseType();
const ScriptingType* nativeTypePtr = &nativeTypeHandle.GetType();
while (nativeTypePtr->Script.Spawn == &ManagedObjectSpawn)
{
nativeTypeHandle = nativeTypePtr->GetBaseType();
nativeTypePtr = &nativeTypeHandle.GetType();
}
ScriptingObject* object = nativeTypePtr->Script.Spawn(params);
if (!object)
{
LOG(Error, "Failed to spawn object of type {0} with native base type {1}.", managedTypePtr->ToString(), nativeTypePtr->ToString());
return nullptr;
}
// Beware! Hacking vtables incoming! Undefined behaviors exploits! Low-level programming!
// What's happening here?
// We create a custom vtable for the C# objects that use a native class object with virtual functions overrides.
// To make it easy to use in C++ we inject custom wrapper methods into C++ object vtable to call C# code from them.
// Because virtual member functions calls are C++ ABI and impl-defined this is quite hard. But works.
if (managedType.Script.ScriptVTable)
{
if (!managedType.Script.VTable)
{
// Duplicate vtable
void** vtable = *(void***)object;
const int32 prefixSize = GetVTablePrefix();
int32 entriesCount = 0;
while (vtable[entriesCount] && entriesCount < 200)
entriesCount++;
const int32 size = entriesCount * sizeof(void*);
managedType.Script.VTable = (void**)((byte*)Platform::Allocate(prefixSize + size, 16) + prefixSize);
Platform::MemoryCopy((byte*)managedType.Script.VTable - prefixSize, (byte*)vtable - prefixSize, prefixSize + size);
// Override vtable entries by the class
for (ScriptingTypeHandle e = nativeTypeHandle; e;)
{
const ScriptingType& eType = e.GetType();
if (eType.Script.SetupScriptObjectVTable)
{
eType.Script.SetupScriptObjectVTable(managedType.Script.ScriptVTable, managedType.Script.ScriptVTableBase, managedType.Script.VTable, entriesCount, 0);
}
e = eType.GetBaseType();
}
}
// Override object vtable with hacked one that has C# functions calls
*(void**)object = managedType.Script.VTable;
}
// Mark as managed type
object->Flags |= ObjectFlags::IsManagedType;
return object;
}
namespace
{
MMethod* FindMethod(MClass* mclass, const MMethod* referenceMethod)
{
const Array<MMethod*>& methods = mclass->GetMethods();
for (int32 i = 0; i < methods.Count(); i++)
{
MMethod* method = methods[i];
if (!method->IsStatic() &&
method->GetName() == referenceMethod->GetName() &&
method->GetParametersCount() == referenceMethod->GetParametersCount() &&
method->GetReturnType() == referenceMethod->GetReturnType()
)
{
return method;
}
}
return nullptr;
}
}
MMethod* ManagedBinaryModule::FindMethod(MClass* mclass, const ScriptingTypeMethodSignature& signature)
{
if (!mclass)
return nullptr;
const auto& methods = mclass->GetMethods();
for (MMethod* method : methods)
{
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;
void* sigParams = nullptr;
mono_signature_get_params(sig, &sigParams);
bool isValid = true;
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) ||
MUtils::GetClass(param.Type) != mono_class_from_mono_type(((MonoType**)sigParams)[paramIdx]))
{
isValid = false;
break;
}
}
if (isValid && MUtils::GetClass(signature.ReturnType) == mono_class_from_mono_type(mono_signature_get_return_type(sig)))
return method;
}
return nullptr;
}
void ManagedBinaryModule::OnLoading(MAssembly* assembly)
{
PROFILE_CPU();
for (ScriptingType& type : Types)
{
type.InitRuntime();
}
}
void ManagedBinaryModule::OnLoaded(MAssembly* assembly)
{
PROFILE_CPU();
ASSERT(ClassToTypeIndex.IsEmpty());
const auto& classes = assembly->GetClasses();
// Cache managed types information
ClassToTypeIndex.EnsureCapacity(Types.Count() * 4);
for (int32 typeIndex = 0; typeIndex < Types.Count(); typeIndex++)
{
ScriptingType& type = Types[typeIndex];
if (type.Type == ScriptingTypes::Interface)
continue; // TODO: generate C# class for interfaces in API
ASSERT(type.ManagedClass == nullptr);
// Cache class
const MString typeName(type.Fullname.Get(), type.Fullname.Length());
classes.TryGet(typeName, type.ManagedClass);
if (type.ManagedClass == nullptr)
{
LOG(Error, "Missing class {0} from assembly {1}.", type.ToString(), assembly->ToString());
continue;
}
// Cache klass -> type index lookup
MonoClass* klass = type.ManagedClass->GetNative();
#if !BUILD_RELEASE
if (ClassToTypeIndex.ContainsKey(klass))
{
LOG(Error, "Duplicated native types for class {0} from assembly {1}.", type.ToString(), assembly->ToString());
continue;
}
#endif
ClassToTypeIndex[klass] = typeIndex;
}
// Cache types for managed-only types that can be used in the engine
_firstManagedTypeIndex = Types.Count();
NativeBinaryModule* flaxEngine = (NativeBinaryModule*)GetBinaryModuleFlaxEngine();
if (flaxEngine->Assembly->IsLoaded())
{
// TODO: check only assemblies that references FlaxEngine.CSharp.dll
MClass* scriptingObjectType = this == flaxEngine ? classes["FlaxEngine.Object"] : ScriptingObject::GetStaticClass();
for (auto i = classes.Begin(); i.IsNotEnd(); ++i)
{
MClass* mclass = i->Value;
const MString& typeName = mclass->GetFullName();
// Check if C# class inherits from C++ object class it has no C++ representation
if (
TypeNameToTypeIndex.Find(typeName) != TypeNameToTypeIndex.End() ||
mclass->IsStatic() ||
mclass->IsAbstract() ||
mclass->IsInterface() ||
!mclass->IsSubClassOf(scriptingObjectType)
)
{
continue;
}
// Find first native base C++ class of this C# class
MClass* baseClass = mclass->GetBaseClass();
ScriptingTypeHandle nativeType;
while (baseClass)
{
int32 typeIndex;
BinaryModule* baseClassModule = GetModule(baseClass->GetAssembly());
ASSERT(baseClassModule);
if (baseClassModule->TypeNameToTypeIndex.TryGet(baseClass->GetFullName(), typeIndex))
{
nativeType = ScriptingTypeHandle(baseClassModule, typeIndex);
if (nativeType.GetType().Script.Spawn != &ManagedObjectSpawn)
break;
}
baseClass = baseClass->GetBaseClass();
}
if (!nativeType)
{
LOG(Error, "Missing native base class for managed class {0} from assembly {1}.", String(typeName), assembly->ToString());
continue;
}
// Scripting Type has Fullname span pointing to the string in memory (usually static data) so store the name in assembly
char* typeNameData = (char*)Allocator::Allocate(typeName.Length() + 1);
Platform::MemoryCopy(typeNameData, typeName.Get(), typeName.Length());
typeNameData[typeName.Length()] = 0;
_managedTypesNames.Add(typeNameData);
// Create scripting type descriptor for managed-only type based on the native base class
const int32 typeIndex = Types.Count();
Types.AddUninitialized();
new(Types.Get() + Types.Count() - 1)ScriptingType(typeName, this, nativeType.GetType().Size, ScriptingType::DefaultInitRuntime, ManagedObjectSpawn, nativeType);
TypeNameToTypeIndex[typeName] = typeIndex;
auto& type = Types[typeIndex];
type.ManagedClass = mclass;
// Register Mono class
MonoClass* klass = mclass->GetNative();
ASSERT(!ClassToTypeIndex.ContainsKey(klass));
ClassToTypeIndex[klass] = typeIndex;
// Create managed vtable for this class (build out of the wrapper C++ methods that call C# methods)
// Call setup for all class starting from the first native type (first that uses virtual calls will allocate table of a proper size, further base types will just add own methods)
for (ScriptingTypeHandle e = nativeType; e;)
{
const ScriptingType& eType = e.GetType();
if (eType.Script.SetupScriptVTable)
{
ASSERT(eType.ManagedClass);
eType.Script.SetupScriptVTable(eType.ManagedClass, type.Script.ScriptVTable, type.Script.ScriptVTableBase);
}
e = eType.GetBaseType();
}
MMethod** scriptVTable = (MMethod**)type.Script.ScriptVTable;
while (scriptVTable && *scriptVTable)
{
const MMethod* referenceMethod = *scriptVTable;
// Find that method overriden in C# class (the current or one of the base classes in C#)
MMethod* method = ::FindMethod(mclass, referenceMethod);
if (method == nullptr)
{
// Check base classes (skip native class)
baseClass = mclass->GetBaseClass();
MClass* nativeBaseClass = nativeType.GetType().ManagedClass;
while (baseClass && baseClass != nativeBaseClass && method == nullptr)
{
method = ::FindMethod(baseClass, referenceMethod);
// 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);
}
baseClass = baseClass->GetBaseClass();
}
}
// Set the method to call (null entry marks unused entries that won't use C# wrapper calls)
*scriptVTable = method;
// Move to the next entry (table is null terminated)
scriptVTable++;
}
}
}
}
void ManagedBinaryModule::OnUnloading(MAssembly* assembly)
{
PROFILE_CPU();
// Clear managed-only types
for (int32 i = _firstManagedTypeIndex; i < Types.Count(); i++)
{
const ScriptingType& type = Types[i];
const MString typeName(type.Fullname.Get(), type.Fullname.Length());
TypeNameToTypeIndex.Remove(typeName);
}
Types.Resize(_firstManagedTypeIndex);
for (int32 i = 0; i < _managedTypesNames.Count(); i++)
Allocator::Free(_managedTypesNames[i]);
_managedTypesNames.Clear();
// Clear managed types information
for (ScriptingType& type : Types)
{
type.ManagedClass = nullptr;
if (type.Type == ScriptingTypes::Script && type.Script.ScriptVTable)
{
Platform::Free(type.Script.ScriptVTable);
type.Script.ScriptVTable = nullptr;
}
}
ClassToTypeIndex.Clear();
}
const StringAnsi& ManagedBinaryModule::GetName() const
{
return Assembly->GetName();
}
bool ManagedBinaryModule::IsLoaded() const
{
return Assembly->IsLoaded();
}
void* ManagedBinaryModule::FindMethod(const ScriptingTypeHandle& typeHandle, const StringAnsiView& name, int32 numParams)
{
const ScriptingType& type = typeHandle.GetType();
return type.ManagedClass ? type.ManagedClass->GetMethod(name.Get(), numParams) : nullptr;
}
void* ManagedBinaryModule::FindMethod(const ScriptingTypeHandle& typeHandle, const ScriptingTypeMethodSignature& signature)
{
const ScriptingType& type = typeHandle.GetType();
return FindMethod(type.ManagedClass, signature);
}
bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Span<Variant> paramValues, Variant& result)
{
const auto mMethod = (MMethod*)method;
MonoMethodSignature* signature = mono_method_signature(mMethod->GetNative());
void* signatureParams = nullptr;
mono_signature_get_params(signature, &signatureParams);
const int32 parametersCount = mono_signature_get_param_count(signature);
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());
return true;
}
// Get instance object
void* mInstance = nullptr;
if (!mMethod->IsStatic())
{
// Box instance into C# object
MonoObject* instanceObject = MUtils::BoxVariant(instance);
// Validate instance
if (!instanceObject || !mono_class_is_subclass_of(mono_object_get_class(instanceObject), mMethod->GetParentClass()->GetNative(), false))
{
if (!instanceObject)
LOG(Error, "Failed to call method '{0}.{1}' (args count: {2}) without object instance", String(mMethod->GetParentClass()->GetFullName()), String(mMethod->GetName()), parametersCount);
else
LOG(Error, "Failed to call method '{0}.{1}' (args count: {2}) with invalid object instance of type '{3}'", String(mMethod->GetParentClass()->GetFullName()), String(mMethod->GetName()), parametersCount, String(MUtils::GetClassFullname(instanceObject)));
return true;
}
// 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;
}
// Marshal parameters
void** params = (void**)alloca(parametersCount * sizeof(void*));
bool failed = false;
bool hasOutParams = false;
for (int32 paramIdx = 0; paramIdx < parametersCount; paramIdx++)
{
auto& paramValue = paramValues[paramIdx];
const bool isOut = mono_signature_param_is_out(signature, paramIdx) != 0;
hasOutParams |= isOut;
// Marshal parameter for managed method
MType paramType(((MonoType**)signatureParams)[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);
return true;
}
}
// Invoke the method
MonoObject* exception = nullptr;
MonoObject* resultObject = mMethod->Invoke(mInstance, params, &exception);
if (exception)
{
MException ex(exception);
ex.Log(LogType::Error, TEXT("InvokeMethod"));
return true;
}
// Unbox result
result = MUtils::UnboxVariant(resultObject);
#if 0
// Helper method invocations logging
String log;
log += result.ToString();
log += TEXT(" ");
log += String(mMethod->GetName());
log += TEXT("(");
for (int32 paramIdx = 0; paramIdx < parametersCount; paramIdx++)
{
if (paramIdx != 0)
log += TEXT(", ");
log += paramValues[paramIdx].ToString();
}
log += TEXT(")");
LOG_STR(Warning, log);
#endif
// Unbox output parameters values
if (hasOutParams)
{
for (int32 paramIdx = 0; paramIdx < parametersCount; paramIdx++)
{
const bool isOut = mono_signature_param_is_out(signature, paramIdx) != 0;
if (isOut)
{
auto& paramValue = paramValues[paramIdx];
auto param = params[paramIdx];
switch (paramValue.Type.Type)
{
case VariantType::String:
paramValue.SetString(MUtils::ToString((MonoString*)param));
break;
case VariantType::Object:
paramValue = MUtils::UnboxVariant((MonoObject*)param);
break;
case VariantType::Structure:
{
const ScriptingTypeHandle paramTypeHandle = Scripting::FindScriptingType(StringAnsiView(paramValue.Type.TypeName));
if (paramTypeHandle)
{
auto& valueType = paramTypeHandle.GetType();
valueType.Struct.Unbox(paramValue.AsBlob.Data, (MonoObject*)((byte*)param - sizeof(MonoObject)));
}
break;
}
}
}
}
}
return false;
}
void ManagedBinaryModule::GetMethodSignature(void* method, ScriptingTypeMethodSignature& signature)
{
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.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;
}
}
void* ManagedBinaryModule::FindField(const ScriptingTypeHandle& typeHandle, const StringAnsiView& name)
{
const ScriptingType& type = typeHandle.GetType();
return type.ManagedClass ? type.ManagedClass->GetField(name.Get()) : nullptr;
}
void ManagedBinaryModule::GetFieldSignature(void* field, ScriptingTypeFieldSignature& fieldSignature)
{
const auto mField = (MField*)field;
fieldSignature.Name = mField->GetName();
fieldSignature.ValueType = MoveTemp(MUtils::UnboxVariantType(mField->GetType().GetNative()));
fieldSignature.IsStatic = mField->IsStatic();
}
bool ManagedBinaryModule::GetFieldValue(void* field, const Variant& instance, Variant& result)
{
const auto mField = (MField*)field;
// Get instance object
MonoObject* 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)
LOG(Error, "Failed to get field '{0}.{1}' without object instance", String(mField->GetParentClass()->GetFullName()), String(mField->GetName()));
else
LOG(Error, "Failed to get field '{0}.{1}' with invalid object instance of type '{2}'", String(mField->GetParentClass()->GetFullName()), String(mField->GetName()), String(MUtils::GetClassFullname(instanceObject)));
return true;
}
}
// Get the value
MonoObject* resultObject = mField->GetValueBoxed(instanceObject);
result = MUtils::UnboxVariant(resultObject);
return false;
}
bool ManagedBinaryModule::SetFieldValue(void* field, const Variant& instance, Variant& value)
{
const auto mField = (MField*)field;
// Get instance object
MonoObject* 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)
LOG(Error, "Failed to set field '{0}.{1}' without object instance", String(mField->GetParentClass()->GetFullName()), String(mField->GetName()));
else
LOG(Error, "Failed to set field '{0}.{1}' with invalid object instance of type '{2}'", String(mField->GetParentClass()->GetFullName()), String(mField->GetName()), String(MUtils::GetClassFullname(instanceObject)));
return true;
}
}
// Set the value
bool failed = false;
mField->SetValue(instanceObject, MUtils::VariantToManagedArgPtr(value, mField->GetType(), failed));
return failed;
}
void ManagedBinaryModule::Destroy(bool isReloading)
{
BinaryModule::Destroy(isReloading);
// Release managed assembly
Assembly->Unload(isReloading);
}
NativeBinaryModule::NativeBinaryModule(const StringAnsiView& name, const MAssemblyOptions& options)
: NativeBinaryModule(New<MAssembly>(nullptr, name, options))
{
}
NativeBinaryModule::NativeBinaryModule(MAssembly* assembly)
: ManagedBinaryModule(assembly)
, Library(nullptr)
{
}
void NativeBinaryModule::Destroy(bool isReloading)
{
ManagedBinaryModule::Destroy(isReloading);
// Release native library
const auto library = Library;
if (library)
{
Library = nullptr;
Platform::FreeLibrary(library);
// Don't do anything after FreeLibrary (this pointer might be invalid)
}
}
NativeOnlyBinaryModule::NativeOnlyBinaryModule(const StringAnsiView& name)
: BinaryModule()
, _name(name)
, Library(nullptr)
{
}
const StringAnsi& NativeOnlyBinaryModule::GetName() const
{
return _name;
}
bool NativeOnlyBinaryModule::IsLoaded() const
{
return true;
}
void NativeOnlyBinaryModule::Destroy(bool isReloading)
{
BinaryModule::Destroy(isReloading);
// Release native library
const auto library = Library;
if (library)
{
Library = nullptr;
Platform::FreeLibrary(library);
// Don't do anything after FreeLibrary (this pointer might be invalid)
}
}
Array<GetBinaryModuleFunc, InlinedAllocation<64>>& StaticallyLinkedBinaryModuleInitializer::GetStaticallyLinkedBinaryModules()
{
static Array<GetBinaryModuleFunc, InlinedAllocation<64>> modules;
return modules;
}
StaticallyLinkedBinaryModuleInitializer::StaticallyLinkedBinaryModuleInitializer(GetBinaryModuleFunc getter)
: _getter(getter)
{
GetStaticallyLinkedBinaryModules().Add(getter);
}
StaticallyLinkedBinaryModuleInitializer::~StaticallyLinkedBinaryModuleInitializer()
{
GetStaticallyLinkedBinaryModules().Remove(_getter);
}