ROS2における共有ライブラリ

【[ROS2関係トップページへ](https://qiita.com/NeK/items/7ac0f4ec10d51dbca084)】

【次:[ROS2の共有ライブラリを使用したプログラムの作成]】

[ROS2の共有ライブラリを使用したプログラムの作成]:https://qiita.com/NeK/items/dfeb32b7762864641e42

Galacticまでは[こちら](https://qiita.com/NeK/items/795fe9f75bdc3f8684a3)
[ROS2における共有ライブラリ:--library-nodeオプションを使用しない方法](https://qiita.com/NeK/items/d3cf94e958c4655da65e)はこちら
更新履歴:
2024/12/27:なんか久々にjazzy触ったらちょっと変わっていたのでアップデート

---
# 概要
ROS2の共有ライブラリを作成する．

共有ライブラリはクラスで作成するので，これまでのようにROS1風のものはない．
また，共有ライブラリは色々なところで使用されるのでnamespaceを用いている．
本当はこれまででもnamespaceを使用した方がよかったのだが，自分が使用するターゲットのみの場合必須でないので省略してきた．
namespaceに関しては[ROS2関係トップページへ](https://qiita.com/NeK/items/7ac0f4ec10d51dbca084)の「知っといてほしい知識」などを参考に．

# 共通ライブラリ概要
ここで作成する共有ライブラリの概要は以下のとおり．
ちなみに作成したライブラリを使用したターゲットはここで作成せず共有ライブラリのみの作成とする．
また作成するクラスは一つでも良かったが，後々のために二つ作成する(MinimalCompNode1, MinimalCompNode2)．基本的には一つのライブラリに複数の機能が収められる．
一つ目(MinimalCompNode1)を作成し，二つ目(MinimalCompNode2)は後ほど追加することで，追加方法も述べる．

- 共有ライブラリのパッケージ名
    - minimal_comp
- 共有ライブラリ名
    - minimal_comp_node
- 共有ライブラリで使用できる機能・クラスを収めたソースファイル
    - minimal_comp_node1.hppと.cpp
        - クラス名:MinimalCompNode1
    - minimal_comp_node2.hppと.cpp
        - クラス名:MinimalCompNode2
- パッケージ共通のnamespace
    - minimal_comp

# 準備

```bash:terminal
$ cd ~/ros2_studies_ws/
$ ros2 pkg create minimal_comp --build-type ament_cmake --dependencies rclcpp --library-name minimal_comp_node1  --license Apache-2.0
```

# 共有ライブラリ
## 対象ファイル
- include/visibility_control.h
    - Windows用ライブラリを作成するのに必要？
        - [Building libraries on Windows](https://docs.ros.org/en/foxy/Guides/Ament-CMake-Documentation.html#building-libraries-on-windows)
- include/minimal_comp_node1.hpp
- src/minimal_comp_node1.cpp
- CMakeLists.txt
- package.xml

後で追加
- include/minimal_comp_node2.hpp
- src/minimal_comp_node2.cpp


`--library-name` オプションを使用しない場合，visibility_control.hが自動的に作成されない．そのため以下の手順が必要となる．

<details><summary>手動での共有ライブラリ作成</summary><div>

## 共有ライブラリ独特の手順

1. visibility.hを作成する
    - 公式からDLして自分のパッケージ用にアレンジするのが楽
    - visibility_control.hとして保存
2. 普通にクラスを作成する
    - 外部に公開するのでsrc/以下にヘッダファイルを置くのではなくinclude/[パッケージ名]/以下にヘッダファイルを置く．

## visibility.hの用意

自分で以下のファイルを用意するのでもよいが，[公式のexamplesにあるminimal_composition](https://github.com/ros2/examples/tree/master/rclcpp/composition/minimal_composition/include/minimal_composition)からvisibility.hをDLして自分用に改変することを推奨．

```bash:terminal
$ curl https://raw.githubusercontent.com/ros2/examples/master/rclcpp/composition/minimal_composition/include/minimal_composition/visibility.h > visibility.h
$ sed s/MINIMAL_COMPOSITION/MINIMAL_COMP/g visibility.h > visibility_control.h
```

一度にやるなら以下のとおり．

```bash:terminal
$ curl https://raw.githubusercontent.com/ros2/examples/master/rclcpp/composition/minimal_composition/include/minimal_composition/visibility.h | sed s/MINIMAL_COMPOSITION/MINIMAL_COMP/g > visibility_control.h
```

今回はパッケージ名がminimal_compであったので上記のようにsedを使用した．ここは自分のパッケージ名に合わせて変更すること．

```bash
$ sed s/MINIMAL_COMPOSITION/[自分のパッケージ名]/g visibility.h > my_visibility.h
```

自分で作成する場合は以下の通り．
ここでもパッケージ名minimal_compで作成しているので，自分のパッケージ名に合わせて適宜変更

```c++:visibility.h
// Copyright 2016 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef MINIMAL_COMP__VISIBILITY_H_
#define MINIMAL_COMP__VISIBILITY_H_

#ifdef __cplusplus
extern "C"
{
#endif

// This logic was borrowed (then namespaced) from the examples on the gcc wiki:
//     https://gcc.gnu.org/wiki/Visibility

#if defined _WIN32 || defined __CYGWIN__

  #ifdef __GNUC__
    #define MINIMAL_COMP_EXPORT __attribute__ ((dllexport))
    #define MINIMAL_COMP_IMPORT __attribute__ ((dllimport))
  #else
    #define MINIMAL_COMP_EXPORT __declspec(dllexport)
    #define MINIMAL_COMP_IMPORT __declspec(dllimport)
  #endif

  #ifdef MINIMAL_COMP_DLL
    #define MINIMAL_COMP_PUBLIC MINIMAL_COMP_EXPORT
  #else
    #define MINIMAL_COMP_PUBLIC MINIMAL_COMP_IMPORT
  #endif

  #define MINIMAL_COMP_PUBLIC_TYPE MINIMAL_COMP_PUBLIC

  #define MINIMAL_COMP_LOCAL

#else

  #define MINIMAL_COMP_EXPORT __attribute__ ((visibility("default")))
  #define MINIMAL_COMP_IMPORT

  #if __GNUC__ >= 4
    #define MINIMAL_COMP_PUBLIC __attribute__ ((visibility("default")))
    #define MINIMAL_COMP_LOCAL  __attribute__ ((visibility("hidden")))
  #else
    #define MINIMAL_COMP_PUBLIC
    #define MINIMAL_COMP_LOCAL
  #endif

  #define MINIMAL_COMP_PUBLIC_TYPE
#endif

#ifdef __cplusplus
}
#endif

#endif  // MINIMAL_COMP__VISIBILITY_H_

```

</div></details>

## 共有ライブラリ化するMinimalCompNode1
以下のひな形が出来ているので利用して作成．

### ソースコード

- include/minimal_comp_node1.hpp
- src/minimal_comp_node1.cpp

```c++:minimal_comp_node1.hpp
#include <rclcpp/rclcpp.hpp>
#include "visibility_control.h"

namespace minimal_comp {

class MinimalCompNode1 : public rclcpp::Node{
public:
  MINIMAL_COMP_PUBLIC
  MinimalCompNode1(
    const std::string& name_space="",
    const rclcpp::NodeOptions& options=rclcpp::NodeOptions()
  );
};

}
```

`MINIMAL_COMP_PUBLIC`はWindows用ライブラリを作成するのに必要？[Building libraries on Windows](https://docs.ros.org/en/foxy/Guides/Ament-CMake-Documentation.html#building-libraries-on-windows)参考．

```c++:minimal_comp_node1.cpp
#include <rclcpp/rclcpp.hpp>
#include "minimal_comp/minimal_comp_node1.hpp"

namespace minimal_comp {

MinimalCompNode1::MinimalCompNode1(
  const std::string& name_space,
  const rclcpp::NodeOptions& options
): Node("minimal_comp1", name_space, options)
{
  RCLCPP_INFO(this->get_logger(),"minimal comp 1 test");
}

}
```

### package.xmlとCMakeLists.txt
以下は追加した・重要な部分のみ抜粋．
詳しくは[ament_cmake User Documentation](https://docs.ros.org/en/jazzy/How-To-Guides/Ament-CMake-Documentation.html)を参照．

***CMakeLists.txt***

```CMakeLists.txt
# 前はlibrary名:minimal_comp_nodeだったがminimal_comp1に変更．ros2 pkg createがそのようにするから．
# 前はSHAREDがlibrary名とsrc/***.cppの間にあったが，今はいらなくなった？明示
add_library(minimal_comp1 # SHARED要らなくなったかもはて
  src/minimal_comp_node1.cpp
)
# デフォルトがMINIMAL_COMP_BUILDING_LIZBRAYになってるのでそつらを使用
# target_compile_definitions(minimal_comp1
#   PRIVATE "MY_LIBRARY_BUILDING_LIBRARY"
# )
target_compile_options(minimal_comp1 PUBLIC -Wall)

# デフォルトを使用
#ament_target_dependencies(minimal_comp1
#  rclcpp
#)

# デフォルトを使用
#target_include_directories(minimal_comp1
#  PUBLIC
#    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
#    $<INSTALL_INTERFACE:include>
#)

# いるんか？わからないのでとりあえず未使用
#if(NOT WIN32)
#  ament_environment_hooks(
#    "${ament_cmake_package_templates_ENVIRONMENT_HOOK_LIBRARY_PATH}"
#  )
#endif()

# HAS_LIBRARY_TRGETを追加しておかなくても良くなったかも？
ament_export_targets(
  export_${PROJECT_NAME} #HAS_LIBRARY_TARGET
)

# このあたりのデフォルトが，ament_export_{include_directories,libraries,targets}となっている．
# foxyでなくなったんじゃ？
#ament_export_dependencies(
#  rclcpp
#)

# このあたりはデフォルト使う
#install(
#  DIRECTORY include/
#  DESTINATION include
#)
#install(TARGETS
#  minimal_comp1
#  EXPORT export_${PROJECT_NAME}
#  ARCHIVE DESTINATION lib
#  LIBRARY DESTINATION lib
#  RUNTIME DESTINATION bin
#  INCLUDES DESTINATION include
#)
```

### 概要
簡単なメッセージを出力するだけのもの
target_compile_definitions(minimal_comp_node PRIVATE "MY_LIBRARY_BUILDING_LIBRARY")はWindows用ライブラリを作成するのに必要？[Building libraries on Windows](https://docs.ros.org/en/foxy/Guides/Ament-CMake-Documentation.html#building-libraries-on-windows)を参照．

<details><summary>ament_export_*に関するFoxy Fitzroyからの変更について</summary><div>
Eloquent Elusorまでは共有ライブラリのエクスポートに関するコマンドは以下の~~4つ~~3つであった．

* ament_export_interfaces
* ament_export_libraries
* ament_export_inclujde_directories
* ~~ament_export_dependencies~~
    * これは必要だった！

これがament_export_targetsの一つになった．使い方としては，ament_export_interfacesの代わりにament_export_targetsを使うような感じらしい．

詳しくは公式のDistributionのニュースのうち，[Foxy Fitzroyでの変更:Classic CMake vs. modern CMake](https://index.ros.org/doc/ros2/Releases/Release-Foxy-Fitzroy/#id10)を参照のこと．
</div></details>

## 共有ライブラリ化するMinimalCompNode2
二つ目は手動で作成してみる．

### ソースコード

- include/minimal_comp_node2.hpp
- src/minimal_comp_node2.cpp

```c++:minimal_comp_node2.hpp
#include <rclcpp/rclcpp.hpp>
#include "visibility_control.h"

namespace minimal_comp {

class MinimalCompNode2 : public rclcpp::Node{
public:
  MINIMAL_COMP_PUBLIC
  MinimalCompNode2(
    const std::string& name_space="",
    const rclcpp::NodeOptions& options=rclcpp::NodeOptions()
  );
};

}
```

```c++:minimal_comp_node2.cpp
#include <rclcpp/rclcpp.hpp>
#include "minimal_comp/minimal_comp_node2.hpp"

namespace minimal_comp {

MinimalCompNode2::MinimalCompNode2(
  const std::string& name_space,
  const rclcpp::NodeOptions& options
): Node("minimal_comp2", name_space, options)
{
  RCLCPP_INFO(this->get_logger(),"minimal comp 2 test");
}

}
```

### CMakeLists.txt
以下は追加した・重要な部分のみ抜粋．
詳しくは[ament_cmake User Documentation](https://index.ros.org/doc/ros2/Tutorials/Ament-CMake-Documentation/)を参照．

***CMakeLists.txt***

```CMakeLists.txt
add_library(minimal_comp_node SHARED
  src/minimal_comp_node1.cpp
  src/minimal_comp_node2.cpp
)
```

## もろもろ
通常のfind_package，ament_target_dependencies以外に以下の要素がある．

- add_library
- target_compile_definitions
- target_compile_options
- target_include_directories
- ament_environment_hooks
- ament_export_interfaces
- ament_export_dependencies
- ament_export_libraries
- ament_export_include_directories
- install 2種類

### 説明:add_library
add_executableと同じように他で使用するライブラリ名を付けつつコンパイルに必要なソースファイルを記述．
ライブラリ名を使用するのは以下の要素．

- target_compile_definitions
- target_compile_options
- ament_target_dependencies
- target_include_directories
- ament_export_libraries
- install

### 説明:target_compile_*
コンパイル時の設定．他でもそのまま使用．

### 説明:target_include_directories
include/[パッケージ名]/以下のヘッダファイルは外部に公開するものである．他のパッケージはtarget_include_directoriesによって外部公開用のヘッダファイルにアクセスできる．共有ライブラリ自身にとってもinclude/[パッケージ名]/以下にヘッダファイルがあるので(src/以下にはないので)，target_include_directoriesを指定する必要がある．

### 説明:ament_environment_hooks
OS依存の処理っぽい．他でもそのまま使用．

### 説明:install
最初のinstallは共有ライブラリを使用するパッケージ用にヘッダファイルをインストールするためのコマンド．

```
install(
  DIRECTORY include/
  DESTINATION include
)
```

二つ目のinstallは作成したライブラリをインストールするためのコマンド．
特にEXPORTはament_export_interfacesで使うためのもの．
名前はなんでもよいがライブラリ名にexport_を付けたものが用いられる．
理由は[ament_cmake User Documentation](https://index.ros.org/doc/ros2/Tutorials/Ament-CMake-Documentation/)のBuilding a Libraryを参照のこと．

```
install(TARGETS
  minimal_comp_node
  EXPORT export_minimal_comp_node
  ARCHIVE DESTINATION lib
  LIBRARY DESTINATION lib
  RUNTIME DESTINATION bin
  INCLUDES DESTINATION include
)
```

### 説明:ament_export_targets
ament_export_targetsは，installのEXPORTで指定したものにHAS_LIBRARY_TARGETを．
ただjazzyの「コンポーネント作成」にはいらないようで，共有ライブラリでいるのかどうか不明．いらなくてもいいのでは？

### 説明:ament_export_dependencies
共有ライブラリを使う側に依存関係を知らせ，使う側で改めて依存関係処理しなくて済むようにするもの．


# ビルド
```
$ cd ~/ros2_studies_ws/
$ colcon build --symlink-install --packages-up-to minimal_comp
$ . install/setup.bash
```

# 複数のライブラリを作成する場合
一つのライブラリの中に複数の機能(クラス)を詰め込むのではなく，複数のライブラリ自体を一つのパッケージで作成する場合．

一つのライブラリのやり方の中で，以下に注目・変更

- ament_export_targets
- install

## 概要
作成したライブラリをインストールするためのinstallにて．

- TARGETS
    - add_libraryのライブラリ名を複数記述
- EXPORT
    - この項は全体で一つ．
    - よって，例えばexport_[package名]などに．
- ament_export_targets
    - EXPORTで名づけしたものを記述
    - よって上記の例ではexport_[package名]

## 例
抜粋

```CMakeLists.txt
ament_export_targets(export_${PROJECT_NAME} HAS_LIBRARY_TARGET)

install(TARGETS
  model1
  model2
  EXPORT export_${PROJECT_NAME}
...
)
```




# 参考

- [teratail](https://teratail.com/questions/38812)
    - curlで特定のファイルをDL
- [curlコマンド 使い方メモ](https://qiita.com/yasuhiroki/items/a569d3371a66e365316f)
- [sed, awk, grepの使い分け](https://qiita.com/lca367/items/e174ce6985a1e7f3c441)