更方便地动态调用DLL导出函数

在一般情况下,动态调用DLL导出函数的方法是:

  1. 用typedef为目标函数定义函数指针类型。
  2. 用GetProcAddress获取函数指针。
  3. 用函数指针进行调用。

但是,如果要调用的函数太多的话,这个方法难免流于繁琐——有太多的typedef、太多的GetProcAddress和太多的函数指针。在本文中将给出一个通用的解决方法,使这些动态调用更加简便。
先看看我们这个函数的声明:

C++代码
  1. BOOL __cdecl DllCall(   
  2.     PCTSTR lpszDll, // 目标函数所在DLL的名称   
  3.     PCSTR lpszFunc, // 目标函数名称   
  4.     int argc,       // 要调用的参数个数   
  5.     PVOID pRet,     // 函数调用的返回值   
  6.     ...   
  7. );  

以MessageBoxA为例,使用方法为:

C++代码
  1. int ret;   
  2. DllCall(_T("user32.dll"), "MessageBoxA", 4, &ret,   
  3.     NULL, "Hello, World!""Hello", MB_ICONINFORMATION | MB_YESNO);  

换用一个参数的MessageBoxIndirectA,则是:

C++代码
  1. MSGBOXPARAMSA param;   
  2. ZeroMemory(&param, sizeof(MSGBOXPARAMSA));   
  3. param.cbSize = sizeof(MSGBOXPARAMSA);   
  4. param.dwLanguageId = GetSystemDefaultLangID();   
  5. param.dwStyle = MB_ICONINFORMATION;   
  6. param.lpszCaption = "Hello";   
  7. param.lpszText = "Hello, World";   
  8.   
  9. int ret;   
  10. DllCall(_T("user32.dll"), "MessageBoxIndirectA", 1, &ret, &param);  

实现的原理是动态生成汇编代码,也就是类似这样的一段:

C++代码
  1. __declspec(nakedDWORD __cdecl DllCallProc(void)   
  2. {   
  3.     __asm   
  4.     {   
  5.         push argn   
  6.         ...   
  7.         push arg2   
  8.         push arg1   
  9.         call proc   
  10.         ret   
  11.     };   
  12. }  

下面列出DllCall的代码,和所有可变参数函数的实现(如sprintf)都差不多。

C++代码
  1. BOOL __cdecl DllCall(   
  2.     PCTSTR lpszDll,   
  3.     PCSTR lpszFunc,   
  4.     int argc,   
  5.     PVOID pRet,   
  6.     ...)   
  7. {   
  8.     va_list arglist;   
  9.     int ret;   
  10.   
  11.     va_start(arglist, pRet);   
  12.     ret = vDllCall(lpszDll, lpszFunc, argc, pRet, arglist);   
  13.     va_end(arglist);   
  14.     return ret;   
  15. }  

最为关键的就是vDllCall的代码了,如下:

C++代码
  1. #pragma pack(push, 1)   
  2. typedef struct {   
  3.     BYTE op;   
  4.     DWORD_PTR dwValue;   
  5. } OPCODE, *POPCODE;   
  6. #pragma pack(pop)   
  7.   
  8. typedef DWORD (__cdecl * DLLCALL)(void);   
  9.   
  10. BOOL __cdecl vDllCall(   
  11.     PCTSTR lpszDll,   
  12.     PCSTR lpszFunc,   
  13.     int argc,   
  14.     PVOID pRet,   
  15.     va_list arglist)   
  16. {   
  17.     HMODULE hDll = LoadLibrary(lpszDll);   
  18.     if (NULL == hDll)   
  19.         return FALSE;   
  20.     FARPROC proc = GetProcAddress(hDll, lpszFunc);   
  21.     if (NULL == proc)   
  22.         return FALSE;   
  23.   
  24.     HANDLE hHeap = GetProcessHeap();   
  25.     POPCODE p = (POPCODE)HeapAlloc(hHeap, 0, sizeof(OPCODE) * (argc + 2));   
  26.   
  27.     int i;   
  28.     for (i = argc - 1; i >= 0; --i)   
  29.     {   
  30.         // push arg[i]   
  31.         p[i].op = 0x68;   
  32.         p[i].dwValue = va_arg(arglist, DWORD_PTR);   
  33.     }   
  34.   
  35.     // call proc   
  36.     p[argc].op = 0xe8;   
  37.     p[argc].dwValue = (INT_PTR)proc - (INT_PTR)&p[argc + 1];   
  38.     // ret   
  39.     p[argc + 1].op = 0xc3;   
  40.     p[argc + 1].dwValue = 0x90909090; // nop nop nop nop   
  41.   
  42.     DLLCALL pfn = (DLLCALL)p;   
  43.     DWORD ret = pfn();   
  44.   
  45.     HeapFree(hHeap, 0, p);   
  46.     FreeLibrary(hDll);   
  47.   
  48.     if (NULL != pRet)   
  49.         *(PDWORD)pRet = ret;   
  50.     return TRUE;   
  51. }  

其中的指针p就是我们动态生成的调用代码,最后转换成DLLCALL类型的函数指针进行了调用。
最后,需要补充说明四点:

  1. DllCall只适用于__stdcall调用约定的目标函数。
  2. 这份vDllCall的代码只适用于x86的CPU,如果在WinCE的环境下(如arm或mips的CPU)使用,需要酌情重新编写动态调用的汇编代码。
  3. 其中argc参数是指实际压栈的参数个数,而不是C语言调用的参数个数。如API函数WindowFromPoint,虽然函数声明中只有一个参数,但是实际上是将POINT::x、POINT::y分别压栈的。在这种情况下,需要将argc设置为2。
  4. DllCall的返回值只获取了eax,如果有的函数会返回一个超过4字节的庞大结构,那么这个返回值将并不是你想要的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值