静态编译的好处是发布的时候不用带上一堆动态链接库。Msys2工具链理论上是支持静态编译的,但实际使用的时候即使直接加上-static开关,链接生成的可执行文件最后运行时还是会提示缺失动态链接库,尤其是Mingw工具链依赖的libwinpthread-1.dlllibstdc++-6.dlllibgcc_s_seh-1.dll这"三大件"。而对于clang编译器,运行时依赖又变成了libc++.dll。本文探讨一种简便的办法,方便针对不同编译器的静态链接编译管理。

1. FLTK项目静态编译

这里用FLTK实现一个简单的窗口应用,FLTK的LICENSE支持发布静态链接版本的程序,而且生成的程序体积非常小,用来编写和发布小工具十分方便。

// --- mian.cpp ---

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Button.H>
#include <FL/fl_ask.H>

class SimpleWindow : public Fl_Window
{
private:
    Fl_Button *button;

public:
    SimpleWindow(int w, int h, const char *title) : Fl_Window(w, h, title)
    {
        color(FL_WHITE);
        button = new Fl_Button(w / 2 - 50, h / 2 - 20, 100, 40, "Click Me!");
        button->color(FL_BLUE);
        button->labelcolor(FL_WHITE);
        button->callback([](Fl_Widget *w, void *data)
                         { 
                            fl_message_title("Click Me!");
                            fl_message("You clicked the button!"); }, NULL);
        end();
    }
};
int main(int argc, char **argv)
{
    SimpleWindow window(400, 300, "FLTK Simple Window");
    window.show(argc, argv);
    return Fl::run();
}

msys2安装的FLTK提供了fltk-config工具用于配置和编译FLTK项目源码,使用该工具可以很方便地将FLTK项目的cpp源码编译成可执行文件。

fltk-config --compile main.cpp --link -static

--link -static表示链接静态库,运行效果如下图所示。

5f13ec9d82726c88e3d8f6fbc4cf90c6.png

2. CMake管理的FLTK项目

fltk-config工具虽然好用,但是只支持bash,只能在msys2和Linux终端中使用。如果有跨平台编译需求,或者项目依赖稍微复杂一点,fltk-config明显就不太够用了。推荐使用CMake管理FLTK项目,CMake配置文件CMakeLists.txt编写如下。

# --- CMakeLists.txt ---

cmake_minimum_required(VERSION 3.21)
set(PROJECT_NAME fltk_sample)
project(${PROJECT_NAME} LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

if(NOT "$ENV{VCPKG_TARGET_TRIPLET}" STREQUAL "")
  set(VCPKG_TARGET_TRIPLET $ENV{VCPKG_TARGET_TRIPLET})
  message(STATUS "VCPKG_TARGET_TRIPLET: ${VCPKG_TARGET_TRIPLET}")
endif()

# find fltk
find_package(FLTK REQUIRED)

if(FLTK_FOUND)
  message(STATUS "Found FLTK: ${FLTK_VERSION}")
  message(STATUS "FLTK_INCLUDE_DIR: ${FLTK_INCLUDE_DIR}")
  include_directories(${FLTK_INCLUDE_DIR})
  message(STATUS "FLTK_LIBRARIES: ${FLTK_LIBRARIES}")
else()
  message(FATAL_ERROR "FLTK not found")
endif()

add_executable(${PROJECT_NAME} main.cpp)

if(MSVC)
  target_link_options(${PROJECT_NAME} PRIVATE "/SUBSYSTEM:WINDOWS" "/ENTRY:mainCRTStartup")
  message(STATUS "Set msvc link flags: /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup")

  if(${VCPKG_TARGET_TRIPLET} MATCHES "static")
    set_property(TARGET ${PROJECT_NAME} PROPERTY
      MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
    message(STATUS "Set msvc runtime library: MultiThreaded$<$<CONFIG:Debug>:Debug>")
  endif()
  target_link_libraries(${PROJECT_NAME} ${FLTK_LIBRARIES})
elseif(MINGW)
  target_link_options(${PROJECT_NAME} PRIVATE "-mwindows")
  message(STATUS "Set mingw link flags: -mwindows")
  include(${CMAKE_CURRENT_SOURCE_DIR}/static.cmake)
  if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
    message(STATUS "Using clang, add -static c++")
    target_link_libraries(${PROJECT_NAME}
      -static c++ ${FLTK_LIBRARIES})
  else()
    message(STATUS "Using gcc, add -static gcc stdc++ winpthread")
    target_link_libraries(${PROJECT_NAME}
      -static gcc stdc++ winpthread ${FLTK_LIBRARIES})
  endif()
endif()

# set installation directories
install(TARGETS ${PROJECT_NAME}
  BUNDLE DESTINATION .
  RUNTIME DESTINATION bin
)

这里对编译器进行了区分,针对gcc用到了-static gcc stdc++ winpthread链接三大件的静态库1,针对clang使用-static c++链接libc++的静态库。

CMake在msys2中默认链接的FLTK库的是动态库,需要自己定义如何链接到静态库。这里通过链接自定义脚本来处理。

# --- static.cmake ---

if(NOT MINGW)
    message(FATAL_ERROR "This script is only for static linking with MinGW")
elseif(NOT FLTK_LIBRARIES)
    message(WARNING "FLTK_LIBRARIES is empty")
else()
    string(REPLACE ".dll.a" ".a" FLTK_LIBRARIES "${FLTK_LIBRARIES}")
    message(STATUS "Static FLTK_LIBRARIES: ${FLTK_LIBRARIES}")
endif()

3. CMake管理的wxWidgets项目

wxWidgets也提供的自己的工具wx-config用来处理编译构建问题,和fltk-config一样只支持bash终端,还是推荐用CMake来管理wxWidgets项目。这里还是用官方的示例代码2来做演示,CMake配置文件CMakeLists.txt编写如下。

# --- CMakeLists.txt ---

cmake_minimum_required(VERSION 3.21)
set(PROJECT_NAME wx_sample)
project(${PROJECT_NAME} LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

if(NOT "$ENV{VCPKG_TARGET_TRIPLET}" STREQUAL "")
  set(VCPKG_TARGET_TRIPLET $ENV{VCPKG_TARGET_TRIPLET})
  message(STATUS "VCPKG_TARGET_TRIPLET: ${VCPKG_TARGET_TRIPLET}")
else()
  set(wxWidgets_USE_STATIC ON)
  message(STATUS "Set wxWidgets_USE_STATIC: ON")
endif()

# find wxWidgets
find_package(wxWidgets REQUIRED COMPONENTS core base)

if(wxWidgets_USE_FILE) # not defined in CONFIG mode
  include(${wxWidgets_USE_FILE})
endif()

message(STATUS "wxWidgets_LIBRARIES: ${wxWidgets_LIBRARIES}")

add_executable(${PROJECT_NAME} main.cpp)

if(MSVC)
  set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "/SUBSYSTEM:WINDOWS")
  message(STATUS "Set msvc link flags: /SUBSYSTEM:WINDOWS")

  if(${VCPKG_TARGET_TRIPLET} MATCHES "static")
    set_property(TARGET ${PROJECT_NAME} PROPERTY
      MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
    message(STATUS "Set msvc runtime library: MultiThreaded$<$<CONFIG:Debug>:Debug>")
  endif()
elseif(MINGW)
  target_link_options(${PROJECT_NAME} PRIVATE "-mwindows")
  message(STATUS "Set mingw link flags: -mwindows")
  if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
    message(STATUS "Using clang, add -static c++")
    set(MINGW_LINK_LIBS -static c++)
  else()
    message(STATUS "Using gcc, add -static gcc stdc++ winpthread")
    set(MINGW_LINK_LIBS -static gcc stdc++ winpthread)
  endif()
endif()

target_link_libraries(${PROJECT_NAME} ${MINGW_LINK_LIBS} ${wxWidgets_LIBRARIES})

# set installation directories
install(TARGETS ${PROJECT_NAME}
  BUNDLE DESTINATION .
  RUNTIME DESTINATION bin
)

wxWidgets通过设置wxWidgets_USE_STATIC开关来指定是否链接到静态库,不过目前只有msys2安装的wxWidgets库有效,或者自己编译 wxWidgets库。

4. 其他

其他ui库,像gtk本身不支持静态编译,Qt则因为许可证问题不推荐静态编译。其他程序也可以参照上面的办法使用CMake编译发布静态链接版本。