一、創(chuàng)建 DLL 工程項(xiàng)目:
1)點(diǎn)擊菜單[File] -> [New]
-> [Project...] 彈出 “New Project”
對話框;
2)在左側(cè) [Project types:] 樹形框中展開 [Visual C++] 選擇
[Win32];
3)在右側(cè) [Templates:] 視圖框中選擇 [Win32
Project];
4)在 [Name:]
對應(yīng)的文本框中填寫好項(xiàng)目名稱;
5)在 [Location:]
對應(yīng)的文本框中選好項(xiàng)目位置;
6)點(diǎn)擊 [OK] 按鈕,彈出 “Win32 Application Wizard -
你起的項(xiàng)目名稱” 對話框;
7)點(diǎn)擊左側(cè)的 [Application Settings] 或 點(diǎn)擊 [Next
>] 進(jìn)入 “Application Settings”
選項(xiàng)頁;
8)在 [Application type:] 中選擇
“DLL”;
9)在 [Additional options:] 中選擇 “Empty
project”;
10)最后點(diǎn)擊 [Finish] 按鈕。
二、編寫 DLL 導(dǎo)出函數(shù)頭文件(exampledll.h):
#ifdef DLL_EXPORTS
#define DLL_API_INT _declspec(dllexport) int
__stdcall
#else
#define DLL_API_INT _declspec(dllimport) int
__stdcall
#endif
#ifdef __cplusplus
// extern "C" 是 C++ 的關(guān)鍵字
extern "C"
{
#endif
DLL_API_INT sum( int a, int b );
DLL_API_INT sub( int a, int b );
#ifdef __cplusplus
}
#endif
注:
1. __declspec(dllexport) 聲明函數(shù)為 DLL 的導(dǎo)出函數(shù),
它就是為了省掉在 *.def
文件中手工定義導(dǎo)出哪些函數(shù)的一個(gè)方法,
如果你的 DLL 里全是 C++ 的類的話,你無法在
*.def 里指定導(dǎo)出的函數(shù),
只能用 __declspec(dllexport)
導(dǎo)出類;
; 舉例 exampledll.def 文件內(nèi)容
LIBRARY exampledll
EXPORTS
sum @ 1
sub @ 2
var DATA
.def文件的規(guī)則為: 注:需要在工程屬性中指定該*.def文件,位置:
[Configuration Properties] ->
[Linker] -> [Input] -> "Module
Definition File" 不用 *.def
文件導(dǎo)出函數(shù)的話,函數(shù)名將是按編譯器的命名規(guī)則導(dǎo)出。
1)LIBRARY 關(guān)鍵字后跟該 def 文件對應(yīng)的 DLL 工程名;
2)EXPORTS 關(guān)鍵字獨(dú)占一行,下面每行列出要導(dǎo)出函數(shù)或變量的名稱;
可以在要導(dǎo)出的函數(shù)名后加 @
數(shù)字,即為要導(dǎo)出函數(shù)排序,在動(dòng)態(tài)加載導(dǎo)出函數(shù)時(shí),此序號有用;
若要導(dǎo)出某全局變量,要有如下格式:
變量名 CONSTANT <-- 過時(shí)的方法,變量名后跟
CONSTANT 關(guān)鍵字
或
變量名 DATA <--
VC++提示的新方法,變量名后跟 DATA 關(guān)鍵字
注意:
用 extern 聲明導(dǎo)入的并不是 DLL
中全局變量本身,而是其地址,
應(yīng)用程序必須先通過類型強(qiáng)制轉(zhuǎn)換指針,再間接取/賦值來使用 DLL
中的全局變量,如:
extern int var;
*(int*)var = 1;
通過 _declspec(dllimport)
方式導(dǎo)入的就是DLL中全局變量本身而不再是其地址了,
建議盡可能的情使用這種方式,如:
extern int _declspec(dllimport)
var;
var = 1;
3)分號(;) 為行注釋符且注釋不能與語句共享一行,要獨(dú)占一行,以分號開頭。
2. __declspec(dllimport) 聲明函數(shù)為 DLL 的導(dǎo)入函數(shù),
不使用 __declspec(dllimport)
也能正確編譯代碼,
但使用 __declspec(dllimport)
使編譯器可以生成更好的代碼,
編譯器之所以能夠生成更好的代碼,是因?yàn)樗梢源_定函數(shù)是否存在于 DLL
中,
這使得編譯器可以生成跳過間接尋址級別的代碼,而這些代碼通常會(huì)出現(xiàn)在跨 DLL
邊界的函數(shù)調(diào)用中。
但是必須使用
__declspec(dllimport) 才能導(dǎo)入 DLL 中使用的變量,
原來 __declspec(dllimport)
是為了更好的處理類中的靜態(tài)成員變量的,
如果沒有靜態(tài)成員變量,那么這個(gè)
__declspec(dllimport) 無所謂;
3. 函數(shù)的調(diào)用方式更改:
方法一:
1)右擊 "Solution Explorer"
中的項(xiàng)目名;
2)選擇
"Properties";
3)在彈出的對話框左側(cè)樹形框中選擇
[Configuration Properties] -> [C/C++]
-> [Advanced];
4)在彈出的對話框右側(cè)柵欄框中修改 "Calling
Convention" 項(xiàng)對應(yīng)值。
方法二:
microsoft 的 VC 默認(rèn)的是 __cdecl
方式,而 windows API 則是 __stdcall ,
如果用 VC 開發(fā) dll 給其他語言用,則應(yīng)該指定
__stdcall 方式。
如果是 __cdecl
方式的函數(shù),則函數(shù)本身則不需要關(guān)心保存參數(shù)的堆棧的清除,
如果是 __stdcall
方式的函數(shù),則一定要在函數(shù)退出前恢復(fù)堆棧。
1)__cdecl 所謂的 C
調(diào)用規(guī)則。按從右至左的順序壓參數(shù)入棧,由調(diào)用者把參數(shù)彈出棧。
切記:對于傳送參數(shù)的內(nèi)存棧是由調(diào)用者來維護(hù)的。
返回值在
EAX 中因此,對于象 printf 這樣變參數(shù)的函數(shù)必須用這種規(guī)則。
編譯器在編譯的時(shí)候?qū)@種調(diào)用規(guī)則的函數(shù)生成修飾名的時(shí)候,
僅在輸出函數(shù)名前加上一個(gè)下劃線前綴,格式為 _functionname
。
2)__stdcall
按從右至左的順序壓參數(shù)入棧,由被調(diào)用者把參數(shù)彈出棧。
__stdcall 是 Pascal
程序的缺省調(diào)用方式,
通常用于
Win32 Api 中,切記:函數(shù)自己在退出時(shí)清空堆棧,返回值在EAX中。
__stdcall 調(diào)用約定在輸出函數(shù)名前加上一個(gè)下劃線前綴,后面加上一個(gè) @
符號和其參數(shù)的字節(jié)數(shù),
格式為
_functionname@number 。
如函數(shù) int
func(int a, double b) 的修飾名是 _func@12 。
3)__fastcall
調(diào)用的主要特點(diǎn)就是快,
因?yàn)樗峭ㄟ^寄存器來傳送參數(shù)的,
實(shí)際上,它用
ECX 和 EDX 傳送前兩個(gè)雙字(DWORD)或更小的參數(shù),
剩下的參數(shù)仍舊自右向左壓棧傳送,被調(diào)用的函數(shù)在返回前清理傳送參數(shù)的內(nèi)存棧。
__fastcall 調(diào)用約定在輸出函數(shù)名前加上一個(gè) @ 符號,后面也是一個(gè) @
符號和其參數(shù)的字節(jié)數(shù),
格式為
@functionname@number 。
這個(gè)和
__stdcall 很象,唯一差別就是頭兩個(gè)參數(shù)通過寄存器傳送。
注意通過寄存器傳送的兩個(gè)參數(shù)是從左向右的,即第一個(gè)參數(shù)進(jìn)ECX,第2個(gè)進(jìn)EDX,
其他參數(shù)是從右向左的入棧。返回仍然通過EAX。
4)__pascal 這種規(guī)則從左向右傳遞參數(shù),通過
EAX 返回,堆棧由被調(diào)用者清除。
5)__thiscall 僅僅應(yīng)用于 C++
成員函數(shù)。
this
指針存放于 CX 寄存器,參數(shù)從右到左壓。thiscall不是關(guān)鍵詞,因此不能被程序員指定。
修飾符的書寫順序如下:
extern "C"
_declspec(dllexport) int __stdcall sum(int a, int
b);
typedef int (__cdecl
*FunPointer)(int a, int b);
4.如果要對編譯器提示使用 C 的方式來處理函數(shù)的話,那么就要使用 extern "C"
來說明。
三、編寫 DLL 導(dǎo)出函數(shù)實(shí)現(xiàn)文件(exampledll.c):
#ifndef DLL_EXPORTS
#define DLL_EXPORTS
#endif
#include "windows.h"
#include "exampledll.h"
#pragma comment(linker,"/section:shared,rws")
#pragma data_seg("shared")
int var = 0;
#pragma data_seg()
extern "C" BOOL __stdcall
DllMain( HINSTANCE
hInstance,
DWORD
dwReason,
LPVOID
lpReserved)
{
switch( dwReason
)
{
case DLL_PROCESS_ATTACH:
return
TRUE;
case DLL_PROCESS_DETACH:
return
TRUE;
case DLL_THREAD_ATTACH:
return
TRUE;
case DLL_THREAD_DETACH:
return
TRUE;
default:
return
TRUE;
}
}
DLL_API_INT sum( int a, int b )
{
return a +
b;
}
DLL_API_INT sub( int a, int b )
{
return a -
b;
}
四、編譯生成 DLL 即可!
|