没有extern“ C”的动态加载

Pra*_*tic 5 c++ name-mangling dynamic-linking dynamic-loading dlopen

我想通常使用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);
}
Run Code Online (Sandbox Code Playgroud)

#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";
}
Run Code Online (Sandbox Code Playgroud)

输出

$ ./main
The int value is 3.
The float value is 5.
Run Code Online (Sandbox Code Playgroud)

程序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);
}
Run Code Online (Sandbox Code Playgroud)

输出

$ ./main_with_dl
The int value is 3.
Run Code Online (Sandbox Code Playgroud)

编译中

生成文件

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
Run Code Online (Sandbox Code Playgroud)

Pra*_*tic 4

感谢 Mooing Duck,我能够使用 clang 并受到 Visual Studio 的启发想出一个解决方案。

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

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

编辑Mooing Duck 给我留下了更多的知识。这是在问题中main_with_dl.cpp适用的一个版本。say.cpp

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

void* handle;

template<class func_sig> func_sig get_func(const char* signature)
{
    dlerror();
    func_sig func = (func_sig) dlsym(handle, signature);
    const char *dlsym_error = dlerror();
    if (dlsym_error) {
        std::cerr << "dlsym error: " << dlsym_error << '\n';
        dlclose(handle);
        exit(1);
    }
    return func;
}

void say(int a) {
    typedef void(*func_sig)(int);
    static func_sig func = get_func<func_sig>(__FUNCDNAME__);
    return func(a);
}

void say(float a) {
    typedef void(*func_sig)(float);
    static func_sig func = get_func<func_sig>(__FUNCDNAME__);
    return func(a);
}

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

    // use function
    int myint = 3;
    say(myint);
    float myfloat = 5.0f;
    say(myfloat);

    // close library
    dlclose(handle);
}
Run Code Online (Sandbox Code Playgroud)

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

代码必须使用for标志clang++来编译才能工作。-fms-extensions__FUNCDNAME__