一、概述
1、動态庫和靜态庫的異同點
動态連結庫(Dynamic Linkable Library,DLL)它提供一些可以直接使用的變量,類和函數。經曆了“無庫—靜态連結庫—動态連結庫”的曆程後,dll應用十分廣泛。
靜态連結庫和動态連結庫都是共享代碼。
如果采用靜态鍊連結庫(.lib),lib中的指令最終都會編譯到連結該靜态庫的exe(或dll)檔案中,釋出軟體時,隻需要釋出exe(或dll)檔案,不需要.lib檔案。但是若使用動态連結庫(. dll),dll中的指令不會編譯到exe檔案中,而是在exe檔案執行期間,動态的加載和解除安裝獨立的dll檔案,需要和exe檔案一起釋出。
靜态連結庫和動态連結庫另一個差別是靜态連結庫不能再包含其他動态連結庫或靜态連結庫,而動态連結庫不受此限制,動态連結庫中可以再包含其他的動态連結庫和靜态連結庫。
2、相關常識
(1)隻要遵循約定的dll接口規範和調用方式,用各種語言編寫的dll可以互相調用。
(2)dll的分類有三種,Non-MFC DLL(非MFC DLL),MFC Regular DLL(MFC規則DLL)和MFC Extension DLL(MFC拓展DLL)。非MFC DLL不采用MFC類庫規則,其導出函數為标準C接口,能被非MFC或MFC編寫的應用程式調用;MFC規則DLL包含一個CWinApp的類,但其無消息循環;MFC拓展DLL采用MFC的動态連結庫版本建立,它隻能被用MFC類庫編寫的應用程式調用。
二、靜态庫
靜态連結庫的字尾是.lib。下面的一個例程将介紹如何生成.lib檔案和如何調用.Lib
1、生成.lib檔案
1)、建立一個空解決方案,方案名稱為StaticLibrary。

2)、添加一個win32項目,類型為Static library,工程名稱為StaticLib,空項目。
選擇win32 project項目類型
選擇Static library, 取消Precompiled header
3)、在該項目中添加lib.h和Lib.cpp檔案,兩個檔案代碼如下圖所示
lib.h檔案代碼如下:
#ifndef __LIB_H__
#define __LIB_H__
int add(int a,int b);
#endif
lib.cpp檔案中提供一個函數,實作兩個整數的相加,傳回兩數的和。代碼如下:
#include "lib.h"
int add(int a,int b)
{
return a+b;
}
4)、庫工程不能單獨運作,需要右鍵點選生成
生成成功後,将在解決方案目錄的debug檔案夾中生成一個StaticLib.lib檔案,該檔案就是靜态庫檔案。
2、如何調用.lib檔案
1)、在解決方案裡再添加一個項目,項目名稱為StaticLibCall,類型為Win32,控制台程式,選擇空項目。
選擇Console application 和Empty project
2)、在項目源檔案檔案中添加main.cpp檔案
#include <stdio.h>
#include "lib.h"
#pragma comment (lib,"StaticLib.lib")//指定與靜态庫一起連接配接
int main()
{
printf("2+3=%d",add(2,3));
}
3)、将剛剛生成的StaticLib.lib檔案和lib.h兩個檔案複制到該項目的目錄下。(一般使用靜态庫時必須提供這兩個檔案,.h檔案提供函數的預定義,而.lib提供函數的實作)
4)、生成的exe檔案是可以獨立運作的運作程式,lib檔案中的函數實作被連結到exe檔案中,lib不再需要了。運作結果如下
三、 動态連結庫
隻介紹一種DLL(非MFC DLL)的建立與調用方法,本Dll實作的功能與第2節介紹的靜态庫實作的功能一樣。
1、如何生成一個dll檔案
1)、建立dll工程的步驟和上面介紹的建立lib的步驟一樣,僅僅在選擇類型時需要選擇dll。建立工程後,添加DLib.h和DLib.cpp檔案
2)、DLib.h和DLib.cpp代碼如下所示:
DLib.h檔案
1 #ifndef __DLIB_H__
2 #define __DLIB_H__
3
4 extern "C" int __declspec(dllexport) add(int a,int b);
5
6
7 #endif
DLib.cpp檔案
1 #include "Dlib.h"
2
3 int add(int a,int b)
4 {
5 return a+b;
6 }
分析該代碼,該工程的.cpp檔案中代碼和第2節的.cpp中代碼完全一樣。而.h檔案不一樣,該工程的.h檔案中對add函數添加extern “C” 是告訴編譯器該函數采用C調用方式進行編譯,而__declspec(impoet)是聲明函數add為dll的導出函數,dll的函數分兩種:
(a) DLL導出函數,可供調用dll的應用程式調用
(b) DLL内部函數,隻能在DLL程式使用,調用DLL的應用程式無法調用
3)、右鍵–生成;生成成功後,将在debug檔案夾中生成一個DynamicLib.dll檔案,同時,在此路徑下也生成DynamicLib.lib檔案,該lib檔案不同于第一節中的靜态庫檔案,此lib檔案隻是dll檔案中導出函數的聲明和定位資訊,并不包含函數的實作(而第一節中的靜态庫檔案,包含了函數的實作),是以此lib檔案隻是在調用對應dll庫的工程編譯時使用,不需要随exe釋出。
2、如何調用.dll檔案
dll檔案的調用方式有兩種,一種是動态調用,一種是靜态調用。
動态調用是由程式設計者調用系統API函數加載和解除安裝dll,程式員可以決定dll檔案何時加載,何時解除安裝,加載哪個dll檔案,将dll檔案的使用權完全交給程式員。
靜态調用是由編譯系統完成對dll檔案的加載和應用程式結束時完成對dll的解除安裝,當調用某dll的應用程式結束時,則windows系統對該dll的應用記錄減1,直到使用該dll的所有應用程式都結束,即對該dll應用記錄為0,作業系統會解除安裝該dll,靜态調用方法簡單,但不如動态調用适用。
(1)、動态調用dll
1)、建立控制台項目,添加main.cpp檔案,将剛剛生成的dynamicLib.dll檔案拷貝到項目目錄下,main.cpp代碼如下
// Test0629.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "windows.h"
typedef int (*lpAddFun)();//宏定義函數指針類型
int main()
{
HINSTANCE hDll;//DLL 句柄
lpAddFun Fun;//函數指針
hDll = LoadLibrary("MsgDLL0629.dll");//動态擷取dll檔案的路徑
if (hDll!=NULL)
{
Fun =(lpAddFun)GetProcAddress(hDll,"Destroy");//根據函數名在dll檔案中擷取該函數的位址
if (Fun!=NULL)
{
Fun();
}
FreeLibrary(hDll);
}
return 0;
}
main.cpp代碼分析:
語句typedef int (*lpAddFun)(int ,int )定義了一個與add函數接收參數類型和傳回值均相同的函數指針類型,随後在main函數中定義了lpAddFun的執行個體addFun;
在函數main中定義了一個DLL HISTANCE句柄執行個體hDll,通過Win32API函數LoadLibrary動态加載DLL子產品并将DLL子產品句柄賦給hDll;
在main函數中通過Win32API函數GetProcAddress得到所加載的DLL子產品中函數add的位址并指派給addFun,經由函數指針addFun進行了對該DLL中add函數的調用;
在完成對dll的調用後,在main函數中通過Win32API函數FreeLibrary釋放已加載的DLL子產品。
通過以上的分析可知:
(a) 動态調用隻需要dll檔案即可,不需要對應的.h頭檔案和.lib檔案,一般情況下,隻要有dll,就可以調用此dll中的導出函數。
(b) 在調用dll中的函數時,需要知道導出函數的函數簽名,若擁有dll對應的頭檔案,可以參照頭檔案即可,若沒有頭檔案,使用特定工具也可以得到dll中導出函數的函數簽名
(c) DLL需要已某種特定的方式聲明導出函數(或變量,類)
(d) 應用程式需要以特定的方式調用DLL的淡出函數(或變量,類)
(2)、靜态調用dll
1)、建立一個工程,命名為DllStaticCall,添加main.cpp檔案,并将剛剛生成的DynamicLib.dll和DynamicLib.lib兩個檔案拷貝到工程目錄下。
main.cpp代碼如下
#include "stdafx.h"
//.lib檔案中僅僅是關于其對應DLL檔案中函數的定位資訊
#pragma comment(lib,".lib檔案路徑")
extern "C" __declspec(dllimport) 函數名(參數);
int main()
{
正常調用方法
return 0;
}
從上述代碼可以看出,靜态調用需要完成兩個動作:
a)#pragma comment(lib,“DynamicLib.lib”)是告訴編譯器與該dll相對應的.lib檔案所在的路徑和檔案名。
在生成dll檔案時,連結器會自動為其生成一個對應的.lib檔案,該檔案包含了dll導出函數的符号名和序号(并沒有實際的代碼)。在應用程式中,.lib檔案将作為dll的替代檔案參與編譯,編譯完成後,.lib檔案就不需要了。
b)extern “C” int __declspec(dllimport) add(int a,int b)是聲明導入函數,這需要與dll工程中.h檔案中的函數聲明一緻。
c)可以将dll工程中的頭檔案包含在工程中,這樣上述代碼中就不需要寫extern “C” int __declspec(dllimport) add(int a,int b)聲明了,但若沒有提供對應的頭檔案,隻有采用本文這種方式聲明函數。
靜态調用不需要使用Win32API函數來加載和解除安裝Dll以及擷取Dll中導出函數的位址,這是因為當通過靜态連結方式編譯生成程式時,編譯器會将.lib檔案中導出函數的函數符号連結到生成的exe檔案中,.lib檔案中包含的與之對應的dll檔案的檔案名也被編譯存儲在exe檔案内部,當應用程式運作過程中需要加載dll檔案時,windows将根據這些資訊查找并加載dll,然後通過符号名實作對dll函數的動态連結,這樣,exe将能直接通過函數名調用dll 的輸出函數,就像調用程式内部的其他函數一樣。
四、 動态連結庫的def檔案
dll導出函數是前面添加__declspec(impoet)語句,聲明該函數為dll的導出函數,還有另一種方式聲明函數為導出函數–通過def檔案
1、如何使用def檔案
(1)建立解決方案,添加兩個項目,DllLib是生成dll檔案的項目,Dllcall是調用該dll的項目。
(2)在dllLib中添加lib.cpp和dlllib.def兩個檔案(不需要.h頭檔案),代碼如下
Lib.cpp代碼:聲明兩個函數,加法和減法
int __stdcall Add(int numa, int numb)
{
return (numa + numb);
}
int __stdcall Sub(int numa, int numb)
return (numa - numb);
dllLib.def代碼如下:
LIBRARY DllLib
EXPORTS
Add @ 1
Sub @ 2
.def檔案的規則為:
(1)LIBRARY語句說明該.def檔案相對于的dll(不需要字尾dll)
(2)EXPORTS語句後面列出要到處的函數名稱,可以在.def檔案中的導出函數名後加@n,表示要導出函數的序号為n,在進行函數調用時,可以根據這個編号調用該函數(參見下面的調用過程)
(3).def檔案中的注釋由每行開始處的分号(;)指定,且注釋不能與語句共需一行。
由此可以看出,例子中的.def檔案的含義是生成名為DllLib的動态連結庫,導出其中的add和sub函數,并且指定add函數序号為1,sub序号為2。
2、調用dll
調用的方法與上面介紹的一樣,本例使用動态調用。将剛剛生成的dll拷貝到項目目錄下。
main.cpp代碼如下:
1 #include <stdio.h>
2 #include <windows.h>
3
4 typedef int (__stdcall *FUN)(int, int);
5 HINSTANCE hInstance;
6
7 FUN fun;
8
9 int main()
10 {
11 hInstance = LoadLibrary(L"DllLib.dll");
12 if(hInstance!=NULL)
13 {
14 fun = (FUN)GetProcAddress(hInstance, "Add");
15 //當在Def檔案中指定函數序号時,可以通過序号導出,否則隻能通過函數名稱導出
16 //fun = (FUN)GetProcAddress(hInstance, MAKEINTRESOURCE(1));
17 if (fun!=NULL)
18 {
19 printf("1+2=%d",fun(1, 2));
20 }
21 }
22 FreeLibrary(hInstance);
23 return 0;
24 }
注:def檔案中定義了函數序号,在動态加載dll時,可以根據這個序号加載函數,這樣做的好處時,當dll工程的導出函數的函數名有變化,而功能沒有變化時,隻要def中定義的函數序号沒有變化,則調用dll的代碼不需要任何改變。
五、 DllMain函數
Windows在加載dll時候,需要一個入口函數,就如同控制台需要main函數,win32程式需要WinMain函數一樣,在前面的例子中dll并沒有提供DllMain函數,應用程式也能成功的調用dll,這是因為Windows在找不到DllMain函數時,系統會從其他運作庫中引用一個不做任何操作的預設DllMain函數版本,并不是意味着dll可以放棄DllMain函數,
根據編寫規範,Windows必須查找并執行dll裡面的DllMain函數作為加載dll的依據,它使得dll能夠駐留在記憶體裡,DllMain函數不屬于導出函數,而是dll的内部函數,這意味着不能直接在應用程式中引用DllMain函數,DllMain函數是自動被調用的。DLLMain函數的作用是可以做一下初始化操作,相當于類的構造函數,關于DLLMain函數和導出類,在這裡不做過多的講解,後續需要時,再深入研究。
1、為dll添加DllMain函數
(1)建立解決方案,添加兩個工程。一個是生成dll,一個是調用dll。
Lib.h代碼
1 #ifndef __LIB_H__
2 #define __LIB_H__
3 extern "C" int __declspec(dllexport) add(int a,int b);
4 #endif
Lib.cpp代碼
1 #include "lib.h"
2 #include "stdio.h"
3 #include "windows.h"
4
5 BOOL APIENTRY DllMain(HANDLE hModule,
6 DWORD ul_reason_for_call,
7 LPVOID lpReserved)
8 {
9 switch (ul_reason_for_call)
10 {
11 case DLL_PROCESS_ATTACH:
12 printf("\nprocess attach of dll");
13 break;
14 case DLL_THREAD_ATTACH:
15 printf("\nthread attach of dll");
16 break;
17 case DLL_THREAD_DETACH:
18 printf("\nprocess detach of dll");
19 break;
20 case DLL_PROCESS_DETACH:
21 printf("\nprocess detach of dll");
22 break;
23 }
24 return TRUE;
25 }
26
27 int add(int a,int b)
28 {
29 return a+b;
30 }
Main.cpp代碼
1 #include "stdio.h"
2 #include "windows.h"
3
4 typedef int (*lpAddFun)(int ,int );//宏定義函數指針類型
5
6 int main()
7 {
8 HINSTANCE hDll;//DLL 句柄
9 lpAddFun addFun;//函數指針
10 hDll = LoadLibrary(L"DllLib.dll");//動态擷取dll檔案的路徑
11 if (hDll!=NULL)
12 {
13 addFun =(lpAddFun)GetProcAddress(hDll,"add");
14 if (addFun!=NULL)
15 {
16 int result =addFun(2,3);
17 printf("\ncall add in dll %d",result);
18 }
19
20 FreeLibrary(hDll);
21 }
22 return 0;
23 }
六、 導出類
point.h
1 #ifndef __POINT_H__
2 #define __POINT_H__
3
4 #ifdef DLL_FILE
5 class _declspec (dllexport) point //導出類point
6 #else
7 class _declspec(dllimport) point //導入類point
8 #endif
9 {
10 public:
11 float y;
12 float x;
13 point();
14 point(float x_coordinate,float y_coordinate);
15 };
16
17 #endif
point.cpp
1 #ifndef DLL_FILE
2 #define DLL_FILE
3 #endif
4 #include"point.h"
5 //類point的預設構造函數
6 point::point()
7 {
8 x=0.0;
9 y=0.0;
10 }
11 point::point(float x_coordinate,float y_coordinate)
12 {
13 x=x_coordinate ;
14 y=y_coordinate;
15 }
circle.h
1 #ifndef __CIRCLE_H__
2 #define __CIRCLE_H__
3 #include "point.h"
4 #ifdef DLL_FILE
5 class _declspec (dllexport) circle //導出類circle
6 #else
7 class _declspec(dllimport) circle //導入類circle
8 #endif
9 {
10 public:
11 void SetCenter(const point crePoint);
12 void SetRadius(float r);
13 float GetGirth();
14 float GetArea();
15 circle();
16 private:
17 float radius;
18 point center;
19 };
20
21 #endif
circle.cpp
1 #ifndef DLL_FILE
2 #define DLL_FILE
3 #endif
4 #include"circle.h"
5 #define PI 3.1415926
6 //類circle的預設構造函數
7 circle::circle()
8 {
9 center =point(0,0);
10 radius =0;
11 }
12 //得到圓的面積
13 float circle::GetArea()
14 {
15 return PI*radius*radius;
16 }
17 //得到圓從周長
18 float circle::GetGirth()
19 {
20 return 2*PI*radius;
21 }
22 //設定圓心坐标
23 void circle::SetCenter(const point crePoint)
24 {
25 center =crePoint;
26 }
27 //設定圓的半徑
28 void circle::SetRadius(float r)
29 {
30 radius =r;
31 }
将circle.h,point.h和生成的.lib檔案放在同級目錄
main.cpp
1 #include "circle.h"
2 #include "stdio.h"
3
4 //.lib檔案中僅僅是關于其對應DLL檔案中函數的定位資訊
5 #pragma comment(lib,"DllExportClass.lib")
6
7 int main()
8 {
9 circle c;
10 point p(2.0,2.0);
11 c.SetCenter(p);
12 c.SetRadius(1.0);
13 printf("area:%f,girth:%f",c.GetArea(),c.GetGirth());
14 scanf("%d");
15 return 0;
16 }
結果為