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






4.87/5 (40投票s)
使用 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 - 首次发布