死磕交叉Qt库有段时间了,也写了几篇文章:
这几篇文章中的区别就是挂载sysroot的方式不同,但都有一个共同的缺陷:只能编译Qt基础的库,没有编译插件,基本无法分发到Linux中运行,在Linux上Qt总是依赖各种各样的库。
为什么在Windows上无法编译插件呢?因为有道无法逾越的鸿沟:检测程序。
在Windows上执行的是configure.bat,在Linux上执行的是configure,因为系统的差异你没法用configure.bat去检测Linux环境,这样就只能编译出Qt5Core、Qt5Gui等基础库,一旦程序分发到Linux上运行,就是没法启动了,比如xcb库等问题,因为根本就没编译出来。
之前我都是在Linux下编译一套满血版的Qt,Windows下编译的Qt只是起到桩的作用(Windows 仅作编译,运行靠Linux库),只编译几个基本的Qt库也够了。
但这样存在隐患:因为两边的Qt库并非使用相同的配置,这可能导致类内存布局的不同,从而引发内存访问异常的错误。而且还要在Windows和Linux下分别编译。
接着我又继续摸索解决方案,比如试图在msys2中编译,但环境不同,也根本行不通。
探索了N条路线后想到一个缝合方案,根本不需要在Windows下编译了,而是将Linux下满血版本拷贝到Windows上使用!
这样的好处是:
- 只用编译一次Qt,确保了ABI一致,不用担心在Windows使用时和程序运行在Linux上的异常行为。
- 编译发生在Linux,所以是满血版的Qt,不会“缺斤少两”。
具体操作步骤:
- 在Linux上编译Qt,然后将
include、lib、plugins这三个目录打包到Windows上(不要打包符号文件,而是直接打包链接的目标文件,tar命令用h参数),这就是所有的头文件和.so文件。
- 编译Windows版本的Qt,因为是跨平台开发,提取
bin、translations目录
这样就拼接出了一个Qt库:
1 2 3 4 5 6 7
| qt ├── bin // Windows平台的二进制工具 ├── include // Linux下编译的头文件 ├── lib // Linux下编译的.so库 ├── mkspecs ├── plugins // Linux下编译的.so库 └── translations // 多语言文件
|
就这么简单!
但是编译程序时会报错,因为在Qt的CMake配置中会检查工具的完整性,在lib/cmake/Qt5Core/Qt5CoreConfigExtras.cmake文件中为qmake、moc和rcc加上.exe后缀。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| if (NOT TARGET Qt5::qmake) add_executable(Qt5::qmake IMPORTED)
set(imported_location "${_qt5Core_install_prefix}/bin/qmake.exe") _qt5_Core_check_file_exists(${imported_location})
set_target_properties(Qt5::qmake PROPERTIES IMPORTED_LOCATION ${imported_location} ) endif()
if (NOT TARGET Qt5::moc) add_executable(Qt5::moc IMPORTED)
set(imported_location "${_qt5Core_install_prefix}/bin/moc.exe") _qt5_Core_check_file_exists(${imported_location})
set_target_properties(Qt5::moc PROPERTIES IMPORTED_LOCATION ${imported_location} ) get_target_property(QT_MOC_EXECUTABLE Qt5::moc LOCATION) endif()
if (NOT TARGET Qt5::rcc) add_executable(Qt5::rcc IMPORTED)
set(imported_location "${_qt5Core_install_prefix}/bin/rcc.exe") _qt5_Core_check_file_exists(${imported_location})
set_target_properties(Qt5::rcc PROPERTIES IMPORTED_LOCATION ${imported_location} ) endif()
|
还有一处在lib/cmake/Qt5Widgets/Qt5WidgetsConfigExtras.cmake文件中为uic加上.exe后缀。
1 2 3 4 5 6 7 8 9 10
| if (NOT TARGET Qt5::uic) add_executable(Qt5::uic IMPORTED)
set(imported_location "${_qt5Widgets_install_prefix}/bin/uic.exe") _qt5_Widgets_check_file_exists(${imported_location})
set_target_properties(Qt5::uic PROPERTIES IMPORTED_LOCATION ${imported_location} ) endif()
|
还没完,在编译过程中Qt会依赖一些Linux下的库,由于我们没有制作sysroot,所以会链接失败。
我的做法是根据编译失败信息,将信息中提示的依赖库从Linux系统中拷贝出来,放到Qt的sysroot目录下,所以最终Qt的目录结构就是这样的:
1 2 3 4 5 6 7 8 9 10 11
| qt ├── bin // Windows平台的二进制工具 ├── include // Linux下编译的头文件 ├── lib // Linux下编译的.so库 ├── mkspecs ├── plugins // Linux下编译的.so库 ├── translations // 多语言文件 └── sysroot └── usr ├── include └── lib
|
但是Qt并不会从sysroot下去搜索头文件和库,所以我们在lib/cmake/Qt5Core/Qt5CoreConfigExtras.cmake文件末尾加点配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| set(_cross_sysroot "${_qt5Core_install_prefix}/sysroot") set(_arch_triple "aarch64-linux-gnu")
list(PREPEND CMAKE_PREFIX_PATH "${_cross_sysroot}/usr")
if(TARGET Qt5::Core) foreach(_path "${_cross_sysroot}/usr/include" "${_cross_sysroot}/usr/include/${_arch_triple}" ) if(EXISTS "${_path}") target_include_directories(Qt5::Core INTERFACE "${_path}") endif() endforeach()
foreach(_path "${_cross_sysroot}/lib" "${_cross_sysroot}/lib/${_arch_triple}" "${_cross_sysroot}/usr/lib" "${_cross_sysroot}/usr/lib/${_arch_triple}" ) if(EXISTS "${_path}") target_link_options(Qt5::Core INTERFACE "-Wl,-rpath-link,${_path}") endif() endforeach() endif()
unset(_cross_sysroot) unset(_arch_triple)
|
最后,lib目录下的.prl和.la文件中会有Linux的路径,不过这两种文件是qmake用的,由于qmake已经被淘汰了,所以我们可以直接删除这两类文件。
至此,我们就得到了一个可以在Windows上编译,运行在Linux,又不担心配置不同而导致ABI等问题的完美的交叉Qt库!