1. 程式人生 > >VC++動態連結庫(DLL)程式設計深入淺出(二)

VC++動態連結庫(DLL)程式設計深入淺出(二)

     (1)DLL匯出函式,可供應用程式呼叫;

  (2) DLL內部函式,只能在DLL程式使用,應用程式無法呼叫它們。

 而應用程式對本DLL的呼叫和對第2節靜態連結庫的呼叫卻有較大差異,下面我們來逐一分析。

  首先,語句typedef int ( * lpAddFun)(int,int)定義了一個與add函式接受引數型別和返回值均相同的函式指標型別。隨後,在main函式中定義了lpAddFun的例項addFun;

  其次,在函式main中定義了一個DLL HINSTANCE控制代碼例項hDll,通過Win32 Api函式LoadLibrary動態載入了DLL模組並將DLL模組控制代碼賦給了hDll;

  再次,在函式main中通過Win32 Api函式GetProcAddress得到了所載入DLL模組中函式add的地址並賦給了addFun。經由函式指標addFun進行了對DLL中add函式的呼叫;

 最後,應用工程使用完DLL後,在函式main中通過Win32 Api函式FreeLibrary釋放了已經載入的DLL模組。

 通過這個簡單的例子,我們獲知DLL定義和呼叫的一般概念:

  (1)DLL中需以某種特定的方式宣告匯出函式(或變數、類);

  (2)應用工程需以某種特定的方式呼叫DLL的匯出函式(或變數、類)。

  下面我們來對“特定的方式進行”闡述。

  4.2 宣告匯出函式

   DLL中匯出函式的宣告有兩種方式:一種為4.1節例子中給出的在函式宣告中加上__declspec(dllexport),這裡不再舉例說明;另外 一種方式是採用模組定義(.def) 檔案宣告,.def檔案為連結器提供了有關被連結程式的匯出、屬性及其他方面的資訊。

  下面的程式碼演示了怎樣同.def檔案將函式add宣告為DLL匯出函式(需在dllTest工程中新增lib.def檔案):

; lib.def : 匯出DLL函式
LIBRARY dllTest
EXPORTS
add @ 1

  .def檔案的規則為:

(1)LIBRARY語句說明.def檔案相應的DLL;

 (2)EXPORTS語句後列出要匯出函式的名稱。可以在.def檔案中的匯出函式名後加@n,表示要匯出函式的序號為n(在進行函式呼叫時,這個序號將發揮其作用);

 (3).def 檔案中的註釋由每個註釋行開始處的分號 (;) 指定,且註釋不能與語句共享一行。

  由此可以看出,例子中lib.def檔案的含義為生成名為“dllTest”的動態連結庫,匯出其中的add函式,並指定add函式的序號為1。

  4.3 DLL的呼叫方式

 在4.1節的例子中我們看到了由“LoadLibrary-GetProcAddress-FreeLibrary”系統Api提供的三位一體“DLL載入-DLL函式地址獲取-DLL釋放”方式,這種呼叫方式稱為DLL的動態呼叫。

  動態呼叫方式的特點是完全由程式設計者用 API 函式載入和解除安裝 DLL,程式設計師可以決定 DLL 檔案何時載入或不載入,顯式連結在執行時決定載入哪個 DLL 檔案。

 與 動態呼叫方式相對應的就是靜態呼叫方式,“有動必有靜”,這來源於物質世界的對立統一。“動與靜”,其對立與統一竟無數次在技術領域裡得到驗證,譬如靜態 IP與DHCP、靜態路由與動態路由等。從前文我們已經知道,庫也分為靜態庫與動態庫DLL,而想不到,深入到DLL內部,其呼叫方式也分為靜態與動態。 “動與靜”,無處不在。《周易》已認識到有動必有靜的動靜平衡觀,《易.繫辭》曰:“動靜有常,剛柔斷矣”。哲學意味著一種普遍的真理,因此,我們經常可以在枯燥的技術領域看到哲學的影子。

 靜態呼叫方式的特點是由編譯系統完成對DLL的載入和應用程式結束時 DLL 的解除安裝。當呼叫某DLL的應用程式結束時,若系統中還有其它程式使用該 DLL,則Windows對DLL的應用記錄減1,直到所有使用該DLL的程式都結束時才釋放它。靜態呼叫方式簡單實用,但不如動態呼叫方式靈活。下面我們來看看靜態呼叫的例子(單擊此處下載本工程附件 ),將編譯dllTest工程所生成的.lib和.dll檔案拷入dllCall工程所在的路徑,dllCall執行下列程式碼:#pragma comment(lib,"dllTest.lib")
//.lib檔案中僅僅是關於其對應DLL檔案中函式的重定位資訊

  由上述程式碼可以看出,靜態呼叫方式的順利進行需要完成兩個動作:

 (1)告訴編譯器與DLL相對應的.lib檔案所在的路徑及檔名,#pragma comment(lib,"dllTest.lib")就是起這個作用。

 程式設計師在建立一個DLL檔案時,聯結器會自動為其生成一個對應的.lib檔案,該檔案包含了DLL 匯出函式的符號名及序號(並不含有實際的程式碼)。在應用程式裡,.lib檔案將作為DLL的替代檔案參與編譯。

  (2)宣告匯入函式,extern "C" __declspec(dllimport) add(int x,int y)語句中的__declspec(dllimport)發揮這個作用。

 靜 態呼叫方式不再需要使用系統API來載入、解除安裝DLL以及獲取DLL中匯出函式的地址。這是因為,當程式設計師通過靜態連結方式編譯生成應用程式時,應用程式 中呼叫的與.lib檔案中匯出符號相匹配的函式符號將進入到生成的EXE 檔案中,.lib檔案中所包含的與之對應的DLL檔案的檔名也被編譯器儲存在 EXE檔案內部。當應用程式執行過程中需要載入DLL檔案時,Windows將根據這些資訊發現並載入DLL,然後通過符號名實現對DLL 函式的動態連結。這樣,EXE將能直接通過函式名呼叫DLL的輸出函式,就象呼叫程式內部的其他函式一樣。

 4.4 DllMain函式

 Windows在載入DLL的時候,需要一個入口函式,就如同控制檯或DOS程式需要main函式、WIN32程式需要WinMain函式一樣。在前面的例子中,DLL並沒有提供DllMain函式,應用工程也能成功引用DLL,這是因為Windows在找不到DllMain的時候,系統會從其它執行庫中引入一個不做任何操作的預設DllMain函式版本,並不意味著DLL可以放棄DllMain函式。

  根據編寫規範,Windows必須查詢並執行DLL裡的DllMain函式作為載入DLL的依據,它使得DLL得以保留在記憶體裡。這個函式並不屬於匯出函式,而是DLL的內部函式。這意味著不能直接在應用工程中引用DllMain函式,DllMain是自動被呼叫的。

  我們來看一個DllMain函式的例子(單擊此處下載本工程附件 )。


   DllMain函式在DLL被載入和解除安裝時被呼叫,在單個執行緒啟動和終止時,DLLMain函式也被呼叫,ul_reason_for_call指明瞭 被呼叫的原因。原因共有4種,即PROCESS_ATTACH、PROCESS_DETACH、THREAD_ATTACH和 THREAD_DETACH,以switch語句列出。

來仔細解讀一下DllMain的函式頭BOOL APIENTRY DllMain( HANDLE hModule, WORD ul_reason_for_call, LPVOID lpReserved )。

  APIENTRY被定義為__stdcall,它意味著這個函式以標準Pascal的方式進行呼叫,也就是WINAPI方式;

   程序中的每個DLL模組被全域性唯一的32位元組的HINSTANCE控制代碼標識,只有在特定的程序內部有效,控制代碼代表了DLL模組在程序虛擬空間中的起始地 址。在Win32中,HINSTANCE和HMODULE的值是相同的,這兩種型別可以替換使用,這就是函式引數hModule的來歷。

  執行下列程式碼:


  我們看到輸出順序為:

  process attach of dll

  call add in dll:5

  process detach of dll

  這一輸出順序驗證了DllMain被呼叫的時機。

   程式碼中的GetProcAddress ( hDll, MAKEINTRESOURCE ( 1 ) )值得留意,它直接通過.def檔案中為add函式指定的順序號訪問add函式,具體體現在MAKEINTRESOURCE ( 1 ),MAKEINTRESOURCE是一個通過序號獲取函式名的巨集,定義為(節選自winuser.h):

    #define MAKEINTRESOURCEA(i) (LPSTR)((DWORD)((WORD)(i)))
    #define MAKEINTRESOURCEW(i) (LPWSTR)((DWORD)((WORD)(i)))
    #ifdef UNICODE
    #define MAKEINTRESOURCE MAKEINTRESOURCEW
    #else
    #define MAKEINTRESOURCE MAKEINTRESOURCEA

  4.5 __stdcall約定

 如果通過VC++編寫的DLL欲被其他語言編 寫的程式呼叫,應將函式的呼叫方式宣告為__stdcall方式,WINAPI都採用這種方式,而C/C++預設的呼叫方式卻為__cdecl。 __stdcall方式與__cdecl對函式名最終生成符號的方式不同。若採用C編譯方式(在C++中需將函式宣告為extern "C"),__stdcall呼叫約定在輸出函式名前面加下劃線,後面加“@”符號和引數的位元組數,形如[email protected];而 __cdecl呼叫約定僅在輸出函式名前面加下劃線,形如_functionname。

  Windows程式設計中常見的幾種函式型別宣告巨集都是與__stdcall和__cdecl有關的(節選自windef.h):

#define CALLBACK __stdcall //這就是傳說中的回撥函式
#define WINAPI __stdcall //這就是傳說中的WINAPI
#define WINAPIV __cdecl
#define APIENTRY WINAPI //DllMain的入口就在這裡
#define APIPRIVATE __stdcall
#define PASCAL __stdcall

  在lib.h中,應這樣宣告add函式:

  int __stdcall add(int x, int y);

 在應用工程中函式指標型別應定義為:

  typedef int(__stdcall *lpAddFun)(int, int);

  若在lib.h中將函式宣告為__stdcall呼叫,而應用工程中仍使用typedef int (* lpAddFun)(int,int),執行時將發生錯誤(因為型別不匹配,在應用工程中仍然是預設的__cdecl呼叫),彈出如圖7所示的對話方塊。

  圖7 呼叫約定不匹配時的執行錯誤

  圖8中的那段話實際上已經給出了錯誤的原因,即“This is usually a result of …”。

  單擊此處下載__stdcall呼叫例子工程原始碼附件

4.6 DLL匯出變數

  DLL定義的全域性變數可以被呼叫程序訪問;DLL也可以訪問呼叫程序的全域性資料,我們來看看在應用工程中引用DLL中變數的例子(單擊此處下載本工程附件 )。


 從lib.h和lib.cpp中可以看出,全域性變數在DLL中的定義和使用方法與一般的程式設計是一樣的。若要匯出某全域性變數,我們需要在.def檔案的EXPORTS後新增:

  變數名 CONSTANT   //過時的方法

  或

  變數名 DATA     //VC++提示的新方法

  在主函式中引用DLL中定義的全域性變數:


 特別要注意的是用extern int dllGlobalVar宣告所匯入的並不是DLL中全域性變數本身,而是其地址,應用程式必須通過強制指標轉換來使用DLL中的全域性變數。這一點,從*(int*)dllGlobalVar可以看出。因此在採用這種方式引用DLL全域性變數時,千萬不要進行這樣的賦值操作:

 dllGlobalVar = 1;

 其結果是dllGlobalVar指標的內容發生變化,程式中以後再也引用不到DLL中的全域性變量了。

 在應用工程中引用DLL中全域性變數的一個更好方法是:


  通過_declspec(dllimport)方式匯入的就是DLL中全域性變數本身而不再是其地址了,筆者建議在一切可能的情況下都使用這種方式。

  4.7 DLL匯出類

 DLL中定義的類可以在應用工程中使用。

  下面的例子裡,我們在DLL中定義了point和circle兩個類,並在應用工程中引用了它們(單擊此處下載本工程附件 )。


  類的引用:


  從上述原始碼可以看出,由於在DLL的類實現程式碼中定義了巨集DLL_FILE,故在DLL的實現中所包含的類宣告實際上為:

class _declspec(dllexport) point //匯出類point
{

}

  和

class _declspec(dllexport) circle //匯出類circle
{

}

 而在應用工程中沒有定義DLL_FILE,故其包含point.h和circle.h後引入的類宣告為:

class _declspec(dllimport) point //匯入類point
{

}

  和

class _declspec(dllimport) circle //匯入類circle
{

}

  不錯,正是通過DLL中的

class _declspec(dllexport) class_name //匯出類circle 
{

}

  與應用程式中的

class _declspec(dllimport) class_name //匯入類
{

}

  匹對來完成類的匯出和匯入的!

   我們往往通過在類的宣告標頭檔案中用一個巨集來決定使其編譯為class _declspec(dllexport) class_name還是class _declspec(dllimport) class_name版本,這樣就不再需要兩個標頭檔案。本程式中使用的是:

#ifdef DLL_FILE
class _declspec(dllexport) class_name //匯出類
#else
class _declspec(dllimport) class_name //匯入類
#endif

 實際上,在MFC DLL的講解中,您將看到比這更簡便的方法,而此處僅僅是為了說明_declspec(dllexport)與_declspec(dllimport)匹對的問題。

 由此可見,應用工程中幾乎可以看到DLL中的一切,包括函式、變數以及類,這就是DLL所要提供的強大能力。只要DLL釋放這些介面,應用程式使用它就將如同使用本工程中的程式一樣!

  本章雖以VC++為平臺講解非MFC DLL,但是這些普遍的概念在其它語言及開發環境中也是相同的,其思維方式可以直接過渡。

 接下來,我們將要研究MFC規則DLL

相關推薦

VC++動態連結(DLL)程式設計深入淺出()

     (1)DLL匯出函式,可供應用程式呼叫;   (2) DLL內部函式,只能在DLL程式使用,應用程式無法呼叫它們。  而應用程式對本DLL的呼叫和對第2節靜態連結庫的呼叫卻有較大差異,下面我們來逐一分析。   首先,語句typedef int ( * lpAddFun)(int,int)定義了一個

VC++動態連結(DLL)程式設計深入淺出(四)

 由於MFC擴充套件DLL匯出函式和變數的方式與其它DLL沒有什麼區別,我們不再細緻講解。下面直接給出一個MFC擴充套件DLL的建立及在應用程式中呼叫它的例子。   6.1 MFC擴充套件DLL的建立  下 面我們將在MFC擴充套件DLL中匯出一個按鈕類CSXButton(擴充套件自MFC的CButton類

VC++動態連結(DLL)程式設計深入淺出(一)

 1.概論  先來闡述一下DLL(Dynamic Linkable Library)的概念,你可以簡單的把DLL看成一種倉庫,它提供給你一些可以直接拿來用的變數、函式或類。在倉庫的發展史上經歷了“無庫-靜態連結庫-動態連結庫”的時代。  靜態連結庫與動態連結庫都是共享代

VC++動態連結(DLL)程式設計深入淺出(三)

 第4節我們對非MFC DLL進行了介紹,這一節將詳細地講述MFC規則DLL的建立與使用技巧。  另外,自從本文開始連載後,收到了一些讀者的e-mail。有的讀者提出了一些問題,筆者將在本文的最後一次連載中選取其中的典型問題進行解答。由於時間的關係,對於讀者朋友的來信,筆者暫

Windows下的VC++動態連結程式設計

VC++動態連結庫程式設計 1、基礎概念 1.1 連結庫的概述 動態連結庫DLL(DynamicLinkable Library),你可以簡單的把它看成一種倉庫,它提供給你一些可以直接拿來用的變數、函式或類。在庫的發展史上經歷了“無庫-靜態連結庫-動態連結庫”的時代。靜態連

VC中使用動態連結DLL:靜態呼叫和動態呼叫

VC中生成DLL的辦法見:www.codeproject.com/KB/DLL/RegDLL.aspx VC中使用DLLhttp://www.cnblogs.com/c1230v/articles/1401448.html 呼叫DLL有兩種方法:靜態呼叫和動態呼叫. (一

Tensorflow安裝在windows 上面出現ImportError: DLL load failed: 動態連結(DLL)初始化例程失敗。

      最近開始學習tensorflow,電腦是win10 64位系統的,已經安裝了python3.6.1 32位的,tensorflow只支援python64位的,所以直接安裝了Python64位3.6.1。直接使用pip install tenso

c#(winform)環境下使用動態連結dll的詳解

  1,什麼是dll檔案? DLL(Dynamic Link Library)檔案為動態連結庫檔案,又稱“應用程式拓展”,是軟體檔案型別。在Windows中,許多應用程式並不是一個完整的可執行檔案,它們被分割成一些相對獨立的動態連結庫,

使用LabVIEW通過動態連結DLL遠端操作Oracle資料庫

很多情況下,遠端操作資料庫時,需要在不裝Oracle客戶端的情況下進行,儘可能降低客戶端安裝各類軟體的時間。 首先我們從網上下載Oracle資料庫操作dll檔案。 動態連結庫通過ildasm.exe反彙編檢視,這個檔案是.net 4.0版本的封裝檔案。 在LabVI

MFC/Qt下呼叫caffe原始碼(一)---將caffe原始碼生成動態連結dll

本人研一,最近想將用caffe訓出的模型,通過MFC做出一個介面,扔進一張圖片,點選預測,即可呼叫預測分類函式完成測試,並且通過MessageBox彈出最終分類的資訊。 首先通過查資料總結出兩種方法,第一:直接呼叫編譯好的caffe原始碼;(本次用到的原始碼是classif

Java通過JNI 呼叫動態連結DLL

JNI(Java Native Interface)Java本地介面,主要作用是實現java程式碼與C、C++編寫的程式碼互動。 在Android程式設計中,so庫的訪問也用到了jni技術。 理論多說無益,還是看java連線dll的實戰吧。如下: 例:java中呼叫demo.

Windows動態連結DLL的使用

windows程式設計使用動態連結庫可以有效的分隔大型專案的模組,DLL裡面主要提供函式的呼叫介面(函式名)供其他的外部引用程式呼叫,呼叫者在完全不知道動態連結庫中的實現方式的情況下,仍然能根據其提供的函式名,函式型別, 和函式的引數實現呼叫。windows程式中建立DLL

codeblocks中建立和呼叫動態連結(dll)

一、建立C語言動態連結庫 1.建立。 File->New->Projects->Dynamic Link library->Go 給專案命名為:Dynamic librar

動態連結 —— Dll 基礎

1. DLL 的初識   在 windows 中,動態連結庫是不可缺少的一部分,windows 應用程式程式介面提供的所有函式都包含在 DLL 中,其中有三個非常重要的系統 DLL 檔案,分別為 Kernel32.dll、User32.dll 和 GDI32.dll,下面說下這三個重要的 DLL 的用途:

動態連結dll的 靜態載入 與 動態載入

dll 兩種連結方式  : 動態連結和靜態連結(連結亦稱載入) 動態連結是指在生成可執行檔案時不將所有程式用到的函式連結到一個檔案,因為有許多函式在作業系統帶的dll檔案中,當程式執行時直接從作業系統中找。   而 靜態連結就是把所有用到的函式全部連結到exe檔案中。 動態連結是隻建立一個引用的介

QT呼叫VC 動態連結 解決QT開啟或儲存檔案時閃退的問題

1,MFC需要用靜態庫 2,MFC中字串編譯用unicode(1個漢字佔2位元組),QT中用的UTF8(1個漢字佔3位元組),因此MFC中需要將unicode轉換成UTF8 貼出QT中的程式碼:QT中做成靜態函式,方便其他位置進行呼叫 static QString Mfc

C#呼叫C/C++動態連結(.dll)詳解

第一篇編譯C的動態連線庫 在實際工作中,我們經常會將C語言中的.lib和.h檔案(靜態庫)編譯成動態連線庫.dll檔案(這裡只提供這兩種檔案,沒有完整的工程),以提供給其他語言平臺呼叫。 1,必須有.lib檔案,只有.h檔案是無法編譯動態連線庫的。 2,我使用的是V

如何建立動態連結(DLL)

動態連結庫 n動態連結庫通常不能直接執行,也不能接受訊息。他們是一些獨立的檔案,其中包含能被可執行程式或其他DLL呼叫來完成某項工作的函式。只有在其他模組呼叫動態連結庫中的函式時,它才發揮作用

C#呼叫動態連結DLL

1.概述 動態連結庫(Dynamic Linked Library):將寫好的函式存在庫中,以供其他程式開發呼叫,呼叫方式為“動態的”。 Windows為應用程式提供了豐富的函式呼叫,這些函式呼叫都包含在動態連結庫中。其中有3個最重要的DLL,Kerne

vs2010 建立和C#使用動態連結(dll)

一、VS 用 C++ 建立動態連結庫 Step 1:建立Win32 Console Application 本例中我們建立一個叫做“Test”的Solution。 Step 2:將Ap