Unfortunately it is true, even if you only use typed pubs and subs (the generic interfaces call dlopen on their own so they’re not static friendly regardless of your typesupport situation.)
Have a look at the generated output from rosidl when multiple type supports are used. This is taken from the builtin interfaces time type for C++ messages with the default dual typesupports you get out of the box (fastrtps and introspection):
// generated from rosidl_typesupport_cpp/resource/idl__type_support.cpp.em
// with input from builtin_interfaces:msg/Time.idl
// generated code does not contain a copyright notice
#include "cstddef"
#include "rosidl_runtime_c/message_type_support_struct.h"
#include "builtin_interfaces/msg/detail/time__functions.h"
#include "builtin_interfaces/msg/detail/time__struct.hpp"
#include "rosidl_typesupport_cpp/identifier.hpp"
#include "rosidl_typesupport_cpp/message_type_support.hpp"
#include "rosidl_typesupport_c/type_support_map.h"
#include "rosidl_typesupport_cpp/message_type_support_dispatch.hpp"
#include "rosidl_typesupport_cpp/visibility_control.h"
#include "rosidl_typesupport_interface/macros.h"
namespace builtin_interfaces
{
namespace msg
{
namespace rosidl_typesupport_cpp
{
typedef struct _Time_type_support_ids_t
{
const char * typesupport_identifier[2];
} _Time_type_support_ids_t;
static const _Time_type_support_ids_t _Time_message_typesupport_ids = {
{
"rosidl_typesupport_fastrtps_cpp", // ::rosidl_typesupport_fastrtps_cpp::typesupport_identifier,
"rosidl_typesupport_introspection_cpp", // ::rosidl_typesupport_introspection_cpp::typesupport_identifier,
}
};
typedef struct _Time_type_support_symbol_names_t
{
const char * symbol_name[2];
} _Time_type_support_symbol_names_t;
#define STRINGIFY_(s) #s
#define STRINGIFY(s) STRINGIFY_(s)
static const _Time_type_support_symbol_names_t _Time_message_typesupport_symbol_names = {
{
STRINGIFY(ROSIDL_TYPESUPPORT_INTERFACE__MESSAGE_SYMBOL_NAME(rosidl_typesupport_fastrtps_cpp, builtin_interfaces, msg, Time)),
STRINGIFY(ROSIDL_TYPESUPPORT_INTERFACE__MESSAGE_SYMBOL_NAME(rosidl_typesupport_introspection_cpp, builtin_interfaces, msg, Time)),
}
};
typedef struct _Time_type_support_data_t
{
void * data[2];
} _Time_type_support_data_t;
static _Time_type_support_data_t _Time_message_typesupport_data = {
{
0, // will store the shared library later
0, // will store the shared library later
}
};
static const type_support_map_t _Time_message_typesupport_map = {
2,
"builtin_interfaces",
&_Time_message_typesupport_ids.typesupport_identifier[0],
&_Time_message_typesupport_symbol_names.symbol_name[0],
&_Time_message_typesupport_data.data[0],
};
static const rosidl_message_type_support_t Time_message_type_support_handle = {
::rosidl_typesupport_cpp::typesupport_identifier,
reinterpret_cast<const type_support_map_t *>(&_Time_message_typesupport_map),
::rosidl_typesupport_cpp::get_message_typesupport_handle_function,
&builtin_interfaces__msg__Time__get_type_hash,
&builtin_interfaces__msg__Time__get_type_description,
&builtin_interfaces__msg__Time__get_type_description_sources,
};
} // namespace rosidl_typesupport_cpp
} // namespace msg
} // namespace builtin_interfaces
namespace rosidl_typesupport_cpp
{
template<>
ROSIDL_TYPESUPPORT_CPP_PUBLIC
const rosidl_message_type_support_t *
get_message_type_support_handle<builtin_interfaces::msg::Time>()
{
return &::builtin_interfaces::msg::rosidl_typesupport_cpp::Time_message_type_support_handle;
}
#ifdef __cplusplus
extern "C"
{
#endif
ROSIDL_TYPESUPPORT_CPP_PUBLIC
const rosidl_message_type_support_t *
ROSIDL_TYPESUPPORT_INTERFACE__MESSAGE_SYMBOL_NAME(rosidl_typesupport_cpp, builtin_interfaces, msg, Time)() {
return get_message_type_support_handle<builtin_interfaces::msg::Time>();
}
#ifdef __cplusplus
}
#endif
} // namespace rosidl_typesupport_cpp
of note, notice the call to get_message_typesupport_handle_function
which comes from type support dispatch and calls dlopen.
In the case of typesupports, if more than one is required typesupport_cpp opts to generate this dynamic loading version. rclcpp only works with the typesupport_cpp shim. Unlike the RMW layer, typesupports all have unique symbols, so you need to use the shim to go from typesupport_cpp to your typesupport of choice. To fix the static linking for multi type support the typesupport_cpp generator would need to be rewritten.
Here you can see the unique symbols from each type support:
# nm -gDC ./libbuiltin_interfaces__rosidl_typesupport_cpp.so | grep get_message_type_support_handle
0000000000001184 T rosidl_message_type_support_t const* rosidl_typesupport_cpp::get_message_type_support_handle<builtin_interfaces::msg::Time_<std::allocator<void> > >()
0000000000001139 T rosidl_message_type_support_t const* rosidl_typesupport_cpp::get_message_type_support_handle<builtin_interfaces::msg::Duration_<std::allocator<void> > >()
000000000000114a T rosidl_typesupport_cpp__get_message_type_support_handle__builtin_interfaces__msg__Duration
0000000000001195 T rosidl_typesupport_cpp__get_message_type_support_handle__builtin_interfaces__msg__Time
# nm -gDC ./libbuiltin_interfaces__rosidl_typesupport_introspection_cpp.so | grep get_message_type_support_handle
000000000000228b T rosidl_message_type_support_t const* rosidl_typesupport_introspection_cpp::get_message_type_support_handle<builtin_interfaces::msg::Time_<std::allocator<void> > >()
00000000000021a7 T rosidl_message_type_support_t const* rosidl_typesupport_introspection_cpp::get_message_type_support_handle<builtin_interfaces::msg::Duration_<std::allocator<void> > >()
00000000000021b8 T rosidl_typesupport_introspection_cpp__get_message_type_support_handle__builtin_interfaces__msg__Duration
000000000000229c T rosidl_typesupport_introspection_cpp__get_message_type_support_handle__builtin_interfaces__msg__Time
# nm -gDC ./libbuiltin_interfaces__rosidl_typesupport_fastrtps_cpp.so | grep get_message_type_support_handle
00000000000032b9 T rosidl_message_type_support_t const* rosidl_typesupport_fastrtps_cpp::get_message_type_support_handle<builtin_interfaces::msg::Time_<std::allocator<void> > >()
00000000000028b1 T rosidl_message_type_support_t const* rosidl_typesupport_fastrtps_cpp::get_message_type_support_handle<builtin_interfaces::msg::Duration_<std::allocator<void> > >()
00000000000028c2 T rosidl_typesupport_fastrtps_cpp__get_message_type_support_handle__builtin_interfaces__msg__Duration
00000000000032ca T rosidl_typesupport_fastrtps_cpp__get_message_type_support_handle__builtin_interfaces__msg__Time
This is why you can’t remove the shim, and the way the shim is implemented isn’t able to be statically linked when more than one type support is required.
And for contrast here you can see why the runtime loader for the RMWs can be bypassed so easily:
# nm -gDC /opt/ros/jazzy/lib/librmw_implementation.so | grep rmw_create_node
0000000000005040 T rmw_create_node
# nm -gDC /opt/ros/jazzy/lib/librmw_fastrtps_cpp.so | grep rmw_create_node
0000000000026c30 T rmw_create_node
# nm -gDC /opt/ros/jazzy/lib/librmw_cyclonedds_cpp.so | grep rmw_create_node
0000000000023fb0 T rmw_create_node