c++dynamic-linkingdlopendynamic-loadingname-mangling

Dynamic Loading Without extern "C"


I'd like to use libdl to dynamically load C++ in general. The problem is identifying symbols at runtime that have been name mangled.

As described here, one solution is to remove name mangling by using extern "C".

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

This solution has the drawback of limiting dynamically loaded resources to C style interfaces. Dynamically loaded functions cannot, for instance, be overloaded functions.

What is a good way to overcome this limitation?

One possible solution would be tools to name mangle the library source code with an accompanying function to get the mangled names when the library needs to be linked. Does llvm provide tools for this?

Maybe a clumsy solution would be a function that takes a function signature, creates dummy code with a function that has the signature, pipes into the compiler that was used with a flag for generating assembly, parses the output to retrieve the mangled name, and returns the mangled name as a string. The string could then be passed to dlsym().

To keep the problem concrete, here are two example programs that illustrate something the extern "C" solution can't dynamically load without modifying library code. The first dynamically links a library in traditional C++ fashion. The second uses dlopen. Linking an overloaded function in the first program is simple. There's no simple way to link the overloaded function in the second program.

Program 1: Loadtime Dynamic Linking

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);
}

say.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";
}

output

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

Program 2: Runtime Dynamic Linking

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);
}

output

$ ./main_with_dl
The int value is 3.

Compiling

Makefile

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

Solution

  • Thanks to Mooing Duck I was able to come up with a solution using clang and inspired by Visual Studio.

    The key is a macro provided by Visual Studio and clang. The __FUNCDNAME__ macro resolves to the mangled name of the enclosing function. By defining functions with the same signature as the ones we want to dynamically link, we can get __FUNCDNAME__ to resolve to the needed name mangle.

    Here's the new version of program 2 that can call both void say(int) and void say(float).

    EDIT Mooing Duck dropped more knowledge on me. Here's a version of main_with_dl.cpp that works with say.cpp in the question.

    #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);
    }
    

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

    The code must be compiled using clang++ with the -fms-extensions flag for __FUNCDNAME__ to work.