編寫DLL所學所思(1)——導出函數

燭秋? http://www.cnblogs.com/cswuyg/archive/2011/09/30/dll.html

動態鏈接庫的使用有兩種方式,一種是顯式調用。一種是隱式調用。

(1)?????? 顯式調用:使用LoadLibrary載入動態鏈接庫、使用GetProcAddress獲取某函數地址。

(2)?????? 隱式調用:可以使用#pragma comment(lib, “XX.lib”)的方式,也可以直接將XX.lib加入到工程中。

?

DLL的編寫

編寫dll時,有個重要的問題需要解決,那就是函數重命名——Name-Mangling。解決方式有兩種,一種是直接在代碼里解決采用extent”c”、_declspec(dllexport)、#pragma comment(linker, "/export:[Exports Name]=[Mangling Name]"),另一種是采用def文件。

(1)編寫dll時,為什么有 extern “C”

原因:因為C和C++的重命名規則是不一樣的。這種重命名稱為“Name-Mangling”(名字修飾或名字改編、標識符重命名,有些人翻譯為“名字粉碎法”,這翻譯顯得有些莫名其妙)

據說,C++標準并沒有規定Name-Mangling的方案,所以不同編譯器使用的是不同的,例如:Borland C++跟Mircrosoft C++就不同,而且可能不同版本的編譯器他們的Name-Mangling規則也是不同的。這樣的話,不同編譯器編譯出來的目標文件.obj 是不通用的,因為同一個函數,使用不同的Name-Mangling在obj文件中就會有不同的名字。如果DLL里的函數重命名規則跟DLL的使用者采用的重命名規則不一致,那就會找不到這個函數。

C標準規定了C語言Name-Mangling的規范(林銳的書有這樣說過)。這樣就使得,任何一個支持C語言的編譯器,它編譯出來的obj文件可以共享,鏈接成可執行文件。這是一種標準,如果DLL跟其使用者都采用這種約定,那么就可以解決函數重命名規則不一致導致的錯誤。

影響符號名的除了C++和C的區別、編譯器的區別之外,還要考慮調用約定導致的Name Mangling。如extern “c” __stdcall的調用方式就會在原來函數名上加上寫表示參數的符號,而extern “c” __cdecl則不會附加額外的符號。

dll中的函數在被調用時是以函數名或函數編號的方式被索引的。這就意味著采用某編譯器的C++的Name-Mangling方式產生的dll文件可能不通用。因為它們的函數名重命名方式不同。為了使得dll可以通用些,很多時候都要使用C的Name-Mangling方式,即是對每一個導出函數聲明為extern “C”,而且采用_stdcall調用約定,接著還需要對導出函數進行重命名,以便導出不加修飾的函數名。

注意到extern “C”的作用是為了解決函數符號名的問題,這對于動態鏈接庫的制造者和動態鏈接庫的使用者都需要遵守的規則。

動態鏈接庫的顯式裝入就是通過GetProcAddress函數,依據動態鏈接庫句柄和函數名,獲取函數地址。因為GetProcAddress僅是操作系統相關,可能會操作各種各樣的編譯器產生的dll,它的參數里的函數名是原原本本的函數名,沒有任何修飾,所以一般情況下需要確保dll’里的函數名是原始的函數名。分兩步:一,如果導出函數使用了extern”C” _cdecl,那么就不需要再重命名了,這個時候dll里的名字就是原始名字;如果使用了extern”C” _stdcall,這時候dll中的函數名被修飾了,就需要重命名。二、重命名的方式有兩種,要么使用*.def文件,在文件外修正,要么使用#pragma,在代碼里給函數別名。

(2)_declspec(dllexport)和_declspec(dllimport)的作用

?????? _declspec還有另外的用途,這里只討論跟dll相關的使用。正如括號里的關鍵字一樣,導出和導入。_declspec(dllexport)用在dll上,用于說明這是導出的函數。而_declspec(dllimport)用在調用dll的程序中,用于說明這是從dll中導入的函數。

?????? 因為dll中必須說明函數要用于導出,所以_declspec(dllexport)很有必要。但是可以換一種方式,可以使用def文件來說明哪些函數用于導出,同時def文件里邊還有函數的編號。

而使用_declspec(dllimport)卻不是必須的,但是建議這么做。因為如果不用_declspec(dllimport)來說明該函數是從dll導入的,那么編譯器就不知道這個函數到底在哪里,生成的exe里會有一個call XX的指令,這個XX是一個常數地址,XX地址處是一個jmp dword ptr[XXXX]的指令,跳轉到該函數的函數體處,顯然這樣就無緣無故多了一次中間的跳轉。如果使用了_declspec(dllimport)來說明,那么就直接產生call dword ptr[XXX],這樣就不會有多余的跳轉了。(參考《加密與解密》第三版279頁)

(3)__stdcall帶來的影響

?????? 這是一種函數的調用方式。默認情況下VC使用的是__cdecl的函數調用方式,如果產生的dll只會給C/C++程序使用,那么就沒必要定義為__stdcall調用方式,如果要給Win32匯編使用(或者其他的__stdcall調用方式的程序),那么就可以使用__stdcall。這個可能不是很重要,因為可以自己在調用函數的時候設置函數調用的規則。像VC就可以設置函數的調用方式,所以可以方便的使用win32匯編產生的dll。不過__stdcall這調用約定會Name-Mangling,所以我覺得用VC默認的調用約定簡便些。但是,如果既要__stdcall調用約定,又要函數名不給修飾,那可以使用*.def文件,或者在代碼里#pragma的方式給函數提供別名(這種方式需要知道修飾后的函數名是什么)。

?

舉例:

?

·extern “C” __declspec(dllexport) bool? __stdcall cswuyg();

·extern “C”__declspec(dllimport) bool __stdcall cswuyg();

?

·#pragma comment(linker, "/export:cswuyg=_cswuyg@0")

?

(4)*.def文件的用途

指定導出函數,并告知編譯器不要以修飾后的函數名作為導出函數名,而以指定的函數名導出函數(比如有函數func,讓編譯器處理后函數名仍為func)。這樣,就可以避免由于microsoft VC++編譯器的獨特處理方式而引起的鏈接錯誤。

也就是說,使用了def文件,那就不需要extern “C”了,也可以不需要__declspec(dllexport)了(不過,dll的制造者除了提供dll之外,還要提供頭文件,需要在頭文件里加上這extern”C”和調用約定,因為使用者需要跟制造者遵守同樣的規則,除非使用者和制造者使用的是同樣的編譯器并對調用約定無特殊要求)。

舉例def文件格式:

LIBRARY? XX(dll名稱這個并不是必須的,但必須確保跟生成的dll名稱一樣)

EXPORTS

[函數名] @ [函數序號]

?

編寫好之后加入到VC的項目中,就可以了。

?????? 另外,要注意的是,如果要使用__stdcall,那么就必須在代碼里使用上__stdcall,因為*.def文件只負責修改函數名稱,不負責調用約定。

也就是說,def文件只管函數名,不管函數平衡堆棧的方式。

?

如果把*.def文件加入到工程之后,鏈接的時候并沒有自動把它加進去。那么可以這樣做:

手動的在link添加:

1)工程的propertiesàConfiguration PropertiesàLinkeràCommand Lineà在“Additional options”里加上:/def:[完整文件名].def

2)工程的propertiesàConfiguration PropertiesàLinkeràInputàModule Definition File里加上[完整文件名].def

?

注意到:即便是使用C的名稱修飾方式,最終產生的函數名稱也可能是會被修飾的。例如,在VC下,_stdcall的調用方式,就會對函數名稱進行修飾,前面加‘_’,后面加上參數相關的其他東西。所以使用*.def文件對函數進行命名很有用,很重要。

(5)、DllMain函數

每一個動態鏈接庫都會有一個DllMain函數。如果在編程的時候沒有定義DllMain函數,那么編譯器會給你加上去。

DllMain函數格式:

BOOL APIENTRY DllMain( HANDLE hModule,

?????????????????????? DWORD? ul_reason_for_call,

?????????????????????? LPVOID lpReserved

???????????????????????????? )

{

???? switch(ul_reason_for_call)

???? {

???? case DLL_PROCESS_ATTACH:

??????????? printf("\nprocess attach of dll");

??????????? break;

???? case DLL_THREAD_ATTACH:

??????????? printf("\nthread attach of dll");

??????????? break;

???? case DLL_THREAD_DETACH:

??????????? printf("\nthread detach of dll");

??????????? break;

???? case DLL_PROCESS_DETACH:

??????????? printf("\nprocess detach of dll");

??????????? break;

???? }

??? return TRUE;

}

(6)、很多都還沒學,如:導出Class、導出變量、DLL更高級的應用。目前先了解點基礎知識。以后補上。

?

?

?

?

?

?

?

?

?

?

2011-8-14補充

?

編寫dll可以使用.def文件對導出的函數名進行命名。

?

1、動態裝入dll,重命名(*.def)的必要性?

因為導出的函數盡可能使用__stdcall的調用方式。而__stdcall的調用方式,無論是C的Name Mangling,還是C++的Name Mangling都會對函數名進行修飾。所以,采用__stdcall調用方式之后,必須使用*.def文件對函數名重命名,不然就不能使用GetProcAddress()通過函數名獲取函數指針。

?

2、隱式調用時,頭文件要注意的地方?

因為使用靜態裝入,需要有頭文件聲明這個要被使用的dll中的函數,如果聲明中指定了__stdcall或者extern “C”,那么在調用這個函數的時候,編譯器就通過Name Mangling之后的函數名去.lib中找這個函數,*.def中的內容是對*.lib里函數的名稱不產生作用,*.def文件里的函數重命名只對dll有用。這就有lib 跟dll里函數名不一致的問題了,但并不會產生影響,DLL的制造者跟使用者采用的是一致函數聲明。

?

3、所以到底要不要使用__stdcall 呢?

我看到一些代碼里是沒有使用__stdcall的。如果不使用__stdcall,而使用默認的調用約定_cdecl,并且有extern ”C”。那么VC是不會任何修飾的。這樣子生成的dll里的函數名就是原來的函數名。也就可以不使用.def文件了。

也有一些要求必須使用__stdcall,例如com相關的東西、系統的回調函數。具體看有沒有需要。

?

?

4、導出函數別名怎么寫?

可以在.def文件里對函數名寫一個別名。

例如:

EXPORTS

cswuygTest(別名) = _showfun@4(要導出的函數)

?

或者:

#pragma comment(linker, "/export:[別名] =[NameMangling后的名稱]")

?

這樣做就可以隨便修改別名了,不會出現找不到符號的錯誤。

?

5、用不用*.def文件?

如果采用VC默認的調用約定,可以不用*.def文件,如果要采用__stdcall調用約定,又不想函數名被修飾,那就采用*.def文件吧,另一種在代碼里寫的重命名的方式不夠方便。

6、什么情況下(不)需要考慮函數重命名的問題?

1)、隱式調用(通過lib)

如果dll的制造者跟dll的使用者采用同樣的語言、同樣編程環境,那么就不需要考慮函數重命名。使用者在調用函數的時候,通過Name Mangling后的函數名能在lib里找到該函數。

如果dll的制造者跟dll使用不同的語言、或者不同的編譯器,那就需要考慮重命名了。

2)、顯示調用(通過GetProcessAddress)

?????? 這絕對是必須考慮函數重命名的。

7、總結

??? 總的來說,在編寫DLL的時候,寫個頭文件,頭文件里聲明函數的NameMingling方式、調用約定(主要是為了隱式調用)。再寫個*.def文件把函數重命名了(主要是為了顯式調用)。提供*.DLL\*.lib\*.h給dll的使用者,這樣無論是隱式的調用,還是顯式的調用,都可以方便的進行。

?

附:

一個簡單DLL導出函數的例子:http://files.cnblogs.com/cswuyg/%E7%BC%96%E5%86%99DLL%E6%89%80%E5%AD%A6%E6%89%80%E6%80%9D.rar

?

學習資料:

http://www.cnblogs.com/dongzhiquan/archive/2009/08/04/1994764.html

http://topic.csdn.net/u/20081126/14/70ac75b3-6e79-4c48-b9fe-918dce147484.html

?

轉載于:https://www.cnblogs.com/adder01/p/4737935.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/273044.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/273044.shtml
英文地址,請注明出處:http://en.pswp.cn/news/273044.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

linux切換任務命令,Linux top詳解之交互命令、命令行選項

top交互命令我們之前說過top是一個交互命令。上一節我們已經遇到了一些命令。這里我們會探索更多的命令。2.1 ‘h’: 幫助首先,我們可以用’h’或者’?’顯示交互命令的幫助菜單。2.2 “或者”: 刷新顯示top命令默認在一個特定間隔(3秒)后刷新顯示。要手動刷新&am…

linux 內核地址隨機化,GNU/Linux內核的地址隨機化

地址空間布局隨機化(ASLR)是一項增加安全性的技術,***者發現漏洞之后開始編寫exploit時如果要考慮繞過ASLR這會增加編寫exploit的難度,最早是2001年Grsecurity社區(強悍的社區,直到今天還在為各種各樣的加固為自由軟件安全社區作出持續而杰出…

Yii2的一些問題

Yii2中刪除能不能串著用 Yii2中find、findAll有什么區別 Yii2中User::findOne($id)和User::find->where([id>1])->one; 會員登錄信息 是以什么樣的形式存放在Yii::$app->user->identity 里面的? session的形式 http://www.cnblogs.com/kuyuecs/archi…

linux系統硬盤設置密碼,LUKS:Linux下磁盤加密

Linux下磁盤加密LUKS(Linux Unified Key Setup)為Linux硬盤加密提供了一種標準,它不僅能通用于不同的Linux發行版本,還支持多用戶/口令。因為它的加密密鑰獨立于口令,所以如果口令失密,我們可以迅速改變口令而無需重新加密真個硬盤…

Hibernate查詢

9.1 Hibernate數據查詢 數據查詢與檢索是Hibernate的一個亮點。Hibernate的數據查詢方式主要有3種,它們是: l Hibernate Query Language(HQL) l Criteria Query l Native SQL 下面對這3種查詢方式分別進…

linux x86 io端口映射,linux中的 IO端口映射和IO內存映射

下面是今天看到兩篇關于linux中的 IO端口映射和IO內存映射的文章,時間關系,沒來得及深入理解,有空好好看看CPU地址空間CPU地址空間(一)地址的概念1)物理地址:CPU地址總線傳來的地址,由硬件電路控制其具體含義。物理地址中很大一部分是留給內存條中的內存…

單例模式 創建對象

兩種選擇 1 使用pthread_once, once是類的成員變量 只執行一次Create create的作用是創建一個對象 2 使用 static lock 如下所示,注意lock必須是static的,否則是局部變量,每個線程都有自己的lock,無法保證只執行一次。…

Linux c編譯庫路徑,【一點一點學Linux C】交叉編譯時候如何配置連接庫的搜索路徑...

交叉編譯的時候不能使用本地(i686機器,即PC機器,研發機器)機器上的庫,但是在做編譯鏈接的時候默認的是使用本地庫,即/usr/lib,/lib兩個目錄。因此,在交叉編譯的時候,要采取一些方法使得在編譯鏈接的時候找到…

[NBUT 1458 Teemo]區間第k大問題,劃分樹

裸的區間第k大問題&#xff0c;劃分樹搞起。 #pragma comment(linker, "/STACK:10240000") #include <map> #include <set> #include <cmath> #include <ctime> #include <deque> #include <queue> #include <stack> #inc…

Linux的軟件包封裝格式有,linux軟件安裝包詳解---全

詳細介紹了常見的四種Linux應用軟件安裝包及其安裝方法。一、解析Linux應用軟件安裝包&#xff0c;通常Linux應用軟件的安裝包有四種&#xff1a;1) tar包&#xff0c;如software-1.2.3-1.tar.gz。他是使用UNIX系統的打包工具tar打包的。2) rpm包&#xff0c;如software-1.2.3-…

人生的第一個博客(●'?'●)??--開博典禮

嘛&#xff0c;說實話&#xff0c;現在才開始&#xff0c;實在是有點晚了&#xff0c;一不小心大學都過去1年了_(:3 」∠)_ 我在專業方面的起步也是相當晚的&#xff0c;身為計算機專業&#xff0c;編程卻從大學才開始正式接觸&#xff0c;進入大學時其他方面的能力也都約等于0…

linux查看運行鐘的tomcat,linux查看tomcat啟動運行日志

Linux0&period;11內核--進程調度分析之2&period;調度[版權所有,轉載請注明出處.出處:http://www.cnblogs.com/joey-hua/p/5596830.html ] 上一篇說到進程調度歸根結底是調用timer_interrupt函數, ...iReport 下載地址iReport 下載地址: https://osdn.jp/projects/sfnet…

8月面試題目收錄

面試題收錄 常見兼容性問題&#xff1f; * png24位的圖片在iE6瀏覽器上出現背景&#xff0c;解決方案是做成PNG8.* 瀏覽器默認的margin和padding不同。解決方案是加一個全局的*{margin:0;padding:0;}來統一。* IE6雙邊距bug:塊屬性標簽float后&#xff0c;又有橫行的margin情況…

linux如何升級php版本升級,Linux?升級php版本

近來因工作需要,又沒有服務器維護人員,只能自己上陣啦。從php5.3.28->5.5.30,先自己下載php包到/usr/local/下?&#xff0c;# 解壓縮安裝包tar zxvf php-5.5.30.tar.gz# 進入目錄cd php-5.5.30// 編譯的時候一定要加入參數--enable-fpm#./configure --prefix/usr/local/php…

opencv配置

OpenCV的簡單安裝和一次性配置在這里就不贅述了&#xff0c;網上教程很多&#xff0c;可以參考一下這個鏈接里面的教程http://wenku.baidu.com/view/3b40de25453610661ed9f46b.html。 但是很多情況下面&#xff0c;我們新建一個項目就要重新配置一次OpenCV&#xff0c;那就相當…

linux ftp 工作過程,linux中ftp的安裝過程記錄[運維篇]

安裝FTP的全過程記錄&#xff0c;對于相同情況希望有所幫助。【centOS】1、查詢本機是否安裝vsftpd: rpm -qa |grep vsftpd &#xff1b;2、安裝ftp服務 yum install vsftpd;3、開啟ftp服務 chkconfig vsftpd on&#xff0c;開機啟動&#xff1b;4、手動操作ftp服務&#xff0c…

代碼命名,代碼里的命名規則:錯誤的和正確的對比 命名方法總結 “自我描述的源代碼”用代碼表達出你的思想,讓其他人通過代碼能明白你的意圖。...

http://www.aqee.net/express-names-in-code-bad-vs-clean/ 編程初學者總是把大量的時間用在學習編程語言&#xff0c;語法&#xff0c;技巧和編程工具的使用上。他們認為&#xff0c;如果掌握了這些技術技巧&#xff0c;他們就能成為不錯的程序員。然而&#xff0c;計算機編程…

linux 動態執行cp,Linux常用命令之cp、mv、rm、cat、more、head、tail、ln命令講解

上一章節中&#xff0c;我們了解到了Linux系統的最基礎的幾個文件處理命令&#xff0c;核心的是ls命令&#xff0c;在今天這章中&#xff0c;我們來繼續學習Linux對于文件操作相關的一些命令&#xff0c;比如復制、移動、刪除、查看等命令。1、cp 命令解釋命令名稱&#xff1a;…

使用DBI(perl)實現文本文件的導入導出mysql

DBI 是perl腳本連接數據庫的一個模塊。perl腳本相對shell更靈活&#xff0c;功能更強大&#xff0c;跨平臺能力強。相對可執行jar包要簡單很多。 ?1、下載安裝包DBI-1.631.tar.gzperl腳本下載的網站http://www.cpan.org/ 很多perl的組件都可以在這個網站上下載 2、解壓tar -xz…

linux 車載視頻監控,基于Linux平臺車載視頻監控系統研發-計算機科學與技術專業論文.docx...

基于Linux平臺車載視頻監控系統研發-計算機科學與技術專業論文目錄HYPERLINK \l "_bookmark0" 第一章 緒論1 HYPERLINK \l "_bookmark1" 1.1 研究背景1 HYPERLINK \l "_bookmark2" 1.2 研究動態1 HYPERLINK \l "_bookmark3" 1.3 本文工…