C++ Windows 打包exe运行方案(cmake)

背景

使用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-cpp
  BZip2::BZip2
  LZ4::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 QueryPerformanceCounter
    static 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);
    }
  #else
    timespec time;
    memset(&time, 0, sizeof(time));
    if (clock_gettime(CLOCK_MONOTONIC, &time) == 0) {
      ret = time.tv_nsec / 1000 + time.tv_sec * 1000000;
    }
  #endif
  return ret;
}

timezone兼容

同上, timezone关键词也是 Unix/Linux 独有,兼容方案如下:

#ifdef _WIN32
  // Windows: Use _get_timezone with a temporary long variable
  long temp_offset = 0;
  _get_timezone(&temp_offset);
  time_offset = static_cast<int64_t>(temp_offset);
#else
  // Linux: Use timezone global variable
  extern long timezone;
  time_offset = timezone;
#endif
return (raw_time - time_offset) * 1000000 + GetTimestamp();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

递归书房

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值