CMake 進行多專案中dll的編譯和連結

qiyuewuyi2333發表於2024-04-22

前言(maybe廢話)

最近正在學習cherno的遊戲引擎教程,他使用的是vs進行構建的,後面換了premake。而我用的是vscode+cmake,所以在構建整個專案的時候踩了不少的坑,也找了很多資料去努力解決,比如b站雙笙子大佬的cmake教程(強推)。
遂有感而發,寫下本篇部落格記錄一下。
duan20030920@gmail.com 這是我的郵箱,如果有朋友有什麼疑問的話,歡迎諮詢。當然也可以評論。

CMake是自由的

眾所周知的是,CMake是基於你檔案系統中的CMakeLists.txt檔案的,所以,只要你資料夾裡面有個CMakeLists檔案,你都可以把該資料夾構建成一個專案。
由此而發,我們很自然地會想,我是否能在一個專案的資料夾中去再構建一個專案呢?
當然是可以的,只要你放CMakeLists,我們就是兄弟(霧)。

扯遠了,咳咳。這篇部落格主要是用來講dll的。

說起dll,大家肯定都不陌生,它是windows平臺下,用於動態連結的庫檔案。
什麼是動態連結我在此就不多做贅述。
那麼,當我們把一個專案編譯成dll之後,我們該如何使用它呢?
是不是隻要和.lib一樣,target_link一下就行了呢?當然不是,win下的動態連結機制較為複雜,大體可以分成兩個部分:

  1. 程式知道我可以連結誰(用誰的函式)
  2. 程式真的匯入了相應的二進位制檔案

過程1

你可能會想,為什麼我直接把程式連結上dll之後,不能直接使用呢?
如果你是高貴的linux使用者,這麼做是沒問題的(.so)。
但是在window下,你不能直接這麼做。
前面我們提到,exe是按需將dll中的函式和資料(以下簡稱“資料”)連結到其中的,所以我們肯定不能一口氣將dll全部複製到程式中,那不就成了lib了。所以我們就需要知道我們要連結哪些資料,以及 我們到哪裡連結哪些資料

前者在我們透過引入dll的標頭檔案解決,但問題是如果沒有後者,即使我們知道要用誰,也是束手無策的。

所以windows就在它的dll動態連結系統中,提供了下面一種架構,在建立dll的同時,建立一個lib,這個lib負責告知exe,dll裡面都有什麼資料,相當於一個宣告,所以它的size很小,也滿足我們靈活性的需求。
(更加細節的部分,請看下方的“為何Windows下連結dll還需要一個.lib?)

過程2

與lib直接將資料複製到exe不同,dll是在執行時,將所需要的資料載入到記憶體的方式,提供給exe使用,並且實現了多個exe的複用。所以很多大型的專案都趨向於將庫做成dll的形式與exe分離,減小exe的大小。
所以,我們的exe能不能找到dll就很關鍵。
於是我們對構建系統進一步發展,設計出dll的兩種連結到exe的方式。它們分別是:隱式連結和顯式連結。
所謂隱式與顯式之別,就是 是否在程式中指定連結dll

隱式連結

就是我們之前提到的,利用.lib和.h匯入我們需要的資訊,然後進行dll中的動態連結。

顯式連結

在程式中指定,這需要window提供的庫函式來實現。
# Dynamic-Link庫函式
這是更底層的,執行緒級對dll載入的控制。
同時,在某種程度上,實現了延時載入和按需載入,也給了我們dll路徑更多的選擇。
是目前超大型專案的不二之選。
這個我們以後再細聊。

more information...

為何.exe執行時需要.dll在身邊

首先要明確的是,dll是在我們的exe執行時動態的連結到程式中的,也就是說,等你要了,我才把我的二進位制檔案給你。
又因為cmake是作用於專案的構建時,所以它不能設定我們的應用程式去搜尋指定目錄下的dll。
windows下dll動態連結系統設定要求,在進行動態連結時,執行時程式會搜尋如下路徑的dll檔案:

  • exe路徑下
  • 系統變數path路徑下
  • system路徑,也就是系統目錄下
    所以在 預設情況下,我們自己專案的dll檔案需要放在exe目錄下,那有沒有其它的解決方案呢?

使用Window.h庫函式動態載入dll

參考:
# c++ 分目錄載入dll依賴
# exe與引用的dll不在同一目錄下

既然它是執行時載入,那我把它寫到我的程式裡不就行了嗎?
但是這種方案也會帶來很多不便,比如說它需要我們修改原始碼,而且不能使用模糊搜尋,優雅,但沒有完全優雅。
不過用於組織我們的專案可以說是綽綽有餘了。

為何Windows下連結dll還需要一個.lib?

在 Windows 系統中使用 DLL(動態連結庫)時需要一個額外的 .lib 檔案作為匯入庫,主要是由於 Windows 連結模型和 DLL 如何被作業系統和應用程式使用的特定設計決策。以下是一些關鍵原因和這種設計的詳細解釋:

1. 連結過程的分隔

在 Windows 上,應用程式通常在編譯時靜態連結到庫,或者在執行時動態連結。DLL 為動態連結提供支援,它們在應用程式啟動後或執行中按需載入。然而,為了在編譯時讓連結器知道哪些函式可用,以及它們在 DLL 中的具體位置,就需要一個額外的匯入庫(.lib 檔案)來作為中介:

  • 匯入庫包含存根:匯入庫 .lib 包含了指向 DLL 中函式的跳轉存根(stubs)。這些存根提供了必要的資訊,以確保在應用程式呼叫 DLL 中的函式時,能夠正確地重定向到相應的函式實現。

2. 解析外部符號

當你的程式使用 DLL 中的函式時,編譯器需要解析這些外部函式呼叫的位置。匯入庫包含了所有公開函式的符號及其入口點,這些資訊在編譯時被連結器使用,以確保執行時能夠找到並呼叫正確的程式碼。

  • 避免編譯時直接依賴 DLL:如果沒有匯入庫,編譯器在編譯時會需要直接訪問 DLL,這在多數開發和構建環境中是不可行的。使用匯入庫簡化了構建過程,允許編譯器和連結器在沒有實際 DLL 檔案的情況下完成其工作。

3. 模組化和部署靈活性

使用匯入庫和 DLL 分離的方式提高了應用程式的模組化和部署靈活性。開發者可以僅重新部署修改過的 DLL 而不需要重新編譯整個應用程式,這樣可以方便地更新和維護大型應用。

  • 允許不同的語言和工具鏈開發:DLL 可以由不同的程式語言編寫和編譯,只要它們遵循相同的呼叫約定。這樣,C#、Visual Basic、C++ 等不同語言編寫的元件都可以使用相同的 DLL,而透過匯入庫與之互動。

4. 執行時載入

DLL 可以在應用程式執行時載入和解除安裝,提供了高度的靈活性。匯入庫使得這種動態載入成為可能,因為它把編譯時的外部依賴轉換為執行時的動態查詢和連結。

總的來說,匯入庫 .lib 在 Windows 系統中的使用提供了一個高效、靈活的機制,使得程式在編譯時不必繫結特定的庫實現,同時在執行時可以利用 DLL 提供的動態連結和模組化的好處。

相關文章