Files
pip/libs/main/system/piplugin.cpp
2022-12-14 14:13:52 +03:00

497 lines
16 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
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 <http://www.gnu.org/licenses/>.
*/
#ifndef MICRO_PIP
# include "piplugin.h"
# include "pidir.h"
# include "pifile.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, \<name\>, lib\<name\> and
//! "dll", "so" and "dylib" extensions, depends on system.\n
//! For example:
//!
//! \~russian
//! Приложение должно использовать класс \a PIPluginLoader
//! для загрузки плагина. Основной метод - это \a load(PIString name).
//! "name" является базовым именем для поиска библиотеки.
//! %PIPluginLoader попробует несколько имен, \<name\>, lib\<name\> и
//! расширения "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 <piplugin.h>
//!
//! PIP_PLUGIN
//!
//! extern "C" {
//! PIP_PLUGIN_EXPORT void myFunc() {
//! piCout << "Hello plugin!";
//! }
//! }
//! \endcode
//!
//! \~english Application:
//! \~russian Приложение:
//! \~\code
//! #include <piplugin.h>
//! 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 <piplugin.h>
//!
//! 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 <piplugin.h>
//!
//! 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<int, void *> 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