ROS2における共有ライブラリ before Foxy Fitzroy

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

更新:2020/06/10:visibility.hのリンク変更に伴う修正
***

ROS2の共有ライブラリを作成する．

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

2019/6: 共有ライブラリの作成とコンポーネントの作成の情報が混ざっていたので分離．共有ライブラリのみの話とする．
2019/7: visibility.hは共有ライブラリの作成に関わるはずがコンポーネントに書いていたので修正する．

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

- 共有ライブラリのパッケージ名
    - 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
```

# 共有ライブラリ

作成物:[github.com](https://github.com/nek009/minimal_comp)の以下のファイル

- include/visibility.h
- include/minimal_comp_node1.hpp
- include/minimal_comp_node2.hpp
- src/minimal_comp_node1.cpp
- src/minimal_comp_node2.cpp

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

1. visibility.hを作成する
    - 公式からDLして自分のパッケージ用にアレンジするのが楽
2. 普通にクラスを作成する
    - 外部に公開するのでsrc/以下にヘッダファイルを置くのではなくinclude/[パッケージ名]/以下にヘッダファイルを置く．
3. 作成したクラスを共有ライブラリ用に少し追記する
    - visibility.hで書いたPUBLICに相当する記述(ここではMINIMAL_COMP_PUBLICになる)をpublicな関数(外部に公開する機能)の前に書く

## 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 > my_visibility.h
my_visibility.hの内容を確認
$ mv my_visibility.h visibility.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.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_
```

## 共有ライブラリ化する二つのクラス
注目点はpublicとしているコンストラクタの前．visibility.hにあるMINIMAL_COMP_PUBLICが付いていることを確認．

```c++:minimal_comp_node1.hpp
#include <rclcpp/rclcpp.hpp>
#include "minimal_comp/visibility.h" // 同一ディレクトリなら#include "visibility.h"でもok

namespace minimal_comp {

class MinimalCompNode1 : public rclcpp::Node{
public:
  MINIMAL_COMP_PUBLIC
  MinimalCompNode1();
};

}
```

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

namespace minimal_comp {

MinimalCompNode1::MinimalCompNode1()
: Node("minimal_comp1")
{
  RCLCPP_INFO(this->get_logger(),"minimal comp 1 test");
}

}
```

```c++:minimal_comp_node2.hpp
#include <rclcpp/rclcpp.hpp>
// 同一ディレクトリなら#include "visibility.h"でもok

namespace minimal_comp {

class MinimalCompNode2 : public rclcpp::Node{
public:
  MINIMAL_COMP_PUBLIC
  MinimalCompNode2();
};

}
```

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

namespace minimal_comp {

MinimalCompNode2::MinimalCompNode2()
: Node("minimal_comp2")
{
  RCLCPP_INFO(this->get_logger(),"minimal comp 2 test");
}

}
```

### 概要
簡単なメッセージを出力するだけのもの

# package.xmlとCMakeLists.txt
以下は追加したところに関係する部分のみ抜粋．
詳しくは[ament_cmake User Documentation](https://index.ros.org/doc/ros2/Tutorials/Ament-CMake-Documentation/)を参照．

## package.xml

```xml:package.xml
<package format="2">
  <build_depend>rclcpp</build_depend>
  <exec_depend>rclcpp</exec_depend>
  <test_depend>rclcpp</test_depend>
```

### CMakeLists.txt

```CMakeLists.txt
find_package(rclcpp REQUIRED)

add_library(minimal_comp_node SHARED
  src/minimal_comp_node1.cpp
  src/minimal_comp_node2.cpp
)
target_compile_definitions(minimal_comp_node
  PRIVATE "MY_LIBRARY_BUILDING_LIBRARY"
)
target_compile_options(minimal_comp_node PUBLIC -Wall)
ament_target_dependencies(minimal_comp_node
  rclcpp
)

target_include_directories(minimal_comp_node
  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()

ament_export_interfaces(export_minimal_comp_node HAS_LIBRARY_TARGET)
ament_export_dependencies(rclcpp)
ament_export_libraries(minimal_comp_node)
ament_export_include_directories(include)

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

### 概要
通常の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_*
ament_export_interfacesは，installのEXPORTで指定したものにHAS_LIBRARY_TARGETを．
ament_export_dependenciesは，ライブラリを作るのに必要なパッケージ名を．
ament_export_librariesは，ライブラリ名を．
ament_export_include_directoriesはincludeを．

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

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

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

- ament_export_interfaces
- ament_export_libraries
- install

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

- TARGETS
    - add_libraryのライブラリ名を複数記述
- EXPORT
    - この項は全体で一つ．
    - よって，例えばexport_[package名]などに．
- ament_export_interfaces
    - EXPORTで名づけしたものを記述
    - よって上記の例ではexport_[package名]
- ament_export_libraries
    - 複数のライブラリがある場合，カッコの中にスペース，改行を用いて書いていく．
        - それぞれのライブラリに対してament_export_librariesの行を記述してもいい

## 例
抜粋

```CMakeLists.txt
ament_export_interfaces(export_model_sims HAS_LIBRARY_TARGET)
ament_export_libraries(
  model1
  model2
)
# 以下でもよさそう
# ament_export_libraries(model1)
# ament_export_libraries(model2)

install(TARGETS
  model1
  model2
  EXPORT export model_sims
...
)
```




# 参考

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