Windows提示和技巧

ROS 2将Windows 10作为一级平台支持,这意味着ROS 2核心中的所有代码必须支持Windows。对于习惯于在Linux或其他类Unix系统上进行传统开发的人来说,在Windows上进行开发可能有些挑战。本文档旨在阐述其中的一些区别。

最大路径长度

默认情况下,Windows的`最大路径长度 <https://docs.microsoft.com/zh-cn/windows/win32/fileio/maximum-file-path-limitation>`__为260个字符。实际上,其中的4个字符始终被驱动器字母、冒号、初始反斜杠和最后的NULL字符使用。这意味着仅有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 可视化器无法工作,可能是由于主机进程无法找到二进制中预期的符号导出。在 Windows 上诊断此问题,Windows 开发人员工具包括一个名为 Gflags 的程序,可启用各种选项。其中一个选项称为“Loader Snaps”,它允许您在调试时检测加载失败。有关更多信息,请访问微软文档中的 GflagsLoader Snaps

在 Windows 上导出符号的两种解决方案是可见性控制头文件和``WINDOWS_EXPORT_ALL_SYMBOLS`` 属性。Microsoft 建议 ROS 开发人员使用可见性控制头文件来控制从二进制中导出符号。可见性控制头文件提供对符号导出宏的更多控制,并提供其他优点,包括更小的二进制文件大小和较短的链接时间。

可见性控制头文件

Visibility Control Headers头的目的是为每个共享库定义一个宏,以正确地声明符号为dllimport或dllexport。这是根据库是被消费还是被自身构建来决定的。宏中的逻辑还考虑了编译器,并包含选择适当语法的逻辑。`GCC可见性文档<https://gcc.gnu.org/wiki/Visibility>`__提供了逐步说明,用于向库添加显式符号可见性,以“产生质量最高的代码,并显著减小二进制大小、加载时间和链接时间”。可以将名为``visibility_control.h``的头文件放置在每个库的``includes``文件夹中,如下面的示例所示。下面的示例展示了如何为名为``my_lib``的库添加可见性控制头,其中包含一个名为``example_class``的类。将可见性头文件添加到库的include文件夹中。在宏中使用库名称作为样板逻辑,以使其在项目中唯一。在另一个库中,``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 <https://github.com/ros2/rviz/blob/ros2/rviz_rendering/include/rviz_rendering/visibility_control.hpp>`__。

要使用该宏,在需要对外部库可见的符号之前添加``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文档<https://cmake.org/cmake/help/latest/prop_tgt/WINDOWS_EXPORT_ALL_SYMBOLS.html>`__。可以通过将以下内容添加到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来完成,具体的方法请参考以下文章:在Windows上创建无需使用declspec()的dll,使用新的CMake导出全部特性

最后,重要的是将导出符号的头文件包含到包中的至少一个``.cpp``文件中,以便宏能够扩展并放置到生成的二进制文件中。否则,这些符号仍然无法被调用。

调试构建

在Windows上以调试模式构建时,有几个非常重要的变化。首先,所有的DLL库都会自动在库名称后面添加``_d``。因此,如果库的名称是``libfoo.dll``,在调试模式下它将变成``libfoo_d.dll``。Windows的动态链接器也会寻找具有该形式的库,因此它将不会找到没有``_d``前缀的库。此外,Windows在调试模式下打开了一整套编译时和运行时的检查,比发布构建更为严格。因此,建议在许多拉取请求上运行Windows调试构建并进行测试。

正斜杠 vs. 反斜杠

在Windows中,默认的路径分隔符是反斜杠(\),这与Linux和macOS中使用的正斜杠(/)不同。大多数Windows API可以处理正斜杠或反斜杠作为路径分隔符,但这并非普遍适用。例如,cmd.exe shell只能在使用反斜杠字符时进行制表符补全,而不能使用正斜杠。为了在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毫秒一次),运行的反病毒和反恶意软件进程的数量以及后台进程的数量。因此,在Windows上的测试*永远不应该*对严格的计时有期望值。所有测试都应该有充足的超时时间,并且只期望事件最终会发生(这也可以防止在Linux上测试出现不稳定的情况)。

外壳

Windows上有两个主要的命令行外壳:传统的``cmd.exe``和PowerShell。

``cmd.exe``是最接近旧DOS外壳的命令外壳,尽管它具有大大增强的功能。它完全基于文本,并且只能理解DOS/Windows的``batch``文件。

PowerShell是较新的、基于对象的外壳,Microsoft建议大多数新应用程序使用它。它可以理解``ps1``文件进行配置。

ROS 2 支持``cmd.exe``和 PowerShell,因此任何更改(尤其是对于``ament``或``colcon``之类的内容)都应在两者上进行测试。