文章目錄
- 背景
- 動態庫梳理
- 打包方案
- 一、使用 Vcpkg 安裝靜態庫(關鍵基礎配置)
- 1. 初始化 Vcpkg
- 2. 安裝靜態庫(注意 `x64-windows-static` 后綴)
- 二、CMakeLists.txt 關鍵配置
- 三、編譯
- 四、驗證
- 不同平臺代碼兼容
- \_\_attribute\_\_((packed)) 兼容
- #include \<arpa/inet.h> 兼容
- time兼容
- timezone兼容
背景
使用C++編寫的一個小項目,需要打包成windows下的可執行文件(免安裝版本),方便分發給其他windows執行,需要把項目的動態庫都打在軟件包中,分發之后可以直接運行,而不需要再重復安裝。
動態庫梳理
經過依賴精簡和梳理,項目最終必須依賴的動態庫包括:pcl, bzip2, lz4, yaml, rosbag(用于讀取rosbag包,后續會有專門的文章會提到如何做到不依賴ros環境)
打包方案
yaml動態庫在前面的文章中已經轉成了靜態庫代碼的形式包含在了項目里。對于其他動態庫,簡單調研了一下,發現可以使用 vcpkg 工具
將所有動態庫以靜態庫的形式來安裝,那就嘗試用這個方案。
一、使用 Vcpkg 安裝靜態庫(關鍵基礎配置)
1. 初始化 Vcpkg
# 克隆 vcpkg 倉庫
git clone https://github.com/microsoft/vcpkg
# 構建 vcpkg
.\vcpkg\bootstrap-vcpkg.bat
2. 安裝靜態庫(注意 x64-windows-static
后綴)
# 安裝核心依賴
.\vcpkg.exe install bzip2:x64-windows-static lz4:x64-windows-static# 安裝 PCL 及其全套依賴(耗時約 30-60 分鐘),可能需要科學上網
.\vcpkg.exe install pcl:x64-windows-static
二、CMakeLists.txt 關鍵配置
cmake_minimum_required(VERSION 3.5)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_TOOLCHAIN_FILE "your_path/vcpkg/scripts/buildsystems/vcpkg.cmake")
project(project_name)# 1. 強制使用靜態運行時庫
if(MSVC)# 靜態鏈接運行時(/MT 或 /MTd)set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")# 刪除所有可能存在的動態運行時選項, 防止依賴沖突報錯string(REGEX REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})string(REGEX REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE})string(REGEX REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG})add_compile_options(/MP) # 啟用多核編譯(可選)add_definitions(-DNOMINMAX) # Avoid conflicts with min/max macros like error C2589: “(”:“::”右邊的非法標記
endif()...
# 添加子目錄編譯 yaml-cpp 庫
add_subdirectory(thirdparty/yaml-cpp)find_package(BZip2 REQUIRED)
find_package(LZ4 REQUIRED)# 這里只依賴了 io 這一個components,其他組件可以按需添加
find_package(PCL REQUIRED COMPONENTS io)
if(PCL_FOUND)message("PCL FOUND")include_directories(${PCL_INCLUDE_DIRS})
else()message(WARNING "can not find PCL")
endif()add_executable(MyApp ...)
target_include_directories(MyApp PUBLIC ${PCL_INCLUDE_DIRS}${CMAKE_SOURCE_DIR}/thirdparty/yaml-cpp/include...
)# 鏈接庫
target_link_libraries(MyApp PUBLIC yaml-cppBZip2::BZip2LZ4::lz4_static${PCL_LIBRARIES}ws2_32
)
三、編譯
# 1. 生成構建系統
cmake -B build -DCMAKE_BUILD_TYPE=Release -DVCPKG_TARGET_TRIPLET=x64-windows-static -DCMAKE_TOOLCHAIN_FILE="your_path/vcpkg/scripts/buildsystems/vcpkg.cmake"
# 2. 編譯項目
cmake --build build --config Release --parallel 8
四、驗證
# 驗證依賴項, 需要
dumpbin /dependents build/Release/MyApp.exe
預期輸出應該只包含以下系統庫:
KERNEL32.dll
USER32.dll
...
不會有其他第三方 DLL(如 boost_*.dll, pcl_*.dll 等)
注意:dumpbin 命令為VisualStudio的工具:
不同平臺代碼兼容
__attribute__((packed)) 兼容
以下代碼中的__attribute__((packed))
與編譯平臺Unix/Linux強相關:
typedef struct MyStruct_s
{int a;char b;
}__attribute__((packed)) MyStruct;
需要做出針對windows的兼容性修改:
// 根據編譯器類型選擇內存對齊語法,建議在 common.h 中定義
#if defined(_MSC_VER)#define PACKED_BEGIN __pragma(pack(push, 1))#define PACKED_END __pragma(pack(pop))#define PACKED_STRUCT /* MSVC 下無需額外關鍵字 */
#elif defined(__GNUC__) || defined(__clang__)#define PACKED_BEGIN#define PACKED_END#define PACKED_STRUCT __attribute__((packed))
#endif// 使用方式:
PACKED_BEGIN
struct MyStruct {int a;char b;
} PACKED_STRUCT;
PACKED_END
#include <arpa/inet.h> 兼容
arpa/inet.h
是Unix/Linux特有的頭文件 ,而Windows平臺沒有這個頭文件。
看代碼中需要用到的只有ntohs
,windows上對應的頭文件為<winsock2.h>
#if defined(_WIN32) || defined(_WIN64)#include <winsock2.h>
#else#include <arpa/inet.h>
#endif
依賴的庫為ws2_32.lib
,需要在CMakeLists.txt中顯式鏈接,否則編譯會報錯:error LNK2001: 無法解析的外部符號 __imp_ntohs, 參考 https://www.cnblogs.com/chai51/p/16931965.html
target_link_libraries(MyApp PUBLIC ...ws2_32
)
time兼容
<time.h> 為Unix/Linux 平臺下獨有,比如 clock_gettime(CLOCK_MONOTONIC, &time)
,在windows上兼容方案如下:
#if defined(_WIN32) || defined(_WIN64)#include <windows.h>
#else#include <time.h>
#endif
unsigned int GetMicroTickCount() {unsigned int ret = 0;#if defined(_WIN32) || defined(_WIN64)// Windows implementation using QueryPerformanceCounterstatic LARGE_INTEGER frequency;static BOOL frequencyInitialized = QueryPerformanceFrequency(&frequency);if (!frequencyInitialized) {return 0; // If the frequency cannot be initialized, return 0}LARGE_INTEGER counter;if (QueryPerformanceCounter(&counter)) {ret = static_cast<unsigned int>((counter.QuadPart * 1000000) / frequency.QuadPart);}#elsetimespec time;memset(&time, 0, sizeof(time));if (clock_gettime(CLOCK_MONOTONIC, &time) == 0) {ret = time.tv_nsec / 1000 + time.tv_sec * 1000000;}#endifreturn ret;
}
timezone兼容
同上, timezone
關鍵詞也是 Unix/Linux 獨有,兼容方案如下:
#ifdef _WIN32// Windows: Use _get_timezone with a temporary long variablelong temp_offset = 0;_get_timezone(&temp_offset);time_offset = static_cast<int64_t>(temp_offset);
#else// Linux: Use timezone global variableextern long timezone;time_offset = timezone;
#endif
return (raw_time - time_offset) * 1000000 + GetTimestamp();