1、DLL的起源
動態鏈接庫(DLL)是從C語言函數庫和Pascal庫單元的概念發展而來的。所有的C語言標准庫函數都存放在某一函數庫中。在鏈接應用程序的過程中,鏈 接器從庫文件中拷貝程序調用的函數代碼,並把這些函數代碼添加到可執行文件中。這種方法同只把函數儲存在已編譯的OBJ文件中相比更有利於代碼的重用。
但隨着Windows這樣的多任務環境的出現,函數庫的方法顯得過於累贅。如果為了完成屏幕輸出、消息處理、內存管理、對話框等操作,每個程序都不得不擁 有自己的函數,那么Windows程序將變得非常龐大。Windows的發展要求允許同時運行的幾個程序共享一組函數的單一拷貝。動態鏈接庫就是在這種情 況下出現的。動態鏈接庫不用重復編譯或鏈接,一旦裝入內存,DLL函數可以被系統中的任何正在運行的應用程序軟件所使用,而不必再將DLL函數的另一拷貝 裝入內存。
2、DLL中函數的聲明
根據微軟DLL的編寫和調用規范,在DLL中,聲明和定義導出函數時,需要在函數前使用__declspec(dllexport)關鍵字,以表明該函數 是DLL的導出函數;在DLL的隱式調用方式中,應用程序在調用導出函數時,必須使用__declspec(dllimport)關鍵字先聲明導入的函 數。這種導入和導出函數的聲明方法也符合C/C++的函數的先聲明再調用的調用規范。
3、DLL導出函數的鏈接類別及引用方式
導出函數在編譯、鏈接過程中,可以采用C鏈接和C++鏈接兩種方式,當采用C鏈接時,編譯器不更改導出函數的名稱,與之相反,當采用C++鏈接時,編譯器則更改導出函數的名稱。
導出函數可以使用C語言編寫,也可以使用C++語言編寫。對於采用C語言編寫的執行文件而言,如果調用采用C++語言編寫的導出函數,應當強制指定使用C 鏈接而不是C++鏈接生成導出函數庫;而對於采用C++語言編寫的執行文件而言,如果調用采用C語言編寫的導出函數,應當強制指定使用C鏈接生成導出函數 庫。根據編譯器規范,指定、聲明函數使用C鏈接,則應當在函數聲明前使用關鍵字extern "C"。
通常情況下,為了確保不同的語言編寫的可執行模塊都能夠正確地訪問到導出函數,習慣上都采用extern "C"來指定導出函數采用C鏈接方式。
4、DLL頭文件格式
在實際的編程中,通常都是把導出函數的聲明統一放在一個頭文件中,而其定義則根據需要分布在不同的CPP文件中,這樣的實現方式較方便對文件及其功能的管理和維護。因此,DLL頭文件的格式如下:
1.
#ifndef _DLLMODULENAME_H
2.
#define _DLLMODULENAME_H
3.
4.
......
01.
/*
02.
* if using C++ Compiler to compile the file, adopting C linkage mode
03.
*/
04.
#ifdef __cplusplus
05.
extern
"C"
{
06.
#endif
07.
08.
// macro define __declspec(dllexport)
09.
#define DLLMODULENAME_LIB_API __declspec(dllexport)
10.
11.
// define export functions
12.
DLLMODULENAME_LIB_API returntype FuncName (parameters);
13.
// ... more declarations as needs
14.
15.
#undef DLLMODULENAME_LIB_API
16.
17.
#ifdef __cplusplus
18.
}
19.
#endif
20.
21.
#endif
根據微軟DLL隱式調用的規范,在使用導出函數前,應當首先聲明該導出函數。在實際編程中,大多采用在一個頭文件中,統一聲明程序運行中調用到的DLL導出函數,然后在所有調用DLL導出函數的文件中,包含該頭文件的方式。因此導出函數的引入頭文件格式如下:
01.
#ifndef _IMPORTFUNC_H
02.
#define _IMPORTFUNC_H
03.
04.
#ifdef __cplusplus
05.
extern
"C"
{
06.
#endif
07.
08.
// macro define __declspec(dllimport)
09.
#define DLLMODULENAME_LIB_API __declspec(dllimport)
10.
11.
// define export functions
12.
DLLMODULENAME_LIB_API returntype FuncName (parameters);
13.
// ... more declarations as needs
14.
15.
#undef DLLMODULENAME_LIB_API
16.
17.
#ifdef __cplusplus
18.
}
19.
#endif
20.
21.
#endif
從上述闡述可以看出,對於DLL導出函數而言,在DLL頭文件中聲明了一次,而在隱式調用時,又聲明了一次,為消除這種重復聲明和減少文件數量,實際應用 中通常將兩個頭文件合並成一個DLL頭文件,同時定義一個宏,用於控制函數處於導出聲明或調用導入聲明狀態。對於DLL定義文件,在包含DLL頭文件之 前,首先定義一個控制宏,用於聲明所有的函數為導出函數;而在隱式調用中,在包含DLL頭文件時不需要定義控制宏,用於聲明所有的函數為導入函數。因此最 終的DLL頭文件格式如下:
01.
#ifndef _DLLMODULENAME_H
02.
#define _DLLMODULENAME_H
03.
04.
#include <>
05.
#include ""
06.
07.
/*
08.
* if using C++ Compiler to compile the file, adopting C linkage mode
09.
*/
10.
#ifdef __cplusplus
11.
extern
"C"
{
12.
#endif
13.
14.
// according to the control macro, deciding whether export or import functions
15.
#ifdef _DLLMODULENAME_
16.
#define DLLMODULENAME_LIB_API __declspec(dllexport)
17.
#else
18.
#define DLLMODULENAME_LIB_API __declspec(dllimport)
19.
#endif
20.
21.
// functions declarations
22.
DLLMODULENAME_LIB_API returntype FuncName (parameters);
23.
// ... more declarations as needs
24.
25.
#undef DLLMODULENAME_LIB_API
26.
27.
#ifdef __cplusplus
28.
}
29.
#endif
30.
31.
#endif
5、DLL頭文件的使用
DLL導出函數的鏈接、導入、導出指示符在函數第一次聲明時確定,在以后的函數聲明和定義時,函數都接受第一次函數的鏈接、導入、導出聲明,不必再次對函數作鏈接、導入、導出聲明,因此DLL導出函數的定義文件中,可以使用如下的編碼格式:
01.
/*
02.
* ensure compiler to compile correctly, through including
03.
* the precompiled headers, or else resulting in C1010 error
04.
*/
05.
#include "stdafx.h"
06.
07.
#define _DLLMODULENAME_
08.
09.
#include "dllmodulename.h"
10.
11.
12.
returntype FuncName (parameters)
13.
{
14.
// function body
15.
}
16.
17.
// other functions definitions
而在調用文件中,只需要包含頭文件即可,即使用#include "dllmodulename.h"語句實現對DLL導出函數的導入聲明。