九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
靜態(tài)鏈接庫(kù)與動(dòng)態(tài)鏈接庫(kù)

靜態(tài)鏈接庫(kù)與動(dòng)態(tài)鏈接庫(kù)  

2010-12-31 20:56:24|  分類: windows程序設(shè)計(jì) |舉報(bào) |字號(hào) 訂閱

一、           介紹

本文意在講解靜態(tài)鏈接庫(kù)與動(dòng)態(tài)鏈接庫(kù)的創(chuàng)建與使用,在此之前先來對(duì)二者的概念、區(qū)別及優(yōu)缺點(diǎn)進(jìn)行簡(jiǎn)要的闡述。其中大多內(nèi)容參考相關(guān)網(wǎng)絡(luò)資料,由于本人能力有限,不能確保完全準(zhǔn)確無(wú)誤,若有偏差之處請(qǐng)不吝指出。文中使用到的代碼均在Visual Studio 2008中編譯通過,如果您使用的IDE與本文不同,可根據(jù)實(shí)際情況進(jìn)行相應(yīng)項(xiàng)目創(chuàng)建與操作。希望本文內(nèi)容對(duì)您有所幫助。

二、           概念定義

1.     分別編譯與鏈接

大多數(shù)高級(jí)語(yǔ)言都支持分別編譯(Compiling),程序員可以顯式地把程序劃分為獨(dú)立的模塊或文件,然后由編譯器(Compiler)對(duì)每個(gè)獨(dú)立部分分別進(jìn)行編譯。在編譯之后,由鏈接器(Linker)把這些獨(dú)立編譯單元鏈接(Linking)到一起。鏈接方式分為兩種:

(1)     靜態(tài)鏈接方式:在程序開發(fā)中,將各種目標(biāo)模塊(.OBJ)文件、運(yùn)行時(shí)庫(kù)(.LIB)文件,以及經(jīng)常是已編譯的資源(.RES)文件鏈接在一起,以便創(chuàng)建Windows.EXE文件。
(2)     動(dòng)態(tài)鏈接方式:在程序運(yùn)行時(shí),Windows把一個(gè)模塊中的函數(shù)調(diào)用鏈接到庫(kù)模塊中的實(shí)際函數(shù)上的過程。

2.     靜態(tài)鏈接庫(kù)與動(dòng)態(tài)鏈接庫(kù)

靜態(tài)鏈接庫(kù)Static Library,簡(jiǎn)稱LIB)與動(dòng)態(tài)鏈接庫(kù)(Dynamic Link Library,簡(jiǎn)稱DLL)都是共享代碼的方式。如果使用靜態(tài)鏈接庫(kù)(也稱靜態(tài)庫(kù)),則無(wú)論你愿不愿意,.LIB文件中的指令都會(huì)被直接包含到最終生成的.EXE文件中。但是若使用.DLL文件,該.DLL文件中的代碼不必被包含在最終的.EXE文件中,.EXE文件執(zhí)行時(shí)可以“動(dòng)態(tài)”地載入和卸載這個(gè)與.EXE文件獨(dú)立的.DLL文件。

2.1.          動(dòng)態(tài)鏈接方式

鏈接一個(gè)DLL有兩種方式:

2.1.1           載入時(shí)動(dòng)態(tài)鏈接(Load-Time Dynamic Linking

使用載入時(shí)動(dòng)態(tài)鏈接,調(diào)用模塊可以像調(diào)用本模塊中的函數(shù)一樣直接使用導(dǎo)出函數(shù)名調(diào)用DLL中的函數(shù)。這需要在鏈接時(shí)將函數(shù)所在DLL的導(dǎo)入庫(kù)鏈接到可執(zhí)行文件中,導(dǎo)入庫(kù)向系統(tǒng)提供了載入DLL時(shí)所需的信息及用于定位DLL函數(shù)的地址符號(hào)。(相當(dāng)于注冊(cè),當(dāng)作API函數(shù)來使用,其實(shí)API函數(shù)就存放在系統(tǒng)DLL當(dāng)中。)

2.1.2           運(yùn)行時(shí)動(dòng)態(tài)鏈接(Run-Time Dynamic Linking

使用運(yùn)行時(shí)動(dòng)態(tài)鏈接,運(yùn)行時(shí)可以通過LoadLibraryLoadLibraryEx函數(shù)載入DLL。DLL載入后,模塊可以通過調(diào)用GetProcAddress獲取DLL函數(shù)的入口地址,然后就可以通過返回的函數(shù)指針調(diào)用DLL中的函數(shù)了。如此即可避免導(dǎo)入庫(kù)文件了。

2.2.          二者優(yōu)點(diǎn)及不足

2.2.1           靜態(tài)鏈接庫(kù)的優(yōu)點(diǎn)

(1)     代碼裝載速度快,執(zhí)行速度略比動(dòng)態(tài)鏈接庫(kù)快;
(2)     只需保證在開發(fā)者的計(jì)算機(jī)中有正確的.LIB文件,在以二進(jìn)制形式發(fā)布程序時(shí)不需考慮在用戶的計(jì)算機(jī)上.LIB文件是否存在及版本問題,可避免DLL地獄等問題。

2.2.2           動(dòng)態(tài)鏈接庫(kù)的優(yōu)點(diǎn)

(1)     更加節(jié)省內(nèi)存并減少頁(yè)面交換;
(2)     DLL文件與EXE文件獨(dú)立,只要輸出接口不變(即名稱、參數(shù)、返回值類型和調(diào)用約定不變),更換DLL文件不會(huì)對(duì)EXE文件造成任何影響,因而極大地提高了可維護(hù)性和可擴(kuò)展性;
(3)     不同編程語(yǔ)言編寫的程序只要按照函數(shù)調(diào)用約定就可以調(diào)用同一個(gè)DLL函數(shù)。

2.2.3           不足之處

(1)     使用靜態(tài)鏈接生成的可執(zhí)行文件體積較大,包含相同的公共代碼,造成浪費(fèi);
(2)     使用動(dòng)態(tài)鏈接庫(kù)的應(yīng)用程序不是自完備的,它依賴的DLL模塊也要存在,如果使用載入時(shí)動(dòng)態(tài)鏈接,程序啟動(dòng)時(shí)發(fā)現(xiàn)DLL不存在,系統(tǒng)將終止程序并給出錯(cuò)誤信息。而使用運(yùn)行時(shí)動(dòng)態(tài)鏈接,系統(tǒng)不會(huì)終止,但由于DLL中的導(dǎo)出函數(shù)不可用,程序會(huì)加載失?。?/span>
(3)     使用動(dòng)態(tài)鏈接庫(kù)可能造成DLL地獄。

2.3.          DLL 地獄

DLL 地獄(DLL Hell)是指因?yàn)橄到y(tǒng)文件被覆蓋而讓整個(gè)系統(tǒng)像是掉進(jìn)了地獄。

簡(jiǎn)單地講,DLL地獄是指當(dāng)多個(gè)應(yīng)用程序試圖共享一個(gè)公用組件時(shí),如某個(gè)DLL或某個(gè)組件對(duì)象模型(COM)類,所引發(fā)的一系列問題。

最典型的情況是,某個(gè)應(yīng)用程序?qū)⒁惭b一個(gè)新版本的共享組件,而該組件與機(jī)器上的現(xiàn)有版本不向后兼容。雖然剛安裝的應(yīng)用程序運(yùn)行正常,但原來依賴前一版本共享組件的應(yīng)用程序也許已無(wú)法再工作。在某些情況下,問題的起因更加難以預(yù)料。比如,當(dāng)用戶瀏覽某些web站點(diǎn)時(shí)會(huì)同時(shí)下載某個(gè)Microsoft ActiveX控件。如果下載該控件,它將替換機(jī)器上原有的任何版本的控件。如果機(jī)器上的某個(gè)應(yīng)用程序恰好使用該控件,則很可能也會(huì)停止工作。

在許多情況下,用戶需要很長(zhǎng)時(shí)間才會(huì)發(fā)現(xiàn)應(yīng)用程序已停止工作。結(jié)果往往很難記起是何時(shí)的機(jī)器變化影響到了該應(yīng)用程序。

這些問題的原因是應(yīng)用程序不同組件的版本信息沒有由系統(tǒng)記錄或加強(qiáng)。而且,系統(tǒng)為某個(gè)應(yīng)用程序所做的改變會(huì)影響機(jī)器上的所有應(yīng)用程序現(xiàn)在建立完全從變化中隔離出來的應(yīng)用程序并不容易。

三、           靜態(tài)鏈接庫(kù)的創(chuàng)建與使用

在此通過一個(gè)實(shí)例來介紹靜態(tài)庫(kù)的創(chuàng)建與使用。在該實(shí)例中,我們將一個(gè)實(shí)現(xiàn)兩整數(shù)相加求和的函數(shù)封裝到靜態(tài)庫(kù)中供其他程序調(diào)用。

1.     創(chuàng)建

首先,使用Visual Studio 2008來創(chuàng)建一個(gè)帶預(yù)編譯頭的靜態(tài)庫(kù)項(xiàng)目Static,該項(xiàng)目包含在名為Library的解決方案中。

1.1.          創(chuàng)建靜態(tài)庫(kù)項(xiàng)目

創(chuàng)建一個(gè)不帶預(yù)編譯頭的靜態(tài)鏈接庫(kù)項(xiàng)目有以下幾個(gè)步驟:

(1)     單擊菜單命令 “文件”-“新建”-“項(xiàng)目”,彈出“新建項(xiàng)目”對(duì)話框;
(2)     在彈出的“新建項(xiàng)目”對(duì)話框中,選擇左邊“類別”列表中選擇 Visual C++-Win32”,在右邊的“模版”中選擇“Win32項(xiàng)目”;
(3)     在下方輸入項(xiàng)目名稱“Static”,并選擇項(xiàng)目創(chuàng)建的位置,勾選“生成解決方案”,并輸入解決方案名稱“Library”,然后點(diǎn)擊“確定”按鈕;
(4)     點(diǎn)擊兩次“下一步”按鈕,進(jìn)入“應(yīng)用程序設(shè)置”界面;
(5)     在“應(yīng)用程序設(shè)置”界面中,選擇“靜態(tài)庫(kù)”,并確保下方“附加選項(xiàng)”中的“預(yù)編譯頭”被勾選,然后點(diǎn)擊“完成”按鈕。

1.2.          編輯項(xiàng)目

經(jīng)過上面的步驟,初步創(chuàng)建了一個(gè)帶預(yù)編譯頭的靜態(tài)庫(kù)項(xiàng)目,接下來編輯該項(xiàng)目以達(dá)到我們的創(chuàng)建靜態(tài)庫(kù)的目的。

首先添加一個(gè)用于定義導(dǎo)出函數(shù)的源文件Static.cpp,編碼實(shí)現(xiàn)兩個(gè)整數(shù)相加的Add函數(shù)。源文件代碼如下:

#include “StdAfx.h”     // 標(biāo)準(zhǔn)頭文件

 

int Add(int a, int b)

{

    return a + b;

}

 

接著點(diǎn)擊菜單命令,“工具”-“生成Static”。如果一切順利的話,就會(huì)在解決方案的“Debug”目錄中生成了名為“Static.lib”的靜態(tài)鏈接庫(kù)。

同時(shí),需要給該靜態(tài)鏈接庫(kù)編寫一個(gè)聲明頭文件Static.h,以便在鏈接時(shí)告知編譯該鏈接庫(kù)中的導(dǎo)出函數(shù)聲明。Static.h中的代碼很簡(jiǎn)單,只要聲明一下Add函數(shù)就可以:

#ifndef __STATIC_H__              // 防止該頭文件重復(fù)引用

#define __STATIC_H__

 

int Add(int a, int b);                   // 聲明導(dǎo)出函數(shù)

 

#endif

 

接著點(diǎn)擊菜單命令,“工具”-“生成Static”。如果一切順利的話,就會(huì)在Library解決方案的Debug目錄中生成了名為MyDLL.lib的靜態(tài)鏈接庫(kù)。

 

2.     使用

Library解決方案下,再添加一個(gè)Win32控制臺(tái)應(yīng)用程序空項(xiàng)目UseLIB。程序主文件名為UseLIB.cpp,其中包含用于調(diào)用Add函數(shù)的程序入口函數(shù)main。將剛才創(chuàng)建的Static.lib及其聲明頭文件Static.h一同復(fù)制到UseLIB項(xiàng)目目錄下。并在源文件UseLIB.cpp中使用預(yù)編譯命令鏈接Static.lib(也可以在IDE的項(xiàng)目屬性中設(shè)置鏈接器選項(xiàng),或者只復(fù)制Static.h文件并設(shè)置UseLIB項(xiàng)目的“項(xiàng)目依賴項(xiàng)”為Static項(xiàng)目)。

源文件UseLIB.cpp中的代碼如下:

#pragma comment(lib, “Static.lib”)         // 鏈接靜態(tài)庫(kù)Static.lib

 

#include <stdio.h>

#include “Static.h”      // 包含Static.lib的聲明頭文件,聲明導(dǎo)出函數(shù)Add

 

int main(void)

{

       int a = 1, b = 2;

 

    printf(“%d+%d=%d\n”, a, b, Add(a, b));      // 調(diào)用Static.lib中的Add函數(shù)

   

    return 0;

}

接下來點(diǎn)擊菜單命令,“工具”-“生成UseLIB”。如果順利的話,就會(huì)在Library解決方案的Debug目錄中生成了名為UseLIB.exe的可執(zhí)行執(zhí)文件,運(yùn)行UseLIB.exe,將在控制臺(tái)中輸出結(jié)果:1+2=3

3.     注意

由于項(xiàng)目中創(chuàng)建的源文件為.CPP文件,即C++源文件,因此Visual C++C++規(guī)范,并采用__cdecl調(diào)用約定對(duì)其進(jìn)行編譯。這樣得到的導(dǎo)出函數(shù)就不能被C語(yǔ)言程序所調(diào)用。解決該問題的辦法是在函數(shù)體名稱前添加extern “C”修飾,告訴編譯器,該函數(shù)按照C語(yǔ)言規(guī)范,并采用__cdecl調(diào)用約定進(jìn)行編譯。因此源文件Add.cpp中的代碼可修改如下:

extern “C” int add(int a, int b)

{

    return a + b;

}

最后重新編譯該靜態(tài)鏈接庫(kù)項(xiàng)目,導(dǎo)出函數(shù)Add就能夠被C語(yǔ)言程序所調(diào)用了。

另一種不改變代碼的方法是在“Static屬性頁(yè)”左邊的列表中選擇“配置屬性”-C/C++-“高級(jí)”,然后在右邊的“調(diào)用約定”選擇“__cdecl (/Gd)”,“編譯為”選擇“編譯為C++代碼 (/TP)”。這種方法在不同的IDE上設(shè)置方法有所不同。

四、           動(dòng)態(tài)鏈接庫(kù)的創(chuàng)建與使用

在此同樣通過一個(gè)實(shí)例來介紹動(dòng)態(tài)鏈接庫(kù)的創(chuàng)建與使用。在實(shí)例中,依然使用Add函數(shù)進(jìn)行講解,這樣一方面可以沿用上面靜態(tài)鏈接的有關(guān)內(nèi)容,另一方面也可以了解動(dòng)態(tài)鏈接庫(kù)與靜態(tài)鏈接庫(kù)在創(chuàng)建和使用上的異同。

1.     創(chuàng)建

首先,在之前創(chuàng)建的Library解決方案中添加一個(gè)帶預(yù)編譯頭的動(dòng)態(tài)鏈接庫(kù)項(xiàng)目,項(xiàng)目名稱為Dynamic。使用不同IDE的朋友可以根據(jù)實(shí)際情況進(jìn)行創(chuàng)建。

1.1.          創(chuàng)建動(dòng)態(tài)鏈接庫(kù)項(xiàng)目

創(chuàng)建一個(gè)帶預(yù)編譯頭的動(dòng)態(tài)鏈接庫(kù)項(xiàng)目有以下幾個(gè)步驟:

(1)     單擊菜單命令 “文件”-“新建”-“項(xiàng)目”,彈出“新建項(xiàng)目”對(duì)話框;
(2)     在彈出的“新建項(xiàng)目”對(duì)話框中,選擇左邊列表的“Visual C++Win32”,在右邊的項(xiàng)目模版中選擇“Win32項(xiàng)目”;
(3)     在下方輸入項(xiàng)目名稱“Dynamic”,并選擇項(xiàng)目創(chuàng)建的位置,然后點(diǎn)擊“確定”按鈕;
(4)     點(diǎn)擊兩次“下一步”按鈕,進(jìn)入“應(yīng)用程序設(shè)置”界面;
(5)     在“應(yīng)用程序設(shè)置”界面中,選擇“DLL”,然后點(diǎn)擊“完成”按鈕。

1.2.          編輯項(xiàng)目

Dynamic項(xiàng)目自動(dòng)生成的dllmain.cpp源文件含有一個(gè)名為DllMain的函數(shù),該函數(shù)是DLL被鏈接時(shí)的入口函數(shù),它由系統(tǒng)自動(dòng)調(diào)用,在這里我們不用去理會(huì)它。

與前面創(chuàng)建靜態(tài)態(tài)鏈接庫(kù)類似的,首先添加一個(gè)用于定義導(dǎo)出函數(shù)的源文件Dynamic.cpp,編碼實(shí)現(xiàn)兩個(gè)整數(shù)相加的Add函數(shù)。源文件代碼如下:

extern “C” __declspec(dllexport) int Add(int a, int b)    // 聲明為DLL導(dǎo)出函數(shù)

{

    return a + b;

}

 

與前面靜態(tài)鏈接庫(kù)不同,在Add函數(shù)體名稱前不只添加了extern “C”修飾,還多添加了一個(gè) __declspec(dllexport)修飾。__declspec(dllexport)修飾的作用是告訴編譯器,這個(gè)函數(shù)將作為導(dǎo)出函數(shù),并在輸入庫(kù)中生成該函數(shù)的地址符號(hào)等信息,這樣其他程序就可以使用載入時(shí)動(dòng)態(tài)鏈接方式來調(diào)用該函數(shù)。另外,extern “C”在封裝DLL還有另一個(gè)作用,就是告訴編譯器,在DLL中的導(dǎo)出函數(shù)不要使用函數(shù)名修飾規(guī)則,這樣在采用運(yùn)行時(shí)動(dòng)態(tài)鏈接時(shí)就可以直接使用原函數(shù)名來調(diào)用導(dǎo)出函數(shù)了。關(guān)于函數(shù)調(diào)用方式和導(dǎo)出方式的詳細(xì)說明在后面還將提出,現(xiàn)在先撇開這些煩人的問題。

接著點(diǎn)擊菜單命令,“工具”-“生成Dynamic”。如果一切順利的話,就會(huì)在Library解決方案的Debug目錄中生成了名為Dynamic.dll的動(dòng)態(tài)鏈接庫(kù)和名為Dynamic.lib的導(dǎo)入庫(kù)(與靜態(tài)鏈接庫(kù)不同,只存放DLL的導(dǎo)出表,不包含代碼)。

最后需要給該Dynamic.dll的輸入庫(kù)Dynamic.lib編寫一個(gè)聲明頭文件Dynamic.h,以便在以后鏈接時(shí)告知編譯器該鏈接庫(kù)中的具體的導(dǎo)入內(nèi)容(一般包括代碼和資源)。Dynamic.h中的代碼很簡(jiǎn)單,只要聲明一下Add函數(shù)就可以:

#ifndef __DYNAMIC_H__   // 防止該頭文件重復(fù)引用

#define __DYNAMIC_H__

 

__declspec(dllexport) int Add(int a, int b);                    // 聲明導(dǎo)出函數(shù)

 

#endif

2.     使用

在同Library解決方案中,添加一個(gè)名為UseDLLWin32控制臺(tái)應(yīng)用程序空項(xiàng)目。程序主文件名為UseDLL.cpp,其中包含用于調(diào)用Add函數(shù)的程序入口函數(shù)main。一下使用兩種動(dòng)態(tài)鏈接方式來鏈接Dynamic.dll。

2.1.          載入時(shí)動(dòng)態(tài)鏈接

載入時(shí)動(dòng)態(tài)鏈接是一種輕松使用動(dòng)態(tài)鏈接庫(kù)的方法,它使得使用動(dòng)態(tài)鏈接庫(kù)如同使用靜態(tài)鏈接庫(kù)一樣方便。將導(dǎo)入庫(kù)Dynamic.lib及其聲明頭文件Dynamic.h一同復(fù)制到UseDLL項(xiàng)目目錄下,并把Dynamic.dll復(fù)制到項(xiàng)目的Debug目錄中。并在源文件UseDLL.cpp中使用預(yù)編譯命令鏈接Dynamic.lib(也可以在IDE的項(xiàng)目屬性中設(shè)置鏈接器選項(xiàng),或者只復(fù)制Dynamic.h文件并設(shè)置UseDLL項(xiàng)目的“項(xiàng)目依賴項(xiàng)”為Dynamic項(xiàng)目)。

源文件UseDLL.cpp中的代碼如下:

#pragma comment(lib, “Dynamic.lib”)    // 鏈接導(dǎo)入庫(kù)Dynamic.lib

 

#include <stdio.h>

#include “Dynamic.h”  // 包含Dynamic.lib的聲明頭文件,提供導(dǎo)出函數(shù)Add的聲明

 

int main(void)

{

       int a = 1, b = 2;

 

printf(“%d+%d=%d\n”, a, b, Add(a, b));      // 調(diào)用Dynamic.DLL中的Add函數(shù)

getchar();                                                       // 用于查看輸出結(jié)果

 

    return 0;

}

幾乎跟使用靜態(tài)鏈接庫(kù)一樣。接下來點(diǎn)擊菜單命令,“工具”-“生成UseDLL”。如果一切順利的話,就會(huì)在Library解決方案的Debug目錄中生成了名為UseDLL.exe的可執(zhí)行文件,運(yùn)行UseDLL.exe文件,將在控制臺(tái)中輸出結(jié)果:1+2=3

2.2.          運(yùn)行時(shí)動(dòng)態(tài)鏈接

運(yùn)行時(shí)動(dòng)態(tài)鏈接的代碼相對(duì)麻煩些,需要使用到Windows的三個(gè)API函數(shù),還要進(jìn)行一些判斷以防止不必要的麻煩。我們?cè)?/span>UseDLL項(xiàng)目的基礎(chǔ)上做些修改來實(shí)現(xiàn)運(yùn)行時(shí)動(dòng)態(tài)鏈接。這里只需要把Dynamic.dll復(fù)制到UseDLL項(xiàng)目的Debug目錄中,因?yàn)椴挥迷诰幾g的時(shí)候鏈接導(dǎo)入庫(kù),只要在運(yùn)行根據(jù)需要鏈接Dynamic.dll。下面先給出修改后的源文件Dynamic.cpp的代碼:

#include <windows.h>       // 用于聲明window API函數(shù)及宏等

#include <stdio.h>

 

typedef int (* FuncAdd)(int a, int b);       // 定義將要調(diào)用的導(dǎo)出函數(shù)Add的指針類型

 

int main(void)

{

       FuncAdd Add;       // 定義Add函數(shù)指針

       int a = 1, b = 2;

       HMODULE hDLL = LoadLibrary(TEXT("MyDLL.dll"));    // 載入DLL,并獲取其句柄

 

       if (hDLL)        // MyDLL.dll載入成功

       {

              Add = (FuncAdd)GetProcAddress(hDLL, "Add");    // 獲取導(dǎo)出函數(shù)Add指針

              if (Add)          // 正確獲取Add函數(shù)指針

              {

                     printf("%d+%d=%d\n", a, b, Add(a, b));      // 調(diào)用導(dǎo)出函數(shù)Add

              }

              else               // 沒有找到Add函數(shù)

              {

                     printf("Add Not Found!\n");

              }

       }

       else        // MyDLL.dll載入失敗

       {

              printf("LoadLibrary Failed!\n");

       }

 

       getchar();

       FreeLibrary((TEXT("MyDLL.dll"));      // 釋放DLL

       return 0;

}

看到了吧,調(diào)用方法比較繁瑣。由于沒有鏈接導(dǎo)入庫(kù),不能使用地址符號(hào)定位導(dǎo)出函數(shù)的入口地址,只能通過GetProcAdress來獲取其在地址空間中的指針,再通過指針調(diào)用。但程序在運(yùn)行之前,GetProcAdress無(wú)法判斷指針的有效性。因此,為了防止Dynamic項(xiàng)目中不存在Add函數(shù)而使程序在運(yùn)行時(shí)出錯(cuò),有必要在調(diào)用Add之前判斷其函數(shù)指針的有效性。

最后,點(diǎn)擊菜單命令,“工具”-“重新生成UseDLL”。如果一切順利的話,就會(huì)在Library解決方案的Debug目錄中生成了名為UseDLL.exe的可執(zhí)行文件,運(yùn)行UseDLL.exe文件,將在控制臺(tái)中輸出結(jié)果:1+2=3

五、           補(bǔ)充閱讀

1.     Visual Studio 2008DLL項(xiàng)目只生成dll文件不生成lib文件的解決方案

在創(chuàng)建動(dòng)態(tài)鏈接庫(kù)項(xiàng)目時(shí),如果在“應(yīng)用程序設(shè)置”中只勾選“預(yù)編譯頭”,而沒有勾選“空項(xiàng)目”或“導(dǎo)出符號(hào)”,那么在對(duì)該項(xiàng)目進(jìn)行編譯鏈接時(shí)將只會(huì)生成動(dòng)態(tài)鏈接庫(kù)dll文件,不生成導(dǎo)入庫(kù)lib文件。此問題的解決辦法如下:

(1)     右鍵單擊該項(xiàng)目,在彈出的菜單中點(diǎn)擊“添加”-“新建項(xiàng)”;
(2)     在彈出的“添加新項(xiàng)”對(duì)話框中,在左邊“類別”列表中選擇“Visual C++-“代碼”,在右邊“模版”中選擇“模塊定義文件(.def)”;
(3)     在“名稱”輸入框中輸入任意名稱,這里使用該項(xiàng)目名稱,如“MyDLL”,單擊“確定”按鈕;
(4)     重新生成該項(xiàng)目,即生成lib文件。

注意,此時(shí)在項(xiàng)目屬性的配置列表“配置屬性”-“鏈接器”-“輸入”中的“模塊定義文件”項(xiàng)目中將出現(xiàn)剛才創(chuàng)建的模塊定義文件MyDLL.def。如果此前不是添加“新建項(xiàng)”,而是添加“現(xiàn)有項(xiàng)”,那么必須在此項(xiàng)目上填寫該現(xiàn)有模塊定義文件的文件名,否則將不會(huì)生成lib文件。

2.     動(dòng)態(tài)鏈接庫(kù)的應(yīng)用舉例

(1)     所有的Windows系統(tǒng)調(diào)用(Windows API函數(shù))都是以動(dòng)態(tài)鏈接庫(kù)的形式提供的。我們?cè)?/span>Windows目錄下的system32文件夾中會(huì)看到kernel32.dll、user32.dllgdi32.dllwindows的大多數(shù)API都包含在這些DLL中。kernel32.dll中的函數(shù)主要處理內(nèi)存管理和進(jìn)程調(diào)度;user32.dll中的函數(shù)主要控制用戶界面;gdi32.dll中的函數(shù)則負(fù)責(zé)圖形方面的操作。與這些動(dòng)態(tài)庫(kù)相對(duì)應(yīng)的導(dǎo)入庫(kù)分別為kernel32.lib、user32.libgdi32.lib
(2)     軟件的自動(dòng)更新。Windows應(yīng)用的開發(fā)者常常利用動(dòng)態(tài)鏈接庫(kù)來分發(fā)軟件更新。他們生成一個(gè)動(dòng)態(tài)庫(kù)的新版本,然后用戶可以下載,并用它替代當(dāng)前的版本。當(dāng)然,新、舊版本動(dòng)態(tài)庫(kù)的輸出接口(即導(dǎo)出函數(shù))必須一致。下一次用戶運(yùn)行應(yīng)用程序時(shí),應(yīng)用將自動(dòng)鏈接和加載新的動(dòng)態(tài)庫(kù)。
(3)     軟件插件技術(shù)。許多Windows應(yīng)用軟件都支持插件擴(kuò)展方式,如IE瀏覽器、Photoshop、Office等等。插件在本質(zhì)上都是動(dòng)態(tài)庫(kù)。
(4)     可擴(kuò)展的Web服務(wù)器。
(5)     每個(gè)Windows驅(qū)動(dòng)程序在本質(zhì)上都是動(dòng)態(tài)鏈接庫(kù)。

 

3.     __declspec(dllexport) .def文件

32 位編譯器版本中,可以使用__declspec(dllexport)關(guān)鍵字從DLL導(dǎo)出數(shù)據(jù)、函數(shù)、類或類成員函數(shù)。__declspec(dllexport)將導(dǎo)出指令添加到對(duì)象文件(obj文件),若要導(dǎo)出函數(shù),__declspec(dllexport)關(guān)鍵字必須出現(xiàn)在調(diào)用約定關(guān)鍵字的左邊(如果指定了關(guān)鍵字)。例如:

__declspec(dllexport) void __cdecl Function1(void);

若要導(dǎo)出類中的所有公共數(shù)據(jù)成員和成員函數(shù),關(guān)鍵字必須出現(xiàn)在類名的左邊,如下所示:

class __declspec(dllexport) CExampleExport : public CObject

{ ... class definition ... };

生成DLL時(shí),通常創(chuàng)建一個(gè)包含正在導(dǎo)出的函數(shù)原型和/或類的頭文件,并將__declspec(dllexport)添加到頭文件中的聲明。

若要提高代碼的可讀性,請(qǐng)為__declspec(dllexport)定義一個(gè)宏并對(duì)正在導(dǎo)出的每個(gè)符號(hào)使用該宏:

#define Export __declspec(dllexport)

模塊定義文件(.def)是包含一個(gè)或多個(gè)描述各種DLL屬性的模塊語(yǔ)句的文本文件。二者的目的都是將公共符號(hào)導(dǎo)入到應(yīng)用程序中或從 DLL 導(dǎo)出函數(shù)。添加__declspec(dllexport)是為了提供不使用.def文件從 .EXE .DLL導(dǎo)出函數(shù)的簡(jiǎn)單方法。如果不使用__declspec (dllimport)__declspec(dllexport)導(dǎo)出DLL函數(shù),則DLL需要.def文件。

并不是任何時(shí)候選擇添加__declspec(dllexport)而放棄.def的方式都是好的。如果DLL是提供給VC++用戶使用的,只需要把編譯DLL時(shí)產(chǎn)生的.lib提供給用戶,它可以很輕松地調(diào)用你的DLL。但是如果DLL是供VBPB、Delphi用戶使用的,那么會(huì)產(chǎn)生一個(gè)小麻煩。因?yàn)?/span>VC++對(duì)于__declspec(dllexport) 聲明的函數(shù)會(huì)進(jìn)行名稱轉(zhuǎn)換,比如函數(shù):

__declspec(dllexport) int __stdcall IsWinNT()

會(huì)轉(zhuǎn)換為IsWinNT@0,這樣你在VB中必須這樣聲明:

Declare Function IsWinNT Lib "my.dll" Alias "IsWinNT@0" () As Long

@的后面的數(shù)由于參數(shù)類型不同而可能不同。這顯然不太方便。所以如果要想避免這種轉(zhuǎn)換,就要使用.def文件方式。

4.     函數(shù)調(diào)用約定(Calling Convention

函數(shù)調(diào)用約定不僅決定了發(fā)生函數(shù)調(diào)用時(shí)函數(shù)參數(shù)的入棧順序,還決定了是由調(diào)用者函數(shù)還是被調(diào)用函數(shù)負(fù)責(zé)清除棧中的參數(shù),還原堆棧。函數(shù)調(diào)用約定有很多方式,除了常見的__cdecl,__fastcall__stdcall之外,C++的編譯器還支持thiscall方式,不少C/C++編譯器還支持naked call方式。這么多函數(shù)調(diào)用約定常常令許多程序員很迷惑,到底它們是怎么回事,都是在什么情況下使用呢?下面就分別介紹這幾種函數(shù)調(diào)用約定。

4.1.          各種調(diào)用約定

4.1.1           __cdecl

編譯器的命令行參數(shù)是/Gd,__cdeclC Declaration的縮寫,是CC++程序的缺省調(diào)用方式。所有參數(shù)從右到左依次入棧,這些參數(shù)由調(diào)用者清除,每一個(gè)調(diào)用它的函數(shù)都包含清空堆棧的代碼,稱為手動(dòng)清棧。所以產(chǎn)生的可執(zhí)行文件大小會(huì)比調(diào)用__stdcall函數(shù)的大。被調(diào)用函數(shù)無(wú)需要求調(diào)用者傳遞多少參數(shù),調(diào)用者傳遞過多或者過少的參數(shù),甚至完全不同的參數(shù)都不會(huì)產(chǎn)生編譯階段的錯(cuò)誤。所有非C++成員函數(shù)和那些沒有用__stdcall__fastcall聲明的函數(shù)都默認(rèn)是__cdecl方式,它使用C函數(shù)調(diào)用方式。

4.1.2           __stdcall

__stdcallStandard Call的縮寫,是Pascal程序的缺省調(diào)用方式,通常用于Win32 API中,是WIN32 API的標(biāo)準(zhǔn)調(diào)用方式:所有參數(shù)從右到左依次入棧,如果是調(diào)用類成員的話,最后一個(gè)入棧的是this指針。這些堆棧中的參數(shù)由被調(diào)用的函數(shù)在返回后清除,使用的指令是 retn XX表示參數(shù)占用的字節(jié)數(shù),CPUret之后自動(dòng)彈出X個(gè)字節(jié)的堆??臻g。稱為自動(dòng)清棧。函數(shù)在編譯的時(shí)候就必須確定參數(shù)個(gè)數(shù),并且調(diào)用者必須嚴(yán)格的控制參數(shù)的生成,不能多,不能少,否則返回后會(huì)出錯(cuò)。

4.1.3           __fastcall

__fastcall 是編譯器指定的快速調(diào)用方式。由于大多數(shù)的函數(shù)參數(shù)個(gè)數(shù)很少,使用堆棧傳遞比較費(fèi)時(shí)。因此__fastcall通常規(guī)定將前兩個(gè)(或若干個(gè))參數(shù)由寄存器傳遞,其余參數(shù)還是通過堆棧傳遞。不同編譯器編譯的程序規(guī)定的寄存器不同。返回方式和__stdcall相當(dāng)。

4.1.4           __thiscall

__thiscall 是為了解決類成員調(diào)用中this指針傳遞而規(guī)定的。__thiscall要求把this指針放在特定寄存器中,該寄存器由編譯器決定。VC使用ecx,BorlandC++編譯器使用eax。返回方式和__stdcall相當(dāng)。

4.1.5           naked call

     采用前面幾種函數(shù)調(diào)用約定的函數(shù),編譯器會(huì)在必要的時(shí)候自動(dòng)在函數(shù)開始添加保存ESI,EDI,EBX,EBP寄存器的代碼,在退出函數(shù)時(shí)恢復(fù)這些寄存器的內(nèi)容,使用naked call方式聲明的函數(shù)不會(huì)添加這樣的代碼,這也就是為什么稱其為naked的原因吧。naked call不是類型修飾符,故必須和_declspec共同使用。

 

4.2.          綜述

 

__fastcall __thiscall涉及的寄存器由編譯器決定,因此不能用作跨編譯器的接口。所以Windows上的COM對(duì)象接口都定義為__stdcall調(diào)用方式。

帶有可變參數(shù)的函數(shù)必須且只能使用__cdecl方式,例如下面的函數(shù):

int printf(char * fmtStr, ...);

int scanf(char * fmtStr, ...);

__stdcall__cdecl這兩個(gè)關(guān)鍵字看起來似乎很少和我們打交道,但是看了下面的定義(來自windef.h),你一定會(huì)覺得驚訝:

  #define CALLBACK    __stdcall

  #define WINAPI      __stdcall

  #define WINAPIV     __cdecl

  #define APIENTRY    WINAPI

  #define APIPRIVATE __stdcall

  #define PASCAL      __stdcall

  #define cdecl _cdecl

  #ifndef CDECL

  #define CDECL _cdecl

  #endif

  幾乎我們寫的每一個(gè)WINDOWS API函數(shù)都是__stdcall類型的,為什么?

  首先,我們談一下兩者之間的區(qū)別:

   WINDOWS的函數(shù)調(diào)用時(shí)需要用到棧。當(dāng)函數(shù)調(diào)用完成后,棧需要清除,這里就是問題的關(guān)鍵,如何清除?

    如果我們的函數(shù)使用了__cdecl,那么棧的清除工作是由調(diào)用者,用COM的術(shù)語(yǔ)來講就是客戶來完成的。這樣帶來了一個(gè)棘手的問題,不同的編譯器產(chǎn)生棧的方式不盡相同,那么調(diào)用者能否正常的完成清除工作呢?答案是不能。

    如果使用__stdcall,上面的問題就解決了,函數(shù)自己解決清除工作。所以,在跨開發(fā)平臺(tái)的調(diào)用中,我們都使用__stdcall(雖然有時(shí)是以WINAPI的樣子出現(xiàn)),如JNI。

    那么為什么還需要__cdecl呢?當(dāng)我們遇到這樣的函數(shù)如fprintf()它的參數(shù)是可變的,不定長(zhǎng)的,被調(diào)用者事先無(wú)法知道參數(shù)的長(zhǎng)度(如typedef int (*MYPROC)(LPTSTR, ...);),事后的清除工作也無(wú)法正常的進(jìn)行,因此,這種情況我們只能使用__cdecl。

到這里我們有一個(gè)結(jié)論,如果你的程序中沒有涉及可變參數(shù),最好使用__stdcall關(guān)鍵字。

5.     函數(shù)名字修飾(Decorated Name)規(guī)則

函數(shù)的名字修飾(Decorated Name)就是編譯器在編譯期間創(chuàng)建的一個(gè)字符串,用來指明函數(shù)的定義或原型。LINK程序或其他工具有時(shí)需要指定函數(shù)的名字修飾來定位函數(shù)的正確位置。多數(shù)情況下程序員并不需要知道函數(shù)的名字修飾,LINK程序或其他工具會(huì)自動(dòng)區(qū)分他們。當(dāng)然,在某些情況下需要指定函數(shù)的名字修飾,例如在C++程序中,為了讓LINK程序或其他工具能夠匹配到正確的函數(shù)名字,就必須為重載函數(shù)和一些特殊的函數(shù)(如構(gòu)造函數(shù)和析構(gòu)函數(shù))指定名字裝飾。另一種需要指定函數(shù)的名字修飾的情況是在匯編程序中調(diào)用CC++的函數(shù)。如果函數(shù)名字,調(diào)用約定,返回值類型或函數(shù)參數(shù)有任何改變,原來的名字修飾就不再有效,必須指定新的名字修飾。CC++程序的函數(shù)在內(nèi)部使用不同的名字修飾方式,下面將分別介紹這兩種方式。

5.1.          C編譯器的函數(shù)名修飾規(guī)則

對(duì)于__stdcall調(diào)用約定,編譯器和鏈接器會(huì)在輸出函數(shù)名前加上一個(gè)下劃線前綴,函數(shù)名后面加上一個(gè)“@”符號(hào)和其參數(shù)的字節(jié)數(shù),例如_functionname@number。__cdecl調(diào)用約定僅在輸出函數(shù)名前加上一個(gè)下劃線前綴,例如_functionname。__fastcall調(diào)用約定在輸出函數(shù)名前加上一個(gè)“@”符號(hào),后面也是一個(gè)“@”符號(hào)和其參數(shù)的字節(jié)數(shù),例如@functionname@number。

5.2.          C++編譯器的函數(shù)名修飾規(guī)則

 C++的函數(shù)名修飾規(guī)則有些復(fù)雜,但是信息更充分,通過分析修飾名不僅能夠知道函數(shù)的調(diào)用方式,返回值類型,參數(shù)個(gè)數(shù)甚至參數(shù)類型。不管__cdecl,__fastcall還是__stdcall調(diào)用方式,函數(shù)修飾都是以一個(gè)“?”開始,后面緊跟函數(shù)的名字,再后面是參數(shù)表的開始標(biāo)識(shí)和按照參數(shù)類型代號(hào)拼出的參數(shù)表。對(duì)于__stdcall方式,參數(shù)表的開始標(biāo)識(shí)是“@@YG”,對(duì)于__cdecl方式則是“@@YA”,對(duì)于__fastcall方式則是“@@YI”。參數(shù)表的拼寫代號(hào)如下所示:

代號(hào)       類型

X            void

D            char

E            unsigned char

F            short

H            int

I             unsigned int

J             long

K            unsigned long

M           float

N            double

_N          bool

O            long double

PA          指針前綴

AA          引用前綴

V類名@@    

 

指針的方式有些特別,用PA表示指針,用PB表示const類型的指針,如果是引用,則在類型代號(hào)前加上AA。后面的代號(hào)表明指針類型,如果相同類型的指針連續(xù)出現(xiàn),以“0”代替,一個(gè)“0”代表一次重復(fù)。如果相同類型的引用連續(xù)出現(xiàn),則以“1”代替,每個(gè)“1”都代表一次重復(fù)。U表示結(jié)構(gòu)類型,通常后跟結(jié)構(gòu)體的類型名,用“@@”表示結(jié)構(gòu)類型名的結(jié)束。函數(shù)的返回值不作特殊處理,它的描述方式和函數(shù)參數(shù)一樣,緊跟著參數(shù)表的開始標(biāo)志,也就是說,函數(shù)參數(shù)表的第一項(xiàng)實(shí)際上是表示函數(shù)的返回值類型。參數(shù)表后以“@Z”標(biāo)識(shí)整個(gè)名字的結(jié)束,如果該函數(shù)無(wú)參數(shù),則以“Z”標(biāo)識(shí)結(jié)束。下面舉三個(gè)個(gè)例子。

函數(shù)聲明:int Function1(char *var1,unsigned long);

函數(shù)修飾:?Function1@@YGHPADK@Z

函數(shù)聲明:void Function2();

函數(shù)修飾:?Function2@@YGXXZ

函數(shù)原型(Test為自定義類):

void abc(int a, long b, char* c, char* d, bool &e, Test f, short g);

函數(shù)修飾名:?abc@@YAXHJPAD0AA_NVTest@@F@Z

對(duì)于C++的類成員函數(shù)(其調(diào)用方式是thiscall),函數(shù)的名字修飾與非成員的C++函數(shù)稍有不同,首先就是在函數(shù)名字和參數(shù)表之間插入以“@”字符引導(dǎo)的類名;其次是參數(shù)表的開始標(biāo)識(shí)不同,公有(public)成員函數(shù)的標(biāo)識(shí)是“@@QAE”,保護(hù)(protected)成員函數(shù)的標(biāo)識(shí)是“@@IAE”,私有(private)成員函數(shù)的標(biāo)識(shí)是“@@AAE”,如果函數(shù)聲明使用了const關(guān)鍵字,則相應(yīng)的標(biāo)識(shí)應(yīng)分別為“@@QBE”,“@@IBE”和“@@ABE”。如果參數(shù)類型是類實(shí)例的引用,則使用“AAV1”,對(duì)于const類型的引用,則使用“ABV1”。下面就以類CTest為例說明C++成員函數(shù)的名字修飾規(guī)則:

class CTest

{

......

private:

     void Function(int);

protected:

     void CopyInfo(const CTest &src);

public:

     long DrawText(HDC hdc, long pos, const TCHAR* text, RGBQUAD color, BYTE bUnder, bool bSet);

     long InsightClass(DWORD dwClass) const;

......

};

 

對(duì)于成員函數(shù)Function,其函數(shù)修飾名為“?Function@CTest@@AAEXH@Z”,字符串“@@AAE”表示這是一個(gè)私有函數(shù)。成員函數(shù)CopyInfo只有一個(gè)參數(shù),是對(duì)類CTestconst引用參數(shù),其函數(shù)修飾名為“?CopyInfo@CTest@@IAEXABV1@@Z”。DrawText是一個(gè)比較復(fù)雜的函數(shù)聲明,不僅有字符串參數(shù),還有結(jié)構(gòu)體參數(shù)和HDC句柄參數(shù),需要指出的是HDC實(shí)際上是一個(gè)HDC__結(jié)構(gòu)類型的指針,這個(gè)參數(shù)的表示就是“PAUHDC__@@”,其完整的函數(shù)修飾名為“?DrawText@CTest@@QAEJPAUHDC__@@JPBDUtagRGBQUAD@@E_N@Z”。InsightClass是一個(gè)共有的const函數(shù),它的成員函數(shù)標(biāo)識(shí)是“@@QBE”,完整的修飾名就是“?InsightClass@CTest@@QBEJK@Z”。

無(wú)論是C函數(shù)名修飾方式還是C++函數(shù)名修飾方式均不改變輸出函數(shù)名中的字符大小寫,這和PASCAL調(diào)用約定不同,PASCAL約定輸出的函數(shù)名無(wú)任何修飾且全部大寫。

extern “C”使得其作用的函數(shù)采用C名字修飾方式進(jìn)行編譯。不要在C程序(源程序文件以.c作為后綴)中使用extern “C”,否則會(huì)出現(xiàn)錯(cuò)誤。因?yàn)?/span>C編譯器不認(rèn)識(shí)extern “C”。

5.3.          查看函數(shù)的名字修飾

有兩種方式可以檢查你的程序中的函數(shù)的名字修飾:使用編譯輸出列表或使用Dumpbin工具。使用/FAc,/FAs/FAcs命令行參數(shù)可以讓編譯器輸出函數(shù)或變量名字列表。使用dumpbin.exe /SYMBOLS命令也可以獲得obj文件或lib文件中的函數(shù)或變量名字列表。此外,還可以使用 undname.exe 將修飾名轉(zhuǎn)換為未修飾形式。

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
隱式鏈接無(wú).LIB動(dòng)態(tài)鏈接庫(kù)
動(dòng)態(tài)鏈接庫(kù)、靜態(tài)庫(kù)區(qū)別與VS2005項(xiàng)目相關(guān)設(shè)置
計(jì)算機(jī)圖形學(xué)和OpenGL(一)OpenGL初步
(轉(zhuǎn)載)VC編譯器命令行詳解
這個(gè)LIB庫(kù)如何用易語(yǔ)言調(diào)用|易語(yǔ)言俱樂部
Windows以函數(shù)的形式為應(yīng)用提供了各種功能 這些形式的函數(shù)稱(MessageBox()提供了顯示消息框的功能它并不是C語(yǔ)言的標(biāo)準(zhǔn)函數(shù)API的目標(biāo)文件存儲(chǔ)在名為DLL文件的特殊庫(kù)文件中)
更多類似文章 >>
生活服務(wù)
熱點(diǎn)新聞
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服