はじめに
この記事は「完全に理解したTalk Advent Calendar 2020」5日目の投稿記事です。
c++で動的ライブラリをうっかりと動的ロードできちゃったので、その話をします。
本記事では、cmakeとc++を使って解説していきます。
動的ライブラリについて
まず、動的ライブラリの拡張子は、OSによって異なります。
MACOSだと、.dylib
Windowsだと、.dll
Linuxだと、.so
どちらも、個人的には差異はないと思っています。
今回、自分の環境はMACOSですので、dylibを前提で進めています。
動的ロードについて
動的ライブラリはプログラムの実行中に読み込むことができます。
これは、動的ロード、またはプラグイン、アドオンなどと呼ばれます。
静的リンク、動的リンクは実行ファイルのビルド時に、
予めライブラリのリンクがされていることで実行ができていました。
動的ロードは、ビルド時にリンクは行わずに、実行ファイリが動作している最中に任意のタイミングでライブラリを読み込むという仕組みになります。
ですので、プログラム中に動的ライブラリを読み込む処理の記述が必要になります。
動的ロードをするためのライブラリ
読み込む処理を実装するのですが、
以下のライブラリを使います。
#include<dlfcn.h>
です。
これを使うことで、ライブラリを簡単に動的ロードする事ができます。
ざっくり、このライブラリに入っている関数の説明すると、以下になります。
dlopen
で動的ライブラリを開きます。
dlsym
で動的ライブラリ内から関数を探します。
dlclose
で開いた動的ライブラリを閉じます。
.cppに書かれている処理を呼び出す
今回は、add.cpp、sub.cppを動的ライブラリにし、
main.cppから、動的ロードでこれらに定義してある、calculation
を呼び出します。
dlsymを用いて、extern "C"
内の処理を呼び出します。
#include <iostream>
#include <dlfcn.h>
int main(int argc, char **argv) {
const auto addlib = dlopen("./libSharedAdd.dylib", RTLD_LAZY);
const auto sublib = dlopen("./libSharedSub.dylib", RTLD_LAZY);
//MACなので、.dylibにしています。Linuxならば、.soにしてください。
auto Add = reinterpret_cast<double(*)(double, double)>(dlsym(addlib, "calculation"));
auto Sub = reinterpret_cast<double(*)(double, double)>(dlsym(sublib, "calculation"));
double a = 100;
double b = 30;
{
std::cout << "a+b = "<< Add(a,b) << std::endl;
}
{
std::cout << "a-b = "<< Sub(a,b) << std::endl;
}
dlclose(addlib);
dlclose(sublib);
}
extern "C" {
double calculation(double a, double b) {
return a+b;
}
}
extern "C" {
double calculation(double a, double b) {
return a-b;
}
}
cmake_minimum_required(VERSION 3.15)
set(CMAKE_CXX_STANDARD 14)
add_library(SharedAdd SHARED add.cpp)
add_library(SharedSub SHARED sub.cpp)
add_executable(main main.cpp)
基底クラスを継承したオブジェクトを動的ロードする
動的ライブラリに含まれる、基底クラスを継承したオブジェクトを呼び出せるようにしたいと思います。
まず。以下のポイントがあります。
・c++のスマートポインタを使う
・関数を通して、objectを生成する
になります。
スマートポインタを用いたことにより、デストラクタが呼び出されるようになります。
また、関数内でオブジェクト生成しそのオブジェクトをmainに移譲するので、扱いが簡単になります。
add.cpp、sub.cppを動的ライブラリにし、
main.cppから、動的ロードでこれらに定義してある、makeObject
を呼び出します。
makeObjectを関数にキャストし、それを呼び出すとオブジェクトが取得できるという流れになります。
#include <iostream>
#include <dlfcn.h>
#include "plugin_header.hpp"
int main(int argc, char **argv) {
const auto addlib = dlopen("./libSharedAdd.dylib", RTLD_LAZY);
const auto sublib = dlopen("./libSharedSub.dylib", RTLD_LAZY);
//MACなので、.dylibにしています。Linuxならば、.soにしてください。
auto addfunc = (PluginInterfaceCreateFunction*)(dlsym(addlib, "makeObject"));
auto subfunc = (PluginInterfaceCreateFunction*)(dlsym(sublib, "makeObject"));
double a = 100;
double b = 30;
{
const auto& add = addfunc(); //addlibに定義してあるオブジェクトを生成
std::cout << "lib = " << add->libID() << std::endl;
std::cout << "a+b = " << add->calculation(a,b) << std::endl;
}
{
const auto& sub = subfunc(); //sublibに定義してあるオブジェクトを生成
std::cout << "lib = " << sub->libID() << std::endl;
std::cout << "a-b = " << sub->calculation(a,b) << std::endl;
}
dlclose(addlib);
dlclose(sublib);
}
インターフェースですので純粋仮想クラスで定義を行います。
#ifndef PLAGUIN_TEST_PLUGIN_HEADER_HPP
#define PLAGUIN_TEST_PLUGIN_HEADER_HPP
#include <memory>
class PluginInterface{
public:
virtual ~PluginInterface()= default;
virtual int libID() =0;
virtual double calculation(double a, double b) =0;
};
typedef std::unique_ptr<PluginInterface> PluginInterfaceCreateFunction();
#endif // PLAGUIN_TEST_PLUGIN_HEADER_HPP
#include <iostream>
#include <memory>
#include "plugin_header.hpp"
class Add : public PluginInterface {
public:
Add() {
std::cout << "Add was create." << std::endl;
}
~Add() {
std::cout << "Add delete." << std::endl;
}
int libID() override {
return 1;
}
double calculation(double a, double b) override {
return a+b;
}
};
extern "C"{
unique_ptr<PluginInterface> makeObject() {
return std::unique_ptr<PluginInterface>(new Add);
}
}
#include <iostream>
#include <memory>
#include "plugin_header.hpp"
class Sub : public PluginInterface {
public:
Sub() {
std::cout << "Sub was create." << std::endl;
}
~Sub() {
std::cout << "Sub delete." << std::endl;
}
int libID() override {
return 2;
}
double calculation(double a, double b) override {
return a-b;
}
};
extern "C"{
unique_ptr<PluginInterface> makeObject() {
return std::unique_ptr<PluginInterface>(new Sub);
}
}
最後に
今回の話は、ライブラリの知識が少し必要な内容になってしまいました。
今後は、
・ライブラリのはなし
・動的ロードは気をつけないと
・俺得デザインパターンの実装のいくつか
・など。
が話せたらと思います。
当分の間はC++の話が多くなるかと思います。
ではまた。