iT邦幫忙

2026 iThome 鐵人賽

DAY 25
0
Software Development

C++ 套件管理器求生指南 Conan vs Vcpkg系列 第 25 篇

D25:vcpkg 動態庫部署:App-Local 與執行期 DLL

  • 分享至 

  • xImage
  •  

用 vcpkg 裝了一個動態連結的函式庫,build 完直接執行,程式就跑起來了。

修旦幾咧,DLL 呢?我們什麼都沒設定,DLL 是從哪裡來的?

這是因為 vcpkg 背後偷偷幫你做了一件事:App-Local。

一、App-Local 是什麼

vcpkg 建置完會自動把 exe 需要的 DLL 複製到 exe 旁邊,連 DLL 自己再用到的 DLL 也一起帶過去,讓程式一啟動就能在自己的目錄裡找到它們。這樣本機執行或把程式夾整包帶走時,不必改 PATH,也不必把那些 DLL 裝進系統目錄。

這就是 App-Local。預設就開著,你什麼都不用做。

二、實際跑一次

先寫一個最小的 main.c,印出 OpenSSL 版本:

#include <stdio.h>
#include <openssl/crypto.h>

int main(void) {
    printf("%s\n", OpenSSL_version(OPENSSL_VERSION));
    return 0;
}

CMakeLists.txt 很普通。vcpkg 的 openssl 用的是 CMake 內建的 FindOpenSSL:

cmake_minimum_required(VERSION 3.23)
project(my_app C)

find_package(OpenSSL REQUIRED)

add_executable(my_app main.c)
target_link_libraries(my_app PRIVATE OpenSSL::Crypto)

install(TARGETS my_app)

vcpkg.json 只要寫一個依賴:

{
  "dependencies": [ "openssl" ]
}

注意這裡沒有任何選項說要動態還是靜態。在 vcpkg,這是由 triplet 決定的。D11 講過,x64-windows 預設就是動態連結,x64-windows-static 才是靜態。

這次把 toolchain 和 triplet 寫進 CMakePresets.json,就不用每次在命令列打一長串 -D 了:

{
  "version": 3,
  "configurePresets": [
    {
      "name": "win",
      "binaryDir": "${sourceDir}/build",
      "cacheVariables": {
        "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
        "VCPKG_TARGET_TRIPLET": "x64-windows"
      }
    }
  ]
}

然後 configure、build:

cmake --preset win
cmake --build build --config Release

打開 build\Release(用 Visual Studio 產生器的話會在這裡):

> dir build\Release
libcrypto-3-x64.dll
my_app.exe

> build\Release\my_app.exe
OpenSSL 3.6.4 25 Aug 2026

你會看到 my_app.exe 旁邊已經躺著 OpenSSL 的 DLL 了。

撰文當下 vcpkg 的 openssl port 是 3.6.4,屬於 3.x 版,所以檔名會是 libcrypto-3-x64.dll 這種格式。實際版本以你電腦上的為準。

還有一個小細節:我們只連結了 OpenSSL::Crypto,所以理論上你只會看到 libcrypto,不會看到 libssl。App-Local 是看 exe 實際依賴哪些 DLL 來複製,不是整個 bin 資料夾倒過來。

直接執行 my_app.exe,印出 OpenSSL 版本。不用設 PATH,也不用手動複製任何 DLL。

蛤?就這樣?就這樣。

三、故意把它關掉

人就是賤,東西好好的就想弄壞看看。

App-Local 由 CMake 變數 VCPKG_APPLOCAL_DEPS 控制,預設是 ON。把它加進 preset:

"cacheVariables": {
  "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
  "VCPKG_TARGET_TRIPLET": "x64-windows",
  "VCPKG_APPLOCAL_DEPS": "OFF"
}

這裡有個規矩:所有影響 vcpkg 的變數,都要在第一次呼叫 project() 之前設定。 因為 vcpkg 的 toolchain 是在 project() 裡被執行的,之後才 set() 就來不及了。放在 preset 的 cacheVariables 裡最省事。

重新 build 之前,請把整個 build 資料夾刪掉。

為什麼?第一,cache 裡已經記住舊的設定。第二,上一次複製進去的 DLL 還好好地躺在 build\Release 裡,不刪掉的話程式照樣能跑,你會以為 App-Local 根本關不掉。

rmdir /s /q build
cmake --preset win
cmake --build build --config Release
build\Release\my_app.exe

程式啟動失敗,錯誤碼 0xC0000135。雙擊執行的話,會跳出找不到 libcrypto DLL 的視窗。

注意,如果你的 PATH 裡剛好有別的 libcrypto-3-x64.dll(例如 Git for Windows 的 mingw64\bin),程式會悄悄載入那一份,不但不會失敗,印出來的版本還可能不一樣。我實測就遇到:關掉 App-Local 後程式照跑,印出 OpenSSL 3.5.5,不是 vcpkg 的 3.6.4。要看到 0xC0000135,得用乾淨的 PATH 啟動(例如只留 C:\Windows\System32;C:\Windows)。所以要驗收部署結果,一定要用乾淨的環境來跑喔。

這證明這段時間都是 App-Local 在默默扛著。

那什麼時候真的要關?除非你的專案有自己一套 DLL 部署流程,否則保留預設值就好。

順帶一提:現在 App-Local 預設是用 vcpkg 執行檔內建的 vcpkg z-applocal 來做。網路上舊教學提到的 applocal.ps1 是舊的 PowerShell 版本,要設定 VCPKG_USE_LEGACY_APPLOCAL=ON 才會用到,一般人不用管它。

四、坑:cmake --install 不會幫你帶 DLL

App-Local 是在 build 完之後 把 DLL 複製到 build 輸出目錄。

但實際出貨時,很多人會走 install:

cmake --install build --config Release --prefix dist

打開 dist\bin,只有孤零零的 my_app.exe。DLL 呢?沒有跟過來。

因為 install(TARGETS my_app) 只會安裝你自己的 target,不會管它依賴哪些外部 DLL。App-Local 複製到 build 資料夾的那些檔案,install 根本不知道。

解法是請 CMake 幫你算 runtime 相依項目。CMake 3.21 開始,install(TARGETS) 可以加上 RUNTIME_DEPENDENCIES:

install(TARGETS my_app
  RUNTIME_DEPENDENCIES
    PRE_EXCLUDE_REGEXES "api-ms-.*" "ext-ms-.*"
    POST_EXCLUDE_REGEXES ".*system32/.*\\.dll"
    DIRECTORIES
      "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}$<$<CONFIG:Debug>:/debug>/bin"
)

逐行解釋:

  • RUNTIME_DEPENDENCIES:install 時掃描 my_app.exe 依賴哪些 DLL,一起裝到 bin。
  • PRE_EXCLUDE_REGEXES:排除 api-ms-*、ext-ms-* 這種 Windows 的 API set 虛擬 DLL,它們根本不是真的檔案。
  • POST_EXCLUDE_REGEXES:排除 system32 裡的系統 DLL。系統 DLL 不要自己塞進安裝包。
  • DIRECTORIES:告訴 CMake 去 vcpkg 的 bin 找 DLL。Debug 版的 DLL 在 debug/bin,所以用了一段 generator expression。

這樣 dist\bin 裡就會有 my_app.exe 加上 libcrypto 的 DLL:

> dir dist\bin
libcrypto-3-x64.dll
my_app.exe

我在 Windows 11、Visual Studio 產生器、CMake 4.4.3 上實測過,加了這段之後 dist\bin 確實多了 libcrypto,用乾淨 PATH 執行 dist\bin\my_app.exe 也能正常印出版本。

用新版 CMake 時,install 階段可能跳出 CMP0207 的 policy 警告(說 C:\WINDOWS\system32/ws2_32.dll 這類路徑的比對方式會改變)。它只是警告,結果沒錯;想消掉的話,可以在 CMakeLists.txt 用 cmake_policy(SET CMP0207 NEW) 明確表態。其他環境我沒全部跑過,建議自己確認 dist\bin 裡面真的有東西。

另外,vcpkg 的 vcpkg.cmake 裡其實藏了一個 X_VCPKG_APPLOCAL_DEPS_INSTALL 選項,可以讓 install 也走 App-Local。但它在原始碼裡標示為 experimental,官方文件也沒寫,正式專案我會先用 CMake 自己的做法。

附註:App-Local 只在 Windows 上作用。Linux 和 macOS 要靠 RPATH 設定,而且 vcpkg 在這兩個平台預設是靜態連結,通常碰不到這個問題。

五、App-Local 救不了的 DLL

App-Local 只認得「建置時看得出來」的 DLL 相依。以下這些它看不到:

  • 執行時才用 LoadLibrary() 拼出檔名來載入的外掛
  • 由外部設定檔指定要載入的 DLL
  • 沒有正確宣告執行期資訊的元件

這些都要自己加進部署流程,然後測試。

還有,Windows 系統 DLL 不要因為「怕少」就亂複製到程式資料夾。那不是你的東西。

小結

App-Local 讓 vcpkg 在開發階段幾乎感覺不到 DLL 的存在。但 build 資料夾不是發布資料夾,出貨前還是要自己處理 install,並在乾淨環境驗收。

明天換 Conan:用 Conan 裝的動態庫,DLL 又要怎麼部署?


上一篇
D24:vcpkg 怎麼升級套件版本?套件更新機制解密
下一篇
D26:Conan 動態庫部署 deployer
系列文
C++ 套件管理器求生指南 Conan vs Vcpkg 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言