412 lines
10 KiB
C++
412 lines
10 KiB
C++
/*
|
|
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 PIP_FREERTOS
|
|
|
|
#include "piplugin.h"
|
|
#include "pifile.h"
|
|
#include "pidir.h"
|
|
#include "piincludes_p.h"
|
|
|
|
/*! \class PIPluginLoader
|
|
* \brief Plugin loader
|
|
*
|
|
* \section PIPluginLoader_sec0 Synopsis
|
|
* This class provides several macro to define plugin and %PIPluginLoader - class
|
|
* to load and check plugin.
|
|
*
|
|
* \section PIPluginLoader_sec1 Plugin side
|
|
* Plugin is a shared library that can be loaded in run-time.
|
|
* This is only 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).
|
|
*
|
|
* \section PIPluginLoader_sec2 Application side
|
|
* 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.
|
|
* For example:
|
|
* \code
|
|
* PIPluginLoader l;
|
|
* l.load("foo");
|
|
* \endcode
|
|
* On Windows, try to open "foo", "libfoo", "foo.dll" and
|
|
* "libfoo.dll".
|
|
* 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 PIP_PLUGIN_EXPORT and "export "C""
|
|
* with functions you want to use with \a resolve(name)!
|
|
*
|
|
* \section PIPluginLoader_sec3 Static sections
|
|
* 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:
|
|
* \code
|
|
* PIP_PLUGIN_STATIC_SECTION_MERGE() {
|
|
* switch (type) {
|
|
* ...
|
|
* }
|
|
* }
|
|
* \endcode
|
|
* \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.
|
|
* 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 plugins) to plugin.
|
|
*
|
|
* \section PIPluginLoader_sec4 Examples
|
|
* Simple plugin:
|
|
* \code
|
|
* #include <piplugin.h>
|
|
*
|
|
* PIP_PLUGIN
|
|
*
|
|
* extern "C" {
|
|
* PIP_PLUGIN_EXPORT void myFunc() {
|
|
* piCout << "Hello plugin!";
|
|
* }
|
|
* }
|
|
* \endcode
|
|
*
|
|
* Application:
|
|
* \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
|
|
*
|
|
* Complex plugin:
|
|
* \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
|
|
*
|
|
* Application:
|
|
* \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();
|
|
}
|
|
|
|
|
|
bool PIPluginLoader::load(const PIString & name) {
|
|
unload();
|
|
PIPluginInfo * ai = PIPluginInfoStorage::instance()->applicationInfo();
|
|
PIPluginInfo * pi = PIPluginInfoStorage::instance()->enterPlugin(this);
|
|
if (!lib.load(findLibrary(name))) {
|
|
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);
|
|
}
|
|
|
|
|
|
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 // PIP_FREERTOS
|