Add support for interfaces in scripting API (cross language support C++/C#/VS)

This commit is contained in:
Wojtek Figat
2021-10-04 12:22:28 +02:00
parent 147e5ada46
commit c3c0a4ef0d
16 changed files with 1039 additions and 355 deletions

View File

@@ -9,6 +9,7 @@
#include "Engine/Utilities/StringConverter.h"
#include "Engine/Content/Asset.h"
#include "Engine/Content/Content.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "ManagedCLR/MAssembly.h"
#include "ManagedCLR/MClass.h"
#include "ManagedCLR/MUtils.h"
@@ -23,6 +24,9 @@
#define ScriptingObject_unmanagedPtr "__unmanagedPtr"
#define ScriptingObject_id "__internalId"
// TODO: don't leak memory (use some kind of late manual GC for those wrapper objects)
Dictionary<ScriptingObject*, void*> ScriptingObjectsInterfaceWrappers;
ScriptingObject::ScriptingObject(const SpawnParams& params)
: _gcHandle(0)
, _type(params.Type)
@@ -71,6 +75,51 @@ MClass* ScriptingObject::GetClass() const
return _type ? _type.GetType().ManagedClass : nullptr;
}
ScriptingObject* ScriptingObject::FromInterface(void* interfaceObj, ScriptingTypeHandle& interfaceType)
{
if (!interfaceObj || !interfaceType)
return nullptr;
PROFILE_CPU();
// Find the type which implements this interface and has the same vtable as interface object
// TODO: implement vtableInterface->type hashmap caching in Scripting service to optimize sequential interface casts
auto& modules = BinaryModule::GetModules();
for (auto module : modules)
{
for (auto& type : module->Types)
{
if (type.Type != ScriptingTypes::Script)
continue;
auto interfaceImpl = type.GetInterface(interfaceType);
if (interfaceImpl && interfaceImpl->IsNative)
{
ScriptingObject* predictedObj = (ScriptingObject*)((byte*)interfaceObj - interfaceImpl->VTableOffset);
void* predictedVTable = *(void***)predictedObj;
void* vtable = type.Script.VTable;
if (!vtable && type.GetDefaultInstance())
{
// Use vtable from default instance of this type
vtable = *(void***)type.GetDefaultInstance();
}
if (vtable == predictedVTable)
{
ASSERT(predictedObj->GetType().GetInterface(interfaceType));
return predictedObj;
}
}
}
}
// Special case for interface wrapper object
for (auto& e : ScriptingObjectsInterfaceWrappers)
{
if (e.Value == interfaceObj)
return e.Key;
}
return nullptr;
}
ScriptingObject* ScriptingObject::ToNative(MonoObject* obj)
{
ScriptingObject* ptr = nullptr;
@@ -575,6 +624,37 @@ public:
obj->ChangeID(*id);
}
static void* GetUnmanagedInterface(ScriptingObject* obj, MonoReflectionType* type)
{
if (obj && type)
{
auto typeClass = MUtils::GetClass(type);
const ScriptingTypeHandle interfaceType = ManagedBinaryModule::FindType(typeClass);
if (interfaceType)
{
const ScriptingType& objectType = obj->GetType();
const ScriptingType::InterfaceImplementation* interface = objectType.GetInterface(interfaceType);
if (interface && interface->IsNative)
{
// Native interface so just offset pointer to the interface vtable start
return (byte*)obj + interface->VTableOffset;
}
if (interface)
{
// Interface implemented in scripting (eg. C# class inherits C++ interface)
void* result;
if (!ScriptingObjectsInterfaceWrappers.TryGet(obj, result))
{
result = interfaceType.GetType().Interface.GetInterfaceWrapper(obj);
ScriptingObjectsInterfaceWrappers.Add(obj, result);
}
return result;
}
}
}
return nullptr;
}
static void InitRuntime()
{
ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_Create1", &Create1);
@@ -586,6 +666,7 @@ public:
ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_FindObject", &FindObject);
ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_TryFindObject", &TryFindObject);
ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_ChangeID", &ChangeID);
ADD_INTERNAL_CALL("FlaxEngine.Object::Internal_GetUnmanagedInterface", &GetUnmanagedInterface);
}
static ScriptingObject* Spawn(const ScriptingObjectSpawnParams& params)