国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

動態鏈結函式庫(DLL-Dynamic Linked Library)

2019-11-17 05:47:39
字體:
來源:轉載
供稿:網友
前言
本章要介紹的是動態鏈結函式庫(Dynamic Linked Library,簡稱DLL)的撰寫、使用及相關主題。動態鏈結函式庫是Windows程式設計的一門重要領域,不信的話,你可以看看在Windows系統目錄下那些數量龐大的 .DLL檔案,它的重要性及使用頻率由此可見一般。
基本上,假如略去VCL軟體元件不談的話,在C++Builder中撰寫及使用DLL的方法是和傳統Windows SDK是一致的,然而如此一來C++Builder也就失去了它傲人的優勢了。因此在本章中我會為你介紹如何撰寫使用VCL元件的 DLL,同時也針對各種不同程式發展平臺如Visual C++, VB之間的DLL使用上應注重的事項,做一個全面的探討。
以C++Builder撰寫動態鏈結函式庫 (DLL)
動態鏈結函式庫(DLL-Dynamic Linked Library)(圖一)圖一 以C++Builder撰寫的About Dialog
圖一所展示的就是我所要撰寫的一個以VCL元件組合而成的About Dialog,如何?看起來是不是頗具商業軟體架勢呢?
C++Builder由於其先天上的優勢,因此在視覺化的程式設計領域游刃有馀。然而在現實的工作環境中,也許在你手中的專案并非使用C++Builder來撰寫,而是以其他程式工具如Visual C++,VB或是Borland C++完成的,假如要全部改寫原來的程式,不僅曠日廢時,而且可能老板也不答應,那麼該怎麼辦呢?對了,就是利用撰寫DLL的途徑來達到程式共享的目的,為了要讓傳統的Windows SDK程式設計人員也可以享受此一優勢,因此你可以將部份視覺程式設計部份以DLL完成,然後提供外部函式供他人呼叫,如此你就可以兼顧兩者,『執其兩端,用於其中』,而順利地解決問題了。
好了!廢話不多說了,現在開始進入正題吧!
建立DLL專案
建立DLL專案的方式和一般應用程式大致相同。同樣地你可以由 【File/New】來建立一個新的專案,然後選擇DLL類型的專案。如圖二所示:

動態鏈結函式庫(DLL-Dynamic Linked Library)(圖二)圖二 選擇DLL專案類型
建選擇完專案類型之後,它就自動為你產生了相關檔案。和應用程式不同的是,它只產生了一個PRoject檔,而不包含表格檔,而該檔案只是一個包含DLL進入點程式的空殼子,程式大致如下:
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*){return 1;}
DllEntryPoint是DLL內定的程式進入點,因為本程式中并不做任何處理,所以就直接return 1了。
加入TForm表格
為了要撰寫如圖一的About Dialog,毫無疑問地,我們必須加入一個TForm表格,因為建立DLL專案時,并未自動產生相關的TForm表格,所以你必須以手動方式加入。此時你可以【File/New Form】來加入一個表格。再來我們就可以用和一般應用程式設計相同的方式,加入必要的軟體元件,如圖叁所示。
動態鏈結函式庫(DLL-Dynamic Linked Library)(圖三)圖叁 在設計時期(Design Time)的 TForm。
你可以看到,我在程式中使用了叁個TPanel元件(除了標出來的之外,另外還有一個用來作為放置所有元件的平臺)。以及一個TImage元件,圖叁個的叁個Panel元件的樣子都不同,那是利用修改其 BevelInner,BevelOuter,BevelWidth來達成的,你可以試著去修改它,看看能否做出更好的效果。至於TImage是用來做為顯示那張雅典娜圖形的元件。
在安排好了所有元件的位置之後,我們再設定所有元件的OnClick事件處理函式,讓它可以在使用者按下滑鼠時,關掉該交談窗。這個事件處理函式很簡單,只有短短的一行。
void __fastcall TForm1::Image1Click(TObject *Sender){Close();}
好了,至此我們已完成加入表格的程序。
撰寫輸出函式(EXPort Function)
在完成的表格的設計後,再來我們就要撰寫輸出函式,該外部程式可以利用呼叫該函式的方式顯示這個表格。我們的輸出函式定義如下:extern "C" void _stdcall ShowImage(void);
其中 extern "C" 是用來告訴編譯器,以C的方式來命名,而不要以C++ 的命名法,因為C++ 的命名法會在函式名稱後加上參數型態等裝飾字,如此會造成其他程式如VC++,VB等無法使用的困擾。另外 __stdcall是用來表示它使用的參數傳入方法。我們在後續單元會針對以上兩者做更為深入的介紹。
再來我們來看函式本身,這個函式很簡單,只是利用new動態產生一個表格,然後利用ShowModal來顯示該表格,ShowModal會一直等到使用者按Click之後才關掉表格,此時我們再以delete指令來釋放占用的記憶體。
void _export _stdcall ShowImage(void){
Form1 = new TForm1(NULL);Form1->ShowModal();delete Form1;}
在完成以上程式之後,你就可以編譯程式。此時C++Builder會產生一個DLL檔,以本程式而言,它會產生一個DLLSAMP.DLL檔案,而這個就是供外部呼叫的動態鏈結函式庫。
在C++Builder中使用DLL
再來我要告訴你如何使用動態鏈結函式庫。我們以前面所產生的DLL為例。使用DLL有兩種方式,分別為明確呼叫及不明確呼叫。
我先說明不明確呼叫的使用方式。不明確呼叫指的是,在程式中并沒有一行程式是用來載入DLL,而是利用鏈結一個記載輸入函式的函式庫檔案(LIB),來進行鏈結,如此系統會自動將該DLL載入,同時在使用完畢後將其釋放,不必由使用者(也就是呼叫它的函式)來進行載入及釋放的動作。
首先必須產生一個LIB檔,你可以利用C++Builder程式目錄內的IMPLIB.EXE來產生該檔案,切忌勿使用Visual C++ 的IMPLIB.EXE,因為Microsoft所使用的格式是COFF格式的LIB檔,而Borland所使用的格式是OMF格式的LIB檔。(同樣地,若是你的LIB檔是要給Visual C++ 鏈結用的,那就要使用它所附的IMPLIB.EXE,在使用時不可不察)。因此我們可用以下指令產生DLLSAMP.LIB檔。
IMPLIB DLLSAMP.LIB DLLSAMP.DLL
如此你就可以得到供程式鏈結用DLLSAMP.LIB檔了。
接著我們來撰寫使用該DLL的范例程式。這個程式相當簡單,我只在表格中放置一個Button,然後撰寫該Button的OnClick事件處理函式,使其呼叫ShowImage函式即可。
有一點要注重的是,你必須將先前產生的DLLSAMP.LIB加入此專案中,利用 【Project/Add to Project】選擇LIB型態檔案,即可將其加入。
最後我們就可以鏈結程式,以下為其執行結果。動態鏈結函式庫(DLL-Dynamic Linked Library)(圖四)圖四 執行結果。
動態鏈結函式庫徹底研究
在前面的范例中,我們已經示范了一個基礎dll的撰寫方式,然而那只能說是少部份的Know-How而已,接下來我想針對DLL做一個徹底的探討,企圖使您對它有一個全面的認知,同時也希望在Know-How之外,可以告訴你一些關於DLL的Know-Why。
DLL的生與死
DLL顧名思義,是一個可以動態鏈結的函式庫。這其中包含兩個意義。第一,它是動態鏈結的,也就是說它必須具有『招之即來,揮之即去』的基本特性,它只有在被需要的時候才會被載入系統中,而在不被需要時,即自系統中釋放。第二,它是一個函式庫,因此它的行為模式和一般的函式庫沒什麼不同,當它載入時,它就視同其他一般的函式般。
『招之即來,揮之即去』的DLL
前面我們提到,DLL必須具備『招之即來,揮之即去』的基本特性。那麼要如何載入及釋放DLL呢?關於此點,我們必須分為兩方面來探討;即所謂的明確呼叫及不明確呼叫。
明確呼叫(explicited linked):所謂明確呼叫(explicited linked)是使用LoadLibrary函式來載入 DLL。使用FreeLibrary函式來釋放 DLL。這種方式是由使用者主動透過LoadLibrary 載入該 DLL,然後以GetProcAddress來取得函式位址,再呼叫該函式。最後在不使用該DLL之後,再將其釋放。使用明確呼叫的優點在於,你可以完全控制該DLL的載入及釋放,最有效地利用系統資源:缺點則是,必須自行利用GetProcAddress來取得叫使用的函式位址,但也由於使用了GetProcAddress來取得函式位址,因此在使用上增加許多彈性。由於此種使用方式載入函式程式是主動且可見的,因此名之為明確呼叫。
不明確呼叫 ( implicited linked):所謂不明確呼叫則是利用鏈結DLL函式庫所相對應的輸出函式庫 ( export library),來達成呼叫函式的目的。因此載入DLL以及釋放DLL的程序是不可見的,當使用該輸出函式庫的程式載入後,系統即將該DLL載入,當使用該輸出函式的程式結束後,系統即將該DLL釋放。使用不明確呼叫的優點在於,使用者可以完全不必顧慮到函式的載入及釋放相關問題,使用時就如同一般的靜態函式般。由於此種使用方式載入函式是非主動且不可見的,因此名之為不明確呼叫。
DLL的使用次數 (Usage Count)
前面提到的載入及釋放其實在定義上是不明確的。為什麼呢?因為DLL的載入及釋放尚牽涉到多行程使用時的載入及釋放。由於DLL是動態鏈結的,因此可以同時有許多程式在使用同一個 DLL,舉例來說:若一個X.DLL同時被A、B、C叁個程式使用著,則X.DLL會被載入叁次。然而系統為了結省資源,當然不會重復載入,因此此時在系統內會有一個表格來記載X.DLL的使用次數。所以當A程式載入X.DLL後,B、C程式再次載入X.DLL時,此時X.DLL并沒有被重復地載入,系統只是將X.DLL的使用次數加一,然後將先前載入的X.DLL位址傳回給 B、C兩個程式使用,如此就可以達到共享函式庫的目的了。同樣地在釋放X.DLL時,若該DLL同時有多人使用時,系統純粹只是將該DLL的使用次數減一,當其使用次數等於0時,系統才會『真正』地將它由系統中釋放。否則若是系統不分青紅皂白即將DLL釋放,會造成系統的災難。
由以上可知,無論我們使用明確呼叫或是不明確呼叫,DLL的載入及釋放都和它的使用次數有關。所以DLL的生與死其實和它的使用次數有關,當它的使用次數不為0時,就表示其『陽壽未盡』,系統就會維持其活動狀態;反之,若其使用次數為0時,則表示它該『壽終正寢』了,系統就將其釋放,并回收其使用的資源。然而若使用該DLL的程式當掉,導致該DLL沒被釋放時,該DLL就會因為使用次數沒有被適時減少,而一直在系統內『陰魂不散』了。這種利用使用次數來治理共享資源的方法,也同時使用在OLE之中。
新知識的實踐
現在我們已了解DLL的使用,尚有另一種明確呼叫的方式,我們可以將前面的范例程式修改為使用明確呼叫的方法來使用 DLL。
void (*ShowImage)(void);void __fastcall TForm1::ShowButtonClick(TObject *Sender){HINSTANCE hInst;hInst = LoadLibrary("DLLSAMP.DLL");(FARPROC &)ShowImage=GetProcAddress(hInst,"ShowImage");ShowImage();FreeLibrary(hInst);}
以上就是修改後的程式,因為程式已改成明確呼叫的方式,因此不需要使用DLLSAMP.LIB了,所以關於BCB和VC所使用的LIB檔格式不同的問題也不存在了。在此我簡單地說明所使用的幾個函式
hInst = LoadLibrary("DLLSAMP.DLL") 是用來載入DLLSAMP.DLL ,同時傳回該DLL的HINSTANCE值,它是據以使用DLL的權杖。
(FARPROC &)ShowImage=GetProcAddress(hInst,"ShowImage") 利用前面得到的HINSTANCE值,呼叫GetProcAddress來得到ShowImage函式的位址,因為GetProcAddress所傳回的值為FARPROC ,因此我們必須做型別轉換。在此我是利用 (FARPROC &) 以reference做型別轉換。
FreeLibrary(hInst) 使用完後,利用FreeLibrary 將該DLL釋放。輸入函式及輸出函式的標準寫法
前面我們使用輸入函式及輸出函式時,為了簡化程式的寫法,因此使用了Borland為了和16位元程式相容而使用的 _export編譯指令,在此我必須指出,這種寫法是非標準的寫法,其實Microsoft在32位元程式中使用了另一種定義輸入函式及輸出函式的寫法,那才是一個放諸四海皆準的寫法,使用 _export式的舊有寫法在諸如Visual C++ 的編譯器中是無法通過編譯的。
在理論上,我們希望可以使用單一的要害字來定義一個輸出函式,就如同 _export一般,然而Microsoft卻在它的32位元程式中使用了另一種要害字來定義輸入及輸出函式,那就是 __declspec要害字,它可以傳入dllimport及dllexport兩個參數,用來分別代表輸入函式及輸出函式。
換句話說,若你要撰寫輸出函式,你必須使用 __declspec(dllexport) 來定義該函式,反之若你要使用輸入函式,則你必須使用 __declspec(dllimport) 來定義該函式。
因此由於輸入及輸出函式的使用方式不同,你必須使用兩個不同的include檔來分別定義之。若你不想如此麻煩,那麼就必須要使用巨集定義來達到一體多用的目的羅,這對少數人持反對論點的人來說,簡直是罪惡(還有人稱之為巨集巫毒--macro woodoo)。
Windows的實作名家Jeffrey Richter,也就是Advanced Windows的作者建議我們使用以下的方法來達到一體多用的效果(同樣是透過巨集巫毒)。
#ifndef _SHOWIMG_H_#define _SHOWIMG_H_
#ifndef IMGDLL#define EXTERN __declspec(dllimport)#else#define EXTERN __declspec(dllexport)#endifvoid EXTERN ShowImage(void);#endif
如此一來,當你在撰寫DLL時撰寫可以撰寫如下函式:
#define IMGDLL#include "image.h"
當使用者在使用DLL時,則只要直接含入image.h即可。如此一來算是解決了利用 __declspec(dllimport) 和 __declspec(dllexport) 的不便了。
  • 必也正名乎的DLL函式命名
  • 談完了標準寫法,再來我們要談談一個更輕易搞混的函式命名原則。本來在正常情況下,我們是不需要理會編譯器的函式命名規則的,因為在使用同一樣編譯器的情況下,不會有什麼太大的問題。然而問題來了,由於DLL是動態連結函式庫,因此它的目標就是希望可以讓多個程式共享程式及資源。所以若是DLL只能為同一種編譯器所使用,那麼它的用途就大打折扣了。因此我們還是必須了解函式的命名方法。同時由於函式命名方式在各種不同的編譯器各不相同,因此我們也必須了解其相異處,最重要的是,我們必須找出其溝通的方式。
C++ Builder的命名規則
  • 除了前面提到的 __declspec編譯指令之外,在C++ Builder尚有幾種修飾字會影響到函數的命名, 它們就是 __cdecl,__stdcall,__pascal,__fastcall四個修飾字。為了了解該修飾字對於函式命名的影響,我們可以用以下的程式來測試之:
#ifndef _DLLNAME01_H_#define _DLLNAME01_H_#ifndef DLLNAME#define EXTERN __declspec(dllimport)#else#define EXTERN __declspec(dllexport)#endifEXTERN void DllName01(void);EXTERN void _stdcall DllName02(void);EXTERN void _cdecl DllName03(void);EXTERN void _pascal DllName04(void);EXTERN void _fastcall DllName05(void);};#endif

以上為程式的定義,同時我們可以在 .CPP檔中撰寫相對應的空函式,然後將其編譯成DLL檔,再利用TDUMP.EXE或是VC++ 內的DUMPBIN.EXE來觀察其內容,由於TDUMP會將函式命名解碼,反而會使混淆原來的名稱,因此以下的輸出是由DUMPBIN.EXE得來。

函式定義 DLL內的函式名 摘要說明void DllName01(void) @DllName01$qv 因為是CPP程式碼void _stdcall DllName02(void) @DllName02$QQsv 所以函式名都被修void _cdecl DllName03(void) @DllName03$qv 飾過。void _pascal DllName04(void) @DLLNAME04$QVvoid _fastcall DllName05(void) @DllName05$qqrv
  • 以上結果是否令你丈二金鋼、摸不著頭緒。這是因為我們的程式名稱若以CPP為延伸名,C++Builder會以C++ 特有的命名方式來為函式命名,這種命名方式會在函式名稱後加上其使用參數的性質,如參數類別等。這在C++ 中有一個非凡的名稱,叫做mangled name,這是一種為了要實作出多載函式所發出的命名規則。(注:在C++ 中Add(int) 和Add(double) 可以同時存在,因此必須在object code區分之)。同時這種命名方式由於各個編譯器廠商使用的方式各不相同,因此在撰寫DLL時要避免使用之。為了要避開以上問題,我們改以下列的宣告方式:
#define _DLLNAME01_H_#ifndef DLLNAME#define EXTERN __declspec(dllimport)#else#define EXTERN __declspec(dllexport)#endifextern "C" {EXTERN void DllName011(void);EXTERN void _stdcall DllName022(void);EXTERN void _cdecl DllName033(void);EXTERN void _pascal DllName044(void);EXTERN void _fastcall DllName055(void);};#endif
其中extern "C" ; 是用來告訴編譯器使用C的命名方式,不要使用C++ 的mangled name。若是其中只有一個函式時,你可以直接以下列方式宣告之:extern "C" void __stdcall ShowImage();
現在我們可以檢視除去mangled name後的函式名稱:
函式定義 DLL內的函式名 摘要說明void DllName01(void) _DllName01 名稱加底線void _stdcall DllName02(void) DllName02 名稱未變void _cdecl DllName03(void) _DllName03 名稱加底線void _pascal DllName04(void) DLLNAME04 名稱大寫void _fastcall DllName05(void) @DllName05 名稱加@
以上我們可得知,在未加修飾字時和使用_cdecl修飾字時的名稱是一樣的。而 _pascal修飾字所產生的函式名則和16位元的標準DLL 函式名相同(這在VC++ 是不被接受的),__fastcall的函式名稱則加上 @。
其中在WIN32中使用最多的是 _stdcall修飾字,這也是你要撰寫一個可以和其他語言共同使用時所使用的修飾字,其次則為 __cdecl修飾字,這是用來傳送不定參數型別的函式如printf、sprintf等使用的。其馀兩者幾乎在DLL沒有機會使用。
結論:由上可知,在C++Builder中撰寫DLL時必須注重以下事項:
  1. 使用 __declspec(dllimport)及 __declspec(dllexport)的標準型式。
  2. 注重C++ 的函式名稱編碼(mangled name)。
  3. 注重修飾字的使用。除非使用不定參數的函式,否則必使用 __stdcall修
飾字。(4) 不要把 __declspec的使用和 __stdcall混淆了。此二者并沒有絕對的相關性。即使是程式老手都可能栽在此處,切記,切記!
怎麼樣,在看完了以上的介紹後,是否有晃然大悟的感覺。在了解以上的規則後,今後不論在撰寫或是使用DLL時遭遇連結的問題時,應該難不倒你吧!最後,我們將標準的DLL宣告方式列於後,以加深你的印象:
#ifndef _SHOWIMG_H_#define _SHOWIMG_H_#ifndef IMGDLL#define EXTERN __declspec(dllimport)#else#define EXTERN __declspec(dllexport)#endifextern "C" EXTERN void __stdcall ShowImage(void);#endif
語言雙雄' C++Builder 和Visual C++ 連結
前面我們已經把關於C++Builder撰寫DLL所應注重到的事項介紹完了,現在我們來談另一個重點 - C++Builder和Visual C++ 的連結。若是你沒有使用過Visual C++ 的話,可以將此部份略去。若是你在程式設計時必須使用到Visual C++ 的DLL或是必須提供DLL給VC++ 或是VB使用時,也許會帶給你意想不到的收獲。
VC++ 使用C++Builder的DLL函式

在Visual C++ 中使用C++Builder的DLL的函式方法和在C++Builder中使用大同小異,唯有幾件事情必須要注重。
(一)Visual C++ 的LIB檔格式和C++Builder的LIB格式不同,因此你必須重新產生一個 LIB。不過,可惜的是VC++ 在32位元的版本中并未提供IMPLIB.EXE函式(這點一直令許多人百思不解),因此你無法很方便地產生LIB檔。解決方法有二:其一是在VC++ 內撰寫一個同名稱的空的DLL函式,令其產生LIB檔,其二則是使用 LoadLibrary、GetProcAddress式的明確呼叫方式。(二)使用前面提到的標準寫法。
C++Builder中使用VC++ 的DLL函式
在C++Builder中使用VC++ 的DLL函式時要注重的是Microsoft在Visual C++ 中使用的非凡命名規則。在VC++ 中命名規則除了前面談到的幾項之外,它還使用了一個非凡的參數命名法,簡言之,就是在函數名稱後面加上參數的大小,這種命名方法會造成C++Builder,VB,Delphi使用的上的困擾。舉例來說
extern "C" _declspec(dllexport) void __stdcall ShowImage(void);
在VC++ 中產生的函式名稱為ShowImage@0(其中0表示參數大小),而不是如在C++Builder中產生的ShowImage,這是VC++ 已知的問題,這個問題也造成了很多使用non-VC++ 的使用者的問題,解決之道是在該DLL的DEF檔中加上以下的敘述EXPORTSShowImage=ShowImage@0如此便可以產生正確的函式名了,若是你不想修改DEF檔,你也可以在程式中加入以下的連結指引#pragma comment(linker,"/exports:ShowImage=ShowImage@0")假設你不確定其正確的名稱,可以利用DumpBin或是TDump觀察之。
以上是針對VC++ 的程式設計的所作的額外說明。最後我們以一個VC++ 程式呼叫本單元的About Dialog DLL做為結束。
動態鏈結函式庫(DLL-Dynamic Linked Library)(圖五)
此程式的要害程式碼如下:void CVcusedllApp::OnAppAbout(){void (*ShowImage)(void);HINSTANCE hInst;hInst = LoadLibrary("DLLSAMP2.DLL");(FARPROC &)ShowImage=GetProcAddress(hInst,"ShowImage");ShowImage();FreeLibrary(hInst);}


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 龙南县| 平安县| 广平县| 沙河市| 安图县| 泾川县| 仙桃市| 台江县| 七台河市| 德钦县| 承德市| 正镶白旗| 晋中市| 陇西县| 水城县| 景德镇市| 北票市| 托克逊县| 株洲市| 定州市| 彭州市| 大余县| 汝城县| 邳州市| 襄樊市| 长武县| 娄烦县| 张家口市| 榆中县| 昌吉市| 桦川县| 公主岭市| 静乐县| 施秉县| 绥棱县| 资源县| 海城市| 盘山县| 阿拉善盟| 晋城| 额敏县|