庫 調 試
庫是實現模塊化和重用的重要手段,也是共享的重要方式。
1.運行庫概述
運行庫是程序在運行時所需要的庫文件,通常運行庫足以lib或DLL形式提供的。運行庫中一般包括編程時常用的函數,如字符串操作、文件操作、界面、邏輯模塊等內容。使用運行庫,可以方便的進行模塊重用,大大縮小編譯後的程序大小,減少程序中的重複代碼。
1.1 靜態鏈接庫
靜態鏈接庫(static library)本質上是一個代碼集,通常以lib的形式提供,它把一些函數做成一個函數集合放在一起,這些函數沒有經過編譯器鏈接。
如果要調用靜態鏈接庫中的函數,這些函數在工程編譯時將參加編譯。在使用靜態鏈接庫文件中的函數時,必須包含該函數對應的頭文件,還必須引用進該lib文件,以允許編譯器去查找已經編譯好的二進制代碼。如果可執行程序調用了靜態鏈接庫,則編譯後靜態鏈接庫中的代碼就要鏈接到可執行程序中去,成爲執行程序的一部分。所以調用了靜態鏈接的可執行文件的體積一般比較大一些。
函數和數據被編譯進一個二進制文件,編譯器在鏈接過程中將從靜態庫中恢復這些函數和數據,並把這些數據和應用程序中的其他模塊組合在一起生成可執行文件,這個過程稱爲“靜態鏈接”,此時因爲應用程序所需的全部內容都是從庫中複製出來,所以靜態庫本身並不需要與可執行文件一起發行。
靜態鏈接庫的依賴項包括靜態鏈接庫頭文件(*.h)、庫文件(*.lib)。在提供靜態鏈接庫時,需要同時提供這兩項才能供其他開發者使用。
1.2動態鏈接庫
動態鏈接庫(dynamic link library)是作爲共享函數庫的可執行文件,它是包含可由多個程序同時使用的代碼和數據的庫。
動態鏈接提供了一種方法,使進程可以調用不屬於其可執行代碼的函數。函數的可執行代碼位於一個DLL中,該DLL包含一個或多個已被編譯、鏈接並與使用它們的進程分開存儲的函數。
使用動態鏈接庫有助於共享數據和資源,多個應用程序可同時訪問內存中單個DLL副本的內容,如在Windows操作系統中,Comdlg32 dll執行與對話框有關的常見函數,因此每個程序都可以使用該DLL中包含的功能來實現“打開”對話框的功能,顯然這種方式促進了代碼重用和內存的有效使用。
使用DLL,可以將程序模塊化,程序由相對獨立的組件組成。動態鏈接庫可以使程序按模塊來銷售,在運行時將各個模塊加載到主程序中。因爲模塊是彼此獨立的,所以程序的加載速度更快,而且模塊只在相應的功能被請求時才加載,提高了效率。
使用動態鏈接可以更容易地將更新應用於各個模塊,而不會影響該程序的其他部分。動態鏈接庫的修改是被隔離的,不會影響到其他模塊,而且DLL修改後,無需重新生成或安裝整個程序,只需要更新相應的DLL就可以應用更新。如果程序中使用了動態鏈接庫,應用程序就不是獨立的,程序的運行將會依賴對應的DLL,在程序發行時,DLL需要同時發行。
動態鏈接庫的依賴項包括頭文件(*.h)、引入庫文件(*.1ib)、庫文件(*.dll)。在提供靜態鏈接庫時,需要同時提供這3項才能供其他開發者使用。
引入庫文件包含被DLL導出的函數名稱和位置、動態鏈接庫中包含實際的函數和數據,應用程序使用引入庫文件鏈接到所需要使用的DLL文件,庫中的函數和數據並不複製到可執行文件中,因此在應用程序的可執行文件中,存放的不是被調用的函數代碼,而是DLL中所要調用的函數的內存地址,這樣當一個或多個應用程序運行時弄把程序代碼和被調用的函數代碼鏈接起來,從而節省了內存資源。
動態鏈接庫和靜態鏈接庫是有區別的。靜態庫就是將需要的代碼直接鏈接進可執行程序;動態庫就是在需要調用其中的函數時,根據函數映射表找到該函數然後調入堆棧執行。
如果在當前工程中有多處對DLL文件中同一個函數的調用,則執行時這個函數只會留下一份副本。如果有多處對lib文件中同一個函數的調用,則執行時該函數將在當前程序的執行空間裏留下多份副本,而且是一處調用就產生一份副本。
2.2 創建鏈接庫
常用的庫有靜態鏈接庫和動態鏈接庫,本節分別介紹創建這兩類庫的方法。
2.2.1 創建靜態鏈接庫
在Visual C++開發環境下可以方便地生成靜態鏈接庫文件。打開Visual C++,在菜單欄中選擇File-New命令,彈出“New(新建)”對話框,切換到Project選項卡,在列表中選擇Win32 Static Library選項,表示要新建一個靜態鏈接庫工程,然後在對話框右側的Project name編輯框中輸入工程的名稱LibExample,在Location編輯框中輸入工程的目錄。單擊OK按鈕,彈出靜態鏈接庫設置對話框。Pre-Compiled header選項表示新建的工程將會包含預編譯頭文件。選擇該選項後生成的工程的編譯選項中將有預編譯頭文件的參數,而且生成的工程文件中將會看到“# include ”stdafx.h””。MFC support表示新建的工程將支持MFC,選擇該選項後,可以在編寫庫文件時使用MFC提供的功能。這裏主要是介紹如何生成靜態鏈接庫,爲了簡單起見,兩個選項都不選擇,這樣將生成一個很簡蓽的靜態鏈接庫工程。單擊Finish按鈕後,提示將生成一個不包含庫源文件的工程,單擊OK按鈕,一個空靜態鏈接庫創建完畢。打開工程文件夾,該文件夾下面只有3個文件:LibExample.dsp、LibExample.dsw、LibExample.ncb。
靜態鏈接庫文件需要在上述的空工程中新建。新建LibExample.h和LibExample.cpp兩個文件。編輯*.h的源代碼如下:
#ifndefLIBEXAMPLE_H
#define LIBEXAMPLE_H
/**兩個整數求和
@param x 加數1
@param y 加數2
@retval 兩數之和
*/
#endif //#ifndef LIBEXAMPLE_H
編輯*.cpp的源代碼如下:
#include ”lib.h”
int Add(int x, int y)
{
return (x+y);
}
編譯該工程就得到了一個LibExample.lib文件,LibExample.lib就是一個函數庫。在發佈時,將頭文件和.lib文件一起提交給用戶,用戶就可以直接使用其中的函數了。
編譯靜態鏈接庫也有調試版本和發佈版本兩種,與編譯普通應用程序一樣,選擇不同的編譯選項生成不同的編譯版本。建議發佈時提供兩種版本,且在調試版本的文件名後增加字母“D”。不用每次在編譯完成後都去修改調試版本的文件名,在工程選項中,可以將調試版本輸出文件的名字改爲LibExample D.lib。
在編碼完成後,在菜單欄選擇Build-Batch Build命令,在彈出的Batch Build對話框中,同時勾選調試版本和發佈版本,單擊Rebuild All按鈕,可以將調試版本和發佈版本一起全部重建。
生成包含預編譯頭文件和支持MFC靜態庫的過程是一樣的,不同的是這兩種選項的代碼稍微複雜一點。
2.2.2 創建動態鏈接庫
在創建和編寫動態鏈接庫之前,需要在開發層面上對動態鏈接庫有所瞭解,下面是關於動態鏈接庫的一些基本概念。
(l)動態鏈接庫的編制與具體的編程語言及編譯器無關
只要遵循約定的DLL接口規範和調用方式,用各種語言編寫的動態鏈接庫都可以相互調用。例如,Windows提供的系統DLL(其中包括了Windows的API),在任何開發環境中都能被調用,不論是在Visual Basic,Visual C++,還是Delphi。
(2) Visual C++動態鏈接庫的分類
Visual C++支持3種DLL,它們分別是Non-MFC DLL(非MFC動態庫)、MFC Regular DLL(MFC規則DLL)和MFC Extension DLL(MFC擴展DLL)。
非MFC動態庫不採用MFC類庫結構,其導出函數爲標準的C接口,能被非MFC或MFC編寫的應用程序所調用;MFC規則DLL包含一個繼承自CWinApp的類,但其無消息循環;MFC擴展DLL採用MFC的動態鏈接版本創建,它只能被用MFC類庫所編寫的應用程序所調用。
(3) DLL導出函數和DLL內部函數
DLL導出函數可供應用程序調用,DLL內部函數而只能在DLL程序使用,應
用程序無法調用它們。
1.創建一個簡單的動態庫
打開Visual C++,在菜單欄中選擇File-New命令,彈出New對話框,切換到Project選項卡,在列表中選擇Win32 Dynamic-Link Library選項,表示要新建一個動態鏈接庫工程,然後在對話框右側的Project name編輯框中輸入工程的名稱LibExample,在Location編輯框中輸入工程的目錄,單擊OK按鈕,彈出類型選擇對話框,包括空dll工程、簡單dll工程以及包含一些導出符號的dll工程。爲了介紹方便,此處選擇空dll工程。建立完畢後,打開工程文件夾,該文件夾下面只有3個文件:LibExample.dsp, LibExample.dsw,LibExample.ncb。
鏈接庫文件需要在上述的空工程中新建。新建NonMfcDll.h和NonMfcDll.cpp兩個文件。編輯NonMfcDll.h的源代碼如下:
#ifndef NONMFCDLL_H
#defineNONMFCOLL_H
/**兩個整數求和
@param x 加數1
@param y 加數2
@retval 兩數之和
*/
extern “C” int _declspecl(dllexport) Add(int x, int y)
#endif //#ifndef NONMFCOLL_H
編輯NonMfcDll.cpp的源代碼如下:
#include ”*.h”
int Add(int x, int y)
{
return (x+y);
}
NonMfcDll.h文件代碼中的dllexport聲明函數Add是dll導出函數,是動態鏈接庫提供給用戶的接口。
編譯該工程就得到了一個NonMfcDll.lib,NonMfcDll.dll文件,NonMfcDll.dll就是需要的動態鏈接庫,NonMfcDll.lib是NonMfcDll.dll的引入庫文件,它提供了兩數求和的功能,即Add函數所提供的功能。在發佈時,將頭文件和.lib和.dll文件一起提交給用戶,用戶就可以直接使用其中的Add函數了。
2.DLL導出函數
DLL中導出函數的聲明有兩種方式:一種是函數聲明中加上_declspecB(dllexport);另一種是採用模塊定義(.def)文件聲明,.def文件爲有關被鏈接程序的導出、屬性及其他方面的信息。
使用_declspec(dllexport)聲明函數導出已經給出,這裏介紹使用.def文件聲明函數導出。在工程中添加.def文件,按照上面的例子,應該在工程中添加*.def文件。.def文件的主要編寫規則如:
①LIBRARY語句說明.def文件相應的DLL。
②EXPORTS語句後列出要導出函數的名稱。可以在.def文件中的導出函數名後加@n,表示要導出函數的序號爲n(在進行函數調用時,這個序號將發揮其作用)。
③.def文件中的註釋由每個註釋行開始處的分號“:”指定,且註釋不能與語句共享一行。
要將示例工程中的Add函數聲明爲導出函數,NonMfcDll.def文件的源碼如下:
;lib.def :導出 DLL函數
LIBRARY NonMfcDll
EXPORTS
Add @ 1
根據.def文件規則,上面代碼的含義爲生成名爲NonMfcDll的動態鏈接庫,導出其中的Add函數,並指定Add函數的序號爲1。
3.DLL導出變量
DLL定義的全局變量可以被調用進程訪問,同時DLL也可以訪問調用進程的全局數據。下面描述在應用工程中如何引用DLL中變量的例子。
在文件NonMfcDll.h中,編寫以下代碼:
extern int g_DllExternVar;
g_DIIExternVar是應用程序中的全局變量,在DLL工程的頭文件中聲明外部變量後,就可以在.c或.cpp文件中使用了。在文件NonMfcDll.c中,編寫以下代碼:
int AddGlobalVal(int x)
{
return (x + g_DIIExtemVar);
}
在def文件中將g_DIIExtemVar聲明爲導出變量,在文件NonMfcDll.def中,編寫代碼如下:
;在DLL中導出變量
LIBRARY “NonMfcDll”
EXPORTS
g_DIIExtemVar DATA;
GetGlobalVar
從NonMfcDll.h和NonMfcDll.cpp中可以看出,全局變量在DLL中的定義和使用方法與一般的程序設計是一樣的。若要導出某全局變量,需要在.def文件的EXPORTS後添加:
變量名 DATA
在應用工程中引用DLL中全局變量方法如下:
#pragma comment(lib,” NonMfcDll”)
//用_declspecl(dllexport)導入變量
extern int _declspecl(dllexport) g_DllExternVar;
int main(intargc,char *argv[])
{
g_DllExternVar=100;
int myVal= g_DllExternVar+200;
return 0;
}
建議讀者通過declspec(dllimport)方式導入DLL中的全局變量,不要圖省事而使用extern int g_DIIExtemVar。用extern int g_DIIExtemVar聲明所導入的並不是DLL中的全局變量本身,而是其地址,應用程序必須通過強制指針轉換來使用DLL中的全局變量。
使用declspec(dllimport)方式導入的是全局變量本身而不是其地址,建議在一切可能的情況下都使用這種方式。
4.DLL導出類
通過DLL導出類,DLL中定義的類可以在應用程序的工程中使用。
下面以學生類(CStudent)爲例,介紹在DLL中導出類的方法。DLL工程依然使用NonMfcDII工程,在添加CStudent類的聲明文件(student.h)和實現文件( student.c)。student.h文件如下所示:
#ifndef STUDENT_H
#define STUDENT _H
#ifdef DLLSTUDENT
class _declspec (dllexport) CStudent //導出類CStudent
#else
class _declspec (dllimport) CStudent //導入類CStudent
#endif
{
public :
CStudent (const char* pName) ;
virtual ~CStudent ( ) ;
public :
void SetID(unsigned int newlD);//修改學生的ID號
void SetScore (unsigned int newScore);//修改分數
private :
charm_Name[20];//姓名
unsigned int m_ID;//學生ID號
unsigned intm_Score;//分數
};
#endif
student.c文件如下所示:
#ifndef DLL_STUDENT
#define DLL_STUDENT
#endif
#include “student.h”
CStudent : : CStudent (const char* pName)
{
memcpy( m_Name, pName,sizeof(pName);
m ID = 11223344;
m Score= 100;
}
CStudent : : ~CStudent ()
{
}
void CStudent : : SetID(unsigned int newlD)
{
m_ID=newID;
}
void CStudent : : Set Score (unsigned int newScore)
{
m_Score=newScore;
}
在應用程序中使用類,代碼如下:
#include “student.h”
#pragma comment(lib,”NonMfcDll.lib”);
int main(int argc,char * argv[])
{
CStudent aStudent(”sly”);
aStudent.SetID(1234567);
aStudent.SetScore(100);
return 0;
}
在實現CStudent類時,使用了宏DLL STUDENT,這個宏很巧妙。在NonMfcDII工程中,在student.c中定義了宏DLL STUDENT,所以在DLL工程進行編譯時,會啓用導出類的那段代碼,即:
class _declspec (dllexport) CStudent //導出類CStudent
{
//…
};
在應用程序的工程中,由於沒有定義宏DLL STUDENT,所以在編譯時會啓
用導入類的那段代碼,即:
class _declspec(dllimport) CStudent //導入類 CStudent
{
//…
};
這樣,在編寫DLL工程的CStudent類以及在應用程序工程中使用CStudent類時,編譯器會有不同的表現,同時省去了在應用程序中添加導入CStudent類的代碼。在student.h文件中,_ declspec(dllexport)與_declspec(dllimport)是匹對的。
通過導出動態鏈接庫中的函數、變量以及類中在應用工程中幾乎可以“看到”DLL中的一切,只要DLL釋放這些接口,應用程序使用它就將如同使用本工程中的程序一樣方便。
5._stdcall約定
動態鏈接庫的編制與具體的編程語言及編譯器無關。如果通過Visual C++編寫的DLL要被其他語言編寫的程序調用,應該將函數的調用方式聲明爲_stdcall方式。
在Windows操作系統中的WINAPI都採用這種方式,而C/C++語言默認的調用方式卻爲 _cdecl。_stdcall方式與_cdecl對函數名最終生成符號的方式不同。
若來用C編譯方式(在C++中需將函數聲明爲extem”C”),_stdcall調用約定在輸出函數名前面加下畫線,後面加“@”符號和參數的字節數,如_functionname@number;而_cdecl調用約定僅在輸出函數名前面加下畫線,如_functionname。
Windows編程中常見的幾種函數類型聲明宏都與_stdcall和_cdecl有關,具體如下:
#define CALLBACK _stdcall //回調函數
#define WINAPI _stdcall // WINAPI
#define WINAPIV _cdecl
#define APIENTRY WINAPI //DlIMain的入口
#define APIPRIVATE _stdcall
#define PASCAL _stdcall
在NonMfcDll.h中,聲明Add函數爲:
int_stdcall add (int x, int y);
在應用工程中函數指針類型應定義爲:
typedef int (_stdcall *lpAddFun) (int,int);
若在NonMfcDll.h中將函數聲明爲_stdcall調用,而應用工程中仍使用typedefint(* lpAddFun)(int,int),運行時將發生錯誤,因爲這兩種編譯方式不匹配,在應用工程中仍然是默認的_cdecl調用。
3 調試靜態鏈接庫
本節介紹如何使用靜態鏈接庫以及靜態鏈接庫的調試方法,並對與靜態鏈接庫相關的常見問題做了總結。
3.1 靜態連接庫的使用
靜態庫包括.lib和.h文件,要在應用程序工程中使用靜態庫,首先要把靜態庫對應的.lib和.h文件複製到工程目錄中,在程序工程目錄中添加靜態鏈接庫的頭文件,然後在程序工程中載入靜態庫。
要在自己的應用程序工程中載入靜態庫,可以通過在項目設置中引用和在應用程序工程代碼中顯示加載兩種方法。下面以靜態庫LibExample的加載爲例來說明兩種方法的具體操作。
(l)在項目設置中引用
選擇菜單欄中的Project-Settings命令,彈出ProjectSettings對話框,切換到Link選項卡,在object/library modules編輯框中添加.lib,此例中應該添加LibExample.lib。如果要添加多個靜態庫,可以將庫文件名都添加進去,文件名之間用空格隔開。
(2)顯示加載
在應用程序代碼中編寫以下代碼進行加載。
#pragma comment (lib,” LibExample.lib”);
#pragma comment (lib,其他靜態鏈接庫文件名);
完成上面的操作後,就可以像使用本工程的函數一樣使用靜態庫提供的功能了。
3.2 靜態鏈接庫的調試
靜態鏈接庫的調試有兩種方法:直接法和間接法。直接法是在靜工程中直接進行程序調試,調試的方法寫調試可執行差不多;間接法庫的應用程序工程去調試靜態鏈接庫。
1.直接法
打開靜態鏈接庫工程,按【F5】鍵或選擇菜單欄中的Build-StartDebug-Go命令,開始執行Debug模式。由於庫文件不能單獨執行,在執行調試時,會彈出靜態鏈接庫調試對話框。
這個對話框要求用戶輸入可執行文件的路徑來啓動庫函數的執行。在Executable file name編輯框中輸入調用該靜態庫的可執行文件目錄和文件名,或通過Executable file name編輯框右邊的箭頭選擇可執行文件,然後單擊OK按鈕,就可以對庫進行調試了,其調試技巧與一般應用工程的調試一樣。
2.間接法
程序員可以將庫工程和調用庫的應用程序工程放置在同一Visual C++工作區,只對應用工程進行調試,在應用工程調用庫中函數的語句處設置斷點,調試過程中如果程序停在斷點處,可以使用Step Into(按【FII]鍵)進入庫函數進行調試,這樣就可以單步調試庫中的函數。
在應用程序工程中的工作區右擊,在彈出的菜單中選擇Add New Project to Workspace命令。在彈出的工程文件選擇對話框中選擇庫文件的工程文件。
操作完成後,在原來的工作區中可以看到添加的靜態庫工程。在應用程序工程中調用靜態庫中的函數,並在函數調用的代碼行添加斷點,代碼如下:
#include “LibExample.h”
void CBookCodeDlg ::ShowBreakPoint()
{
int a =Add(3,5) //Add是庫函數,在該行設置斷點後,可進入函數進行單步調試
}
執行調試運行,當程序在斷點處停下時,可進入庫函數對其進行單步調試。
上述調試方法對靜態鏈接庫和動態鏈接庫而言是一致的。
3.設置工程依賴
將庫工程和應用程序放在同一個工程下方便對庫函數的調試。如果在調試過程中,修改了庫函數的實現,則需要對庫進行重新編譯生成新的lib;如果沒有修改完庫函數的代碼後都編譯一遍庫工程,然後再進行應用程序工程編譯,是很麻煩的。
設置工程的依賴之後,可以在編譯工程之前,編譯該工程所依賴的工程後再編譯本工程。
要設置工程依賴,首先將依賴的工程添加到本工程中,添加完成後,選擇菜單欄中的Project-Dependencies命令,在彈出的ProjectDependencies對話框中的列表框中選擇依賴的工程即可。
3.3 常見問題及處理方法
初學者在使用靜態鏈接庫時,經常容易忘記加載.lib,在編譯時Visual C++會提示LNK2001錯誤。如果既沒有在編譯選項中設置,也沒有顯示加載LibExample.lib,Visual C++會警告,提示找不到函數Add。例如:
BookCodeDlg.obj : error LNK2001: unresolvedexternal symbol Add Debug/BookCode.exe :fatal error LNK1120: I unresol\red externals
這時需要加載靜態鏈接庫。
4調試動態鏈接庫
本節介紹如何使用動態鏈接庫以及與動態鏈接庫的調試相關的問題。
4.1 動態鏈接庫的使用
靜態庫包括.lib,.dll,.h文件,要在應用程序工程中使用動態鏈接庫,首先要把動態鏈接庫對應的.lib,.dll,.h文件複製到工程目錄中,在程序工程目錄中添加靜態鏈接庫的頭文件,然後在程序工程中載入動態鏈接庫。
這裏要說明的是,在編碼中加載動態鏈接庫只需要將動態鏈接庫的引入庫文件加人到工程中就可以了。引入庫的加載方式與靜態鏈接庫的加載方式相同。
在實際開發中,可能需要知道某個動態鏈接庫導出的函數有哪些,動態鏈接庫中的導出接口可以使用Visual C++的Depends工具進行查看。
打開Depends工具,工具啓動後,選擇菜單欄中的File-Open命令,選擇上面例子中編寫的NonMFC.dll,從Depends工具中的列表可以看到NonMFC.dll導出函數有Add,Product,Minus。
4.2 DLL衝突
當程序使用DLL時,一個稱爲依賴性的問題可能導致該程序無法運行。當程序使用DLL時,就會創建一個依賴項。如果其他程序改寫和損壞了該依賴項,原來的程序就可能無法成功運行。如果發生下列操作之一,則該程序可能無法運行:
·依賴DLL升級到新版本;
· 修復了依賴DLL;
· 依賴DLL被其早期版本覆蓋:
· 從計算機中刪除了依賴DLL。
這些操作通常稱爲DLL衝突。如果沒有強制實現向後兼容性,則該程序可能無法成功運行。爲了最大限度地減少依賴性問題,在Windows 2000和校高版本的Windows操作系統中引入了文件保護和專用DLL。
(l) Windows文件保護
在Windows文件保護中,操作系統禁止未經授權的代理更新或刪除系統DLL。因此,當程序安裝操作嘗試刪除或更新被定義爲系統DLL的DLL時,Windows文件保護將尋找有效的數字簽名。
(2)專用DLL
通過專用DLL可以使程序避免遭受對共享DLL進行的更改。專用DLL使用版本特定信息或空local文件來強制要求程序所使用的DLL的版本。如果要使用專用DLL,可以在程序根文件夾中查找DLL。對於新程序,要向該DLL中添加版本特定信息。對於舊程序,要使用空Iocal文件。每個方法都告訴操作系統使用位於程序根文件夾中的專用DLL。
在編碼開發的層面上講,導致DLL衝突的主要原因是lib和dll版本不符。
發佈的動態鏈接庫包括頭文件(*.h)、引入庫文件(*.1ib)、庫文件(*.dll)。而引入庫文件包含被DLL導出的函數的名稱和位置、動態鏈接庫中包含實際的函數和數據,在發佈時引入庫文件和庫文件是一一對應的。如果在移動動態鏈接庫文件時,將版本搞錯,則會出現DLL衝突的問題。
在進行程序開發時,如果使用了多個動態鏈接庫,建議將動態鏈接庫工程的配置進行修改,將工程輸出目錄設置爲應用程序的工作目錄,即將編譯後最新的lib和dll同時輸出到應用程序工程目錄下,這樣可以保證lib和dll的版本一致性。
4.3 獲取DLL的相關信息
獲取指定DLL的相關信息是很重要的,如DLL的舨本信息。動態鏈接庫爲模塊重用提供了方便,但是要使應用程序順利的與DLL對接是一件很不容易的事情。這時,DLL的版本信息可以爲程序開發和調試提供很多有價值的信息,而且很多時候需要程序中判斷DLL的版本來決定下一步的代碼執行方案。
DLL相關的信息包括產品版本信息、產品名稱、文件描述、文件版本信息內部名稱等。
1.VerQueryValua函數
VerQueONalue函數用於從版本資源中獲取信息。調用這個函數前,必須GetFileVersionlnfo函數獲取版本資源信息。這個函數會檢查資源信息,並將需的數據複製到一個緩衝區裏。函數VerQueryValue的函數原型爲:
BOOL VerQueryValue (
const LPVOID pBlock, // addressof buffer for version resource
LPTSTR lpSubBlock, // address of value to retrieve
LPVOID *lplpBuffer, // addressof buffer for version value pointer
PUINT puLen // address oflength buffer
);
參數說明:
·pBlock:指定一個內存塊第一個字節的地址。這個內存塊包含了由GetFileVersionlnfo函數取回的版本數據信息。
· lpSubBlock String:可取下面的參數爲”\”獲取文件的VS_FIXEDFILE_INFO結構:”WarFilelnfo\Translation”獲取文件的翻譯表;”\StringFilelnfo\...”獲取文件的字串信息。
· lplpBuffer Long:指定一個Long變量的地址,該變量用於裝載一個緩衝區的地址。請求的版本信息最終會裝載到那個緩衝區裏。
· puLen Long:指定由lplpBuffer參數引用的數據值的長度,以字節爲單位。
函數返回TRUE(非0)表示執行成功。例如,請求的信息不存在,或pBlock不屬於有效版本信息,則返回FALSE。
在調用函數VerQueryValue時,如lplpBuffer參數爲”\StringFilelnfo\...”,緩衝區裏就會載入一個整數數組,每一對整數都代表一種語言和代碼頁,它們描繪了可用的字串信息。
通過用下面這3部分指定一個字串,可以獲得StringFilelnfo字串數據:”\StringFilelnfo\langua8ecodepage\stringname”,其中languagecodepage(語言代碼頁)是採用字串形式的一個8字符十六進制數字,如翻譯表中的語言代碼頁條目是&H04090000,這個字串就應該是04090000。stringname(字串名)指定的是一個字串名。
2.獲取信息
定義:
LPVOID m_lpData;
UINT m_uiDataSize;
用於返回欲獲取的具體信息內容,在適當的位置將這兩個參數初始化爲:
m_uiDataSize=80;
m_lpBuffer=malloc(1024);
下面是獲取各種信息的具體辦法:
//獲取產品版本
::VerQueryValue(m_lpBuffer,TEXT(“\\StringFileInfo\\040904B0\\ProductVersion”),
&m_lpData,&m_uiDataSize);
//獲取產品名稱
::VerQueryValue(m_lpBuffer,TEXT(“\\StringFileInfo\\040904B0\\ProductName”),
&m_lpData,&m_uiDataSize);
//獲取產品描述
::VerQueryValue(m_lpBuffer,TEXT(“\\StringFileInfo\\040904B0\\FileDescription”),
&m_lpData,&m_uiDataSize);
//獲取文件版本
::VerQueryValue(m_lpBuffer,TEXT(“\\StringFileInfo\\040904B0\\FileVersion”),
&m_lpData,&m_uiDataSize);
//獲取內部名稱
::VerQueryValue(m_lpBuffer,TEXT(“\\StringFileInfo\\040904B0\\InternalName”),
&m_lpData,&m_uiDataSize);
4.4 列舉程序加載的模塊
在開發過程中,有時候需要獲取某個程序加載的所有模塊,這些模塊包括靜態鏈接庫和動態鏈接庫。這裏介紹枚舉進程加載的所有模塊的方法。操作步驟爲:
提升當前進程的權限、獲取目標進程的進程ID (PID)、枚舉目標進程中的所有模塊。
提升當前進程的權限使其有權限對其他進程進行操作,操作方法如下:
int ModifyPrivilege()
{
HANDLE hToken;
LUID sedebugnameValue;
TOKEN_PRIVILEGES tkp;
//獲取當前進程的令牌句柄
If(!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,
&hToken)){
return 1;
}
}
//查詢當前進程權限
If(!LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&sedebugnameValue)){
CloseHandle(hToken);
return 1;
}
tkp.PrivilegeCount=1;
tkp.Privilege[0].Luid=sedebugnameValue;
tkp.Privilege[0].Attributes=SE_PRIVILEGE_ENABLED;
//修改當前進程權限
If(!AdjustTokenPrivileges(hToken,FALSE,&tkp,sizeof tkp,NULL,NULL))
{
CloseHandle(hToken);
return 1;
}
}
獲取目標進程的進程ID的方法如下:
DWORD GetCurProcessId(){
//獲取目標進程的PID
DWORD pid;
HANDLE snapshot;
snapshot=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
struct tagPROCESSENTRY32 processsnap;
processsnap.dwsize=sizeof(tagPROCESSENTRY32);
for(Process32First(snapshot,&processsnap);Process32Next(snapshot,&processsnap);){
if(!stricmp(processsnap.szExeFile,exename))
{
pid=processsnap.th32ProcessID;
break;
}
}
}
CloseHandle(snapshot);
return 1;
}
枚舉目標進程中的所有模塊:
int EnumProcessModule(DWORD pid){
MODULEENTRY32 pe32;
//設置結構的大小
pe32.dwSize=sizeof(pe32);
//給進程內所有模塊拍一個快照
HANDLE hProcessSnap=CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,pid);
If(hProcessSnap==INVALID_HANDLE_VALUE){
//建立快照失敗
return-1;
}
//遍歷進程快照,輪流顯示每個進程的信息
BOOL bMore=Module32First(hProcessSnap,&pe32);
while(bMore){
printf(“\n[DLL NAME]\t%s\n”,pe32.szModule);
printf(“[DLL PATH]\t%s\n”,pe32.szExePath);
bMore=Module32Next(hProcessSnap,&pe32);
}
//清除snapshot對象
CloseHandle(hProcessSnap);
return 1;
}