添加内部调用(与 C# 和 C++ 相互通信)
如果您希望 C# 代码调用 C++ 文件中的某个函数,则只应添加内部调用, 但是如果您希望增加 C++ 的性能,或者因为需要与 C# 接口进行通信,或向 C# 公开 C++ 的 API,则可能需要添加内部调用。
我们将首先提供添加内部调用的相关指南,请您 务必 遵循这些指南, 这些指南是为了让其他人更容易理解以及维护您所编写的代码,并保持代码的一致性。
在 C# 中编写内部调用的相关指南
当您向 C# API 添加内部调用时,您需要考虑以下几点:
- 内部方法应该 始终 在
InternalCalls.cs
文件中定义,我们从不在使用这些方法的类中进行定义。 - 内部方法应该 始终 在
#region
代码块内定义,以保持项目文件的组织结构体清晰,您也可以参考相关代码块,看看我们是如何编写的。 - 内部方法应该 始终 以以下格式命名:
ClassName_FunctionName
。例如:TransformComponent_GetTranslation
或MeshComponent_GetMaterial
。 - 内部方法应该 始终 定义为
internal static extern
。 - 内部方法在 C++ 中的名称应该 始终 与C#中的名称相同,因此在 C++ 中也应该调用名为
TransformComponent_GetTranslation
的方法。 - 内部方法 必须 标记此属性:
[MethodImpl(MethodImplOptions.InternalCall)]
- 如果您需要将基元类型(numerical types,
bool
,char
或object
) 传递给内部调用,则应复制它们,或将其作为out
传递, 而不是作为ref
传递。 - 如果您需要一个内部方法来返回一个基元类型,那么它要么将该类型作为返回类型,要么将其作为
out
参数传递。 例如:如果internal static extern bool
或internal static extern void SomeClass_SomeFunc(out bool result)
需要返回多个参数时,应该使用out
传递。 - 如果需要将
struct
传递给内部调用,则 必须 将其作为ref
或out
传递, 这取决于 C++ 函数是读取结构体还是写入结构体。 - 将结构体作为
ref
传递意味着 C++ 函数将只从结构体中读取,而不会对其进行写入。遵循这一规则是 您 的责任,Mono 不会强制执行它。 - 将结构体作为
out
传递意味着 C++ 函数将读取或写入该结构体。 - 将结构体作为
out
或ref
传递意味着 C++ 函数将采用等效的 C++ 结构体作为指针。 - 枚举不应作为
ref
或out
传递!它们只是基元类型,如果存在 C++ 等效枚举,它们可以按原样传递,或者它们可以作为 int 传递。
这些是您需要记住并注意的一些基本内容,如果您认为这些指南存在问题应该修改或是应该详细的展开来说,请随时联系我们!
在 C++ 中编写内部调用的相关指南
当您向 C++ API 添加内部调用时,您需要考虑以下几点:
- 要在 C++ 中实现内部调用应该 始终 在
ScriptGlue.h
文件中声明,并在ScripGlue.cpp
文件中进行定义。 - 为了向 C++ API 注册内部调用,必须在代码中添加以下行:
ETOD_ADD_INTERNAL_CALL(ClassName_FunctionName);
到ScriptGlue::RegisterInternalCalls
, 最好与InternalCalls.cs
文件中的声明顺序相同。 - 如果使用内部方法记录日志消息,则最好将这些日志消息记录到
EToD-Editor
控制台, 例如使用ETOD_CONSOLE_LOG_INFO
这行代码实现,但这不是必要的,主要取决于您正在记录的日志内容。 - 始终在 C++ 中
InternalCalls
的 命名空间 内添加内部调用,这是注册函数所 必须 的。
可以了解到的是,需要记住的东西还有很多,但关键是要遵循这些准则,并确保您所添加的代码与 C++ 和 C# API 中已经存在的代码相一致。
示例
下面是一个如何向脚本 API 添加内部调用的非常基本的示例,我们将从使用 C# 声明内部调用开始:
假设我们在 C# 中有一个自定义结构体,我们希望用 C++ 中的一些数据填充该结构体,如果成功,我们的内部调用将返回 true
,下面是该结构体的一种代码示例:
// 必须将此属性添加到要传递到的 C++ 结构体中
[StructLayout(LayoutKind.Sequential)]
public struct MyCustomData
{
public float MyFloat;
public int SomeInt;
}
// InternalCalls.cs
...
#region MyCustomComponent
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool MyCustomComponent_GetCustomStruct(ulong entityID, out MyCustomData outData);
#endregion
...
在这里,您可以看到我们定义了内部调用,它返回一个 bool
,并接收两个参数,
" entityID " 参数只是为了保持一致性。在这里,所有 C# 组件都必须将实体的 ID 传递给 C++,这样引擎就可以知道组件属于哪个实体。
以及它还将我们的自定义结构体作为 out
参数,这意味着我们希望 C++ 将一些数据写入该结构体,但它也可以从中读取数据。
这就是在 C# 中定义方法所需要做的全部工作,然后我们要在 C++ 中添加以下代码:
// MyCustomData struct (可以是C++中的类,但在可能的情况下我们可以使用结构体)
struct MyCustomData
{
float MyFloat;
int SomeInt;
};
// ScriptGlue.h
namespace InternalCalls {
...
#pragma region MyCustomComponent
bool MyCustomComponent_GetCustomStruct(uint64_t entityID, MyCustomData* outData);
#pragma endregion
...
}
然后在 ScriptGlue.cpp
文件中添加实现此函数的相关代码:
// ScriptGlue.cpp
namespace InternalCalls {
...
#pragma region MyCustomComponent
bool MyCustomComponent_GetCustomStruct(uint64_t entityID, MyCustomData* outData)
{
// 注:伪代码,查看现有的内部调用以供参考
if (!is_entity_valid)
{
// 在此处记录一些错误日志信息
return false;
}
if (!entity_has_MyCustomComponent)
{
// 几乎不会发生,但如果发生则记录错误信息
return false;
}
*outData = myCustomComponent.MyData;
// 或是:
*outData.MyFloat = someFloatValue;
*outData.SomeInt = someIntValue;
return true;
}
#pragma endregion
...
}
这只是介绍了添加内部调用的基础知识,但它应该能让您了解我们该如何向 C++ 或是 C# API 中添加内部调用。