创建c++插件
您可以加载自定义库模块(一个*.dll或*.so文件),并通过Plugin类接口在Unigine运行时访问其服务和库函数。 使用插件系统可以选择加载哪些库,而无需重新编译Unigine可执行文件。
要创建插件库,您需要执行下面给出的说明。
实现一个插件接口#
实现插件接口的步骤如下:
- 通过C++ Visual Studio或C++ GNU Make(根据操作系统)创建一个插件项目。
- 对于 Visual Studio, 请确保在项目属性中指定所有必需的依赖项,包括 UNIGINE SDK 的 include 和 lib 目录的路径。
- 在 GNU Make, 使用 Makefile 中的变量和标志定义 UNIGINE SDK 的包含目录和库目录。 要指定包含目录,请使用 -I 标志,对于库,请使用 -L 标志。
-
包括所有必需的头文件,并指定要使用的Unigine名称空间。
注意源代码 (C++)#include <UniginePlugin.h> using namespace Unigine;
- 实现动态链接库的方法和类,这些方法和类可以从Unigine应用程序的脚本或c++端访问。
源代码 (C++)
my_plugin_function() {...} class MyExternClass { public: MyExternClass() { } ~MyExternClass() { } void my_member_function() { ... } };
-
创建plugin类——插件接口的实现,通过Unigine可执行文件提供对动态链接库的访问。该类必须包含以下内容:构造函数、析构函数、初始化函数(init())、关闭函数(shutdown())、销毁函数(destroy())。
Don't forget to declare two functions:
- get_name()返回插件的名称。
- get_order()返回插件的执行顺序。
注意每个插件都有它的执行顺序,它决定了插件功能的顺序(update() / postUpdate() / render() / shutdown())将被执行。 唯一的例外是 init() 函数,因为它是在加载插件后立即调用的。 请记住,在编写需要与其他插件交互的插件时,需要指定正确的顺序值以避免出现问题并确保正确的执行顺序。 如果在您的情况下顺序无关紧要,请设置默认的 0 值。如果有必要,还可以实现一个update(), render(), swap()和Plugin类中可用的其他函数。这些方法将在引擎执行序列的相应阶段自动调用。
源代码 (C++)// 1. Declare the class class MyPlugin : public Plugin { public: // constructor MyPlugin() {...} // destructor virtual ~MyPlugin() {...} // plugin data virtual void *get_data() { return this; } // plugin name virtual const char * get_name() override { return "MyPluginName"; } // plugin execution order virtual int get_order() override { return 10; } // initialize the plugin virtual int init(); // shutdown the plugin virtual int shutdown(); // destroy the plugin virtual void destroy(); }; // 2. Define the plugin's initialization function. // The engine will automatically call it on its start-up int MyPlugin::init() { ... return 1; } // 3. Define the plugin shut down function. // The engine will automatically call it on its shut down int MyPlugin::shutdown() { ... return 1; } // 4. Define the function which is called on the video mode restart void MyPlugin::destroy() { ... return 1; }
导出插件:公开创建的插件接口实现。引擎将搜索这些功能连接和断开插件库。
源代码 (C++)// 1. Define the function required for adding the plugin library. // It returns a new instance of the custom plugin extern "C" UNIGINE_EXPORT void *CreatePlugin() { return new MyPlugin(); } // 2. Define the function required for releasing the plugin library. // For that, a void pointer should be casted to the custom plugin type and then deleted extern "C" UNIGINE_EXPORT void ReleasePlugin(void *plugin) { delete static_cast<MyPlugin*>(plugin); }
CreatePlugin()和ReleasePlugin()函数被声明为extern "C",以便编译为普通的C函数。这需要防止函数名的混淆,从而确保命名装饰方案在Unigine可执行文件和创建的动态链接库之间匹配。否则,由两个不同的c++编译器生成的二进制代码可能不兼容,因为没有公认的c++应用程序二进制接口。
下面的实现是没有必要的,但是他们允许扩展插件接口:
如果你打算在应用程序的脚本端使用插件的类和函数,你应该在插件的init()和shutdown()函数中相应地实现导出和删除这些类和函数。
注意应包括UngineInterface.h出口定制类和函数的脚本。例如:
源代码 (C++)// 1. Implement exporting of the custom functions and classes into the script // on plugin initialization int MyPlugin::init() { ... // create a pointer to the function and register its name to be used from the script Interpreter::addExternFunction("my_plugin_function",MakeExternFunction(&my_plugin_function)); // export the custom class into the script: // create a pointer to the object of the external class ExternClass<MyExternClass> *my_class = MakeExternClass<MyExternClass>(); // and register the class constructor. // if it was not declared explicitly, a default one will be registered my_class->addConstructor(); // register the class member function my_class->addFunction("my_member_function",&MyExternClass::my_member_function); // register the class Interpreter::addExternClass("MyExternClass",my_class); return 1; } // 2. Implement removing of the custom functions and classes on plugin shut down int MyPlugin::shutdown() { ... // remove the function Interpreter::removeExternFunction("my_plugin_function"); // remove the external class Interpreter::removeExternClass("MyExternClass"); return 1; }
如果你要干预引擎初始化,主循环或关闭,实现类继承自World/System/EditorLogic类。你可以实现自己的逻辑(即回调函数)在这些类,然后将其添加到引擎执行init(), update(), shutdown(),等通过插件接口。
注意需要包含UnigineLogic.h来访问World/System/EditorLogic类的方法。例如:
源代码 (C++)// 1. Declare the class which has the same logic as the system script: // all the implemented MySystemLogic class functions will be called by the engine // after corresponding system script's methods class MySystemLogic : public SystemLogic { public: virtual int init(); virtual int shutdown(); }; /* */ // 2. Implement the init() function that will be called // after system script initialization int MySystemLogic::init() { Log::message("MySystemLogic::init(): called\n"); return 1; } // 3. Implement the shutdown() function that will be called // after the system script shut down int MySystemLogic::shutdown() { Log::message("MySystemLogic::shutdown(): called\n"); return 1; } // 4. Declare the class which has the same logic as the world script: // all the implemented MyWorldLogic class functions will be called by the engine // after corresponding world script's methods class MyWorldLogic : public WorldLogic { public: virtual int init(); virtual int shutdown(); }; // 5. Implement the init() function that will be called // after the world script initialization int MyWorldLogic::init() { Log::message("MyWorldLogic::init(): called\n"); return 1; } // 6. Implement the shutdown() function that will be called // after the world script shut down int MyWorldLogic::shutdown() { Log::message("MyWorldLogic::shutdown(): called\n"); return 1; } // 7. Declare the class which has the same logic as the editor script: // all the implemented MyEditorLogic class functions will be called by the engine // after corresponding editor script's methods class MyEditorLogic : public EditorLogic { public: virtual int init(); virtual int shutdown(); }; /* */ // 8. Implement the init() function that will be called // after the editor script initialization int MyEditorLogic::init() { Log::message("MyEditorLogic::init(): called\n"); return 1; } // 9. Implement the shutdown() function that will be called // after the editor script shut down int MyEditorLogic::shutdown() { Log::message("MyEditorLogic::shutdown(): called\n"); return 1; } // 10. Declare instances of the classes inherited from System/World/EditorLogic classes in the plugin class class MyPlugin : public Plugin { public: MyPlugin() {...} virtual ~MyPlugin() {...} virtual void *get_data() { return this; } // plugin name virtual const char * get_name() override { return "MyPluginName"; } // plugin execution order virtual int get_order() override { return 0; } virtual int init(); virtual int shutdown(); virtual void destroy(); private: MySystemLogic system_logic; MyWorldLogic world_logic; MyEditorLogic editor_logic; }; // 11. Add C++ callbacks that are called on the engine initialization int MyPlugin::init() { ... Engine *engine = Engine::get(); // init() functions of the MySystem/MyWorld/MyEditorLogic classes will be called // after init() functions of the corresponding scripts engine->addSystemLogic(&system_logic); engine->addWorldLogic(&world_logic); engine->addEditorLogic(&editor_logic); return 1; } // 12. Remove C++ callbacks that are called on the engine shut down int MyPlugin::shutdown() { ... Engine *engine = Engine::get(); // shutdown() functions of the MySystem/MyWorld/MyEditorLogic classes will be called // after shutdown() functions of the corresponding scripts engine->removeSystemLogic(&system_logic); engine->removeWorldLogic(&world_logic); engine->removeEditorLogic(&editor_logic); return 1; }
编译库#
编译库,您可以使用创建c++应用程序文章中描述的方法之一。注意应满足以下要求:
- 该库应该被编译为.dll或.so(根据所使用的操作系统)。
- 遵循UNIGINE命名约定,文件存放在相应文件夹。
- 要与Unigine调试构建一起使用,应该编译库的调试版本(libname_x64d或libname_double_x64d对于双坐标精度):指定debug=1命令行选项。
- 在双精度的支持下,编译这个库指定double=1命令行选项。
命名约定#
你编译的库应该遵循UNIGINE命名约定:
- <VendorName><PluginName>_plugin_<precision>_x64<debug_version>.* Windows二进制文件
- Linux二进制文件的lib<VendorName><PluginName>_plugin_<precision>_x64<debug_version>.so
- <VendorName><PluginName>.h头
- 编辑器插件需要_editorplugin后缀而不是1_ight
澄清:
<precision>意味着单引号或双坐标精度:单精度情况下什么都不用写,双精度情况下用double。
例子:MyPluginName_plugin_double_x64.dll -双精度文件,MyPluginName_plugin_x64.dll -单精度文件。
<debug_version>标识您的库是调试或发布版本。如果是debug,则添加d;如果是发布,什么都不用写。
示例:MyPluginName_plugin_double_x64d.dll -调试版本,MyPluginName_plugin_double_x64.dll -发布版本。
一个例子的名称编辑插件:MyEditorPluginName_editorplugin_double_x64d.dll。
插件文件路径#
UNIGINE插件位于相应的文件夹:
- 二进制文件: bin\plugins\Unigine\<PluginName>(例如bin\plugins\Unigine\ARTTracker\UnigineARTTracker_plugin_double_x64d.dll)
- 头文件: include\plugins\Unigine\<PluginName>\Unigine<PluginName>.h (e.g. include\plugins\Unigine\ARTTracker\UnigineARTTracker.h)
用户插件同样应该位于相应的文件夹中:
- 二进制文件: bin\plugins\<VendorName>\<PluginName>(例如bin\plugins\Vendor\Plugin\VendorPlugin_plugin_double_x64.dll)
- 头文件: include\plugins\<VendorName>\<PluginName>\<VendorName><PluginName>.h (e.g. include\plugins\Vendor\Plugin\VendorPlugin.h)
从Unigine调用库函数的应用程序#
您可以访问的类和函数导出创建动态链接库的脚本和c++的水平。
从脚本中调用库函数#
如果库中的类和函数正确地导出,则可以在脚本端直接访问它们。例如:
int init() {
log.message("\n");
// call the function exported from the library
my_print("Hello world");
log.message("\n");
// create new instances of the class exported from the library
MyExternClass my_class = new MyExternClass();
// call the exported member functions of the class
my_class.print("Hello world");
delete my_class;
return 1;
}
从c++端调用库函数#
要从应用程序的c++端访问库的类和函数,您应该执行以下操作:
一个插件的头文件包含在您的c++代码:
源代码 (C++)#include "plugin.h"
注意如果您只在.cpp文件中实现了插件,请包含它,而不是创建一个单独的头文件。获得插件实例项目的世界逻辑:
源代码 (C++)class AppWorldLogic : public WorldLogic { public: AppWorldLogic() {} virtual ~AppWorldLogic() {} virtual int init() { // 1. Assume that the plugin has been loaded EnginePtr engine; // specify a path to the plugin relative to the binary executable int id = engine->findPlugin("plugin/plugin"); if (id == -1) { Log::message("Cannot find the plugin\n"); return 0; } // 2. Get the plugin interface Plugin *plugin = Engine::get()->getPluginInterface(id); if (!plugin) { Log::message("The plugin is null\n"); return 0; } // 3. Cast the plugin interface to your plugin class MyPlugin *plugin = static_cast<MyPlugin*>(plugin); if (!plugin) { Log::message("Cannot cast the plugin\n"); return 0; } // call the plugin function my_plugin_function(); // create a new instance of the class declared in the plugin MyExternClass *extern_class = new MyExternClass(); // and call its member functions extern_class->my_member_function(); return 1; } }; // start the main loop with the world logic int main(int argc, char **argv) { EnginePtr engine(argc, argv); AppWorldLogic world_logic; engine->main(NULL, &world_logic, NULL); return 0; }
- 编译并运行c++应用程序。
加载插件库#
插件库可以在Unigine运行时的任何时刻加载,使用以下方式之一:
- 通过插件作为 extern_plugin命令行参数。一旦通过,就写进配置文件。
- 在配置文件中直接指定插件(data/configs/default.boot, extern_plugin 字符串)。
- 通过 engine.addPlugin() 在世界脚本中添加和使用插件。 在项目的世界逻辑中,同样可以通过c++端的Engine::addPlugin() 或c#端的 Engine.addPlugin() 来完成。
-
在系统脚本 (unigine.cpp)中添加并使用插件:
- 通过 engine.addPlugin() 添加插件,并在世界脚本 中使用它。 您不能在系统脚本中初始化插件并同时从中调用插件函数。
- 通过命令行参数 extern_plugin初始化插件后,在系统脚本中使用该插件。
在项目的系统逻辑中,同样可以通过c++端的Engine::addPlugin() 或c#端的 Engine.addPlugin() 来完成。
插件库应该在引擎启动时加载。下面的例子展示了如何通过-extern_plugin命令动态链接库:
- 对应的链接库的版本将使用Unigine可执行文件的名称。
-
库的名称应该指定为不带任何前缀或后缀的。例如,要在应用程序启动时加载VendorNamePluginName_plugin_x64d.dll库,应该运行Unigine项目的二进制可执行文件如下:
命令行main.exe -extern_plugin VendorNamePluginName
main.exe二进制可执行文件中创建应用程序构建。
注意如果库的路径是相对的,它应该相对于二进制可执行文件。它也可以是绝对的。
本页面上的信息适用于 UNIGINE 2.19.1 SDK.