由於學習DLL時,沒有例子的協助是很難瞭解DLL的精髓,現在我想做一個例子來介紹:
一、在DLL裡面加一個函數:fun(double a,double b)
//這裡我懶得打字,就剽竊別人(很多人,在此謝謝了)的一部分過來:
//////////////////////////////////////////////////////////////////
1、靜態載入DLL方法:
函數的定義和使用方法:
第一步:
運行AppWizard,定義項目名為mydll,選擇MFC AppWizard(dll),而不是MFC AppWizards(exe)。
第二步:
在這個例子中,只有一個AppWizard螢幕出現,選擇MFC擴
展DLL(MFC Extension DLL (using shared MFC DLL),點擊FINISH產生工程。
第三步:
點擊File中的New,選擇C/C++ Header File,File Name中輸入dll,點擊OK,建立dll.h。
輸入extern "C" __declspec(dllexport) int fun(int x1,int x2,int x3); ,儲存。
第四步:
點擊File中的New,選擇C++ Source File,File Name中輸入dll,點擊OK,建立dll.cpp。輸入
#include "stdafx.h"
#include "dll.h"
extern "C" __declspec(dllexport) int fun(int x1,int x2,int x3)
{
return x1+x2+x3;
}
編譯產生mydll.dll和mydll.lib。
第五步:
選擇Project 中Add To Project 中的New , 重建一個工程,選擇MFC AppWizards(exe),項目名
為mydlltest , 選擇Single Document ,點擊FINISH,產生一個新的工程。選擇菜單中
的Project Set as Active Project mydlltest ,將mydlltest設為當前活動工程。
第六步:
拷貝…/mydll/debug/mydll.dll 到 ../mydlltest/debug/下,拷貝…/mydll/debug/mydll.lib到…/mydlltest/目錄下。
第七步:
在mydlltestView.cpp中的#endif下添加
extern "C" __declspec(dllimport) int fun(int x1,int x2,int x3);
在void CMydlltestView::OnDraw(CDC* pDC)中添加代碼如下:
void CMydlltestView::OnDraw(CDC* pDC)
{
CMydlltestDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
int x=fun(1,2,3);
CString str;
str.Format("%d",x);
pDC->TextOut(10,10,str);
}
第八步:
在WorkSpace中的mydlltest files上點擊右鍵,選擇Add files to Project ,將mydll.lib添加到工程。
好了,我們的工作做完了,運行一下看看吧!
///////////////////////////////////////////////////////////
2、動態載入DLL:
從以上的第六步開始:
在void CMydlltestView::OnDraw(CDC* pDC)中添加代碼如下:
void CMydlltestView::OnDraw(CDC* pDC)
{
CMydlltestDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
int x;
typedef int(*padd)(int x1,int x2,int x3);
HINSTANCE hdll;
padd add;
hdll = LoadLibrary("mydll.dll");
add =(padd)GetProcAddress(hdll,"fun");
x = add(2,6,5);
CString str;
str.Format("%d",x);
pDC->TextOut(10,10,str);
}
在WorkSpace中的mydlltest files上點擊右鍵,選擇Add files to Project ,將mydll.lib添加到工程。
好了,我們的工作做完了,運行一下看看吧!
本文引用地址: http://blog.csdn.net/sgnah/services/trackbacks/330837.aspx
Win32下動態連結程式庫(DLL)編程原理的深探
主題:DLL是Windows最重要的組成要素,Windows中的許多新功能、新特性都是通過DLL來
實現的,因此掌握它、應用它是非常重要的。
動態連結程式庫不僅可以作為一個運行模組,包括函數代碼,而且可以包含程式以外的任何資料
或資源(位元影像、表徵圖等等)。動態連結程式庫就是給應用程式提供函數或者資源。
DLL是一種磁碟檔案(通常帶有DLL副檔名),它由全域資料、服務函數和資源群組成,
在運行時被系統載入到進程的虛擬空間中,成為調用進程的一部分。在運行時,只有當EXE
程式確實要調用這些DLL模組的情況下,系統才會將它們裝載到記憶體空間中。每個進程都複
制了自己的讀/寫全域變數。如果想要與其它進程共用記憶體,必須使用記憶體對應檔或者聲
明一個共用資料區段。DLL模組需要的堆棧記憶體都是從運行進程的堆棧中分配出來的。
DLL檔案中包含一個匯出函數表。這些匯出函數由它們的符號名和稱為標識號的整數與外界聯絡起來。
匯入匯出函數:
在DLL代碼中,必須像下面這樣明確聲明匯出函數:
__declspec(dllexport) int MyFunction(int n);
但也可以在模組定義(DEF)檔案中列出匯出函數,不過這樣做常常引起更多的麻煩。在應用程式方面,要
求像下面這樣明確聲明相應的輸入函數:
__declspec(dllimport) int MyFuncition(int n); (這是隱式連結dll)
僅有匯入和匯出聲明並不能使應用程式內部的函數調用連結到相應的DLL檔案上。應用程式的項目必須為
連結程式指定所需的輸入庫(LIB檔案)。而且應用程式事實上必須至少包含一個對DLL函數的調用。
隱式連結和顯式連結dll
顯式:typedef double(SQRTPROC)(double);
HINSTANCE hInstance; //設定全域變數用於儲存DLL控制代碼
SQRTPROC* pFunction; 第二個變數ShowMe是指向DLL,庫中函數的指標
VERIFY(hInstance=::LoadLibrary("c://winnt//system32//mydll.dll"));
VERIFY(pFunction=(SQRTPROC*)::GetProcAddress(hInstance,"SquareRoot"));
double d=(*pFunction)(81.0);//調用該DLL函數
隱式: 程式員通過靜態連結方式編譯產生應用程式時,應用程式中的調用函數與LIB檔案中匯出符號相
匹配,這些符號或標識號進入到產生的EXE檔案中.
隱式連結和顯式連結dll的區別:
在隱式連結方式中,所有被應用程式調用的DLL檔案都會在應用程式EXE檔案載入時被載入在到記憶體中;但
如果採用顯式連結方式,程式員可以決定DLL檔案何時載入或不載入。
DLLMAIN函數:
當Windows載入DLL模組時調用這一函數。系統首先調用全域對象的建構函式,然後調用全域函數DLLMain。
DLLMain函數不僅在將DLL連結載入到進程時被調用,在DLL模組與進程分離時(以及其它時候)也被調用。
進程中的每個DLL模組被全域唯一的32位元組的HINSTANCE控制代碼標識。進程自己還有一個HINSTANCE控制代碼。所
有這些模組控制代碼都只有在特定的進程內部有效,它們代表了DLL或EXE模組在進程虛擬空間中的起始地址。
尋找DLL的路徑:
Windows將遵循下面的搜尋順序來定位DLL:
1. 包含EXE檔案的目錄,
2. 進程的當前工作目錄,
3. Windows系統目錄,
4. Windows目錄,
5. 列在Path環境變數中的一系列目錄。
要點:
一:DLL檔案的省缺名稱是與工程名一致的(也是在.DEF檔案中LIBRARY 之後的名字),不要試圖在製作完畢之後通
過簡單的修改.DLL檔案的檔案名稱來改變它,這會導致使用該DLL的應用程式錯誤.
DLL中定義有兩種函數:
匯出函數(exportfunction): 可以被其他模組調用
內建函式(internalfunction): 只能在DLL內部使用
基於MFC的DLL不適用與製作讀取二進位檔案的DLL (不易移植,無法正確讀取與DOS應用程式共用的二進位檔案)
基於API的DLL可以正確讀取在DOS環境中建立的二進位檔案.
純標準函數dll可以在很多平台使用!(移植性最強)
為了讓其它語言使用:除非你絕對需要使用C++編碼,否則我還是推薦使用C來寫DLL。
VC下的DLL類型:
Non-MFC DLL:指的是不用MFC的類庫結構,直接用C語言寫的DLL,其輸出的函數一
般用的是標準C介面,並能被非MFC或MFC編寫的應用程式所調用。LL,
Regular DLL:和下述的Extension Dlls一樣,是用MFC類庫編寫的。明顯的特點是
在源檔案裡有一個繼承CWinApp的類。其又可細分成靜態串連到MFC和動態串連到MFC上
的。但靜態串連到MFC的動態串連庫只被VC的專業般和企業版所支援。
Extension DLL:用來實現從MFC所繼承下來的類的重新利用,也就是說,用這種類
型的動態串連庫,可以用來輸出一個從MFC所繼承下來的類。Extension DLL使用MFC的
動態串連版本所建立的,並且它只被用MFC類庫所編寫的應用程式所調用。
用來實現從MFC所繼承下來的類的重新利用,也就是說,用這種類型的動態串連庫,可以用
來輸出一個從MFC所繼承下來的類。它輸出的函數僅可以被使用MFC且動態連結到MFC的應用
程式使用。可以從MFC繼承你所想要的、更適於你自己用的類,並把它提供給你的應用程式。
你也可隨意的給你的應用程式提供MFC或MFC繼承類的對象指標。Extension DLL使用MFC的動
態串連版本所建立的,並且它只被用MFC類庫所編寫的應用程式所調用。Extension DLLs 和
Regular DLLs不一樣,它沒有一個從CWinApp繼承而來的類的對象,所以,你必須為自己DllMain
函數添加初始化代碼和結束代碼。
和規則DLL相比,有以下不同:
1、它沒有一個從CWinApp派生的對象;
2、它必須有一個DllMain函數;
3、DllMain調用AfxInitExtensionModule函數,必須檢查該函數的傳回值,如果返回0,DllMmain也返回0;
4、如果它希望輸出CRuntimeClass類型的對象或者資源(Resources),則需要提供一個初始化函數來建立一個CDynLinkLibrary對象。並且,有必要把初始化函數輸出;
5、使用擴充DLL的MFC應用程式必須有一個從CWinApp派生的類,而且,一般在InitInstance裡調用擴充DLL的初始化函數。
所有從DLL輸出的函數應該以如下語句開始:
AFX_MANAGE_STATE(AfxGetStaticModuleState( ))
此語句用來正確地切換MFC模組狀態。
在製作DLL時,VC++對函數的省缺聲明是"__cedcl",也就是說,如果你在聲明你的函數時不作特殊聲明的話,你
製作的DLL將只能被C/C++調用,如果你想用其他開發語言(比如VB5.0)調用它就會報錯,即使調用方法完全正確.
二:宣告類型詳解:
三:制DLL所要的檔案:
模 塊 定 義 文 件(.DEF) 是 由 一 個 或 者 多 個 用 於 描 述DLL 屬 性 的 模 塊 語 句 組 成
的 文 本 文 件, 每 個.DEF 文 件 至 少 必 須 包 含 以 下 模 塊 定 義 語 句:
第 一 個 語 句 必 須 是LIBRARY 語 句, 指 出DLL 的 名 字。
EXPORTS 語 句 列 出 被 導 出 函 數 的 名 字。
可 以 使 用DESCRIPTION 語 句 描 述DLL 的 用 途( 此 句 可 選)。
";" 對 一 行 進 行 注 釋( 可 選)
實 現 文 件(.cpp 文 件 為 例)
實 現 入 口 表 函 數 的.cpp 文 件 中, 包 含DLL 入 口 點 處 理 的API 函 數 和 導 出 函 數 的 代 碼。
四:
介紹Non-MFC DLLs的編寫方法:如果用VC的嚮導那就很方便了!
寫法:
BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,
LPVOID lpReserved)
{
switch( ul_reason_for_call ) {
case DLL_PROCESS_ATTACH:
.......
case DLL_THREAD_ATTACH:
.......
case DLL_THREAD_DETACH:
.......
case DLL_PROCESS_DETACH:
.......
}
return TRUE;
}
每一個DLL必須有一個進入點,這就象我們用C編寫的應用程式一樣,
必須有一個WINMAIN函數一樣。
在這個樣本中,DllMain是一個預設的入口函數,你不需要編寫自己
的DLL入口函數,並用linker的命令列的參數開關/ENTRY聲明。用這個缺
省的入口函數就能使動態串連庫被調用時得到正確的初始化,當然了,你
不要在初始化的時候填寫使系統崩潰的代碼了。
參數中,hMoudle是動態庫被調用時所傳遞來的一個指向自己的控制代碼
(實際上,它是指向_DGROUP段的一個選擇符)
ul_reason_for_call是一個說明動態庫被調原因的標誌。當進程或線程
裝入或卸載動態串連庫的時候,作業系統調用入口函數,並說明動態串連庫
被調用的原因。它所有的可能值為:
DLL_PROCESS_ATTACH: 進程被調用
DLL_THREAD_ATTACH: 線程被調用
DLL_PROCESS_DETACH: 進程被停止
DLL_THREAD_DETACH: 線程被停止
lpReserved是一個被系統所保留的參數。
入口函數已經寫了,餘下的也不難,你可以在檔案中加入你所想要輸
出的函數或變數或c++類等
五:純資源DLL的編寫
純資源的DLL就是只包含資源的DLL,例如:表徵圖,位元影像,字串,聲音,
視頻,對話方塊等。使用純資源DLL可以節約可執行檔的大小,可以被所有的應
用程式所共用,從而提高系統效能。純資源DLL的編寫比普通的DLL要簡單的多,
首先建立一個WIN32 DLL工程,不是MFC的DLL,然後建立一個資源檔 *.RC,添
加到資源DLL的工程中去。然後添加一個初始化DLL的原檔案。
#include <windows.h>
extern "C"
BOOL WINAPI DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID )
{
return 1;
}
這是純資源DLL所必須需的代碼,儲存這個檔案為*.CPP。編譯這個資源DLL。
在應用程式顯示的調用這個DLL,使用LoadLibrary函數裝入資源DLL,
FindResource和LoadResource來裝入各種資源,或者使用下列的特定的資源裝入函數:
FormatMessage
LoadAccelerators
LoadBitmap
LoadCursor
LoadIcon
LoadMenu
LoadString
當資源使用結束,你的應用程式須調用FreeLibrary函數來釋放資源。
下面就講一下如何調用編寫好的資源DLL
首先在應用程式中聲明一個DLL的控制代碼,HINSTANCE m_hLibrary;在OnCreate( )函數
中調用LoadLirbrary( ),在OnDestory( )中調用FreeLibrary()。下載一個工程檔案看一看,就什麼都搞定了。
關於約定:
六、關於約定:(此要點六zz rivershan(原作))
動態庫輸出函數的約定有兩種:呼叫慣例和名字修飾約定。
1)呼叫慣例(Calling convention):決定函數參數傳送時入棧和出棧的順序,由調用者還是被調用者把參數彈出棧,以及編譯器用來識別函數名字的修飾約定。
函數呼叫慣例有多種,這裡簡單說一下:
1、__stdcall呼叫慣例相當於16位動態庫中經常使用的PASCAL呼叫慣例。在32位的VC++5.0中PASCAL呼叫慣例不再被支援(實際上它已被定義為__stdcall。除了__pascal外,__fortran和__syscall也不被支援),取而代之的是__stdcall呼叫慣例。兩者實質上是一致的,即函數的參數自右向左通過棧傳遞,被調用的函數在返回前清理傳送參數的記憶體棧,但不同的是函數名的修飾部分(關於函數名的修飾部分在後面將詳細說明)。
_stdcall是Pascal程式的預設調用方式,通常用於Win32 Api中,函數採用從右至左的壓棧方式,自己在退出時清空堆棧。VC將函數編譯後會在函數名前面加上底線首碼,在函數名後加上"@"和參數的位元組數。
2、C呼叫慣例(即用__cdecl關鍵字說明)按從右至左的順序壓參數入棧,由調用者把參數彈出棧。對於傳送參數的記憶體棧是由調用者來維護的(正因為如此,實現可變參數的函數只能使用該呼叫慣例)。另外,在函數名修飾約定方面也有所不同。
_cdecl是C和C++程式的預設調用方式。每一個調用它的函數都包含清空堆棧的代碼,所以產生的可執行檔大小會比調用_stdcall函數的大。函數採用從右至左的壓棧方式。VC將函數編譯後會在函數名前面加上底線首碼。是MFC預設呼叫慣例。
3、__fastcall呼叫慣例是“人”如其名,它的主要特點就是快,因為它是通過寄存器來傳送參數的(實際上,它用ECX和EDX傳送前兩個雙字(DWORD)或更小的參數,剩下的參數仍舊自右向左壓棧傳送,被調用的函數在返回前清理傳送參數的記憶體棧),在函數名修飾約定方面,它和前兩者均不同。
_fastcall方式的函數採用寄存器傳遞參數,VC將函數編譯後會在函數名前面加上"@"首碼,在函數名後加上"@"和參數的位元組數。
4、thiscall僅僅應用於“C++”成員函數。this指標存放於CX寄存器,參數從右至左壓。thiscall不是關鍵詞,因此不能被程式員指定。
5、naked call採用1-4的呼叫慣例時,如果必要的話,進入函數時編譯器會產生代碼來儲存ESI,EDI,EBX,EBP寄存器,退出函數時則產生代碼恢複這些寄存器的內容。naked call不產生這樣的代碼。naked call不是類型修飾符,故必須和_declspec共同使用。
關鍵字 __stdcall、__cdecl和__fastcall可以直接加在要輸出的函數前,也可以在編譯環境的Setting.../C/C++ /Code Generation項選擇。當加在輸出函數前的關鍵字與編譯環境中的選擇不同時,直接加在輸出函數前的關鍵字有效。它們對應的命令列參數分別為/Gz、/Gd和/Gr。預設狀態為/Gd,即__cdecl。
要完全模仿PASCAL呼叫慣例首先必須使用__stdcall呼叫慣例,至於函數名修飾約定,可以通過其它方法模仿。還有一個值得一提的是WINAPI宏,Windows.h支援該宏,它可以將出函數翻譯成適當的呼叫慣例,在WIN32中,它被定義為__stdcall。使用WINAPI宏可以建立自己的APIs。
2)名字修飾約定
1、修飾名(Decoration name)
“C”或者“C++”函數在內部(編譯和連結)通過修飾名識別。修飾名是編譯器在編譯函數定義或者原型時產生的字串。有些情況下使用函數的修飾名是必要的,如在模組定義檔案裡頭指定輸出“C++”重載函數、建構函式、解構函式,又如在彙編代碼裡調用“C””或“C++”函數等。
修飾名由函數名、類名、呼叫慣例、傳回型別、參數等共同決定。
2、名字修飾約定隨呼叫慣例和編譯種類(C或C++)的不同而變化。函數名修飾約定隨編譯種類和呼叫慣例的不同而不同,下面分別說明。
a、C編譯時間函數名修飾約定規則:
__stdcall呼叫慣例在輸出函數名前加上一個底線首碼,後面加上一個“@”符號和其參數的位元組數,格式為_functionname@number。
__cdecl呼叫慣例僅在輸出函數名前加上一個底線首碼,格式為_functionname。
__fastcall呼叫慣例在輸出函數名前加上一個“@”符號,後面也是一個“@”符號和其參數的位元組數,格式為@functionname@number。
它們均不改變輸出函數名中的字元大小寫,這和PASCAL呼叫慣例不同,PASCAL約定輸出的函數名無任何修飾且全部大寫。
b、C++編譯時間函數名修飾約定規則:
__stdcall呼叫慣例:
1、以“?”標識函數名的開始,後跟函數名;
2、函數名後面以“@@YG”標識參數表的開始,後跟參數表;
3、參數表以代號表示:
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,
....
PA--表示指標,後面的代號表明指標類型,如果相同類型的指標連續出現,以“0”代替,一個“0”代表一次重複;
4、參數表的第一項為該函數的傳回值類型,其後依次為參數的資料類型,指標標識在其所指資料類型前;
5、參數表後以“@Z”標識整個名字的結束,如果該函數無參數,則以“Z”標識結束。
其格式為“?functionname@@YG*****@Z”或“?functionname@@YG*XZ”,例如
int Test1(char *var1,unsigned long)-----“?Test1@@YGHPADK@Z”
void Test2() -----“?Test2@@YGXXZ”
__cdecl呼叫慣例:
規則同上面的_stdcall呼叫慣例,只是參數表的開始標識由上面的“@@YG”變為“@@YA”。
__fastcall呼叫慣例:
規則同上面的_stdcall呼叫慣例,只是參數表的開始標識由上面的“@@YG”變為“@@YI”。
VC++對函數的省缺聲明是"__cedcl",將只能被C/C++調用.
extern "C" {void __declspec(dllexport) __cdecl Test(int var);}
其輸出函數名為:Test
MFC提供了一些宏,就有這樣的作用。
AFX_CLASS_IMPORT:__declspec(dllexport)
AFX_API_IMPORT:__declspec(dllexport)
AFX_DATA_IMPORT:__declspec(dllexport)
AFX_CLASS_EXPORT:__declspec(dllexport)
AFX_API_EXPORT:__declspec(dllexport)
AFX_DATA_EXPORT:__declspec(dllexport)
AFX_EXT_CLASS: #ifdef _AFXEXT
AFX_CLASS_EXPORT
#else
AFX_CLASS_IMPORT
AFX_EXT_API:#ifdef _AFXEXT
AFX_API_EXPORT
#else
AFX_API_IMPORT
AFX_EXT_DATA:#ifdef _AFXEXT
AFX_DATA_EXPORT
#else
AFX_DATA_IMPORT
像AFX_EXT_CLASS這樣的宏,如果用於DLL應用程式的實現中,則表示輸出(因為_AFX_EXT被定義,通常是在編譯器的標識參數中指定該選項/D_AFX_EXT);如果用於使用DLL的應用程式中,則表示輸入(_AFX_EXT沒有定義)。
要輸出整個的類,對類使用_declspec(_dllexpot);要輸出類的成員函數,則對該函數使用_declspec(_dllexport)。如:
class AFX_EXT_CLASS CTextDoc : public CDocument
{
…
}
extern "C" AFX_EXT_API void WINAPI InitMYDLL();
這幾種方法中,最好採用第三種,方便好用;其次是第一種,如果按順序號輸出,調用效率會高些;最次是第二種。