通過編寫.def文件使連接庫調用統一命名規則

<!--StartFragment-->
    如果你想要自己編寫的動態庫可以適用更多的其它編程語言,你可以使用定義文件和WINAPI宏來編寫自己的API動態庫。你將會發現,使用這樣的動態庫輸出函數就象使用API函數一樣。
   
    一、爲什麼要使用DEF文件?

    因爲微軟的MFC動態庫都是使用DEF文件創建的。
    這回答夠有份量吧,但有點大帽子壓人的感覺。下面呢,我從原理上來說說爲什麼要使用DEF文件。
    無論使用C語言或者C++語言來編寫動態庫,其編譯器都會爲每個函數甚至變量生成一個對應的修飾名(我是這樣翻譯的。原文是the decorated names),連接器將編譯後的目標代碼連接成DLL,其輸出函數名或變量名依舊是編譯後的修飾名。並且修飾名是與編譯器相關的,也就是說你的源程序是C,生成的修飾名是一個樣子;如果你的源程序是C++,則生成的修飾名是另一種樣子。(關於修飾名的討論,我將放在一個單獨的章節進行,敬請等待。糧草未動,廣告先行。真是的....)而我們的應用習慣是直接使用函數名,而非修飾名,我們在用API時一直就是如此。那麼,問題就來了,比如你在VB6中使用VC6寫的動態庫:你先在VB6中使用函數名來描述你要調用的函數,然後寫好調用代碼,接下來運行,你的VB6這時會告訴你,它在動態庫中找不到你剛剛描述過的函數的入口點,你的程序拒絕執行了。怎麼辦?解決問題的方法至少有兩種:1、修改你的VB6代碼中對動態庫輸出函數的描述部分,在別名欄添加動態庫輸出函數的修飾名。2、修改你的動態庫,添加一個DEF文件,並使用DEF文件的EXPORTS項來輸出你的動態庫函數(在其下簡單地列出你要輸出的函數名即可)。雖然,這樣一來問題是解決了(還沒有解決?可能嗎?有可能啦。但這裏先停一下,隨後再說。),但我們有必要將該問題進一步討論下去。我們寫動態庫的目的大多是想讓我們的動態庫不侷限於某一種編程語言,是爲了更加廣泛地被應用,我是喜歡用VB寫界面,而用VC來完成更重要的工作,如對數據進行分析、訪問硬件端口等,而且VC寫好的動態庫很少再改動,VB寫的界面倒是一改再改。說這麼多,目的只有一個,寫動態庫要着眼於“大局”,要一切符合“標準”。什麼是大局?大局就是走可持續發展的道路,就是複用(好象是在做政治報告)。什麼是標準呢?就是符合API的標準(也就是使用DEF文件輸出函數,就象微軟的MFC動態庫)。
    其實,使用DEF文件來輸出函數的一個最主要目的就是:將編譯器生成的函數修飾名去掉,用更加自然的、容易理解的、容易記憶的名字,而不是修飾名來輸出函數。這裏的名字可以不是函數名,這時須使用DEF文件的NAME格式。但由於習慣,大多情況下,只使用函數名,因爲這樣最簡單省事。是否存在偷懶的嫌疑?我的理解,不管是羅列函數名,還是其它輸出名,其本質上是一樣的,即都是使用了定義文件的NAME格式。直接羅列函數名,就相當於“函數名”=“函數修飾名”,只是可以忽略等號後面的部分,而連接器會自動完成函數入口的匹配和設置工作。而一旦決定使用非函
數名的其它名字輸出函數,則必須書寫完整的格式,即“函數輸出名”=“要輸出的函數修飾名”,這裏等號後面的部分必須書寫正確,否則,連接時就通不過了。舉個例子:假設動態庫中一個函數描述如下,
int WINAPI TestAdd(int A,int B)
{
    return (A+B);
}

其DEF文件的EXPORTS段描述如下,
EXPORTS
  TestAdd
  Add=?TestAdd@@YGHHH@Z

這裏TestAdd和Add實際上指向同一個入口。如果在VB程序中調用TestAdd和Add,其結果是一樣的。
   
    二、爲什麼要使用WINAPI宏?
    看看上面的舉例,在函數前加了一個WINAPI宏。這一點很重要,它直接關係着函數輸出什麼樣子的修飾名,使用WINAPI宏的TestAdd函數,對應的輸出修飾名就是“?TestAdd@@YGHHH@Z”。
    爲什麼要使用WINAPI呢?這牽涉到動態庫的另一個特徵,調用協議(Calling convention)。如果沒有一定的協議,動態庫的調用是不可想象的。一般常用的動態庫調用協議有:
   __cdecl
   __stdcall
   __fastcall
這些協議各有各的長處,這裏暫不一一描述。上面在談到解決VB程序調用VC寫的動態庫時,曾列舉兩種解決方法,但並不一定可以實現,它還取決於所使用的調用協議。VB所遵循的是PASCAL協議,如果在動態庫中沒有使用相應的協議,則VB程序執行時就會報告“調用協議錯”。而PASCAL協議在VC6中已被廢棄,取而代之的是__stdcall,即標準調用協議,這也是大多32位編程語言支持的一種通用協議。在WINDOWS.H中WINAPI也是被定義爲__stdcall。這裏提議使用WINAPI的理由也就在這,它能夠表達出更加多的信息----這樣定義的輸出函數(的調用協議)和 WINDOWS API函數(的調用協議)一樣。
    其它一些使用WINAPI宏的理由:你只要在所定義的函數前加上該宏,就不必要在每次連接時再去理會各種與調用協議相關的設置。況且,你可能並不需要將所有定義的函數都輸出,爲了提高執行速度,你可能會將沒有輸出的函數使用__fastcall來定義,爲了使用變參,你可能使用__cdecl來定義某些非輸出函數,或者諸如此類的理由......需要提醒的是,VC默認的調用協議是__cdecl。如果你在沒有修改調用協議的情況下,直接使用DEF文件輸出函數,編譯連接都不會出錯,但是VB調用的時候肯定出錯。而如果使用了WINAPI宏,你不必再去理會這些。編譯器自動使用WINAPI的定義替代集成環境裏的相關設置,這裏函數前的說明優先級最高。
    回過頭來再看上面有關輸出函數的修飾名的討論,上面提到修飾名與語言有關,另外,它還與調用協議有關。如果需要使用非函數名的名字用來輸出,你必須清楚你使用的調用協議及語言種類,也就是你必須清楚修飾名的生成規則,或者你採用一些技巧,讓DUMPBIN.EXE工具來幫忙。

    三、總結
    一句話,如果想建立自己的標準API動態庫,建議使用WINAPI描述你要輸出的函數,然後使用定義文件輸出它。

 

 

個人要說的話:

在工作根目錄下(不是debug哦)加這個xxx.def 內容如:

LIBRARY Dll2

EXPORTS
add
subtract

這樣動態連接庫可以不寫倒出的標識了.倒出名字就按這個文件走了.

關於更多的調用方式.C/C++函數調用方式內幕


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章