我花了几天阅读并重新阅读我在这个主题上找到的每一个教程,花了几个小时(甚至几天)在这里浏览相关问题,但我仍然无法让以下工作。如果这是重复的话,请接受我的道歉:我有可能多次看到并重读了重复的问题,但无法理解答案与我的问题的相关性。随之而来......
我正在尝试为我的应用程序实现插件架构。插件编译并作为库安装。在运行时,应用程序然后使用dlopen()/ dlsym()加载并链接到插件的函数。
想法是插件(库)将实现一组函数以将数据返回到主Application,或者操纵从Application传递的数据。
为了测试这个想法,我尝试实现一个函数(在插件内),它将返回插件本身的(人类可读)名称(作为std :: string)。我认为这将是一件简单的事情......: - /
这是我到目前为止所得到的:
// Plugin.cpp
extern "C" void plugin_name(std::string *name) {
name = new std::string("Example plugin name");
}
// Application.cpp
void* handle = dlopen("libplugin.so", RTLD_LAZY);
typedef void (*plugin_t)(std::string*);
dlerror(); // Reset errors.
plugin_t call_plugin_name = (plugin_t) dlsym(handle, "plugin_name");
// ... Some error handling code.
std::string my_plugin_name;
call_plugin_name(&my_plugin_name);
dlclose(handle);
// More code that displays my_plugin_name.
我尝试了很多不同的组合,包括一个看起来更直接(但效果不好)的组合,其中返回了插件名称:
// Plugin.cpp
extern "C" std::string plugin_name(void) {
return std::string("Example plugin name");
}
我知道我很接近:代码编译,应用程序停止崩溃;)
但是,我有一个空的空间,我希望看到实际的插件名称。
到目前为止,我所阅读的所有教程都非常快速地通过两种方式传递数据的机制:plugin< =>应用。我正在尝试使用“简单”的std :: string,我希望稍后使用更复杂的对象(即插件函数将通过引用获取对象并更改其某些属性)。教程或多或少全部停止在用dlsym()创建指针的时候,并没有给出关于如何使用这个指针的很多例子。
那么,怎么做呢?
另一个相关的问题:我是否使用了一个公共标题,我将它与应用程序和插件一起使用,我在哪里定义函数调用签名?我该怎么做?这会有什么帮助?
答案 0 :(得分:5)
函数的签名是根据其名称和参数类型生成的(返回值类型无关紧要)。当使用extern“C”声明函数时,使用C符号命名方案,它显然无法处理像std :: string这样的C ++类型。这就是为什么将std :: string作为参数传递不起作用。
我无法解释为什么返回std :: string不起作用。也许使用不同的调用约定。
无论如何,从共享库导入C ++代码的正确方法是从入口点返回指向C ++类型的指针。此入口点必须具有C中可用类型的参数。(入口点是从共享库导出的文档函数)
Here是一篇关于从共享库加载C ++类的基本方面的好文章。本文将全面回答您的问题。
请注意,将共享库中引发的异常用于主应用程序时存在缺陷。并使用在库中创建的对象的dynamic_cast。我已经提到过这个话题,以便在面对这些问题时你可以做一些准备。
<强> [编辑] 强>
为了使我的回答更清楚,我将添加几个例子。
要获取插件名称,您可以使用:
extern "C" const char * plugin_name() {
return "Example plugin name";
}
// main.cc:
void* handle = dlopen("libplugin.so", RTLD_LAZY);
// ...
typedef const char * (*plugin_t)();
plugin_t call_plugin_name = (plugin_t) dlsym(handle, "plugin_name");
// ...
std::string my_plugin_name(call_plugin_name());
// use it
要真正使用插件功能,您应该在标头中声明基类:
// plugin.h
class Plugin {
public:
virtual void doStuff() = 0;
virtual ~Plugin() = 0;
};
// plugin.cc
Plugin::~Plugin() {
}
// myplugin.cc
class MyPlugin : public Plugin {
virtual void doStuff() {
std::cout << "Hello from plugin" << std::endl;
}
};
extern "C" Plugin *createMyPluginInstance() {
return new MyPlugin;
}
答案 1 :(得分:2)
尝试:
extern "C" void plugin_name(std::string **name) {
*name = new std::string("Example plugin name");
}
...
std::string *my_plugin_name;
call_plugin_name(&my_plugin_name);
当您指定作为参数传递的指针的副本时,而不是您要分配的指针的副本。
编辑在这里你去: 文件 main.cpp
#include <iostream>
#include <dlfcn.h>
#include <string>
// Application.cpp
int main() {
void* handle = dlopen("libplugin.so", RTLD_LAZY);
typedef void (*plugin_t)(std::string**);
dlerror(); // Reset errors.
plugin_t call_plugin_name = (plugin_t) dlsym(handle, "plugin_name");
// ... Some error handling code.
std::string *my_plugin_name;
call_plugin_name(&my_plugin_name);
dlclose(handle);
// More code that displays my_plugin_name.
std::cout << "Plugin name is " << *my_plugin_name << std::endl;
delete my_plugin_name;
return 0;
}
文件 plugin.cpp
#include <string>
extern "C" void plugin_name(std::string **name) {
*name = new std::string("example plugin name");
}
只需一句警告。虽然这个编译和运行,但是在dll边界上传递C ++类型是有风险的,上面的代码只是你的代码已经足够编译和运行,它不安全并且具有非常明确的内存处理。您可能希望以不同的方式解决问题。
答案 2 :(得分:1)
请阅读this question及其答案。在C ++中,跨共享库边界存在许多不兼容的机会。