Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

One Line Template Call for a Dynamically Loaded DLL Function - Extensions to GetProcAddress() and Invoke()

4.87/5 (40 votes)
19 Feb 2015CPOL1 min read 55.2K  
One-call of any DLL function with custom parameters, implemented with C++ tuple and templates.

Introduction

I'm really bored of this sort of code:

C++
// to call MessageBoxTimeoutW
typedef int (__stdcall *MSGBOXWAPI)(IN HWND hWnd, IN LPCWSTR lpText, IN LPCWSTR lpCaption, 
             IN UINT uType, IN WORD wLanguageId, IN DWORD dwMilliseconds);

HMODULE hM = LoadLibrary(L"USER32.DLL");
MSGBOXWAPI a = (MSGBOXWAPI)GetProcAddress(hM,"MessageBoxTimeoutW");
if (a)
   a(hParent,L"Hello",L"there",MB_OK,0,1000);

Too many lines of code over and over again. Here is a simple trick, based on C++ templates, template metaprogramming and std::tuple to have the call in one line:

C++
int j = DLLCall<int>(L"USER32.DLL","MessageBoxTimeoutW",
std::make_tuple<>(nullptr,L"Do you like me?",L"Message",MB_YESNO | MB_ICONINFORMATION,0,3000));

Woah. You liked it, yes? Here's its implementation.

Code

C++
#include <windows.h>
#include <tuple>
#include <functional>

namespace DLLCALL
    {
    using namespace std;

    // Pack of numbers.
    // Nice idea, found at 
    // http://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer
    template<int ...> struct seq {};

    // Metaprogramming Expansion
    template<int N,int ...S> struct gens : gens < N - 1,N - 1,S... > {};
    template<int ...S> struct gens <0,S... >
        {
        typedef seq<S...> type;
        };

    // Function that performs the actual call
    template<typename R,int ...S,typename...Args>
    R ActualCall(seq<S...>,std::tuple<Args...> tpl,std::function<R(Args...)> func)
        {
        // It calls the function while expanding the std::tuple to it's arguments via std::get<S>
        return func(std::get<S>(tpl) ...);
        }

#pragma warning(disable:4290)

    // Functions to be called from your code
    template <typename R,typename...Args> R DLLCall(const wchar_t* module,
              const char* procname,std::tuple<Args...> tpl) throw(HRESULT)
        {
        HMODULE hModule = LoadLibrary(module);
        FARPROC fp = GetProcAddress(hModule,procname);
        if (!fp)
            throw E_POINTER;
        typedef R(__stdcall *function_pointer)(Args...);
        function_pointer P = (function_pointer)fp;

        std::function<R(Args...)> f = P;
        R return_value = ActualCall(typename gens<sizeof...(Args)>::type(),tpl,f);
        FreeLibrary(hModule);
        return return_value;
        }
 
int __stdcall WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
    {
    int j = DLLCALL::DLLCall<int>(L"USER32.DLL","MessageBoxTimeoutW",
     std::make_tuple<>(nullptr,L"Do you like me?",L"Message",MB_YESNO | MB_ICONINFORMATION,0,3000));
    if (j == 32000)
        MessageBox(nullptr,L"Was timeout",L"Message",MB_OK);
    return 0;
    }

What is happening here? First, we use an integer pack so, later, the ActualCall function can expand all the parameters we have passed to the std::function we use to store the pointer. The actual arguments we want are passed insode a std::tuple. I use std::make_tuple<> to auto-deduct the types in this trivial example - you could specify the parameters if there is any ambiguity, for example std::tuple<HWND,LPCTSTR,LPCTSTR,UINT,WORD,UINT> for MessageBoxTimeoutW.

The function throws E_POINTER in case that the function is not found in the module.

It only works with __stdcall, but you may change this line to work for other calling conventions:

C++
typedef R(__stdcall *function_pointer)(Args...);

Parameterized GetProcAddress()

C++
template <typename R,typename ...Args> std::function<R(Args...)> 
                     GetProcAddress(HMODULE hModule,const char* procname)
                {
                FARPROC fp = GetProcAddress(hModule,procname);
                if (!fp)
                    return nullptr;
                typedef R(__stdcall *function_pointer)(Args...);
                function_pointer P = (function_pointer)fp;
                std::function<R(Args...)> f = P;
                return f;
                }

This is simpler. You do not need a tuple, but a simple Args... expansion here. Call it as follows:

C++
auto j2 = DLLCALL::GetProcAddress<HWND,const char*,const char*,UINT>
                                (LoadLibrary("USER32.DLL","MessageBoxA");
if (j2)
  j2(0,"hello","there",MB_OK);

// or, something more hardcore
auto xx = DLLCALL::GetProcAddress<UINT,HWND,const char*,const char*,UINT>
    ((HMODULE)LoadLibrary(L"USER32.DLL"),(const char*)"MessageBoxA")(0,"hello","there",MB_OK);

// The above code would throw, of course, if the function cannot be found.

IDispatch::Invoke()

In the CComPtr class, they use the C old-style ellipsis in CComPtr::InvokeN() to pass the arguments to the call. Here is a C++ 11 implementation. Note that, since all the parameter types are VARIANT (CComVariant), we do not need a std::tuple, but a simpler std::initializer_list:

C++
HRESULT InvokeX(CComPtr<IDispatch>& di,LPCOLESTR lpszName,
                std::initializer_list<CComVariant> li,VARIANT* pvarRet)
    {
    HRESULT hr;
    DISPID dispid;
    hr = di.GetIDOfName(lpszName,&dispid);
    if (SUCCEEDED(hr))
        {
        DISPPARAMS dispparams = {0};
        std::vector<CComVariant> vs;
        for (auto a : li)
            vs.insert(vs.begin(),a); // It's reverse in IDispatch::Invoke()
        dispparams.cArgs = li.size();
        if (vs.size())
            dispparams.rgvarg = &vs[0];
        hr = di->Invoke(dispid,IID_NULL,LOCALE_USER_DEFAULT,DISPATCH_METHOD,
                                      &dispparams,pvarRet,NULL,NULL);
        }
    return hr;
    }

Without the Tuple

As Stuard Dootson said, you don't even need std::tuple. Hey, it gets simpler.

C++
template <typename R, typename... Args>
R DLLCall(const wchar_t* module, const char* procname, Args... args) throw(HRESULT)
{
   HMODULE hModule = LoadLibrary(module);
   FARPROC fp = GetProcAddress(hModule, procname);
   if (!fp)
      throw E_POINTER;
   typedef R(__stdcall * function_pointer)(Args...);
   function_pointer P = (function_pointer)fp;
   const auto return_value = (*P)(std::forward<Args>(args)...);
   FreeLibrary(hModule);
   return return_value;
}

As with my GetProcAddress() implementation, this version does not require the template expansion that would occur when using std::tuple.

Points of Interest

The base idea for this was found here.

History

  • 20/02/2015 - Simplification, thanks to Stuard Dootson
  • 07/02/2015 - Added the IDispatch::InvokeX implementation
  • 23/11/2014 - First release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)