Skip to content

Allow use of C message types #2229

@emersonknapp

Description

@emersonknapp

Feature request

Feature description

Some interfaces with rcl internals, or other C-based implementations, are more easily accomplished by sticking with C message types - given that there is no universal conversion from C++-generated types to C-generated types, they are totally separate.

In one example case, rcl implements a service and provides a callback for it, but it is desirable to let rclcpp handle the executor/waitsets to take advantage of its threading/execution infrastructure. In that case, the RCL callback handler needs C message types to operate on.

I'll show the following example for Services, but there are similar difficulties using Publishers, etc that we could cover as part of this investigation.

The following

#include "std_srvs/srv/empty.h"
// ...
rclcpp::Service<std_srvs__srv__Empty> service;

fail to compile, for a couple of reasons:

  1. rosidl_generator_c does not generate a toplevel entity std_srvs__srv__Empty, instead just the _Request and _Response structs - as well as a typesupport getter function for the Service type
  2. Service looks for members as ::Request, etc.

Implementation considerations

Workaround example I am using for a C-based service (some error checking omitted for brevity)

// Helper wrapper for rclcpp::Service to access ::Request and ::Response types for allocation.
struct GetTypeDescriptionC
{
  using Request = type_description_interfaces__srv__GetTypeDescription_Request;
  using Response = type_description_interfaces__srv__GetTypeDescription_Response;
  using Event = type_description_interfaces__srv__GetTypeDescription_Event;
};
}  // namespace

// Helper function for C typesupport.
namespace rosidl_typesupport_cpp
{
template<>
rosidl_service_type_support_t const *
get_service_type_support_handle<GetTypeDescriptionC>()
{
  return ROSIDL_GET_SRV_TYPE_SUPPORT(type_description_interfaces, srv, GetTypeDescription);
}
}  // namespace rosidl_typesupport_cpp

void business(std::shared_ptr<rclcpp::Node> node) 
{
      auto rcl_node = node->get_node_base_interface()->get_rcl_node_handle();
      rcl_node_type_description_service_init(rcl_node);
    
      rcl_service_t * rcl_srv = nullptr;
      rcl_node_get_type_description_service(rcl_node, &rcl_srv);

      rclcpp::AnyServiceCallback<GetTypeDescriptionC> cb;
      cb.set(
        [node](
          std::shared_ptr<rmw_request_id_t> header,
          std::shared_ptr<GetTypeDescriptionC::Request> request,
          std::shared_ptr<GetTypeDescriptionC::Response> response
        ) {
          rcl_node_type_description_service_handle_request(
            node->get_node_base_interface()->get_rcl_node_handle(),
            header.get(),
            request.get(),
            response.get());
        });

      auto type_description_srv_ = std::make_shared<Service<GetTypeDescriptionC>>(
        node->get_node_base_interface()->get_shared_rcl_node_handle(),
        rcl_srv,
        cb);
      node->get_node_services_interface()->add_service(
        std::dynamic_pointer_cast<ServiceBase>(type_description_srv_),
        nullptr);
}

Maybe we want to call that "acceptable overhead" to create a C-based service? But I personally think that there could be a better way.

A followup note on this, there is no way to let rcl do its own take, because every code path in the default Executor does a take_request before ever invoking the user callback.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions