Создание плагина на C++
Вы можете загрузить пользовательский библиотечный модуль (файл *.dll или *.so) и получить доступ к его службам и библиотечным функциям во время выполнения Unigine через интерфейс класса Plugin. Использование системы плагинов позволяет выбирать, какие библиотеки загружать "на лету", без перекомпиляции исполняемого файла Unigine.
Чтобы создать библиотеку плагинов, вам необходимо выполнить инструкции, приведенные ниже.
Реализация интерфейса плагина#
Чтобы реализовать интерфейс плагина, выполните следующие действия:
- Создайте проект плагина с помощью C++ Visual Studio или C++ GNU Make (в зависимости от операционной системы).
- В Visual Studio, укажите все необходимые зависимости в настройках проекта, в том числе пути до таких директорий UNIGINE SDK, как include и lib.
- В GNU Make, укажите пути до директорий include и lib из UNIGINE SDK с помощью флагов в вашем Makefile. Используйте флаг -I для include директории и флаг -L для библиотек.
-
Включите все необходимые заголовки и укажите используемое пространство имен Unigine.
ПримечаниеЗаголовок UniginePlugin.h должен быть включен обязательно, поскольку он требуется для доступа к методам класса Plugin.Исходный код (C++)#include <UniginePlugin.h> using namespace Unigine;
- Реализуйте методы и классы библиотеки динамических ссылок, которые будут доступны со стороны скрипта или C++ вашего приложения Unigine.
Исходный код (C++)
my_plugin_function() {...} class MyExternClass { public: MyExternClass() { } ~MyExternClass() { } void my_member_function() { ... } };
-
Создайте класс plugin - реализацию интерфейса плагина, который обеспечивает доступ к библиотеке динамических ссылок с помощью исполняемого файла Unigine. Этот класс должен содержать следующее: конструктор, деструктор, функцию инициализации (init()), функцию завершения работы (shutdown()), функцию уничтожения (destroy()).
Не забудьте объявить две функции:
- 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
- lib<VendorName><PluginName>_plugin_<precision>_x64<debug_version>.so для двоичных файлов Linux
- <VendorName><PluginName>.h для заголовков
- Плагины редактора требуют постфикса _editorplugin вместо _plugin
Пояснения:
<precision> подразумевает одинарную или двойную точность координат: в случае одинарной точности вы ничего не пишете, в случае двойной точности — слово double.
Примеры: MyPluginName_plugin_double_x64.dll — для файла двойной точности, MyPluginName_plugin_x64.dll — для файла одинарной точности.
<debug_version> указывает на то, что ваша библиотека является отладочной или релизной версией. Если debug, то добавляется d; если release — ничего не добавляется.
Примеры: 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 (например, 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 (например, 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).
- Добавьте и используйте плагин в world script через engine.addPlugin(). То же самое можно сделать в логике мира проекта через Engine::addPlugin() на стороне C++ или через Engine.addPlugin() на стороне C# проекта.
-
Добавьте и используйте плагин в системном скрипте (unigine.cpp):
- Добавьте плагин через engine.addPlugin() и используйте его в world script. Вы не можете инициализировать плагин в системном скрипте и вызывать из него функции плагина одновременно.
- Используйте плагин в системном скрипте после его инициализации с помощью аргумента командной строки extern_plugin.
То же самое можно сделать в системной логике проекта через Engine::addPlugin() на стороне C++ или через Engine.addPlugin() на стороне C# вашего проекта.
Библиотека плагинов должна быть загружена при запуске движка. В следующем примере показано, как динамически связать библиотеку с помощью команды -extern_plugin:
- Будет использоваться версия библиотеки, соответствующая названию исполняемого файла Unigine.
-
Название библиотеки должно быть указано без каких-либо префиксов или постфиксных исправлений. Например, чтобы загрузить библиотеку VendorNamePluginName_plugin_x64d.dll при запуске приложения, двоичный исполняемый файл проекта Unigine должен быть запущен следующим образом:
Shell-командыmain.exe -extern_plugin VendorNamePluginName
Двоичный исполняемый файл main.exe был создан во время сборки приложения.
ПримечаниеЕсли путь к библиотеке относительный, он должен быть относителен к двоичному исполняемому файлу . Оно также может быть абсолютным.
Информация, представленная на данной странице, актуальна для версии UNIGINE 2.19.1 SDK.