您的位置:首页 > 其它

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

2009-04-23 21:50 330 查看
在一般情况下,动态调用DLL导出函数的方法是:

用typedef为目标函数定义函数指针类型。

用GetProcAddress获取函数指针。

用函数指针进行调用。

但是,如果要调用的函数太多的话,这个方法难免流于繁琐——有太多的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 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类型的函数指针进行了调用。
最后,需要补充说明四点:

DllCall只适用于__stdcall调用约定的目标函数。

这份vDllCall的代码只适用于x86的CPU,如果在WinCE的环境下(如arm或mips的CPU)使用,需要酌情重新编写动态调用的汇编代码。

其中argc参数是指实际压栈的参数个数,而不是C语言调用的参数个数。如API函数WindowFromPoint,虽然函数声明中只有一个参数,但是实际上是将POINT::x、POINT::y分别压栈的。在这种情况下,需要将argc设置为2。

DllCall的返回值只获取了eax,如果有的函数会返回一个超过4字节的庞大结构,那么这个返回值将并不是你想要的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: