// 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, void(*)(ScriptingObject*, void*, bool)> ScriptingEvents::EventsTable; Delegate, 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 ScriptingTypeHandle& 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(nullptr, name, options)) { } ManagedBinaryModule::ManagedBinaryModule(MAssembly* assembly) { Assembly = assembly; // Bind for C# assembly events assembly->Loading.Bind(this); assembly->Loaded.Bind(this); assembly->Unloading.Bind(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(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& 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; } ManagedBinaryModule* ManagedBinaryModule::FindModule(MonoClass* 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) { auto managedModule = dynamic_cast(e); if (managedModule && managedModule->Assembly->GetMonoImage() == mImage) { module = managedModule; break; } } return module; } 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]; 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(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 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(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>& StaticallyLinkedBinaryModuleInitializer::GetStaticallyLinkedBinaryModules() { static Array> modules; return modules; } StaticallyLinkedBinaryModuleInitializer::StaticallyLinkedBinaryModuleInitializer(GetBinaryModuleFunc getter) : _getter(getter) { GetStaticallyLinkedBinaryModules().Add(getter); } StaticallyLinkedBinaryModuleInitializer::~StaticallyLinkedBinaryModuleInitializer() { GetStaticallyLinkedBinaryModules().Remove(_getter); }