更方便地動態調用DLL匯出函數

來源:互聯網
上載者:User

原文連結:http://www.titilima.cn/?action=show&id=275

在一般情況下,動態調用DLL匯出函數的方法是:

  1. 用typedef為目標函數定義函數指標類型。
  2. 用GetProcAddress擷取函數指標。
  3. 用函數指標進行調用。

但是,如果要調用的函數太多的話,這個方法難免流於繁瑣——有太多的typedef、太多的GetProcAddress和太多的函數指標。在本文中將給出一個通用的解決方案,使這些動態調用更加簡便。

先看看我們這個函數的聲明:

  1. BOOL __cdecl DllCall(
  2.     PCTSTR lpszDll, // 目標函數所在DLL的名稱
  3.     PCSTR lpszFunc, // 目標函數名稱
  4.     int argc,   // 要調用的參數個數
  5.     PVOID pRet, // 函數調用的傳回值
  6.     ...
  7. );

以MessageBoxA為例,使用方法為:

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

換用一個參數的MessageBoxIndirectA,則是:

  1. MSGBOXPARAMSA param;
  2. ZeroMemory(¶m, 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, ¶m);

實現的原理是動態產生彙編代碼,也就是類似這樣的一段:

  1. __declspec(naked) DWORD __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)都差不多。

  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的代碼了,如下:

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

其中的指標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位元組的龐大結構,那麼這個傳回值將並不是你想要的。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.