/* PIP - Platform Independent Primitives Plugin helpers Ivan Pelipenko peri4ko@yandex.ru This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ #ifndef MICRO_PIP #include "piplugin.h" #include "pifile.h" #include "pidir.h" #include "piincludes_p.h" //! \class PIPluginLoader piplugin.h //! \~\details //! \~english \section PIPluginLoader_sec0 Synopsis //! \~russian \section PIPluginLoader_sec0 Краткий обзор //! //! \~english //! This file provides several macro to define plugin and %PIPluginLoader - class //! to load and check plugin. //! //! \~russian //! Этот файл предоставляет несколько макросов для объявления плагина и //! %PIPluginLoader - класс для его загрузки и проверки. //! //! //! \~english \section PIPluginLoader_sec1 Plugin side //! \~russian \section PIPluginLoader_sec1 Сторона плагина //! \~english //! Plugin is a shared library that can be loaded in run-time. //! This is only \a PIP_PLUGIN macro necessary to define plugin. //! If you want to set and check some version, use macro //! \a PIP_PLUGIN_SET_USER_VERSION(version). Also you can //! define a function to merge static sections between application //! and plugin with macro \a PIP_PLUGIN_STATIC_SECTION_MERGE. Before //! merge, you should set pointers to that sections with macro //! \a PIP_PLUGIN_ADD_STATIC_SECTION(type, ptr). //! //! \~russian //! Плагин - это разделяемая библиотека, которая может быть //! загружена динамически. Необходимым является только макрос \a PIP_PLUGIN. //! Можно установить версию для дополнительной проверки с помощью //! макроса \a PIP_PLUGIN_SET_USER_VERSION(version). Также //! можно объявить метод для слияния статических секций между //! приложением и плагином с помощью макроса \a PIP_PLUGIN_STATIC_SECTION_MERGE. //! Перед слиянием необходимо установить указатели для секций с //! помощью макроса \a PIP_PLUGIN_ADD_STATIC_SECTION(type, ptr). //! //! //! \~english \section PIPluginLoader_sec2 Application side //! \~russian \section PIPluginLoader_sec2 Сторона приложения //! \~english //! Application should use class \a PIPluginLoader to load //! plugin. Main function is \a load(PIString name). //! "name" is base name of library, %PIPluginLoader //! try to use sevaral names, \, lib\ and //! "dll", "so" and "dylib" extensions, depends on system.\n //! For example: //! //! \~russian //! Приложение должно использовать класс \a PIPluginLoader //! для загрузки плагина. Основной метод - это \a load(PIString name). //! "name" является базовым именем для поиска библиотеки. //! %PIPluginLoader попробует несколько имен, \, lib\ и //! расширения "dll", "so" и "dylib", в зависимости от системы.\n //! Например: //! //! \~\code //! PIPluginLoader l; //! l.load("foo"); //! \endcode //! //! \~english //! On Windows, try to open "foo", "libfoo", "foo.dll" and //! "libfoo.dll".\n //! If you using user version check, you should set it //! with macro \a PIP_PLUGIN_SET_USER_VERSION(version). //! When plugin is successfully loaded and checked, //! you can load your custom symbols with function //! \a resolve(name), similar to PILibrary. //! \note You should use \a PIP_PLUGIN_EXPORT and [C-linkage](https://en.cppreference.com/w/cpp/language/language_linkage) //! with functions you want to use with \a resolve(name)! //! //! \~russian //! На Windows будут опробованы "foo", "libfoo", "foo.dll" и //! "libfoo.dll".\n //! Если нужна проверка пользовательской версии, установите //! ей с помощью макроса \a PIP_PLUGIN_SET_USER_VERSION(version). //! Когда плагин будет успешно загружен и проверен, можно будет //! получать из него свои методы с помощью \a resolve(name), //! аналогично PILibrary. //! \note Необходимо использовать \a PIP_PLUGIN_EXPORT и [C-linkage](https://en.cppreference.com/w/cpp/language/language_linkage) //! для методов, получаемых через \a resolve(name)! //! //! //! \~english \section PIPluginLoader_sec3 Static sections //! \~russian \section PIPluginLoader_sec3 Статические секции //! \~english //! Macro \a PIP_PLUGIN_STATIC_SECTION_MERGE defines function //! with arguments (int type, void * from, void * to), so you //! can leave this macro as declaration or define its body next: //! //! \~russian //! Макрос \a PIP_PLUGIN_STATIC_SECTION_MERGE объявляет метод //! с аргументами (int type, void * from, void * to), поэтому //! можно оставить его как объявление, или реализовать тут же: //! //! \~\code //! PIP_PLUGIN_STATIC_SECTION_MERGE() { //! switch (type) { //! ... //! } //! } //! \endcode //! //! \~english //! \note If you using singletones, remember that cpp-defined //! singletones in shared libraries are single for whole application, //! including plugins! But if you use h-defined singletones or //! static linking, there are many objects in application and you //! should merge their content with this macro. //! //! Anyway, if this is macro \a PIP_PLUGIN_STATIC_SECTION_MERGE, it //! called once while loading plugin with "from" - plugin side //! and "to" - application side, and second (optionally) on method //! \a mergeStatic() with "from" - application side and "to" - plugin side.\n //! First direction allow you to copy all defined static content from plugin //! to application, and second - after loading all plugins (for example) //! to copy static content from application (and all other plugins) to plugin. //! //! \~russian //! \note При использовании синглтонов надо помнить, что реализованные //! в cpp синглтоны в разделяемых библиотеках едины для всего приложения, //! включая плагины! Однако реализованные в h-файлах синглтоны, либо //! из статических библиотек, не являются реальными одиночками, //! и для каждого плагина и приложения будет свой объект. В этом случае //! можно провести слияние статических секций. //! //! В любом случае, если используется макрос \a PIP_PLUGIN_STATIC_SECTION_MERGE, //! то он вызывается первый раз во время загрузки плагина с "from" - областью плагина, //! "to" - областью приложения, и второй раз (необязательно) при вызове //! \a mergeStatic() с "from" - областью приложения и "to" - областью плагина.\n //! Первый вызов позволяет скопировать статические секции из плагина в приложение, //! второй - из приложения (и всех остальных плагинов) в плагин. //! //! \~english \section PIPluginLoader_sec4 Examples //! \~russian \section PIPluginLoader_sec4 Примеры //! \~english Simple plugin: //! \~russian Простой плагин: //! \~\code //! #include //! //! PIP_PLUGIN //! //! extern "C" { //! PIP_PLUGIN_EXPORT void myFunc() { //! piCout << "Hello plugin!"; //! } //! } //! \endcode //! //! \~english Application: //! \~russian Приложение: //! \~\code //! #include //! int main() { //! PIPluginLoader pl; //! pl.load("your_lib"); //! if (pl.isLoaded()) { //! typedef void(*MyFunc)(); //! MyFunc f = (MyFunc)pl.resolve("myFunc"); //! if (f) f(); //! } //! return 0; //! } //! \endcode //! //! \~english Complex plugin: //! \~russian Сложный плагин: //! \~\code //! #include //! //! PIStringList global_list; //! //! PIP_PLUGIN //! PIP_PLUGIN_SET_USER_VERSION("1.0.0") //! PIP_PLUGIN_ADD_STATIC_SECTION(1, &global_list) //! //! STATIC_INITIALIZER_BEGIN //! global_list << "plugin_init"; //! STATIC_INITIALIZER_END //! //! PIP_PLUGIN_STATIC_SECTION_MERGE { //! PIStringList * sfrom = (PIStringList*)from, * sto = (PIStringList*)to; //! *sto << *sfrom; //! sto->removeDuplicates(); //! } //! \endcode //! //! \~english Application: //! \~russian Приложение: //! \~\code //! #include //! //! PIStringList global_list; //! //! PIP_PLUGIN_SET_USER_VERSION("1.0.0"); //! PIP_PLUGIN_ADD_STATIC_SECTION(1, &global_list); //! //! int main() { //! global_list << "app"; //! PIPluginLoader pl; //! pl.load("your_lib"); //! pl.mergeStatic(); //! piCout << "list =" << global_list; //! return 0; //! } //! \endcode //! #define STR_WF(s) #s #define STR(s) STR_WF(s) PIPluginInfo::PIPluginInfo() { } void PIPluginInfo::setUserVersion(const PIString & v) { user_version = v; } void PIPluginInfo::setStaticSection(int type, void * ptr) { static_sections[type] = ptr; } PIString PIPluginInfo::userVersion() const { return user_version; } PIMap PIPluginInfo::staticSections() const { return static_sections; } PIPluginInfoStorage::PIPluginInfoStorage() { enterPlugin(0); } PIPluginInfo * PIPluginInfoStorage::currentInfo() { return info[current]; } PIPluginInfo * PIPluginInfoStorage::pluginInfo(void * p) { return info.value(p, nullptr); } PIPluginInfo * PIPluginInfoStorage::applicationInfo() { return info[0]; } PIPluginInfo * PIPluginInfoStorage::enterPlugin(void * p) { current = p; PIPluginInfo *& i(info[p]); if (!i) i = new PIPluginInfo(); return i; } void PIPluginInfoStorage::unloadPlugin(void * p) { if (current == p) current = 0; PIPluginInfo *& i(info[p]); if (i) delete i; info.remove(p); } PIPluginInfoStorage * PIPluginInfoStorage::instance() { static PIPluginInfoStorage ret; return &ret; } PIPluginLoader::PIPluginLoader(const PIString & name) { func_loader_version = nullptr; func_static_merge = nullptr; error = Unknown; loaded = false; messages = true; if (!name.isEmpty()) load(name); } PIPluginLoader::~PIPluginLoader() { unload(); } //! \~\details //! \~english //! Loader try prefix "lib" and suffix ".dll", //! ".so" or ".dylib", depends on platform. //! \~russian //! Загрузчик пробует приставку "lib" и окончания //! ".dll", ".so" или ".dylib", в зависимости от системы bool PIPluginLoader::load(const PIString & name) { unload(); PIPluginInfo * ai = PIPluginInfoStorage::instance()->applicationInfo(); PIPluginInfo * pi = PIPluginInfoStorage::instance()->enterPlugin(this); PIString fname = findLibrary(name); if (fname.isEmpty()) { error = NoSuchFile; error_str = "Load plugin \"" + lib.path() + "\" error: can`t find lib for \"" + name + "\""; if (messages) piCout << error_str; return false; } if (!lib.load(fname)) { unload(); error = LibraryLoadError; error_str = "Load plugin \"" + lib.path() + "\" error: can`t load lib: " + lib.lastError(); if (messages) piCout << error_str; return false; } //piCout << "loading" << lib.path() << "..."; func_loader_version = (FunctionLoaderVersion)lib.resolve(STR(__PIP_PLUGIN_LOADER_VERSION_FUNC__)); if (!func_loader_version) { unload(); error = MissingSymbols; error_str = "Load plugin \"" + lib.path() + "\" error: can`t find " + STR(__PIP_PLUGIN_LOADER_VERSION_FUNC__); if (messages) piCout << error_str; return false; } if (__PIP_PLUGIN_LOADER_VERSION__ != func_loader_version()) { unload(); error = InvalidLoaderVersion; error_str = "Load plugin \"" + lib.path() + "\" error: invalid loader version: application = " + PIString::fromNumber(func_loader_version()) + ", plugin = " + PIString::fromNumber(__PIP_PLUGIN_LOADER_VERSION__); if (messages) piCout << error_str; return false; } if (ai->userVersion().size_s() > 1) { PIString pversion = pi->userVersion(), lversion = ai->userVersion(); if (pversion != lversion) { unload(); error = InvalidUserVersion; error_str = "Load plugin \"" + lib.path() + "\" error: invalid user version: application = " + lversion + ", plugin = " + pversion; if (messages) piCout << error_str; return false; } } func_static_merge = (FunctionStaticMerge)lib.resolve(STR(__PIP_PLUGIN_STATIC_MERGE_FUNC__)); if (func_static_merge) { auto pss = pi->staticSections(), lss = ai->staticSections(); //piCout << lss.keys() << pss.keys(); auto it = lss.makeIterator(); while (it.next()) { if (!pss.contains(it.key())) continue; void * from = pss.value(it.key()), * to = it.value(); if (from != to) func_static_merge(it.key(), from, to); } } loaded = true; error = NoError; return true; } void PIPluginLoader::unload() { lib.unload(); loaded = false; error_str.clear(); error = Unknown; PIPluginInfoStorage::instance()->unloadPlugin(this); } bool PIPluginLoader::isLoaded() const { return loaded; } PIPluginLoader::Error PIPluginLoader::lastError() const { return error; } PIString PIPluginLoader::lastErrorText() const { return error_str; } void PIPluginLoader::setMessages(bool yes) { messages = yes; } bool PIPluginLoader::isMessages() const { return messages; } PIString PIPluginLoader::libPath() { return lib.path(); } void * PIPluginLoader::resolve(const char * name) { if (!loaded) return nullptr; return lib.resolve(name); } //! \~\details //! \~english //! Call PIP_PLUGIN_STATIC_SECTION_MERGE function //! on every common type, with "from" - application scope, //! "to" - plugin scope //! \~russian //! void PIPluginLoader::mergeStatic() { if (!loaded || !func_static_merge) return; PIPluginInfo * ai = PIPluginInfoStorage::instance()->applicationInfo(); PIPluginInfo * pi = PIPluginInfoStorage::instance()->pluginInfo(this); auto pss = pi->staticSections(), lss = ai->staticSections(); auto it = lss.makeIterator(); while (it.next()) { if (!pss.contains(it.key())) continue; void * from = it.value(), * to = pss.value(it.key()); if (from != to) func_static_merge(it.key(), from, to); } } PIStringList PIPluginLoader::pluginsDirectories(const PIString & name) { static PIStringList dl({".", "./plugins", "../PlugIns"}); PIString ret; piForeachC (PIString d, dl) { PIString dp = d + "/" + name; if (PIDir::isExists(dp)) ret << dp; } return ret; } PIString PIPluginLoader::findLibrary(const PIString & path) { static const PIStringList prefixes({"", "lib"}); static const PIStringList suffixes({"", libExtension()}); PIFile::FileInfo fi(path); PIString dir = fi.dir(), name = fi.name(); piForeachC (PIString & p, prefixes) { piForeachC (PIString & s, suffixes) { PIString fn = dir + p + name + s; if (PIFile::isExists(fn)) return fn; } } return PIString(); } PIString PIPluginLoader::libExtension() { return #ifdef WINDOWS ".dll" #elif defined(MAC_OS) ".dylib" #else ".so" #endif ; } #endif // MICRO_PIP