dll的def文件與__declspec(dllexport)導出函數方式比較

dll的def文件與__declspec(dllexport)導出函數方式比較html

【__declspec(dllexport) 方式】
首先對C和C++編譯(extern "C")與調用約定(__cdecl、__stdcall、__fastcall)進行組合測試:
【C++編譯】
__declspec(dllexport) int add(int, int);windows

__declspec(dllexport) int __cdecl add(int, int);app

__declspec(dllexport) int __stdcall add(int, int);函數

__declspec(dllexport) int __fastcall add(int, int);工具

對於C++編譯器的函數名修飾規則:無論__cdecl, __fastcall仍是__stdcall調用方式,函數修飾名都是以"?"開始,後面是函數在名字,再後面是函數返回類型和參數類型按照代號拼出的參數表。對於__stdcall方式,參數表的開始標示是"@@YG」,對於__cdecl方式則是"@@YA」,對於__fastcall方式則是"@@YI」.
參數表後以"@Z」標示整個名字的結束,若是該函數無參數,則以"Z」標識結束。 
更詳細的dll基礎知識請參考:
http://hi.baidu.com/luosiyong/blog/item/92bbdcfe860435375c600812.html
更深刻的C++函數名修飾編碼規則請參考:
http://hi.baidu.com/wanggang2008/blog/item/cd43e60756021bc07a89470a.html測試


【C編譯】
extern "C" __declspec(dllexport) int add(int, int);編碼

extern "C" __declspec(dllexport) int __cdecl add(int, int);spa

extern "C" __declspec(dllexport) int __stdcall add(int, int);設計

extern "C" __declspec(dllexport) int __fastcall add(int, int);htm

若是建立dll和可執行文件都是使用的VC,那用__declspec(dllexport)足夠解決問題。可是若是建立出來的dll要和別的廠商的工具包構建的可執行文件連接,那就有一些額外的問題來了。
在開發dll的時候,通常不讓編譯器改變函數名,因此一般是以C方式編譯,即加入了extern "C"說明。可是看上面的組合測試結果,__stdcall和__fastcall編譯出來的函數名仍是和原始的函數名不一樣。就拿__stdcall來講,它以C編譯導出的時候,會在函數前面加入下劃線,並在函數後面加入@和參數總大小的字節數。
或許如今你就想,__cdecl不就是沒有改變名稱的方式嗎,而且默認也是__cdecl調用約定,的確,咱們本身寫的dll幾乎均可以使用__cdecl方式。可是,Windows中最廣泛的調用方式都是__stdcall(好比CALLBACK、 WINAPI),一些經常使用的定義以下:
#define CALLBACK __stdcall
#define WINAPI __stdcall
#define WINAPIV __cdecl
#define APIENTRY WINAPI
#define APIPRIVATE __stdcall
#define PASCAL __stdcall
如今假如用VC的__stdcall方式開發的一個dll,裏面包含了上面那樣的add函數,若是要在VB中使用,VB的程序員須要以下聲明:
Declare Function add Lib "win.dll" Alias"_add@8"() As Integer
注意他須要寫的名稱是 "_add@8",而不是簡單的"add",不然就會出現函數未定義的連接錯誤。
【備註】
__declspec(dllexport)的位置:
To export functions, the __declspec(dllexport) keyword must appear to the left of the calling-convention keyword, if a keyword is specified.

For example:
__declspec(dllexport) void __cdecl Function1(void);


To export all of the public data members and member functions in a class, the keyword must appear to the left of the class name as follows:
class __declspec(dllexport) CExampleExport : public CObject

{  class definition  };
Reference:

1. 
http://msdn.microsoft.com/en-us/library/d91k01sh(VS.80).aspx

2. 
http://msdn.microsoft.com/en-us/library/a90k134d(VS.80).aspx

【def文件導出方式】
首先了解一下 使用def文件從dll導出:
http://msdn.microsoft.com/zh-cn/library/d91k01sh(v=VS.80).aspx

具體到測試實例,咱們的def文件內容以下:
LIBRARY "win"

EXPORTS
add @1

其中LIBRARY指定dll的模塊名稱,即dll名字,EXPORTS後的每一行指定一個導出函數名字,這個名字和頭文件中的聲明一致,後面能夠跟@序號指定該函數的序號(這個是可選的,後面按序號導入函數的時候再詳細說)。

而後再測試一下__stdcall和__fastcall是否會對導出函數更名,測試結果以下,均未更名:

extern "C" int __stdcall add();

extern "C" int __fastcall add();

另一種方案是在代碼中給連接器指定導出函數名字:
extern "C" __declspec(dllexport) int __fastcall add(int a, int b);
#pragma comment(linker, "/export:add=@add@8")
這裏告訴連接器,導出一個函數名爲add的函數,函數入口點和@add@8相同

這樣,咱們既可使用add,也可使用@add@8了。
__stdcall方式和這相似,爲add=_add@8。

【按序號而不是按名稱從dll導出函數】
http://msdn.microsoft.com/zh-cn/library/e7tsx612%28VS.80%29.aspx
def文件定義以下:
LIBRARY "win"

EXPORTS
add @1 NONAME

函數名稱和Hint都不見了。
這樣也能夠用來隱藏dll中一些重要函數。

隱藏了函數名稱,在應用程序中使用序號來導入函數:
#include <windows.h>
#include <stdio.h>

int main()
{
typedef int (* AddFunc)(int, int);
HMODULE hModule = LoadLibrary("dll.dll");
AddFunc add = (AddFunc)GetProcAddress(hModule, MAKEINTRESOURCE(1)); //注意這裏序號的指定方式
printf("%d\n", add(1, 2));
return 0;
}

【備註】
def文件和__declspec(dllexport)方式優缺點對比:

1、__declspec(dllexport)

在 32 位編譯器版本中,可使用 __declspec(dllexport) 關鍵字從 DLL 導出數據、函數、類或類成員函數。__declspec(dllexport) 在link時會將導出指令添加到obj文件中,所以不須要使用 .def 文件。固然,即便用了__declspec(dllexport)依然可使用*.def文件,由於不一樣編譯器對於類的成員函數的name mangling規則不一樣,能夠定義.def文件經過序號調用。爲每一個dll寫def顯得很繁雜,目前def使用已經比較少了,更多的是使用__declspec(dllexport)在 源代碼中定義dll的輸出函數。

 

若要輸出類的全部成員:數據or函數,__declspec(dllexport)要放在類名左邊聲明:
class __declspec(dllexport) Class1{}
若是類沒有數據成員,__declspec(dllexport)放在class關鍵字前聲明就會被編譯器忽略,就沒有lib生成,以下:
__declspec(dllexport) class Class1{}

使用 __declspec(dllexport) 的優缺點(zz)
使用 __declspec(dllexport) 很是方便,由於沒必要考慮維護 .def 文件和獲取導出函數的修飾名。例如,若是您設計的 DLL 供本身控制的應用程序使用,則此方法很適用。若是經過新的導出函數從新生成 DLL,還必須從新生成應用程序,由於若是使用不一樣版本的編譯器進行從新編譯,則導出的 C++ 函數的修飾名可能會發生變化。
2、def文件
其實def文件的功能至關於extern 「C」 __declspec(dllexport)
def文件中PRIVTATE的做用
The optional keyword PRIVATE prevents entryname from being placed in the import library generated by LINK. It has no
effect on the export in the image also generated by LINK.用了PRIVATE,生成的lib裏沒有對應方法或者數據的entryname所以不能被客戶隱式調用。

 

使用 .DEF 文件的優缺點(zz)
在 .def 文件中導出函數使您得以控制導出序號。當將附加的導出函數添加到 DLL 時,能夠給它們分配更高的序號值(高於任何其餘導出函數)。當您進行此操做時,使用隱式連接的應用程序沒必要與包含新函數的新導入庫從新連接。這很是重要,例如,在設計將由許多應用程序使用的第三方DLL 時。能夠經過添加附加功能不斷地加強 DLL,同時確保現有應用程序繼續正常使用新的 DLL。MFC DLL 是使用 .def 文件生成的。

使用 .def 文件的另外一個優勢是:可使用 NONAME 屬性導出函數,該屬性僅將序號放到 DLL 的導出表中。對具備大量導出函數的 DLL,使用NONAME 屬性能夠減少 DLL 文件的大小。有關編寫模塊定義語句的信息,請參見模塊定義語句的規則。有關序號導出的更多信息,請參見按序號而不是按名稱從 DLL 導出函數。
使用 .def 文件的主要缺點是:在 C++ 文件中導出函數時,必須將修飾名放到 .def 文件中,或者經過使用外部「C」用標準 C 連接定義導出函數,以免編譯器進行名稱修飾。若是須要將修飾名放到 .def 文件中,則能夠經過使用 DUMPBIN 工具或 /MAP 連接器選項來獲取修飾名。請注意,編譯器產生的修飾名是編譯器特定的。若是將 Visual C++ 編譯器產生的修飾名放到 .def 文件中,則連接到 DLL 的應用程序必須也是用相同版本的 Visual C++ 生成的,這樣調用應用程序中的修飾名才能與 DLL 的 .def 文件中的導出名相匹配。 
相關文章
相關標籤/搜索