用 vcpkg 裝了一個動態連結的函式庫,build 完直接執行,程式就跑起來了。
修旦幾咧,DLL 呢?我們什麼都沒設定,DLL 是從哪裡來的?
這是因為 vcpkg 背後偷偷幫你做了一件事: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 不會幫你帶 DLLApp-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 相依。以下這些它看不到:
LoadLibrary() 拼出檔名來載入的外掛這些都要自己加進部署流程,然後測試。
還有,Windows 系統 DLL 不要因為「怕少」就亂複製到程式資料夾。那不是你的東西。
App-Local 讓 vcpkg 在開發階段幾乎感覺不到 DLL 的存在。但 build 資料夾不是發布資料夾,出貨前還是要自己處理 install,並在乾淨環境驗收。
明天換 Conan:用 Conan 裝的動態庫,DLL 又要怎麼部署?