由於我們經常要調用一些第三方廠商或其他編譯器編寫的動態鏈接庫,但是一般都不提供源文件或.lib文件,而作為VC隱式鏈接到DLL (implicitly link to the DLL)調用,這些卻是必需的。本文將主要討論在沒有源文件及.lib輸入庫文件或欲調用 Windows未公開函數的情況下重建.Lib文件的方法。在建立之前,我們首先要了解一下DLL輸出函數的幾種方式。
一、從DLL中輸出函數的方式(calling conventions )
_cdecl是C和C++程序的缺省調用方式。每一個調用它的函數都包含清空堆棧的代碼,所以產生的可執行文件大小會比調用_stdcall函數的大。函數采用從右到左的壓棧方式。VC將函數編譯後會在函數名前面加上下劃線前綴。
_stdcall是Pascal程序的缺省調用方式,通常用於Win32 Api中,函數采用從右到左的壓棧方式,自己在退出時清空堆棧。VC將函數編譯後會在函數名前面加上下劃線前綴,在函數名後加上"@"和參數的字節數。
_fastcall方式的函數采用寄存器傳遞參數,VC將函數編譯後會在函數名前面加上"@"前綴,在函數名後加上"@"和參數的字節數。
用VC建立一個空的動態鏈接庫,並加入以下三個文件:
//noname.h 動態鏈接庫頭文件
extern "C" void _stdcall stdcallproc(void);
extern "C" void _cdecl cdeclproc(void);
extern "C" void _fastcall fastcallproc(void);
//noname.cpp 動態鏈接庫實現文件
#include <Windows.h>
extern "C" void _stdcall stdcallproc(void)
{MessageBox(0,"stdcall function","dll call",0);
}
extern "C" void _cdecl cdeclproc(void)
{MessageBox(0,"cdecl function ","dll call",0);
}
extern "C" void _fastcall fastcallproc(void)
{MessageBox(0,"fastcall function ","dll call",0);
}
//noname.def 動態鏈接庫輸出函數定義
LIBRARY "noname"
EXPORTS
stdcallproc @1 noname
cdeclproc @2
fastcallproc @3
編譯後生成noname.lib,輸出函數_cdeclproc,_stdcallproc@0,@fastcallproc@0;生成的noname.dll在Exescope等PE格式的工具中只能看到cdeclproc和fastcallproc函數,因為stdcallproc被指定noname屬性,沒有名字輸出,類似於Windows未公開函數。
二、可執行程序調用DLL的方式
可執行程序可以采用隱式鏈接(implicit linking)或顯式鏈接(explicit linking)兩種方式調用一個DLL。
使用顯式鏈接時,使用DLL的程序在使用之前必須加載(LoadLibrary)加載DLL從而得到一個DLL模塊的句柄,然後調用 GetProcAddress函數得到輸出函數的指針,在退出之前必須卸載DLL(FreeLibrary),因為不是本文重點,具體例程請參考有關文檔。顯然,在調用大量的函數時這種方法會很不方便。
使用隱式鏈接時,可執行程序鏈接到一個包含DLL輸出函數信息的輸入庫文件(.LIB文件)。操作系統在加載使用可執行程序時加載DLL。可執行程序直接通過函數名調用DLL的輸出函數,調用方法和程序內部其他的函數是一樣的。
三、重建.Lib輸入庫文件
根據微軟的建議,要想隱式地鏈接到一個DLL,可執行程序必須從DLL的提供者那兒得到一個包含輸出函數的頭文件(.h文件)、一個用於鏈接的輸入庫(.lib文件)。願望是很好的,但是一般情況下,我們都無法得到第三方動態鏈接庫的輸入庫文件,或者我們需要調用Windows未公開函數。如果你是使用Delphi或Visual Basic開發程序,那麼,你只要簡單的申明一下函數和輸出庫就可以了。但是,使用VC的朋友們只好重建.Lib文件了。
1.刪掉第一步中生成的noname.lib(假設我們沒有這個文件)。
2.用微軟的DumpBin.exe:dumpbin /exports noname.dll>noname.def,留下noname.def文件的輸出段:
ordinal hint RVA name
2 0 00001005 cdeclproc
3 1 0000100F fastcallproc
1 0000100A [NONAME]
修改為:
LIBRARY "noname"
EXPORTS
cdeclproc @2
fastcallproc @3
nonameproc @1 //請注意與第一步中noname.def的區別:nonameproc可以自己指定為任何名字
再執行 lib.exe /def:noname.def即可生成noname.lib文件(但如果這個動態鏈接庫不僅僅包含_cdecl類型函數,那麼這個noname.lib還不是最終可用的.lib文件,具體請看下文)。
3.建立一個名為DllCaller的Win32控制台程序,將剛才生成的noname.dll和noname.lib拷入DllCallerdebug目錄。
//DllCaller.cpp
//聲明函數原型
extern "C" void _stdcall nonameproc(void);
extern "C" void _cdecl cdeclproc(void);
extern "C" void _fastcall fastcallproc(void);
//鏈接輸入庫文件
#pragma comment( lib, "debugoname.lib" )
int main(int argc, char* argv[])
{
nonameproc();
cdeclproc();
fastcallproc();
return 0;
}
編譯器產生如下錯誤:
DllCaller.obj : error LNK2001: unresolved external symbol @fastcallproc@0
DllCaller.obj : error LNK2001: unresolved external symbol _nonameproc@0
根據錯誤提示信息將noname.def更改如下:
@fastcallproc@0 @3
nonameproc@0 @1
重新生成noname.lib,即可重新編譯DllCaller.exe。
四、調用Windows未公開函數
根據以上分析,下面給出一個簡單的調用Window98系統Shell32.DLL中序號為60的未公開函數,執行後將出現重新啟動的對話框。
//shell32.def ,據此生成Shell32.LIB
LIBRARY "shell32"
EXPORTS
SHShutDownDialog@4 @60
// DllCaller.cpp : 調用未公開函數的控制台程序
//函數聲明
extern "C" long _stdcall SHShutDownDialog(long lShutdown);
//鏈接輸入庫文件
#pragma comment( lib, "debug\shell32.lib" )
int main(int argc, char* argv[])
{
SHShutDownDialog(0);
return 0;
}