3

我花了几天时间阅读和重新阅读我在该主题上找到的每个教程,并花了几个小时(甚至几天)在 SO 上浏览相关问题,但我仍然无法让以下工作。如果这是重复的,请接受我的道歉:很可能我已经多次看到并重新阅读了重复的问题,但无法理解答案与我的问题的相关性。有了这个...

我正在尝试为我的应用程序实现插件架构。插件作为库编译和安装。在运行时,应用程序然后使用 dlopen() / dlsym() 加载并链接到插件的功能。
这个想法是插件(库)将实现一组函数以将数据返回到主应用程序,或操作从应用程序传递的数据。

为了测试这个想法,我尝试实现一个函数(在插件内部),它将返回插件本身的(人类可读的)名称(作为 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 <=> Application. 我正在尝试使用“简单”std::string 做什么,我希望稍后使用更复杂的对象(即插件函数将通过引用获取对象并更改其某些属性)。教程或多或少都停留在使用 dlsym() 创建指针的地方,并且没有提供太多关于如何使用该指针的示例。

那么,如何做到这一切呢?

另一个相关的问题:我是否使用了一个通用的标头,我将同时与应用程序和插件一起使用,以及在哪里定义函数调用签名?我将如何做到这一点,这将如何帮助?

4

3 回答 3

5

函数的签名由其名称和参数类型生成(返回值类型无关紧要)。当你用 extern "C" 声明函数时,使用了 C 符号命名方案,它显然不能处理像 std::string 这样的 C++ 类型。这就是为什么将 std::string 作为参数传递不起作用的原因。

我无法解释为什么返回 std::string 不起作用。也许使用了不同的调用约定。

无论如何,从共享库导入 C++ 代码的正确方法是从入口点返回指向 C++ 类型的指针。并且此入口点必须具有 C 中可用类型的参数。(入口点是从共享库导出的文档化函数)

是一篇关于从共享库加载 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;
}
于 2011-01-17T13:29:59.783 回答
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;
}

文件插件.cpp

#include <string>

extern "C" void plugin_name(std::string **name) {
    *name = new std::string("example plugin name");
}

只是一个警告。虽然这可以编译和运行,但通过 dll 边界传递 C++ 类型是有风险的,并且上面的代码只是您的代码固定到足以编译和运行,它不安全并且具有非常明确的内存处理。您可能想以不同的方式解决问题。

于 2011-01-17T13:02:57.177 回答
1

请阅读这个问题及其答案。在 C++ 中,跨共享库边界存在许多不兼容的机会。

于 2011-01-17T13:30:21.267 回答