[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:
- You may simply not have the required import
.lib
- You may want to delay load the library to improve startup time
- You may not know the names of the functions at compile time
- You may not know the name of the DLL at compile time
- 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:
- Call
LoadLibrary()
to load the DLL in your process - Call
GetProcAddress()
with the name of each function you want to use - 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:
- You need to have the
typedef
exactly right - You need to make sure that you don’t call
FreeLibrary()
too soon - There is a lot of repetition
The Modern C++ Way
It turns out that you could use C++ to fix each:
- You can use
decltype()
to extract the type from the declaration present in the header file. - You can use RAII to load and unload the library
- 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.