65.9K
CodeProject 正在变化。 阅读更多。
Home

动态加载的 DLL 函数的一行模板调用 - GetProcAddress() 和 Invoke() 的扩展

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (40投票s)

2014 年 11 月 23 日

CPOL

1分钟阅读

viewsIcon

55842

使用 C++ 元组和模板实现,可以用一行调用任何 DLL 函数,并传递自定义参数。

引言

我真的厌倦了这种代码

// 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);

代码行数太多,重复出现。这里有一个简单的技巧,基于 C++ 模板、模板元编程和 std::tuple,可以将调用简化为一行。

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));

哇哦。你喜欢吗?这是它的实现。

代码

#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;
    }

这里发生了什么?首先,我们使用一个整数包,以便稍后 ActualCall 函数可以展开我们传递给用于存储指针的 std::function 的所有参数。实际参数我们想要的是通过一个 std::tuple 传递的。我使用 std::make_tuple<> 在这个简单的例子中自动推断类型 - 如果存在任何歧义,你可以指定参数,例如 std::tuple<HWND,LPCTSTR,LPCTSTR,UINT,WORD,UINT> 用于 MessageBoxTimeoutW

如果模块中找不到该函数,该函数会抛出 E_POINTER 异常。

它仅适用于 __stdcall,但你可以更改这一行以适用于其他调用约定。

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

参数化的 GetProcAddress()

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;
                }

这更简单。你不需要元组,而只需要在这里进行一个简单的 Args... 展开。像这样调用它:

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()

CComPtr 类中,他们使用 C 老式的省略号在 CComPtr::InvokeN() 中传递参数进行调用。这里是一个 C++ 11 实现。请注意,由于所有参数类型都是 VARIANT (CComVariant),我们不需要 std::tuple,而只需要一个更简单的 std::initializer_list

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;
    }

没有元组

正如 Stuard Dootson 所说,你甚至不需要 std::tuple。嘿,它变得更简单了。

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;
}

与我的 GetProcAddress() 实现一样,这个版本不需要在使用 std::tuple 时会发生的模板展开。

关注点

这个基本想法来自 这里

历史

  • 2015/02/20 - 简化,感谢 Stuard Dootson
  • 2015/02/07 - 添加了 IDispatch::InvokeX 实现
  • 2014/11/23 - 首次发布
© . All rights reserved.