【问题标题】:Dynamic Loading Without extern "C"没有外部“C”的动态加载
【发布时间】:2014-07-28 02:46:14
【问题描述】:

一般来说,我想使用 libdl 来动态加载 C++。问题是在运行时识别名称已损坏的符号。

如此处所述,一种解决方案是通过使用 extern "C" 删除名称修改。

http://www.tldp.org/HOWTO/C++-dlopen/theproblem.html

此解决方案的缺点是将动态加载的资源限制为 C 样式接口。例如,动态加载的函数不能是重载函数。

有什么好的方法可以克服这个限制?

一种可能的解决方案是使用工具对库源代码进行命名,并附带一个函数,以便在需要链接库时获取已损坏的名称。 llvm 是否为此提供工具?

也许一个笨拙的解决方案是一个函数,它采用函数签名,使用具有签名的函数创建虚拟代码,管道进入与标志一起用于生成程序集的编译器,解析输出以检索损坏的名称, 并以字符串形式返回损坏的名称。然后可以将字符串传递给 dlsym()。

为了使问题具体化,这里有两个示例程序来说明外部“C”解决方案在不修改库代码的情况下无法动态加载的内容。第一个以传统 C++ 方式动态链接库。第二个使用 dlopen。在第一个程序中链接一个重载函数很简单。在第二个程序中链接重载函数没有简单的方法。

程序 1:加载时动态链接

main.cpp

// forward declarations of functions that will be linked
void say(int);
void say(float);

int main() {
    int myint = 3;
    say(myint);
    float myfloat = 5.0f;
    say(myfloat);
}

说.cpp

#include <iostream>

//extern "C" function signatures would collide

//extern "C" void say(int a) {
void say(int a) {
    std::cout << "The int value is " << a << ".\n";
}

//extern "C" void say(float a) {
void say(float r) {
    std::cout << "The float value is " << r << ".\n";
}

输出

$ ./main
The int value is 3.
The float value is 5.

程序 2:运行时动态链接

main_with_dl.cpp

#include <iostream>
#include <dlfcn.h>

int main() {
    // open library
    void* handle = dlopen("./say_externC.so", RTLD_LAZY);
    if (!handle) {
        std::cerr << "dlopen error: " << dlerror() << '\n';
        return 1;
    }

    // load symbol
    typedef void (*say_t)(int);

    // clear errors, find symbol, check errors
    dlerror();
    say_t say = (say_t) dlsym(handle, "say");
    const char *dlsym_error = dlerror();
    if (dlsym_error) {
        std::cerr << "dlsym error: " << dlsym_error << '\n';
        dlclose(handle);
        return 1;
    }

    // use function
    int myint = 3;
    say(myint);
    // can't load in void say(float)
    // float myfloat = 5.0f;
    // say(myfloat);

    // close library
    dlclose(handle);
}

输出

$ ./main_with_dl
The int value is 3.

编译

生成文件

CXX = g++

all: main main_with_dl say_externC.so

main: main.cpp say.so
    $(CXX) -o $@ $^

main_with_dl: main_with_dl.cpp
    $(CXX) -o $@ $<

%.so : %.cpp
    $(CXX) -shared -o $@ $<

.PHONY: clean
clean:
    rm main main_with_dl say.so say_externC.so

【问题讨论】:

  • 我正在研究如何将修饰名称传递给dlsym,但我突然想到,如果您尝试调用重载函数并且需要进行转换 i> 那么这还不足以削减它。由于在 C++ 中(还)无法确定转换后将调用的重载函数的签名,因此我知道的唯一选择是制作包装器来解决可以委托给 Program1 的 Program2 中的重载。除非...typeid?
  • 失去自动转换并不是悲剧,重要的是可以在 client 代码中解决。
  • 但这意味着您必须在客户端代码中有一个具有完全相同签名的包装器,这意味着以后不能将重载添加到库中。这对你来说是个问题吗?
  • @Praxeolitic:Visual Studio 在生成动态库的同时,也会生成链接库。您将客户端代码与链接库链接,链接库知道签名和错误名称等,并自动解决所有这些问题。在我对这个问题进行研究之前,我什至没有意识到这些东西很复杂,它只适用于 Windows。实际上,刚刚意识到,这些都不适用于您不知道其名称的 dll,例如插件。没关系。
  • @Praxeolitic - 遗憾的是,我认为这甚至都不起作用。如果您正在处理不同的编译器,您甚至无法保证它们构建 vtable 的顺序,因此即使是纯虚拟库也不好。

标签: c++ dynamic-linking dlopen dynamic-loading name-mangling


【解决方案1】:

感谢 Mooing Duck,我能够在 Visual Studio 的启发下使用 clang 提出一个解决方案。

key是Visual Studio和clang提供的一个宏。 __FUNCDNAME__ 宏解析为封闭函数的错位名称。通过定义与我们想要动态链接的函数具有相同签名的函数,我们可以让 __FUNCDNAME__ 解析为所需的名称 mangle。

这是程序 2 的新版本,它可以同时调用 void say(int) 和 void say(float)。

编辑 Mooing Duck 向我透露了更多知识。这是与问题中的 say.cpp 一起使用的 main_with_dl.cpp 版本。

http://coliru.stacked-crooked.com/a/7249cc6c82ceab00

必须使用带有 -fms-extensions 标志的 clang++ 编译代码才能使 __FUNCDNAME__ 工作。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-09-17
  • 1970-01-01
  • 1970-01-01
  • 2020-06-22
  • 2021-05-21
  • 2011-01-07
  • 2016-06-20
相关资源
最近更新 更多