0
$\begingroup$

Rosanswers logo

Background: I work in underwater robotics, and DCCL is a library for encoding of messages to fit within our extremely constrained message sizes based on an original protobuf format. Just for practice before applying this to one of our real messages, I'm modifying the basic publisher/subscriber from the tutorial to encode the message to DCCL before sending. It seems to do just fine with the protobuf library, but I'm getting an undefined reference error on the DCCL library, which I assuming is a linking error.

Error:

teddybouch@Norby:~/workspace/ros2_ws$ colcon build --packages-select cpp_pubsub
Starting >>> cpp_pubsub
--- stderr: cpp_pubsub                             
/usr/bin/ld: CMakeFiles/talker.dir/src/publisher_member_function.cpp.o: in function `MinimalPublisher::timer_callback()':
publisher_member_function.cpp:(.text._ZN16MinimalPublisher14timer_callbackEv[_ZN16MinimalPublisher14timer_callbackEv]+0xa2): undefined reference to `dccl::Codec::Codec(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'
/usr/bin/ld: publisher_member_function.cpp:(.text._ZN16MinimalPublisher14timer_callbackEv[_ZN16MinimalPublisher14timer_callbackEv]+0xed): undefined reference to `testdccl::NavigationReport::NavigationReport()'
/usr/bin/ld: publisher_member_function.cpp:(.text._ZN16MinimalPublisher14timer_callbackEv[_ZN16MinimalPublisher14timer_callbackEv]+0x1d5): undefined reference to `dccl::Codec::encode(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*, google::protobuf::Message const&, bool, int)'
/usr/bin/ld: publisher_member_function.cpp:(.text._ZN16MinimalPublisher14timer_callbackEv[_ZN16MinimalPublisher14timer_callbackEv]+0x3b7): undefined reference to `testdccl::NavigationReport::~NavigationReport()'
/usr/bin/ld: publisher_member_function.cpp:(.text._ZN16MinimalPublisher14timer_callbackEv[_ZN16MinimalPublisher14timer_callbackEv]+0x3c6): undefined reference to `dccl::Codec::~Codec()'
/usr/bin/ld: publisher_member_function.cpp:(.text._ZN16MinimalPublisher14timer_callbackEv[_ZN16MinimalPublisher14timer_callbackEv]+0x48b): undefined reference to `testdccl::NavigationReport::~NavigationReport()'
/usr/bin/ld: publisher_member_function.cpp:(.text._ZN16MinimalPublisher14timer_callbackEv[_ZN16MinimalPublisher14timer_callbackEv]+0x4a3): undefined reference to `dccl::Codec::~Codec()'
/usr/bin/ld: CMakeFiles/talker.dir/src/publisher_member_function.cpp.o: in function `void dccl::Codec::load<testdccl::NavigationReport>()':
publisher_member_function.cpp:(.text._ZN4dccl5Codec4loadIN8testdccl16NavigationReportEEEvv[_ZN4dccl5Codec4loadIN8testdccl16NavigationReportEEEvv]+0x11): undefined reference to `testdccl::NavigationReport::descriptor()'
/usr/bin/ld: publisher_member_function.cpp:(.text._ZN4dccl5Codec4loadIN8testdccl16NavigationReportEEEvv[_ZN4dccl5Codec4loadIN8testdccl16NavigationReportEEEvv]+0x28): undefined reference to `dccl::Codec::load(google::protobuf::Descriptor const*, int)'
collect2: error: ld returned 1 exit status
make[2]: *** [CMakeFiles/talker.dir/build.make:132: talker] Error 1
make[1]: *** [CMakeFiles/Makefile2:82: CMakeFiles/talker.dir/all] Error 2
make: *** [Makefile:141: all] Error 2
---
Failed   <<< cpp_pubsub [0.97s, exited with code 2]

Summary: 0 packages finished [1.16s]
  1 package failed: cpp_pubsub
  1 package had stderr output: cpp_pubsub

Except for the addition of the proto directory and the protobuf message definition file in it, I think that the only relevant changes are in the CMakeLists.txt and publisher_member_function.cpp, which I'm including below, but if I've missed something or anyone wants all the code for some reason, the whole package is on my Dropbox here.

CMakeLists.txt:

cmake_minimum_required(VERSION 3.5)
project(cpp_pubsub)

# Default to C99
if(NOT CMAKE_C_STANDARD)
  set(CMAKE_C_STANDARD 99)
endif()

# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 14)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
find_package(dccl REQUIRED)
message(STATUS "Using DCCL in ${DCCL_DIR}")
find_package(Protobuf REQUIRED)

if(NOT DEFINED DCCL_INCLUDE_DIR)
  #for DCCL 3.0.3 and newer
  get_target_property(DCCL_INCLUDE_DIR dccl INTERFACE_INCLUDE_DIRECTORIES)
endif()

if(DCCL_INCLUDE_DIR)
  message(STATUS "\tIncluding DCCL protobuf dir: ${DCCL_INCLUDE_DIR}")
  include_directories("${DCCL_INCLUDE_DIR}")
  #protobuf_include_dirs("${DCCL_INCLUDE_DIR}")
endif()

# build the protobuf messages
file(GLOB ProtoFiles "${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto")
PROTOBUF_GENERATE_CPP(ProtoSources ProtoHeaders ${ProtoFiles})
add_library(proto STATIC ${ProtoSources} ${ProtoHeaders})
target_link_libraries(proto ${PROTOBUF_LIBRARY})

message( STATUS ${CMAKE_BINARY_DIR} )
include_directories( ${CMAKE_BINARY_DIR} )

add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs dccl Protobuf)

add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp std_msgs)

install(TARGETS
  talker
  listener
  DESTINATION lib/${PROJECT_NAME})

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()

publisher_member_function.cpp:

#include <chrono>
#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

#include "dccl.h"
#include "navreport.pb.h"

using namespace std::chrono_literals;

/* This example creates a subclass of Node and uses std::bind() to register a
 * member function as a callback from the timer. */

class MinimalPublisher : public rclcpp::Node
{
public:
  MinimalPublisher()
  : Node("minimal_publisher"), count_(0)
  {
    publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
    timer_ = this->create_wall_timer(
      500ms, std::bind(&MinimalPublisher::timer_callback, this));
  }

private:
  void timer_callback()
  {
    auto message = std_msgs::msg::String();
    std::string encoded_bytes;
    dccl::Codec codec;
    
    // Create the DCCL-encoded protobuf message
    codec.load<testdccl::NavigationReport>();
    
    testdccl::NavigationReport report;
    report.set_x( count_++ );
    report.set_y( 0 - count_ );
    report.set_z( -999 );
    //report.set_veh_class( testdccl::NavigationReport::AUV );
    report.set_battery_ok( true );
    
    codec.encode( &encoded_bytes, report );
    
    // Serialize the protobuf data
    message.data = encoded_bytes;
    RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
    publisher_->publish(message);
  }
  rclcpp::TimerBase::SharedPtr timer_;
  rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
  size_t count_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalPublisher>());
  rclcpp::shutdown();
  return 0;
}

Truth be told I'm still getting the hang of CMake and linking in general, so I think that what's going on is that I'm getting confused with the intersection of ROS, colcon, protobuf, DCCL, and CMake and I'm probably missing something obvious, but I've spent over an hour now looking between the CMake and DCCL documentation (plus various other Googling) to no avail, so if anyone has a moment to spare to look at it I would be grateful. Thanks!

  • gcc version: 9.4.0
  • CMake version: 3.16.3
  • ROS version: foxy

Originally posted by teddybouch on ROS Answers with karma: 320 on 2022-05-06

Post score: 0

$\endgroup$

1 Answer 1

0
$\begingroup$

Rosanswers logo

TLDR, these are the lines you've got to change in your CMakeLists.txt:

add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)  # Remove dccl and Protobuf
target_link_libraries(talker dccl proto)  # Link non-ament dependencies

In more detail:

There seems to be two issues in the error log you posted:

  1. Not linking against dccl correctly
  2. Not linking against your proto file correctly

and they are both related to linking, as you suspected.

In regards to not linking against dccl correctly - dccl is a pure CMake package (doesn't follow ament guidelines), and so you have to use target_link_libraries instead of ament_target_dependencies. There is no issue calling one after the other on the same target (ie. talker).

In regards to not linking against your proto file correctly - you have to use target_link_libraries to link against the target (ie. proto) you generate from the proto file in the line - add_library(proto.... Note that you already have Protobuf linked to the proto target, so if you link the talker to the proto target, you don't need to link Protobuf.


Originally posted by ijnek with karma: 460 on 2022-05-08

This answer was ACCEPTED on the original site

Post score: 2


Original comments

Comment by teddybouch on 2022-05-08:
Thank you so much! Not only did this fix the immediate problem, but your explanation was excellent and helped me understand the context a lot better as well!

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.