Создание C++ плагина
You can load a custom library module (a *.dll, *.so or *.dylib file) and access its services and library functions in Unigine run-time via the Plugin class interface. Using a plugin system enables to choose which libraries to load on-the-fly without recompiling a Unigine executable.Вы можете загрузить специальный библиотечный модуль (файл *.dll, *.so или *.dylib) и получить доступ к его службам и библиотечным функциям во время выполнения Unigine через интерфейс класса Plugin. Использование системы плагинов позволяет выбирать, какие библиотеки загружать "на лету" без перекомпиляции исполняемого файла Unigine.
To create the plugin library, you need to perform the instructions given below.Чтобы создать библиотеку плагинов, вам необходимо выполнить инструкции, приведенные ниже.
Implementing a Plugin InterfaceРеализация интерфейса плагина#
To implement the plugin interface, perform the following steps:Чтобы реализовать интерфейс плагина, выполните следующие действия:
- Create a plugin project via C++ Visual Studio or C++ GNU Make (according to the operating system).Создайте проект плагина через C++ Visual Studio или C++ GNU Make (в зависимости от операционной системы).
- Include all required headers and specify a Unigine namespace to be used.
The UniginePlugin.h header must be included necessarily as it is required for accessing the Plugin class methods.Включите все необходимые заголовки и укажите пространство имен Unigine, которое будет использоваться.
#include <UniginePlugin.h> using namespace Unigine;
#include <UniginePlugin.h> using namespace Unigine;
- Implement methods and classes of the dynamic-link library that will be accessible from the script or C++ side of your Unigine application.
Реализуйте методы и классы библиотеки динамической компоновки, которые будут доступны со стороны сценария или C ++ вашего приложения Unigine.
my_plugin_function() {...} class MyExternClass { public: MyExternClass() { } ~MyExternClass() { } void my_member_function() { ... } };
my_plugin_function() {...} class MyExternClass { public: MyExternClass() { } ~MyExternClass() { } void my_member_function() { ... } };
-
Create a plugin class - an implementation of the plugin interface which provides access to the dynamic-link library by the Unigine executable. This class must contain the following: a constructor, a destructor, an initialization function (init()), a shut down function (shutdown()), a destroying function (destroy()).Создайте класс плагина - реализацию интерфейса плагина, которая обеспечивает доступ к динамически подключаемой библиотеке с помощью исполняемого файла Unigine. Этот класс должен содержать следующее: конструктор, деструктор, функцию инициализации (init()), функцию выключения (shutdown()), функцию удаления (destroy())
Don't forget to declare two functions:
- get_name() - returning the plugin's name.
- get_order() returning the plugin's execution order.
- get_name() - возвращает имя плагина.
- get_order() - возвращает порядок выполнения плагина.
Each plugin has its execution order, which determines the sequence in which plugin's functions (update() / postUpdate() / render() / shutdown()) will be executed. The only exception is the init() function as it is called just after loading the plugin. Remember, when writing a plugin, that requires interaction with other ones, specifying correct order value is required to avoid issues and ensure proper execution sequence. If in your case the order doesn't matter, set the default 0 value.У каждого плагина есть свой порядок выполнения, который определяет последовательность, в которой будут выполняться функции плагина (update() / postUpdate() / render() / shutdown()). Единственным исключением является функция init(), поскольку она вызывается сразу после загрузки плагина. Помните, что при написании плагина, который требует взаимодействия с другими, необходимо указать правильное значение порядка, чтобы избежать проблем и обеспечить правильную последовательность выполнения. Если в вашем случае порядок не имеет значения, установите значение по умолчанию 0.If necessary, you can also implement an update(), render(), swap() and other functions available in the Plugin class. These methods will be automatically called on the corresponding stage of the engine execution sequence.При необходимости вы также можете реализовать 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; }
Export the plugin: expose the created implementation of the plugin interface. The engine will search for these functions to add and release the plugin library.Экспорт плагина: покажите созданную реализацию интерфейса плагина. Движок будет искать эти функции для добавления и выпуска библиотеки плагинов.
// 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() and ReleasePlugin() functions are declared as extern "C" to be compiled as regular C functions. It is required to prevent mangling of the function names and thus ensure that the naming decoration scheme matches between the Unigine executable and the created dynamic-link library. Otherwise, binary code generated by two different C++ compilers may be incompatible, because there is no recognized C++ application binary interface.Функции CreatePlugin() и ReleasePlugin() объявлены как extern "C" для компиляции как обычные функции C. Это требуется для предотвращения искажения имен функций и, таким образом, обеспечения совпадения схемы оформления имен между исполняемым файлом Unigine и созданной библиотекой динамической компоновки. В противном случае двоичный код, сгенерированный двумя разными компиляторами C ++, может быть несовместим, поскольку не существует распознанного двоичного интерфейса приложения C ++.
The following implementations are not necessary, but they allow extending the plugin interface:Следующие реализации не нужны, но они позволяют расширить интерфейс плагина:
If you are going to use plugin's classes and functions on the script side of your application, you should implement exporting and removing of these classes and functions in the plugin's init() and shutdown() functions correspondingly. If you are going to use plugin's classes and functions on the script side of your application, you should implement exporting and removing of these classes and functions in the plugin's init() and shutdown() functions correspondingly.
It is required to include UngineInterface.h for exporting custom classes and functions to the script.Требуется включить UngineInterface.h для экспорта пользовательских классов и функций в скрипт.For example:Например:
// 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; }
If you are going to interfere the engine initialization, main loop or shutdown, implement classes inherited from the World/System/EditorLogic classes. You can implement your own logic (namely, callback functions) in these classes and then add it to the engine to be executed in its init(), update(), shutdown(), etc. via the plugin interface.Если вы собираетесь помешать инициализации движка, главному циклу или выключению, реализуйте классы, унаследованные отWorld/System/EditorLogicклассы. Вы можете реализовать свою собственную логику (а именно, функции обратного вызова) в этих классах, а затем добавить ее в движок для выполнения в его init(), update(), shutdown() и т.д. Через интерфейс плагина.
It is required to include UnigineLogic.h for accessing the World/System/EditorLogic classes' methods.Для доступа к методам классов World / System / EditorLogic необходимо включить UnigineLogic.h.For example:Например:
// 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; }
Compiling the LibraryКомпиляция библиотеки#
To compile your library, you can use one of the ways described in the Creating C++ Application article. Note that the following requirements should be met:Чтобы скомпилировать свою библиотеку, вы можете использовать один из способов, описанных в статье Создание приложения C ++ . Обратите внимание, что должны быть выполнены следующие требования:
- The library should be compiled as .dll, .so or .dylib (according to the operating system used).Библиотека должна быть скомпилирована как .dll, .so или .dylib (в зависимости от используемой операционной системы).
- For Linux the library should be named with the lib prefix (e.g. libplugin_x64.so or libplugin_double_x64.so for double builds).Для Linux библиотека должна быть названа с префиксом lib (например, libplugin_x64.so или libplugin_double_x64.so для сборок с двойной точностью).
- Сompile the library (libname_x64 or libname_double_x64 for double builds).Скомпилируйте библиотеку (libname_x64 или libname_double_x64 для сборок с двойной точностью).
- To use with a Unigine debug build, a debug version of the library should be compiled (libname_x64d or libname_double_x64d for double builds): specify the debug=1 command-line option. Для использования с отладочной сборкой Unigine необходимо скомпилировать отладочную версию библиотеки (libname_x64d или libname_double_x64d для сборок с двойной точностью): укажите параметр командной строки debug=1.
- To compile the library with double precision support, specify the double=1 command-line option. Чтобы скомпилировать библиотеку с поддержкой двойной точности, укажите параметр командной строки double=1.
Calling Library Functions from Unigine ApplicationВызов функций библиотеки из приложения Unigine#
You can access classes and functions exported from the created dynamic-link library from both the script and C++ level.Вы можете получить доступ к классам и функциям, экспортированным из созданной библиотеки динамической компоновки, как на уровне сценария, так и на уровне C++.
Calling Library Functions from Script SideВызов функций библиотеки со стороны скрипта#
Classes and functions of the library can be directly accessed on the script side if they were properly exported. For example:К классам и функциям библиотеки можно получить прямой доступ на стороне сценария, если они были правильно экспортированы . Например:
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;
}
Calling Library Functions from C++ SideВызов функций библиотеки со стороны C ++#
To access classes and functions of the library from the C++ side of the application, you should do the following:Чтобы получить доступ к классам и функциям библиотеки со стороны C ++ приложения, вы должны сделать следующее:
-
Include a plugin's header file in your C++ code:Включите файл заголовка плагина в свой код C ++:
#include "plugin.h"
If you implemented the plugin in the .cpp file only, include it instead of creating a separate header file.Если вы реализовали плагин только в файле .cpp, включите его вместо создания отдельного файла заголовка. -
Get the plugin instance in the project's world logic:Получите экземпляр плагина в мировой логике проекта:
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; }
- Compile and run the C++ application. Скомпилируйте и запустите приложение C ++.
Loading the Plugin LibraryЗагрузка библиотеки плагинов#
The plugin library can be loaded at any moment of Unigine runtime by using one of the following ways: Библиотеку плагина можно загрузить в любой момент выполнения Unigine одним из следующих способов:
- Pass the plugin as a command-line argument extern_plugin. Once passed, it is written into the configuration file. Передайте плагин как аргумент командной строки extern_plugin. После прохождения он записывается в файл конфигурации.
- Specify the plugin directly in the configuration file (data/configs/default.boot, extern_plugin string).Укажите плагин прямо в конфигурационном файле (строка data/configs/default.boot, extern_plugin).
- Add and use the plugin in the world script via engine.addPlugin(). The same can be done in the project's world logic via Engine::addPlugin() on the C++ side or via Engine.addPlugin() on the C# side of the project.Добавьте и используйте плагин в скрипте мира через engine.addPlugin(). То же самое можно сделать в мировой логике проекта через Engine::addPlugin() на стороне C ++ или через Engine.addPlugin() на стороне C # проекта.
-
Add and use the plugin in the system script (unigine.cpp):Добавьте и используйте плагин в системном скрипте (unigine.cpp):
- Add the plugin via engine.addPlugin() and use it in the world script. You cannot initialize the plugin in the system script and call plugin functions from it at the same time.Добавьте плагин через engine.addPlugin() и используйте его в мировом скрипте . Вы не можете инициализировать плагин в системном скрипте и одновременно вызывать из него функции плагина.
- Use the plugin in the system script after initializing it via the command-line argument extern_plugin. Используйте плагин в системном скрипте после его инициализации с помощью аргумента командной строки extern_plugin.
The same can be done in the project's system logic via Engine::addPlugin() on the C++ side or via Engine.addPlugin() on the C# side of your project.То же самое можно сделать в системной логике проекта через Engine::addPlugin() на стороне C ++ или через Engine.addPlugin() на стороне C# вашего проекта.
The plugin library should be loaded at the engine start-up. The following example shows how to dynamically link the library via the -extern_plugin command:Библиотека плагинов должна быть загружена при запуске двигателя. В следующем примере показано, как динамически связать библиотеку с помощью команды -extern_plugin:
- The library version that corresponds to the name of the Unigine executable will be used.Будет использована версия библиотеки, соответствующая имени исполняемого файла Unigine.
- Имя библиотеки следует указывать без префиксов и постфиксов. Например, чтобы загрузить библиотеку plugin_x64d.dll при запуске приложения, исполняемый двоичный файл проекта Unigine должен быть запущен следующим образом:
main.exe -extern_plugin data/plugin
The name of the library should be specified without any prefixes or postfixes. For example, to load the plugin_x64d.dll library on the application start-up, the binary executable of the Unigine project should be run as follows:Исполняемый двоичный файл main.exe был создан во время сборки приложения.
Если путь к библиотеке относительный, он должен быть относительно двоичного исполняемого файла . Он также может быть абсолютным.