Click here to Skip to main content
15,884,388 members
Articles / Desktop Programming / Win32

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

Rate me:
Please Sign up or sign in to vote.
4.87/5 (40 votes)
19 Feb 2015CPOL1 min read 53.1K   82   13
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)


Written By
Software Developer
Greece Greece
I'm working in C++, PHP , Java, Windows, iOS, Android and Web (HTML/Javascript/CSS).

I 've a PhD in Digital Signal Processing and Artificial Intelligence and I specialize in Pro Audio and AI applications.

My home page: https://www.turbo-play.com

Comments and Discussions

 
SuggestionI get this type-safe version . Pin
Member 1394841923-Aug-18 5:21
Member 1394841923-Aug-18 5:21 
GeneralMy vote of 5 Pin
David A. Gray2-Jul-15 20:20
David A. Gray2-Jul-15 20:20 
QuestionNice article, but...? Pin
Stuart Dootson9-Feb-15 22:24
professionalStuart Dootson9-Feb-15 22:24 
AnswerRe: Nice article, but...? Pin
Michael Chourdakis10-Feb-15 5:28
mvaMichael Chourdakis10-Feb-15 5:28 
QuestionThanks Pin
jacobwilliam7-Feb-15 20:05
jacobwilliam7-Feb-15 20:05 
GeneralMy vote of 5 Pin
Volynsky Alex7-Feb-15 7:50
professionalVolynsky Alex7-Feb-15 7:50 
Questionhave you consider to post this as a tip? Pin
Nelek7-Dec-14 1:03
protectorNelek7-Dec-14 1:03 
AnswerRe: have you consider to post this as a tip? Pin
Liju Sankar9-Feb-15 5:19
professionalLiju Sankar9-Feb-15 5:19 
QuestionI have done the similar thing like you in other ways Pin
hanzz20072-Dec-14 22:00
hanzz20072-Dec-14 22:00 
GeneralMy vote of 5 Pin
Sharjith28-Nov-14 1:40
professionalSharjith28-Nov-14 1:40 
GeneralMy vote of 5 Pin
Volynsky Alex27-Nov-14 22:38
professionalVolynsky Alex27-Nov-14 22:38 
Questionerror Pin
HateCoding23-Nov-14 22:12
HateCoding23-Nov-14 22:12 
AnswerRe: error Pin
BigDaveDev24-Nov-14 2:12
BigDaveDev24-Nov-14 2:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.