[C++] How to GetProcAddress() like a boss

When programming on Windows, you usually use the functions from another DLL through an import .lib that links the functions used in your program with the functions exported by a DLL.

Manual DLL loading

However, there are a few situations where you cannot use this scheme:

  1. You may simply not have the required import .lib
  2. You may want to delay load the library to improve startup time
  3. You may not know the names of the functions at compile time
  4. You may not know the name of the DLL at compile time
  5. You may want to load a DLL that is not in the search path.

In these situations, you need to do the job of the .lib manually:

  1. Call LoadLibrary() to load the DLL in your process
  2. Call GetProcAddress() with the name of each function you want to use
  3. When finished, call FreeLibrary() to unload the DLL

The Good Old Way

Here is how you would do in C:

typedef int(WINAPI *ShellAboutProc)(HWND, LPCSTR, LPCSTR, HICON);

int main() {
  HMODULE hModule = LoadLibrary(TEXT("Shell32.dll"));

  ShellAboutProc shellAbout =
      (ShellAboutProc)GetProcAddress(hModule, "ShellAboutA");

  shellAbout(NULL, "hello", "world", NULL);

  FreeLibrary(hModule);
}

Which could be OK if you just have to import one function but would quickly become a nightmare when more functions are imported.

In this code snippet, there are three pain points:

  1. You need to have the typedef exactly right
  2. You need to make sure that you don't call FreeLibrary() too soon
  3. There is a lot of repetition

The Modern C++ Way

It turns out that you could use C++ to fix each:

  1. You can use decltype() to extract the type from the declaration present in the header file.
  2. You can use RAII to load and unload the library
  3. You can use implicit cast operator to reduce the repetition.

First, let's see the code in action:

class ShellApi {
  DllHelper _dll{"Shell32.dll"};

public:
  decltype(ShellAboutA) *shellAbout = _dll["ShellAboutA"];
};

int main() {
  ShellApi shellApi;
  shellApi.shellAbout(NULL, "hello", "world", NULL);
}

Not only this is more readable, but it's also more scalable: you can add more functions in the ShellApi class and it will stay simple.

How it works

Now let's see what's inside the box:

class ProcPtr {
public:
  explicit ProcPtr(FARPROC ptr) : _ptr(ptr) {}

  template <typename T, typename = std::enable_if_t<std::is_function_v<T>>>
  operator T *() const {
    return reinterpret_cast<T *>(_ptr);
  }

private:
  FARPROC _ptr;
};

class DllHelper {
public:
  explicit DllHelper(LPCTSTR filename) : _module(LoadLibrary(filename)) {}

  ~DllHelper() { FreeLibrary(_module); }

  ProcPtr operator[](LPCSTR proc_name) const {
    return ProcPtr(GetProcAddress(_module, proc_name));
  }

  static HMODULE _parent_module;

private:
  HMODULE _module;
};

First, the ProcPtr class wraps the return value of GetProcAddress() so that it can be implicitly casted to any function type (There is an SFINAE to prevent a cast to anything else).

Then the DllHelper class implements the RAII and exposes a convenient operator[] that calls GetProcAddress().

That's all!

The full code of this article is available on GitHub.