Submitted by 李马
您可以任意转载这篇文章,但请在转载时注明原始链接和作者,谢谢。
在一般情况下,动态调用DLL导出函数的方法是:
但是,如果要调用的函数太多的话,这个方法难免流于繁琐——有太多的typedef、太多的GetProcAddress和太多的函数指针。在本文中将给出一个通用的解决方法,使这些动态调用更加简便。
先看看我们这个函数的声明:
C++代码
BOOL __cdecl DllCall(PCTSTR lpszDll, // 目标函数所在DLL的名称 PCSTR lpszFunc, // 目标函数名称 int argc, // 要调用的参数个数 PVOID pRet, // 函数调用的返回值 ...); //函数传参数
以MessageBoxA为例,使用方法为:
C++代码
int ret; DllCall(_T("user32.dll"), "MessageBoxA", 4, &ret, NULL, "Hello, World!", "Hello", MB_ICONINFORMATION | MB_YESNO);
换用一个参数的MessageBoxIndirectA,则是:
C++代码
MSGBOXPARAMSA param; ZeroMemory(¶m, sizeof(MSGBOXPARAMSA)); param.cbSize = sizeof(MSGBOXPARAMSA); param.dwLanguageId = GetSystemDefaultLangID(); param.dwStyle = MB_ICONINFORMATION; param.lpszCaption = "Hello"; param.lpszText = "Hello, World"; int ret; DllCall(_T("user32.dll"), "MessageBoxIndirectA", 1, &ret, ¶m);
实现的原理是动态生成汇编代码,也就是类似这样的一段:
C++代码
__declspec(naked) DWORD __cdecl DllCallProc(void) { __asm { push argn ... push arg2 push arg1 call proc ret }; }
下面列出DllCall的代码,和所有可变参数函数的实现(如sprintf)都差不多。
C++代码
BOOL __cdecl DllCall(PCTSTR lpszDll, PCSTR lpszFunc, int argc, PVOID pRet, ...) { va_list arglist; int ret; va_start(arglist, pRet); ret = vDllCall(lpszDll, lpszFunc, argc, pRet, arglist); va_end(arglist); return ret; }
最为关键的就是vDllCall的代码了,如下:
C++代码
#pragma pack(push, 1) typedef struct { BYTE op; DWORD_PTR dwValue; } OPCODE, *POPCODE; #pragma pack(pop) typedef DWORD (__cdecl * DLLCALL)(void);
BOOL __cdecl CRfidNfc::vDllCall(PCTSTR lpszDll, PCSTR lpszFunc, int argc, PVOID pRet, va_list arglist) { HMODULE hDll = LoadLibrary(lpszDll); if (NULL == hDll) return FALSE; FARPROC proc = GetProcAddress(hDll, lpszFunc); if (NULL == proc) return FALSE; HANDLE hHeap = GetProcessHeap(); POPCODE p = (POPCODE)HeapAlloc(hHeap, 0, sizeof(OPCODE) * (argc + 2)); int i; for (i = argc - 1; i >= 0; --i) { // push arg[i] p[i].op = 0x68; p[i].dwValue = va_arg(arglist, DWORD_PTR); } // call proc p[argc].op = 0xe8; p[argc].dwValue = (INT_PTR)proc - (INT_PTR)&p[argc + 1]; // ret p[argc + 1].op = 0xc3; p[argc + 1].dwValue = 0x90909090; // nop nop nop nop DLLCALL pfn = (DLLCALL)p; DWORD ret = pfn(); HeapFree(hHeap, 0, p); FreeLibrary(hDll); if (NULL != pRet) *(PDWORD)pRet = ret; return TRUE; }
其中的指针p就是我们动态生成的调用代码,最后转换成DLLCALL类型的函数指针进行了调用。
最后,需要补充说明四点:
http://www.titilima.cn/show-275-1.html