创建C ++插件
您可以加载自定义库模块(*.dll,*.so或*.dylib文件),并通过Plugin类接口在Unigine运行时中访问其服务和库函数。 使用插件系统可以选择即时加载哪些库,而无需重新编译Unigine可执行文件。
要创建插件库,您需要执行以下说明。
实施插件接口#
要实现插件接口,请执行以下步骤:
- 通过C++ Visual Studio或C++ GNU Make创建插件项目(根据操作系统)。
- 包括所有必需的标头,并指定要使用的Unigine命名空间。
#include <UniginePlugin.h> using namespace Unigine;
- 实现可从Unigine应用程序的脚本或C ++端访问的动态链接库的方法和类。
my_plugin_function() {...} class MyExternClass { public: MyExternClass() { } ~MyExternClass() { } void my_member_function() { ... } };
-
创建一个插件类-插件接口的实现,该接口提供Unigine可执行文件对动态链接库的访问。此类必须包含以下内容:构造函数,析构函数,初始化函数(init()),关闭函数(shutdown()),销毁函数(destroy()),返回插件名称的函数(get_name())和执行顺序(get_order())。
每个插件都有其执行顺序,该顺序确定了插件功能(update() / postUpdate() / render() / shutdown())的执行顺序。唯一的例外是加载插件后立即调用的init()函数。请记住,编写插件时需要与其他插件交互,因此需要指定正确的订单值,以避免出现问题并确保正确的执行顺序。如果您的顺序无关紧要,请设置默认的0值。如有必要,您还可以实现update(), render(), swap()和Plugin类中可用的其他函数。这些方法将在引擎执行序列的相应阶段自动调用。
// 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; }
导出插件:公开创建的插件接口实现。引擎将搜索这些功能以添加和发布插件库。
// 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。例如:
// 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()执行的引擎中。
访问World / System / EditorLogic类的方法时,必须包含UnigineLogic.h。例如:
// 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或.dylib(根据所使用的操作系统)。
- 对于Linux,该库应以lib前缀(例如libplugin_x64.so或libplugin_double_x64.so用于双精度构建)命名。
- 编译库(libname_x64或libname_double_x64用于双精度构建)。
- 要与Unigine调试版本一起使用,应编译该库的调试版本(libname_x64d或libname_double_x64d用于双精度构建):指定debug=1命令行选项。
- 要以双精度支持方式编译库,请指定double=1命令行选项。
从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 ++代码中包含插件的头文件:
#include "plugin.h"
如果仅在.cpp文件中实现了插件,请包括该插件,而不要创建单独的头文件。在项目的世界逻辑中获取插件实例:
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(UNIGINE_VERSION, argc, argv); AppWorldLogic world_logic; engine->main(NULL, &world_logic, NULL); return 0; }
- 编译并运行C ++应用程序。
加载插件库#
- 将插件作为命令行参数 extern_plugin传递。一旦通过,它将被写入配置文件。
- 直接在配置文件中指定插件(data/configs/default.boot,extern_plugin字符串)。
- 通过engine.addPlugin()在世界脚本中添加并使用该插件。在项目的世界逻辑中,可以通过C ++一侧的Engine::addPlugin()或项目C#一侧的Engine.addPlugin()进行相同的操作。
- 在系统脚本(unigine.cpp)中添加并使用该插件:
- 通过engine.addPlugin()添加插件,并在世界脚本中使用它。您不能在系统脚本中初始化插件,也不能同时从中调用插件函数。
- 通过命令行参数 extern_plugin初始化插件后,请在系统脚本中使用该插件。
插件库应在引擎启动时加载。以下示例显示如何通过-extern_plugin命令动态链接库:
- 将使用与Unigine可执行文件名称相对应的库版本。
库的名称应指定为没有任何前缀或后缀。例如,要在应用程序启动时加载plugin_x64d.dll库,Unigine项目的二进制可执行文件应按以下方式运行:
main.exe -extern_plugin data/plugin
main.exe二进制可执行文件是在应用程序构建期间创建的。
如果库的路径是相对的,则它应相对于二进制可执行文件。它也可以是绝对的。