更方便地动态调用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字节的庞大结构,那么这个返回值将并不是你想要的。
用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字节的庞大结构,那么这个返回值将并不是你想要的。
相关文章推荐
- 在 C# 中动态调用 native dll 的导出函数
- 在C#中动态调用native dll的导出函数
- 通过GetProcAddress函数动态调用dll中地函数,是否必须通过extern C声明导出函数?(转)
- 通过GetProcAddress函数动态调用dll中地函数,是否必须通过extern C声明导出函数?
- 通过GetProcAddress函数动态调用dll中地函数,是否必须通过extern C声明导出函数?
- 动态链接库DLL导出函数的声明和调用
- C#动态地调用Win32 DLL中导出的函数
- vb动态加载dll的一个类,实现vb动态加载dll并动态调用dll导出的函数的一个方便办法
- DLL导出函数方法以及动态调用
- 在 C# 中动态调用 native dll 的导出函数
- 动态加载DLL实现不同DLL的相同导出函数调用问题
- 动态调用DLL函数有时正常,有时报Access violation的异常
- C#调用C++动态链接库之Win32dll(函数)、MFCdll(对话框)
- NSIS使用System::Call调用自定义dll中的导出函数
- 透过汇编另眼看世界之DLL导出函数调用
- 只有dll的情况下如何动态调用里面的类成员函数
- 调用未知DLL中的导出函数[转]
- 【转】调用未知DLL中的导出函数
- 在自己构造的DLL动态链接库中调用winmm.lib的mciSendString函数有问题
- 动态链接库中导出函数的调用