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 文档以获取有关 GflagsLoaders 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,因此任何更改(尤其是对 amentcolcon 之类的更改)都应在两者上进行测试。