C++ 動態鏈接庫、靜態鏈接庫


一、概述

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 }
復制代碼

結果為


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM