Windows 提示和技巧
ROS 2 支持 Windows 10 作为 Tier 1 平台,这意味着进入 ROS 2 核心的所有代码都必须支持 Windows。 对于习惯在 Linux 或其他类 Unix 系统上进行传统开发的人来说,在 Windows 上进行开发可能有点困难。 本文档旨在列出其中的一些差异。
最大路径长度
默认情况下,Windows 的“最大路径长度 <https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation>”为 260 个字符。 实际上,驱动器号、冒号、初始反斜杠和最终 NULL 字符始终使用其中的 4 个字符。 这意味着路径所有部分的*总和*只有 256 个字符可用。 这对 ROS 2 有两个实际影响:
一些 ROS 2 内部路径名相当长。因此,我们始终建议对 ROS 2 目录的根目录使用短路径名,例如“C:dev”。
从源代码构建 ROS 2 时,colcon 的默认隔离构建模式可以生成非常长的路径名。要避免这些非常长的路径名,请在 Windows 上构建时使用“–merge-install”。
注意:可以将 Windows 更改为具有更长的最大路径长度。 有关更多信息,请参阅“本文<https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later>”__。
符号可见性
Microsoft Visual C++ 编译器 (MSVC) 仅在明确导出动态链接库 (DLL) 中的符号时才公开它们。 clang 和 gcc 编译器有执行相同操作的选项,但默认情况下处于关闭状态。 因此,当在 Windows 上构建之前在 Linux 上构建的库时,其他库可能无法解析外部符号。 以下是由于未公开符号而导致的常见错误消息示例:
error C2448: '__attribute__': function-style initializer appears to be a function definition
'visibility': identifier not found
CMake Error at C:/ws_ros2/install/random_numbers/share/random_numbers/cmake/ament_cmake_export_libraries-extras.cmake:48 (message):
Package 'random_numbers' exports the library 'random_numbers' which
couldn't be found
符号可见性也会影响二进制加载。 如果您发现可组合节点未运行或 Qt Visualizer 不工作,则可能是托管进程无法从二进制文件中找到预期的符号导出。 要在 Windows 上诊断此问题,Windows 开发人员工具包含一个名为 Gflags 的程序来启用各种选项。 其中一个选项称为 Loader Snaps,它使您能够在调试时检测加载失败。 请访问 Microsoft 文档以获取有关 Gflags 和 Loaders snaps 的更多信息。
在 Windows 上导出符号的两种解决方案是可见性控制标头和 WINDOWS_EXPORT_ALL_SYMBOLS
属性。
Microsoft 建议 ROS 开发人员使用 Visibility Control Headers 来控制从二进制文件中导出符号。
Visibility Control Headers 提供了对符号导出宏的更多控制,并提供了其他好处,包括更小的二进制大小和更短的链接时间。
Visibility Control Headers
Visibility Control Headers 标头的目的是为每个共享库定义一个宏,该宏正确地将符号声明为 dllimport 或 dllexport。 这取决于库是被使用还是自己构建。 宏中的逻辑还考虑了编译器,并包括选择适当语法的逻辑。 GCC 可见性文档 包括向库添加显式符号可见性的分步说明,“以最大程度地减少二进制大小、加载时间和链接时间的方式产生最高质量的代码”。 可以将名为“visibility_control.h”的标头放在每个库的“includes”文件夹中,如下例所示。 下面的示例显示了如何为带有名为“example_class”的类的“my_lib”库添加可见性控制标头。 将可见性标头添加到库的包含文件夹中。 样板逻辑与宏中使用的库名称一起使用,使其在项目中独一无二。 在另一个库中,“MY_LIB”将被替换为库名称。
#ifndef MY_LIB__VISIBILITY_CONTROL_H_
#define MY_LIB__VISIBILITY_CONTROL_H_
#if defined _WIN32 || defined __CYGWIN__
#ifdef __GNUC__
#define MY_LIB_EXPORT __attribute__ ((dllexport))
#define MY_LIB_IMPORT __attribute__ ((dllimport))
#else
#define MY_LIB_EXPORT __declspec(dllexport)
#define MY_LIB_IMPORT __declspec(dllimport)
#endif
#ifdef MY_LIB_BUILDING_LIBRARY
#define MY_LIB_PUBLIC MY_LIB_EXPORT
#else
#define MY_LIB_PUBLIC MY_LIB_IMPORT
#endif
#define MY_LIB_PUBLIC_TYPE MY_LIB_PUBLIC
#define MY_LIB_LOCAL
#else
// Linux visibility settings
#define MY_LIB_PUBLIC_TYPE
#endif
#endif // MY_LIB__VISIBILITY_CONTROL_H_
有关此标头的完整示例,请参阅 rviz_rendering。
要使用该宏,请在需要对外部库可见的符号前添加 MY_LIB_PUBLIC
。例如:
Class MY_LIB_PUBLIC example_class {}
MY_LIB_PUBLIC void example_function (){}
为了使用正确导出的符号构建库,您需要将以下内容添加到 CMakeLists.txt 文件中:
target_compile_definitions(${PROJECT_NAME}
PRIVATE "MY_LIB_BUILDING_LIBRARY")
WINDOWS_EXPORT_ALL_SYMBOLS 目标属性
CMake 在 Windows 上实现了 WINDOWS_EXPORT_ALL_SYMBOLS
属性,这会导致函数符号被自动导出。
有关其工作原理的更多详细信息,请参阅 WINDOWS_EXPORT_ALL_SYMBOLS CMake 文档。
可以通过将以下内容添加到 CMakeLists 文件来实现该属性:
set_target_properties(${LIB_NAME} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
如果 CMakeLists 文件中有多个库,则需要对每个库分别调用“set_target_properties”。
请注意,Windows 上的二进制文件只能导出 65,536 个符号。 如果二进制文件导出的符号超过这个数目,您将收到错误,并且应该使用visibility_control 标头。 对于全局数据符号,此方法有一个例外。 例如,如下所示的全局静态数据成员。
class Example_class
{
public:
static const int Global_data_num;
在这些情况下,必须明确应用 dllimprort/dllexport。 可以使用 generate_export_header 完成此操作,如以下文章所述:“使用新的 CMake 导出所有功能在 Windows 上创建不使用 declspec() 的 dll <https://blog.kitware.com/create-dlls-on-windows-without-declspec-using-new-cmake-export-all-feature/>”__。
最后,重要的是,导出符号的头文件必须包含在包中的至少一个 .cpp
文件中,以便宏将被展开并放入生成的二进制文件中。
否则,符号仍然无法调用。
调试构建
在 Windows 上以调试模式构建时,有几个非常重要的事情发生了变化。
首先,所有 DLL 都会自动将 _d
附加到库名称中。
因此,如果库名为“libfoo.dll”,则在调试模式下它将为“libfoo_d.dll”。
Windows 上的动态链接器也知道查找该形式的库,因此它不会找到没有“_d”前缀的库。
此外,Windows 在调试模式下会打开一整套编译时和运行时检查,这些检查比发布版本严格得多。
出于这些原因,运行 Windows 调试版本并在许多拉取请求上进行测试是个好主意。
正斜杠与反斜杠
在 Windows 中,默认路径分隔符是反斜杠(“”),它与 Linux 和 macOS 中使用的正斜杠(“/”)不同。 大多数 Windows API 都可以将两者作为路径分隔符处理,但这并非普遍适用。 例如,“cmd.exe”shell 只有在使用反斜杠字符时才能进行制表符补全,而不能使用正斜杠。 为了在 Windows 上实现最大兼容性,应始终使用反斜杠作为 Windows 上的路径分隔符。
修补供应商软件包
在 ROS 2 中供应商软件包时,通常需要应用补丁来修复错误、添加功能等。
执行此操作的典型方法是修改 ExternalProject_add
调用以使用 patch
可执行文件添加 PATCH
命令。
不幸的是,chocolatey 提供的 patch
可执行文件需要管理员权限才能运行。
解决方法是在将补丁应用于外部项目时使用 git apply-patch
。
git apply-patch
有其自身的问题,因为它只有在应用于 git 存储库时才能正常工作。
因此,外部项目应始终使用 GIT
方法获取项目,然后使用 PATCH_COMMAND
调用 git apply-patch
。
以上所有内容的示例用法如下所示:
ExternalProject_Add(mylibrary-${version}
GIT_REPOSITORY https://github.com/lib/mylibrary.git
GIT_TAG ${version}
GIT_CONFIG advice.detachedHead=false
# Suppress git update due to https://gitlab.kitware.com/cmake/cmake/-/issues/16419
# See https://github.com/ament/uncrustify_vendor/pull/22 for details
UPDATE_COMMAND ""
TIMEOUT 600
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}_install
${extra_cmake_args}
-Wno-dev
PATCH_COMMAND
${CMAKE_COMMAND} -E chdir <SOURCE_DIR> git apply -p1 --ignore-space-change --whitespace=nowarn ${CMAKE_CURRENT_SOURCE_DIR}/install-patch.diff
)
Windows 慢速计时器(总体上很慢)
通常,在 Windows 上运行的软件比在 Linux 上运行的软件慢得多。 这是由于许多因素造成的,从默认时间片(每 20 毫秒,根据“文档 <https://docs.microsoft.com/en-us/windows/win32/procthread/multitasking>`__),到正在运行的防病毒和反恶意软件进程的数量,再到正在运行的后台进程的数量。 由于所有这些原因,测试不应该期望在 Windows 上进行严格的计时。 所有测试都应有充足的超时时间,并且只期望事件最终发生(这也可以防止在 Linux 上测试不稳定)。
Shell
Windows 上有两个主要的命令行 shell:古老的“cmd.exe”和 PowerShell。
“cmd.exe”是最接近模拟旧 DOS shell 的命令 shell,但功能大大增强。
它完全基于文本,并且只理解 DOS/Windows batch
文件。
PowerShell 是 Microsoft 推荐用于大多数新应用程序的较新的基于对象的 shell。
它理解用于配置的 ps1
文件。
ROS 2 同时支持 cmd.exe
和 PowerShell,因此任何更改(尤其是对 ament
或 colcon
之类的更改)都应在两者上进行测试。