Files
FlaxEngine/Source/Engine/Scripting/BinaryModule.cpp

1631 lines
57 KiB
C++

// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "BinaryModule.h"
#include "ScriptingObject.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Utilities.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "ManagedCLR/MAssembly.h"
#include "ManagedCLR/MClass.h"
#include "ManagedCLR/MMethod.h"
#include "ManagedCLR/MField.h"
#include "ManagedCLR/MProperty.h"
#include "ManagedCLR/MUtils.h"
#include "ManagedCLR/MException.h"
#include "FlaxEngine.Gen.h"
#include "Scripting.h"
#include "Events.h"
#include "Internal/StdTypesContainer.h"
Dictionary<Pair<ScriptingTypeHandle, StringView>, void(*)(ScriptingObject*, void*, bool)> ScriptingEvents::EventsTable;
Delegate<ScriptingObject*, Span<Variant>, ScriptingTypeHandle, StringView> ScriptingEvents::Event;
ManagedBinaryModule* GetBinaryModuleCorlib()
{
#if COMPILE_WITHOUT_CSHARP
return nullptr;
#else
static ManagedBinaryModule assembly("corlib");
return &assembly;
#endif
}
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];
}
#if USE_CSHARP
MClass* ScriptingTypeHandle::GetClass() const
{
ASSERT_LOW_LAYER(Module && Module->Types[TypeIndex].ManagedClass);
return Module->Types[TypeIndex].ManagedClass;
}
#endif
bool ScriptingTypeHandle::IsSubclassOf(ScriptingTypeHandle c) const
{
auto type = *this;
if (type == c)
return false;
for (; type; type = type.GetType().GetBaseType())
{
if (type == c)
return true;
}
return false;
}
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.InterfacesOffsets = 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.InterfacesOffsets = 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.InterfacesOffsets = 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, int32 size, EnumItem* items)
: ManagedClass(nullptr)
, Module(module)
, InitRuntime(DefaultInitRuntime)
, Fullname(fullname)
, Type(ScriptingTypes::Enum)
, BaseTypePtr(nullptr)
, Interfaces(nullptr)
, Size(size)
{
Enum.Items = items;
}
ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, InitRuntimeHandler initRuntime, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable, GetInterfaceWrapper getInterfaceWrapper)
: ManagedClass(nullptr)
, Module(module)
, InitRuntime(initRuntime)
, Fullname(fullname)
, Type(ScriptingTypes::Interface)
, BaseTypePtr(nullptr)
, Interfaces(nullptr)
, Size(0)
{
Interface.SetupScriptVTable = setupScriptVTable;
Interface.SetupScriptObjectVTable = setupScriptObjectVTable;
Interface.GetInterfaceWrapper = getInterfaceWrapper;
}
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.InterfacesOffsets = 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:
Enum.Items = other.Enum.Items;
break;
case ScriptingTypes::Interface:
Interface.SetupScriptVTable = other.Interface.SetupScriptVTable;
Interface.SetupScriptObjectVTable = other.Interface.SetupScriptObjectVTable;
Interface.GetInterfaceWrapper = other.Interface.GetInterfaceWrapper;
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.InterfacesOffsets = other.Script.InterfacesOffsets;
other.Script.InterfacesOffsets = 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:
Enum.Items = other.Enum.Items;
break;
case ScriptingTypes::Interface:
Interface.SetupScriptVTable = other.Interface.SetupScriptVTable;
Interface.SetupScriptObjectVTable = other.Interface.SetupScriptObjectVTable;
Interface.GetInterfaceWrapper = other.Interface.GetInterfaceWrapper;
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.InterfacesOffsets);
Platform::Free(Script.ScriptVTable);
Platform::Free(Script.ScriptVTableBase);
break;
case ScriptingTypes::Structure:
break;
case ScriptingTypes::Enum:
break;
case ScriptingTypes::Interface:
break;
default: ;
}
}
void ScriptingType::DefaultInitRuntime()
{
}
ScriptingObject* ScriptingType::DefaultSpawn(const ScriptingObjectSpawnParams& params)
{
return nullptr;
}
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;
}
void ScriptingType::SetupScriptVTable(ScriptingTypeHandle baseTypeHandle)
{
// 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 = baseTypeHandle; e;)
{
const ScriptingType& eType = e.GetType();
if (eType.Script.SetupScriptVTable)
{
ASSERT(eType.ManagedClass);
eType.Script.SetupScriptVTable(eType.ManagedClass, Script.ScriptVTable, Script.ScriptVTableBase);
}
auto interfaces = eType.Interfaces;
if (interfaces && Script.ScriptVTable)
{
while (interfaces->InterfaceType)
{
auto& interfaceType = interfaces->InterfaceType->GetType();
if (interfaceType.Interface.SetupScriptVTable)
{
ASSERT(eType.ManagedClass);
const auto scriptOffset = interfaces->ScriptVTableOffset; // Shift the script vtable for the interface implementation start
Script.ScriptVTable += scriptOffset;
Script.ScriptVTableBase += scriptOffset;
interfaceType.Interface.SetupScriptVTable(eType.ManagedClass, Script.ScriptVTable, Script.ScriptVTableBase);
Script.ScriptVTable -= scriptOffset;
Script.ScriptVTableBase -= scriptOffset;
}
interfaces++;
}
}
e = eType.GetBaseType();
}
}
NO_SANITIZE_ADDRESS
void ScriptingType::SetupScriptObjectVTable(void* object, ScriptingTypeHandle baseTypeHandle, int32 wrapperIndex)
{
// Analyze vtable size
void** vtable = *(void***)object;
const int32 prefixSize = GetVTablePrefix();
int32 entriesCount = 0;
while (vtable[entriesCount] && entriesCount < 200)
entriesCount++;
// Calculate total vtable size by adding all implemented interfaces that use virtual methods
const int32 size = entriesCount * sizeof(void*);
int32 totalSize = prefixSize + size;
int32 interfacesCount = 0;
for (ScriptingTypeHandle e = baseTypeHandle; e;)
{
const ScriptingType& eType = e.GetType();
auto interfaces = eType.Interfaces;
if (interfaces)
{
while (interfaces->InterfaceType)
{
auto& interfaceType = interfaces->InterfaceType->GetType();
if (interfaceType.Interface.SetupScriptObjectVTable)
{
void** vtableInterface = *(void***)((byte*)object + interfaces->VTableOffset);
int32 interfaceCount = 0;
while (vtableInterface[interfaceCount] && interfaceCount < 200)
interfaceCount++;
totalSize += prefixSize + interfaceCount * sizeof(void*);
interfacesCount++;
}
interfaces++;
}
}
e = eType.GetBaseType();
}
// Duplicate vtable
Script.VTable = (void**)((byte*)Platform::Allocate(totalSize, 16) + prefixSize);
Utilities::UnsafeMemoryCopy((byte*)Script.VTable - prefixSize, (byte*)vtable - prefixSize, prefixSize + size);
// Override vtable entries
if (interfacesCount)
Script.InterfacesOffsets = (uint16*)Platform::Allocate(interfacesCount * sizeof(uint16*), 16);
int32 interfaceOffset = size;
interfacesCount = 0;
for (ScriptingTypeHandle e = baseTypeHandle; e;)
{
const ScriptingType& eType = e.GetType();
if (eType.Script.SetupScriptObjectVTable)
{
// Override vtable entries for this class
eType.Script.SetupScriptObjectVTable(Script.ScriptVTable, Script.ScriptVTableBase, Script.VTable, entriesCount, wrapperIndex);
}
auto interfaces = eType.Interfaces;
if (interfaces)
{
while (interfaces->InterfaceType)
{
auto& interfaceType = interfaces->InterfaceType->GetType();
if (interfaceType.Interface.SetupScriptObjectVTable)
{
// Analyze interface vtable size
void** vtableInterface = *(void***)((byte*)object + interfaces->VTableOffset);
int32 interfaceCount = 0;
while (vtableInterface[interfaceCount] && interfaceCount < 200)
interfaceCount++;
const int32 interfaceSize = interfaceCount * sizeof(void*);
// Duplicate interface vtable
Utilities::UnsafeMemoryCopy((byte*)Script.VTable + interfaceOffset, (byte*)vtableInterface - prefixSize, prefixSize + interfaceSize);
// Override interface vtable entries
const auto scriptOffset = interfaces->ScriptVTableOffset;
const auto nativeOffset = interfaceOffset + prefixSize;
void** interfaceVTable = (void**)((byte*)Script.VTable + nativeOffset);
interfaceType.Interface.SetupScriptObjectVTable(Script.ScriptVTable + scriptOffset, Script.ScriptVTableBase + scriptOffset, interfaceVTable, interfaceCount, wrapperIndex);
Script.InterfacesOffsets[interfacesCount++] = (uint16)nativeOffset;
interfaceOffset += prefixSize + interfaceSize;
}
interfaces++;
}
}
e = eType.GetBaseType();
}
}
void ScriptingType::HackObjectVTable(void* object, ScriptingTypeHandle baseTypeHandle, int32 wrapperIndex)
{
if (!Script.ScriptVTable)
return;
if (!Script.VTable)
{
// Ensure to have valid Script VTable hacked
BinaryModule::Locker.Lock();
if (!Script.VTable)
{
SetupScriptObjectVTable(object, baseTypeHandle, wrapperIndex);
}
BinaryModule::Locker.Unlock();
}
// Override object vtable with hacked one that has calls to overriden scripting functions
*(void**)object = Script.VTable;
if (Script.InterfacesOffsets)
{
// Override vtables for interfaces
int32 interfacesCount = 0;
for (ScriptingTypeHandle e = baseTypeHandle; e;)
{
const ScriptingType& eType = e.GetType();
auto interfaces = eType.Interfaces;
if (interfaces)
{
while (interfaces->InterfaceType)
{
auto& interfaceType = interfaces->InterfaceType->GetType();
if (interfaceType.Interface.SetupScriptObjectVTable)
{
void** interfaceVTable = (void**)((byte*)Script.VTable + Script.InterfacesOffsets[interfacesCount++]);
*(void**)((byte*)object + interfaces->VTableOffset) = interfaceVTable;
interfacesCount++;
}
interfaces++;
}
}
e = eType.GetBaseType();
}
}
}
String ScriptingType::ToString() const
{
return String(Fullname.Get(), Fullname.Length());
}
StringAnsiView ScriptingType::GetName() const
{
int32 lastDotIndex = Fullname.FindLast('.');
if (lastDotIndex != -1)
{
lastDotIndex++;
return StringAnsiView(Fullname.Get() + lastDotIndex, Fullname.Length() - lastDotIndex);
}
return Fullname;
}
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, int32 size, ScriptingType::EnumItem* items)
: ScriptingTypeHandle(module, module->Types.Count())
{
// Enum
module->Types.AddUninitialized();
new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, items);
#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, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable, ScriptingType::GetInterfaceWrapper getInterfaceWrapper)
: ScriptingTypeHandle(module, module->Types.Count())
{
// Interface
module->Types.AddUninitialized();
new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, initRuntime, setupScriptVTable, setupScriptObjectVTable, getInterfaceWrapper);
#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;
}
CriticalSection BinaryModule::Locker;
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)
{
// Destroy any default script instances
for (const auto& type : Types)
{
if (type.Type == ScriptingTypes::Script && type.Script.DefaultInstance)
{
Delete(type.Script.DefaultInstance);
type.Script.DefaultInstance = nullptr;
}
}
// Remove any scripting events
for (auto i = ScriptingEvents::EventsTable.Begin(); i.IsNotEnd(); ++i)
{
const ScriptingTypeHandle type = i->Key.First;
if (type.Module == this)
ScriptingEvents::EventsTable.Remove(i);
}
// Unregister
GetModules().RemoveKeepOrder(this);
}
ManagedBinaryModule::ManagedBinaryModule(const StringAnsiView& name)
: ManagedBinaryModule(New<MAssembly>(nullptr, name))
{
}
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);
assembly->Unloaded.Bind<ManagedBinaryModule, &ManagedBinaryModule::OnUnloaded>(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();
if (managedTypePtr->ManagedClass && managedTypePtr->ManagedClass->IsAbstract())
{
LOG(Error, "Failed to spawn abstract type '{}'", managedTypePtr->ToString());
return nullptr;
}
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!
managedType.HackObjectVTable(object, nativeTypeHandle, 0);
// Mark as managed type
object->Flags |= ObjectFlags::IsManagedType;
return object;
}
#if !COMPILE_WITHOUT_CSHARP
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;
}
bool VariantTypeEquals(const VariantType& type, MType* mType, bool isOut = false)
{
MClass* mClass = MCore::Type::GetClass(mType);
MClass* variantClass = MUtils::GetClass(type);
if (variantClass != 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 (mClass == stdTypes.Vector2Class && (type.Type == VariantType::Float2 || type.Type == VariantType::Double2))
return true;
if (mClass == stdTypes.Vector3Class && (type.Type == VariantType::Float3 || type.Type == VariantType::Double3))
return true;
if (mClass == stdTypes.Vector4Class && (type.Type == VariantType::Float4 || type.Type == VariantType::Double4))
return true;
return false;
}
return true;
}
}
#endif
MMethod* ManagedBinaryModule::FindMethod(MClass* mclass, const ScriptingTypeMethodSignature& signature)
{
#if USE_CSHARP
if (!mclass)
return nullptr;
const auto& methods = mclass->GetMethods();
for (MMethod* method : methods)
{
if (method->IsStatic() != signature.IsStatic)
continue;
if (method->GetName() != signature.Name)
continue;
if (method->GetParametersCount() != signature.Params.Count())
continue;
bool isValid = true;
for (int32 paramIdx = 0; paramIdx < signature.Params.Count(); paramIdx++)
{
auto& param = signature.Params[paramIdx];
MType* type = method->GetParameterType(paramIdx);
if (param.IsOut != method->GetParameterIsOut(paramIdx) ||
!VariantTypeEquals(param.Type, type, param.IsOut))
{
isValid = false;
break;
}
}
if (isValid && VariantTypeEquals(signature.ReturnType, method->GetReturnType()))
return method;
}
#endif
return nullptr;
}
#if USE_CSHARP
ManagedBinaryModule* ManagedBinaryModule::FindModule(const MClass* klass)
{
ManagedBinaryModule* module = nullptr;
if (klass && klass->GetAssembly())
{
auto& modules = BinaryModule::GetModules();
for (auto e : modules)
{
auto managedModule = dynamic_cast<ManagedBinaryModule*>(e);
if (managedModule && managedModule->Assembly == klass->GetAssembly())
{
module = managedModule;
break;
}
}
}
return module;
}
ScriptingTypeHandle ManagedBinaryModule::FindType(const MClass* klass)
{
auto typeModule = FindModule(klass);
if (typeModule)
{
int32 typeIndex;
if (typeModule->ClassToTypeIndex.TryGet(klass, typeIndex))
return ScriptingTypeHandle(typeModule, typeIndex);
}
return ScriptingTypeHandle();
}
#endif
void ManagedBinaryModule::OnLoading(MAssembly* assembly)
{
PROFILE_CPU();
for (ScriptingType& type : Types)
{
type.InitRuntime();
}
}
void ManagedBinaryModule::OnLoaded(MAssembly* assembly)
{
#if !COMPILE_WITHOUT_CSHARP
PROFILE_CPU();
ASSERT(ClassToTypeIndex.IsEmpty());
ScopeLock lock(Locker);
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 StringAnsi 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
MClass* klass = type.ManagedClass;
#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;
// Check if C# class inherits from C++ object class it has no C++ representation
if (mclass->IsStatic() ||
mclass->IsInterface() ||
!mclass->IsSubClassOf(scriptingObjectType)
)
{
continue;
}
InitType(mclass);
}
}
// Invoke module initializers
if (flaxEngine->Assembly->IsLoaded() && this != flaxEngine)
{
const MClass* attribute = flaxEngine->Assembly->GetClass("FlaxEngine.ModuleInitializerAttribute");
ASSERT_LOW_LAYER(attribute);
for (auto i = classes.Begin(); i.IsNotEnd(); ++i)
{
MClass* mclass = i->Value;
if (mclass->IsStatic() && !mclass->IsInterface() && mclass->HasAttribute(attribute))
{
const auto& methods = mclass->GetMethods();
for (const MMethod* method : methods)
{
if (method->GetParametersCount() == 0)
{
MObject* exception = nullptr;
method->Invoke(nullptr, nullptr, &exception);
if (exception)
{
MException ex(exception);
String methodName = String(method->GetName());
ex.Log(LogType::Error, methodName.Get());
LOG(Error, "Failed to call module initializer for class {0} from assembly {1}.", String(mclass->GetFullName()), assembly->ToString());
}
}
}
}
}
}
#endif
}
void ManagedBinaryModule::InitType(MClass* mclass)
{
#if !COMPILE_WITHOUT_CSHARP
// Skip if already initialized
const StringAnsi& typeName = mclass->GetFullName();
if (TypeNameToTypeIndex.ContainsKey(typeName))
return;
// Find first native base C++ class of this C# class
MClass* baseClass = mclass->GetBaseClass();
ScriptingTypeHandle baseType;
baseType.Module = FindModule(baseClass);
if (!baseClass)
{
LOG(Error, "Missing base class for managed class {0} from assembly {1}.", String(typeName), Assembly->ToString());
return;
}
if (baseType.Module == this)
InitType(baseClass); // Ensure base is initialized before
baseType.Module->TypeNameToTypeIndex.TryGet(baseClass->GetFullName(), *(int32*)&baseType.TypeIndex);
// So we must special case this flow of a generic class of which its possible the generic base class is not in the same module
if (baseType.TypeIndex == -1 && baseClass->IsGeneric())
{
auto genericNameIndex = baseClass->GetFullName().FindLast('`');
// we add 2 because of the way generic names work its `N
auto genericClassName = baseClass->GetFullName().Substring(0, genericNameIndex + 2);
// We check for the generic class name instead of the baseclass fullname
baseType.Module->TypeNameToTypeIndex.TryGet(genericClassName, *(int32*)&baseType.TypeIndex);
}
if (baseType.TypeIndex == -1 || baseType.Module == nullptr)
{
if (baseType.Module)
LOG(Error, "Missing base class for managed class {0} from assembly {1}.", String(baseClass->GetFullName()), baseType.Module->GetName().ToString());
else
LOG(Error, "Missing base class for managed class {0} from unknown assembly.", String(baseClass->GetFullName()));
return;
}
ScriptingTypeHandle nativeType = baseType;
while (true)
{
auto& type = nativeType.GetType();
if (type.Script.Spawn != &ManagedObjectSpawn)
break;
nativeType = type.GetBaseType();
if (!nativeType)
{
LOG(Error, "Missing base class for managed class {0} from assembly {1}.", String(typeName), Assembly->ToString());
return;
}
}
// 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;
_managedMemoryBlocks.Add(typeNameData);
// Initialize scripting interfaces implemented in C#
int32 interfacesCount = 0;
MClass* klass = mclass;
const Array<MClass*>& interfaceClasses = klass->GetInterfaces();
for (const MClass* interfaceClass : interfaceClasses)
{
const ScriptingTypeHandle interfaceType = FindType(interfaceClass);
if (interfaceType)
interfacesCount++;
}
ScriptingType::InterfaceImplementation* interfaces = nullptr;
if (interfacesCount != 0)
{
interfaces = (ScriptingType::InterfaceImplementation*)Allocator::Allocate((interfacesCount + 1) * sizeof(ScriptingType::InterfaceImplementation));
interfacesCount = 0;
for (const MClass* interfaceClass : interfaceClasses)
{
const ScriptingTypeHandle interfaceTypeHandle = FindType(interfaceClass);
if (!interfaceTypeHandle)
continue;
auto& interface = interfaces[interfacesCount++];
auto ptr = (ScriptingTypeHandle*)Allocator::Allocate(sizeof(ScriptingTypeHandle));
*ptr = interfaceTypeHandle;
_managedMemoryBlocks.Add(ptr);
interface.InterfaceType = ptr;
interface.VTableOffset = 0;
interface.ScriptVTableOffset = 0;
interface.IsNative = false;
}
Platform::MemoryClear(interfaces + interfacesCount, sizeof(ScriptingType::InterfaceImplementation));
_managedMemoryBlocks.Add(interfaces);
}
// 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, baseType.GetType().Size, ScriptingType::DefaultInitRuntime, ManagedObjectSpawn, baseType, nullptr, nullptr, interfaces);
TypeNameToTypeIndex[typeName] = typeIndex;
auto& type = Types[typeIndex];
type.ManagedClass = mclass;
// Register C# class
ASSERT(!ClassToTypeIndex.ContainsKey(klass));
ClassToTypeIndex[klass] = typeIndex;
// Create managed vtable for this class (build out of the wrapper C++ methods that call C# methods)
type.SetupScriptVTable(nativeType);
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())
{
MClass* parentClass = mclass->GetBaseClass();
MMethod* parentMethod = parentClass->GetMethod(referenceMethod->GetName().Get(), 0);
method = parentMethod->InflateGeneric();
}
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++;
}
#endif
}
void ManagedBinaryModule::OnUnloading(MAssembly* assembly)
{
PROFILE_CPU();
// Clear managed types typenames
for (int32 i = _firstManagedTypeIndex; i < Types.Count(); i++)
{
const ScriptingType& type = Types[i];
const StringAnsi typeName(type.Fullname.Get(), type.Fullname.Length());
TypeNameToTypeIndex.Remove(typeName);
}
}
void ManagedBinaryModule::OnUnloaded(MAssembly* assembly)
{
PROFILE_CPU();
// Clear managed-only types
Types.Resize(_firstManagedTypeIndex);
for (int32 i = 0; i < _managedMemoryBlocks.Count(); i++)
Allocator::Free(_managedMemoryBlocks[i]);
_managedMemoryBlocks.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;
}
}
#if !COMPILE_WITHOUT_CSHARP
ClassToTypeIndex.Clear();
#endif
}
const StringAnsi& ManagedBinaryModule::GetName() const
{
return Assembly->GetName();
}
bool ManagedBinaryModule::IsLoaded() const
{
#if COMPILE_WITHOUT_CSHARP
return true;
#else
return Assembly->IsLoaded();
#endif
}
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)
{
#if USE_CSHARP
const auto mMethod = (MMethod*)method;
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());
return true;
}
// Get instance object
void* mInstance = nullptr;
const bool withInterfaces = !mMethod->IsStatic() && mMethod->GetParentClass()->IsInterface();
if (!mMethod->IsStatic())
{
// Box instance into C# object (and validate the type)
MObject* instanceObject = MUtils::BoxVariant(instance);
if (!instanceObject)
{
LOG(Error, "Failed to call method '{0}.{1}' (args count: {2}) without object instance", String(mMethod->GetParentClass()->GetFullName()), String(mMethod->GetName()), parametersCount);
return true;
}
const MClass* instanceObjectClass = MCore::Object::GetClass(instanceObject);
if (!instanceObjectClass->IsSubClassOf(mMethod->GetParentClass(), withInterfaces))
{
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;
}
#if USE_NETCORE
mInstance = instanceObject;
#else
// For value-types instance is the actual boxed object data, not te object itself
mInstance = instanceObjectClass->IsValueType() ? MCore::Object::Unbox(instanceObject) : instanceObject;
#endif
}
// Marshal parameters
void** params = (void**)alloca(parametersCount * sizeof(void*));
void** outParams = nullptr;
bool failed = false;
bool hasOutParams = false;
for (int32 paramIdx = 0; paramIdx < parametersCount; paramIdx++)
{
Variant& paramValue = paramValues[paramIdx];
const bool isOut = mMethod->GetParameterIsOut(paramIdx);
hasOutParams |= isOut;
// Marshal parameter for managed method
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, MCore::Type::ToString(paramType), paramIdx, paramValue.Type);
return true;
}
if (isOut && MCore::Type::IsReference(paramType) && MCore::Type::GetType(paramType) == MTypes::Object)
{
// Object passed as out param so pass pointer to the value storage for proper marshalling
if (!outParams)
outParams = (void**)alloca(parametersCount * sizeof(void*));
outParams[paramIdx] = params[paramIdx];
params[paramIdx] = &outParams[paramIdx];
}
}
// Invoke the method
MObject* exception = nullptr;
#if USE_NETCORE // NetCore uses the same path for both virtual and non-virtual calls
MObject* resultObject = mMethod->Invoke(mInstance, params, &exception);
#else
MObject* resultObject = withInterfaces ? mMethod->InvokeVirtual((MObject*)mInstance, params, &exception) : mMethod->Invoke(mInstance, params, &exception);
#endif
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++)
{
if (mMethod->GetParameterIsOut(paramIdx))
{
auto& paramValue = paramValues[paramIdx];
auto param = params[paramIdx];
switch (paramValue.Type.Type)
{
case VariantType::String:
paramValue.SetString(MUtils::ToString((MString*)param));
break;
case VariantType::Object:
paramValue = MUtils::UnboxVariant((MObject*)param);
break;
case VariantType::Structure:
{
const ScriptingTypeHandle paramTypeHandle = Scripting::FindScriptingType(StringAnsiView(paramValue.Type.TypeName));
if (paramTypeHandle)
{
auto& valueType = paramTypeHandle.GetType();
MObject* boxed = MCore::Object::Box(param, valueType.ManagedClass);
valueType.Struct.Unbox(paramValue.AsBlob.Data, boxed);
}
break;
}
default:
{
MType* paramType = mMethod->GetParameterType(paramIdx);
if (MCore::Type::IsReference(paramType) && MCore::Type::GetType(paramType) == MTypes::Object)
paramValue = MUtils::UnboxVariant((MObject*)outParams[paramIdx]);
break;
}
}
}
}
}
return false;
#else
return true;
#endif
}
void ManagedBinaryModule::GetMethodSignature(void* method, ScriptingTypeMethodSignature& signature)
{
#if USE_CSHARP
const auto mMethod = (MMethod*)method;
signature.Name = mMethod->GetName();
signature.IsStatic = mMethod->IsStatic();
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(mMethod->GetParameterType(paramIdx)));
param.IsOut = mMethod->GetParameterIsOut(paramIdx);
}
#endif
}
// Pointers with the highest bit turned on are properties
#if PLATFORM_64BITS
#define ManagedBinaryModuleFieldIsPropertyBit (uintptr)(1ull << 63)
#else
#define ManagedBinaryModuleFieldIsPropertyBit (uintptr)(1ul << 31)
#endif
void* ManagedBinaryModule::FindField(const ScriptingTypeHandle& typeHandle, const StringAnsiView& name)
{
const ScriptingType& type = typeHandle.GetType();
void* result = type.ManagedClass ? type.ManagedClass->GetField(name.Get()) : nullptr;
if (!result && type.ManagedClass)
{
result = type.ManagedClass->GetProperty(name.Get());
if (result)
result = (void*)((uintptr)result | ManagedBinaryModuleFieldIsPropertyBit);
}
return result;
}
void ManagedBinaryModule::GetFieldSignature(void* field, ScriptingTypeFieldSignature& fieldSignature)
{
#if USE_CSHARP
if ((uintptr)field & ManagedBinaryModuleFieldIsPropertyBit)
{
const auto mProperty = (MProperty*)((uintptr)field & ~ManagedBinaryModuleFieldIsPropertyBit);
fieldSignature.Name = mProperty->GetName();
fieldSignature.ValueType = MoveTemp(MUtils::UnboxVariantType(mProperty->GetType()));
fieldSignature.IsStatic = mProperty->IsStatic();
}
else
{
const auto mField = (MField*)field;
fieldSignature.Name = mField->GetName();
fieldSignature.ValueType = MoveTemp(MUtils::UnboxVariantType(mField->GetType()));
fieldSignature.IsStatic = mField->IsStatic();
}
#endif
}
bool ManagedBinaryModule::GetFieldValue(void* field, const Variant& instance, Variant& result)
{
#if USE_CSHARP
bool isStatic;
MClass* parentClass;
StringAnsiView name;
if ((uintptr)field & ManagedBinaryModuleFieldIsPropertyBit)
{
const auto mProperty = (MProperty*)((uintptr)field & ~ManagedBinaryModuleFieldIsPropertyBit);
isStatic = mProperty->IsStatic();
parentClass = mProperty->GetParentClass();
name = mProperty->GetName();
}
else
{
const auto mField = (MField*)field;
isStatic = mField->IsStatic();
parentClass = mField->GetParentClass();
name = mField->GetName();
}
// Get instance object
MObject* instanceObject = nullptr;
if (!isStatic)
{
// Box instance into C# object
instanceObject = MUtils::BoxVariant(instance);
// Validate instance
if (!instanceObject || !MCore::Object::GetClass(instanceObject)->IsSubClassOf(parentClass))
{
if (!instanceObject)
LOG(Error, "Failed to get '{0}.{1}' without object instance", String(parentClass->GetFullName()), String(name));
else
LOG(Error, "Failed to get '{0}.{1}' with invalid object instance of type '{2}'", String(parentClass->GetFullName()), String(name), String(MUtils::GetClassFullname(instanceObject)));
return true;
}
}
// Get the value
MObject* resultObject;
if ((uintptr)field & ManagedBinaryModuleFieldIsPropertyBit)
{
const auto mProperty = (MProperty*)((uintptr)field & ~ManagedBinaryModuleFieldIsPropertyBit);
resultObject = mProperty->GetValue(instanceObject, nullptr);
}
else
{
const auto mField = (MField*)field;
resultObject = mField->GetValueBoxed(instanceObject);
}
result = MUtils::UnboxVariant(resultObject);
return false;
#else
return true;
#endif
}
bool ManagedBinaryModule::SetFieldValue(void* field, const Variant& instance, Variant& value)
{
#if USE_CSHARP
bool isStatic;
MClass* parentClass;
StringAnsiView name;
if ((uintptr)field & ManagedBinaryModuleFieldIsPropertyBit)
{
const auto mProperty = (MProperty*)((uintptr)field & ~ManagedBinaryModuleFieldIsPropertyBit);
isStatic = mProperty->IsStatic();
parentClass = mProperty->GetParentClass();
name = mProperty->GetName();
}
else
{
const auto mField = (MField*)field;
isStatic = mField->IsStatic();
parentClass = mField->GetParentClass();
name = mField->GetName();
}
// Get instance object
MObject* instanceObject = nullptr;
if (!isStatic)
{
// Box instance into C# object
instanceObject = MUtils::BoxVariant(instance);
// Validate instance
if (!instanceObject || !MCore::Object::GetClass(instanceObject)->IsSubClassOf(parentClass))
{
if (!instanceObject)
LOG(Error, "Failed to set '{0}.{1}' without object instance", String(parentClass->GetFullName()), String(name));
else
LOG(Error, "Failed to set '{0}.{1}' with invalid object instance of type '{2}'", String(parentClass->GetFullName()), String(name), String(MUtils::GetClassFullname(instanceObject)));
return true;
}
}
// Set the value
bool failed = false;
if ((uintptr)field & ManagedBinaryModuleFieldIsPropertyBit)
{
const auto mProperty = (MProperty*)((uintptr)field & ~ManagedBinaryModuleFieldIsPropertyBit);
mProperty->SetValue(instanceObject, MUtils::BoxVariant(value), nullptr);
}
else
{
const auto mField = (MField*)field;
mField->SetValue(instanceObject, MUtils::VariantToManagedArgPtr(value, mField->GetType(), failed));
}
return failed;
#else
return true;
#endif
}
void ManagedBinaryModule::Destroy(bool isReloading)
{
BinaryModule::Destroy(isReloading);
// Release managed assembly
Assembly->Unload(isReloading);
}
NativeBinaryModule::NativeBinaryModule(const StringAnsiView& name)
: NativeBinaryModule(New<MAssembly>(nullptr, name))
{
}
NativeBinaryModule::NativeBinaryModule(MAssembly* assembly)
: ManagedBinaryModule(assembly)
, Library(nullptr)
{
}
void NativeBinaryModule::Destroy(bool isReloading)
{
ManagedBinaryModule::Destroy(isReloading);
// Skip native code unloading from core libs
if (this == GetBinaryModuleCorlib() || this == GetBinaryModuleFlaxEngine())
return;
// 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);
}