9

How can I load an arbitrary dynamic-link library (dll) function into a std::function object using a single function?

For example I would like to compile two functions into a dll:

// test.dll

int plusFive(int value) {
    return value + 5;
}

void printHello() {
    std::cout << "Hello!" << std::endl;
}

And load them both at runtime using a single function like this:

// main.cc

#include <functional>

int main() {
    std::function<int(int)> func1(loadDllFunc("test.dll", "plusFive"));
    std::function<void()> func2(loadDllFunc("test.dll", "printHello"));
}
ildjarn
  • 62,044
  • 9
  • 127
  • 211
Felix Glas
  • 15,065
  • 7
  • 53
  • 82

1 Answers1

9

Use the WinAPI functions provided in windows.h (descriptions taken from MSDN Dev Center).

  • LoadLibrary - Loads the specified module into the address space of the calling process. Returns a handle to the module.
  • GetProcAddress - Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL). Returns the address of the exported function or variable.

Use this function to load a specific function and return a std::function object:

// main.cc

#include <iostream>
#include <string>
#include <functional>
#include <windows.h>

template <typename T>
std::function<T> loadDllFunc(const std::string& dllName, const std::string& funcName) {
    // Load DLL.
    HINSTANCE hGetProcIDDLL = LoadLibrary(dllName.c_str());

    // Check if DLL is loaded.
    if (hGetProcIDDLL == NULL) {
        std::cerr << "Could not load DLL \"" << dllName << "\"" << std::endl;
        exit(EXIT_FAILURE);
    }

    // Locate function in DLL.
    FARPROC lpfnGetProcessID = GetProcAddress(hGetProcIDDLL, funcName.c_str());

    // Check if function was located.
    if (!lpfnGetProcessID) {
        std::cerr << "Could not locate the function \"" << funcName << "\" in DLL\"" << dllName << "\"" << std::endl;
        exit(EXIT_FAILURE);
    }

    // Create function object from function pointer.
    std::function<T> func(reinterpret_cast<__stdcall T*>(lpfnGetProcessID));

    return func;
}

The DLL source should be written like this:

// test.cc (test.dll)
#include <iostream>

// Declare function prototypes with "extern C" to prevent name mangling.
// Declare functions using __declspec(dllexport) to signify the intent to export.
extern "C" {
    __declspec(dllexport) int __stdcall plusFive(int);
    __declspec(dllexport) void __stdcall printHello();
}

int plusFive(int value) {
    return value + 5;
}

void printHello() {
    std::cout << "Hello!" << std::endl;
}

And then use loadDllFunc like this:

// main.cc

int main() {
    auto func1 = loadDllFunc<int(int)>("test.dll", "plusFive");
    auto func2 = loadDllFunc<void()>("test.dll", "printHello");

    std::cout << "Result of func1: " << func1(1) << std::endl;
    func2();
}

Output:

Result of func1: 6
Hello!

As a sidenote the DLL can be compiled using GCC (4.7.2) like this:

g++ -shared -o test.dll test.cc -std=c++11

Edit:

I'm not sure that the cast in loadDllFunc gives the correct type:

std::function<T> func(reinterpret_cast<__stdcall T*>(lpfnGetProcessID));

It seems to cast it to __stdcall int (*)(int) when it should be int (__stdcall *)(int).

Here is another way to implement loadDllFunc using an auxiliary parser class. This solution will correctly cast the function pointer to int (__stdcall *)(int).

template <typename T>
struct TypeParser {};

template <typename Ret, typename... Args>
struct TypeParser<Ret(Args...)> {
    static std::function<Ret(Args...)> createFunction(const FARPROC lpfnGetProcessID) {
        return std::function<Ret(Args...)>(reinterpret_cast<Ret (__stdcall *)(Args...)>(lpfnGetProcessID));
    }
};

template <typename T>
std::function<T> loadDllFunc(const std::string& dllName, const std::string& funcName) {
    // Load DLL.
    HINSTANCE hGetProcIDDLL = LoadLibrary(dllName.c_str());

    // Check if DLL is loaded.
    if (hGetProcIDDLL == NULL) {
        std::cerr << "Could not load DLL \"" << dllName << "\"" << std::endl;
        exit(EXIT_FAILURE);
    }

    // Locate function in DLL.
    FARPROC lpfnGetProcessID = GetProcAddress(hGetProcIDDLL, funcName.c_str());

    // Check if function was located.
    if (!lpfnGetProcessID) {
        std::cerr << "Could not locate the function \"" << funcName << "\" in DLL\"" << dllName << "\"" << std::endl;
        exit(EXIT_FAILURE);
    }

    // Create function object from function pointer.
    return TypeParser<T>::createFunction(lpfnGetProcessID);
}
Felix Glas
  • 15,065
  • 7
  • 53
  • 82
  • You forgot to `FreeLibrary` when you're done with the function pointer. – beerboy Mar 06 '13 at 22:06
  • 1
    @beerboy Actually at first I included a call to `FreeLibrary` at the end of `loadDllFunc`, just before returning the `std::function`. This worked and the returned function was valid even though the DLL was unloaded. I don't know why it worked, but to be sure I removed the call to `FreeLibrary`. Also, according to the post in this [link](http://forums.codeguru.com/showthread.php?168908-Is-it-a-Bad-Practice%99-to-omit-FreeLibrary()), it might not be necessary to call `FreeLibrary` as the DLL should unload automatically upon exit. – Felix Glas Mar 06 '13 at 22:40
  • Also there's a bug where the calling convention of the functions in the DLL are defined as `__stdcall`, but they aren't defined as such when they are imported in main.cc. – beerboy Mar 06 '13 at 23:30
  • Edit: sorry, the __stdcall bug isn't in main.cc, and actually can't be fixed there - the problem is the reinterpret_cast in loadDllFunc: `reinterpret_cast(lpfnGetProcessID);` casts a `FARPROC` to a function pointer `R(*)(..)` rather than `R(__stdcall *)(..)`. – beerboy Mar 06 '13 at 23:50
  • @beerboy Didn't see that. I edited the `reinterpret_cast` to include the correct calling convention. I think this fixes the problem. Thanks! – Felix Glas Mar 07 '13 at 11:08
  • I couldn't compile your edited version on Visual Studio 2010. I got about 29 syntax error. any help would be appreciated? can you post complete code? – Pritesh Acharya Feb 03 '14 at 07:39
  • @PriteshAcharya Did you make sure to include the necessary headers? You need ``, ``, `` & `` at least. – Felix Glas Feb 03 '14 at 12:50
  • @PriteshAcharya Also I don't know if VS2010 has enough C++11 support. Try a different more up to date compiler if you still get compilation errors (and enable the C++11 flags e.g. in **g++-4.8**: `-std=c++11`). – Felix Glas Feb 03 '14 at 12:56
  • @Snps Can you explain how this part of the code works? `template struct TypeParser {}; template struct TypeParser { ...` **How typename T gets split into Ret and Args?** – T.s. Arun Apr 01 '20 at 13:08