1450 lines
51 KiB
C++
1450 lines
51 KiB
C++
// Copyright (c) 2012-2022 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"
|
|
#include "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", MAssemblyOptions(false)); // Don't precache all corlib classes
|
|
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_MONO
|
|
|
|
MonoClass* ScriptingTypeHandle::GetMonoClass() const
|
|
{
|
|
ASSERT_LOW_LAYER(Module && Module->Types[TypeIndex].ManagedClass);
|
|
return Module->Types[TypeIndex].ManagedClass->GetNative();
|
|
}
|
|
|
|
#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();
|
|
}
|
|
}
|
|
|
|
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);
|
|
Platform::MemoryCopy((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
|
|
Platform::MemoryCopy((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
|
|
SetupScriptObjectVTable(object, baseTypeHandle, wrapperIndex);
|
|
}
|
|
|
|
// 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());
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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!
|
|
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, MonoType* monoType)
|
|
{
|
|
MonoClass* monoClass = mono_class_from_mono_type(monoType);
|
|
if (MUtils::GetClass(type) != monoClass)
|
|
{
|
|
// Hack for Vector2/3/4 which alias with Float2/3/4 or Double2/3/4 (depending on USE_LARGE_WORLDS)
|
|
const auto& stdTypes = *StdTypesContainer::Instance();
|
|
if (monoClass == stdTypes.Vector2Class->GetNative() && (type.Type == VariantType::Float2 || type.Type == VariantType::Double2))
|
|
return true;
|
|
if (monoClass == stdTypes.Vector3Class->GetNative() && (type.Type == VariantType::Float3 || type.Type == VariantType::Double3))
|
|
return true;
|
|
if (monoClass == stdTypes.Vector4Class->GetNative() && (type.Type == VariantType::Float4 || type.Type == VariantType::Double4))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
MMethod* ManagedBinaryModule::FindMethod(MClass* mclass, const ScriptingTypeMethodSignature& signature)
|
|
{
|
|
if (!mclass)
|
|
return nullptr;
|
|
const auto& methods = mclass->GetMethods();
|
|
for (MMethod* method : methods)
|
|
{
|
|
#if USE_MONO
|
|
MonoMethodSignature* sig = mono_method_signature(method->GetNative());
|
|
if (method->IsStatic() != signature.IsStatic ||
|
|
method->GetName() != signature.Name ||
|
|
(int32)mono_signature_get_param_count(sig) != signature.Params.Count())
|
|
continue;
|
|
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) ||
|
|
!VariantTypeEquals(param.Type, ((MonoType**)sigParams)[paramIdx]))
|
|
{
|
|
isValid = false;
|
|
break;
|
|
}
|
|
}
|
|
if (isValid && VariantTypeEquals(signature.ReturnType, mono_signature_get_return_type(sig)))
|
|
return method;
|
|
#endif
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
#if USE_MONO
|
|
|
|
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<ManagedBinaryModule*>(e);
|
|
if (managedModule && managedModule->Assembly->GetMonoImage() == mImage)
|
|
{
|
|
module = managedModule;
|
|
break;
|
|
}
|
|
}
|
|
return module;
|
|
}
|
|
|
|
ScriptingTypeHandle ManagedBinaryModule::FindType(MonoClass* 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());
|
|
|
|
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;
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void ManagedBinaryModule::InitType(MClass* mclass)
|
|
{
|
|
#if !COMPILE_WITHOUT_CSHARP
|
|
// Skip if already initialized
|
|
const MString& typeName = mclass->GetFullName();
|
|
if (TypeNameToTypeIndex.ContainsKey(typeName))
|
|
return;
|
|
|
|
// Find first native base C++ class of this C# class
|
|
MClass* baseClass = nullptr;
|
|
MonoClass* baseKlass = mono_class_get_parent(mclass->GetNative());
|
|
MonoImage* baseKlassImage = mono_class_get_image(baseKlass);
|
|
ScriptingTypeHandle baseType;
|
|
auto& modules = GetModules();
|
|
for (int32 i = 0; i < modules.Count(); i++)
|
|
{
|
|
auto e = dynamic_cast<ManagedBinaryModule*>(modules[i]);
|
|
if (e && e->Assembly->GetMonoImage() == baseKlassImage)
|
|
{
|
|
baseType.Module = e;
|
|
baseClass = e->Assembly->GetClass(baseKlass);
|
|
break;
|
|
}
|
|
}
|
|
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);
|
|
if (!baseType)
|
|
{
|
|
LOG(Error, "Missing base class for managed class {0} from assembly {1}.", String(typeName), Assembly->ToString());
|
|
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#
|
|
MonoClass* interfaceKlass;
|
|
void* interfaceIt = nullptr;
|
|
int32 interfacesCount = 0;
|
|
MonoClass* klass = mclass->GetNative();
|
|
while ((interfaceKlass = mono_class_get_interfaces(klass, &interfaceIt)))
|
|
{
|
|
const ScriptingTypeHandle interfaceType = FindType(interfaceKlass);
|
|
if (interfaceType)
|
|
interfacesCount++;
|
|
}
|
|
ScriptingType::InterfaceImplementation* interfaces = nullptr;
|
|
if (interfacesCount != 0)
|
|
{
|
|
interfaces = (ScriptingType::InterfaceImplementation*)Allocator::Allocate((interfacesCount + 1) * sizeof(ScriptingType::InterfaceImplementation));
|
|
interfacesCount = 0;
|
|
interfaceIt = nullptr;
|
|
while ((interfaceKlass = mono_class_get_interfaces(klass, &interfaceIt)))
|
|
{
|
|
const ScriptingTypeHandle interfaceTypeHandle = FindType(interfaceKlass);
|
|
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 Mono 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())
|
|
{
|
|
// 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++;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
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 < _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_MONO
|
|
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;
|
|
const bool withInterfaces = !mMethod->IsStatic() && mMethod->GetParentClass()->IsInterface();
|
|
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(), withInterfaces))
|
|
{
|
|
if (!instanceObject)
|
|
LOG(Error, "Failed to call method '{0}.{1}' (args count: {2}) without object instance", String(mMethod->GetParentClass()->GetFullName()), String(mMethod->GetName()), parametersCount);
|
|
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
|
|
MObject* exception = nullptr;
|
|
MonoObject* resultObject = withInterfaces ? mMethod->InvokeVirtual((MonoObject*)mInstance, params, &exception) : 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;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
void ManagedBinaryModule::GetMethodSignature(void* method, ScriptingTypeMethodSignature& signature)
|
|
{
|
|
#if USE_MONO
|
|
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;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
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)
|
|
{
|
|
#if USE_MONO
|
|
const auto mField = (MField*)field;
|
|
fieldSignature.Name = mField->GetName();
|
|
fieldSignature.ValueType = MoveTemp(MUtils::UnboxVariantType(mField->GetType().GetNative()));
|
|
fieldSignature.IsStatic = mField->IsStatic();
|
|
#endif
|
|
}
|
|
|
|
bool ManagedBinaryModule::GetFieldValue(void* field, const Variant& instance, Variant& result)
|
|
{
|
|
#if USE_MONO
|
|
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;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool ManagedBinaryModule::SetFieldValue(void* field, const Variant& instance, Variant& value)
|
|
{
|
|
#if USE_MONO
|
|
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;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
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);
|
|
}
|