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

原文:http://hi.baidu.com/qinpc/blog/item/a1e7d262e9f2d5d8e7113a96.html

 

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

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



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



__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);



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



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

如果創建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)方式優缺點對比:

一、__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++ 函數的修飾名可能會發生變化。

二、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 文件中的導出名相匹配。 
發佈了15 篇原創文章 · 獲贊 15 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章