通常一個 Windows 下 C/C++ 的動態連結函式庫(Dynamic-link library、維基百科、微軟的文件),會有三種東西:開發時使用的 header 檔、連結階段需要的 lib 檔、以及執行階段才需要的 dll 檔。
在這個架構下,實際上所有的程式都已經編譯好、儲存在 dll 檔案裡面了,實際上真的需要,也是可以在執行階段直接去讀取 dll、抓出裡面的函式來用的;但是在有 header 和 lib 的時候,開發上還是比較方便的。
而實際上,lib 檔主要的功能,就是告訴編譯器(其實是連結器)這個 dll 檔裡面有哪些函式可以用而已;在編譯的時候,系統只會去檢查 lib 的內容,而不會去管 dll 檔。
一般 C++ 的函式庫專案,在建置後自然就會有這三項東西了~只要按照正常的流程,就可以使用。
不過,由於 lib 裡面會決定在呼叫某個函式的時候,要去從哪個 dll 檔裡面找,所以實際上 lib 檔的檔名雖然可以隨便修改,但是對應的 dll 卻不行。
比如說有一個函式庫有 libHello.lib 和 libHello.dll 這兩個檔案,如果自己手動改成 libHellod.lib 和 libHellod.dll 來區別 debug / release 的話(很多函式庫預設是這樣在檔名最後加個 d 來標示 debug 版),實際上在執行的時候,他還是會回去找 libHello.dll 的。
雖然一般來說,可以透過修改 Visual C++ 的專案設定,來調整要輸出的檔案名稱,但是有的時候卻很難做到…
而在有需要修改 lib / dll 的檔案名稱、但是卻又沒辦法在建置階段就先改好,該怎麼辦呢?一個方法,就是根據既有的 dll 重新產生一次對應的 lib 檔。
Heresy 這邊的作法,是參考《Modify the dll file name》這篇文章來做的。
這邊的操作,基本上要使用 Visual Studio 提供的兩個工具程式:DUUMPBIN(官方文件)以及 LIB(官方文件),所以在使用的時候,會建議在 Visual Studio 的 Command Prompt 下執行;其主要流程基本上是:
- 透過 DUMPBIN 從 dll 中擷取出定義(def 檔)
- 修改 def 檔的內容以符合 LIB 工具的需求
- 使用 LIB、由修改好的 def 檔產生對應的 lib 檔
透過 DUMPBIN 從 dll 中擷取出定義(def 檔)
這部份很簡單,只要執行下面的指令就可以了:
DUMPBIN libHellod.dll /EXPORTS /OUT:libHellod.def
這樣他就會產生我們需要的 def 檔了。
而 Heresy 這邊的作法,是先把 dll 檔改名好、以免混淆。
修改 def 檔的內容
DUMPBIN 產生的 def 檔案,它的內容形式大概會是下面的樣子(這邊是使用 NVIDIA AIAA Client 做例子、GitHub):
Dump of file NvidiaAIAAClient.dll File Type: DLL Section contains the following exports for NvidiaAIAAClient.dll 00000000 characteristics FFFFFFFF time date stamp 0.00 version 1 ordinal base 96 number of functions 96 number of names ordinal hint RVA name 1 0 000877E0 ??0Client@aiaa@nvidia@@QEAA@$$QEAV012@@Z 2 1 00087820 ??0Client@aiaa@nvidia@@QEAA@AEBV012@@Z 3 2 00087850 ??0Client@aiaa@nvidia@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@H@Z ... ... 95 5E 0009F0A0 ?toString@Model@aiaa@nvidia@@SA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@W4ModelType@123@@Z 96 5F 000A4AC0 ?to_lower@Utils@aiaa@nvidia@@SA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@V45@@Z Summary 24000 .data 2A000 .pdata 25F000 .rdata 23000 .reloc 1000 .rsrc 4AE000 .text
前面有一些檔頭的資訊,後面也有總結(Summary);而這邊需要的,則只有中間黃色的部分。
這一區的資料,基本上分成四欄:ordinal、hint、RVA、name,最後要用到的只有 ordinal 和 name 而已;在這個例子裡面,總共有 96 個函式。
而這邊的修改的方法,就是第一行輸入「EXPORTS」,然後把黃色區域的定義中的 name(第四欄)放到最前面,後面再加上一個「@」、然後接 ordinal(第一欄的數字);這樣的結果大概會是:
EXPORTS ??0Client@aiaa@nvidia@@QEAA@$$QEAV012@@Z @1 ??0Client@aiaa@nvidia@@QEAA@AEBV012@@Z @2 ??0Client@aiaa@nvidia@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@H@Z @3 ... ... ?toString@Model@aiaa@nvidia@@SA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@W4ModelType@123@@Z @95 ?to_lower@Utils@aiaa@nvidia@@SA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@V45@@Z @96
然後把所有的函式定義都處理好(人工其實很麻煩),就算完成了~
使用 LIB 產生需要的 lib 檔
在修改好 def 檔後,只要執行下面的指令,就可以產生需要的 lib 檔了。
LIB /DEF:libHellod.def /machine:X64 /OUT:libHellod.lib
如此一來,搭配一開始改名好的 libHellod.dll,這邊就有對應的 libHellod.lib 了~
不過也要注意,這邊是針對 x64 的版本來寫的。
GenLibFromDll
由於要用人工去處理 def 檔的修改有點繁瑣,而 Heresy 這邊又想弄成腳本自動建置的形式,所以後來就自己用 C++ 寫了一個按照上面的流程、從 dll 檔案產生 lib 檔的程式了~
專案是:https://github.com/KHeresy/GenLibFromDll
在 release 裡面也有提供建置好的檔案(x64)。
執行的時候,只要在 Visual Studio 的命令提示字元下執行
GenLibFromDll.exe libHello.dll
就可以了。
DUMPBIN 和 LIB 的呼叫、以及 def 的修改,都會在程式裡做掉。
不過這邊也要注意,在 def 的處理上,其實比想像中的複雜一點。
一般的 dll 確實是只會有 ordinal、hint、RVA、name 這四個項目,可以簡單地用空白來切割;但是如果是 debug 版的 dll,根據編譯參數,格式可能會是下面的樣子:
ordinal hint RVA name 1 0 00088277 qt_plugin_instance = @ILT+17010(qt_plugin_instance) 2 1 0008480C qt_plugin_query_metadata = @ILT+2055(qt_plugin_query_metadata)
也就是第四欄的 name 會是 xxxx = yyy 這樣的形式。
所以要做字串切割的話,一般的 dll 在這邊會切成四項;但是遇到這種狀況,則會是六項。
處理後則會變成下面的樣子:
EXPORTS qt_plugin_instance = @ILT+17010(qt_plugin_instance) @1 qt_plugin_query_metadata = @ILT+2055(qt_plugin_query_metadata) @2
而更麻煩的是,在某些狀況下,還會出現比六項還多的狀況…而在最後面的東西,感覺則像是註釋或標記?如果留下來不刪除了的話,執行 LIB 會認為是語法錯誤。
所以其實 Heresy 後來的處理方法是只針對可以拆成四項、六項的來做處理;第 6 項以後的東西就都強制丟了。這樣會不會碰到其他問題?有碰到再說吧~