2016/09/16

C/C++言語でモジュール・プラグインを実装してGLibで読み込む

ここでは、汎用のライブラリGLibを用いてモジュールやプラグインと呼ばれるような外部ファイルを読み込んで用いるための手順と小さなテスト用サンプルプログラム(C言語のみのものとC++のクラスを用いたもの)を扱っている。

  1. GLibでモジュール・プラグインをプログラムの実行中に読み込んで使用する方法
  2. モジュール・プラグインの実装と使用例
    1. C言語
    2. C++言語(クラス使用)

GLibでモジュール・プラグインをプログラムの実行中に読み込んで使用する方法

モジュールやプラグインをプログラムの実行中に読み込んでその中の関数を呼び出すなどの操作を行う方法は、GNU/Linuxなどのdlopen()系関数が利用できる環境とLoadLibrary()系関数を用いるWindowsとで異なる。

GLibにはGModuleという型とこれを用いてモジュールやプラグインを扱う関数群があり、いずれの環境にも対応したプログラムを書くことができる。ただしWindowsにはGLibは標準では提供されないため、(ビルドする環境と動かす環境のいずれにおいても)GLibのWindows版DLLとこれが依存するlibintlライブラリ(GNU gettextのランタイムライブラリ)などを用意する必要がある。GLibの他の機能を一切使わないのであれば、同ライブラリの機能は使用せずにOSごとに処理を分岐する書き方をしたほうがよい場合もある。また、GLibを使用すると同ライブラリのライセンス(LGPL 2以上)の制約も受ける点にも注意が必要。

GLibのモジュールやプラグインを扱う機能の使い方としては

  • g_module_open()で入力ファイルの場所を指定してモジュールやプラグインのファイルを開き、戻り値としてGModule構造体へのポインタを得る(後で他の操作を行う関数を呼ぶ際には最初の引数にこれを指定する)
    • g_module_build_path()を用いると、ファイルの配置ディレクトリと名前(拡張子は付けない)からOSに合ったパス名が得られる(戻り値は不要になったらg_free()による解放が必要)が、得られるファイル名は “lib[2番目の引数].[拡張子]” の形となるため、 “lib” が先頭に付くようにする必要がある
    • g_module_open()の2番目の引数を “G_MODULE_BIND_LAZY” にすると(モジュールの読み込み時にではなく)必要なときだけシンボルの解決処理が行われるようになる
  • ファイルを開いた後でg_module_symbol()に取得したい変数や関数の名前とこれを呼び出すためのポインタ変数の場所を指定して呼び出すことで、それが使用可能な状態になる
  • 使用可能な状態になったらポインタ経由で中の関数や変数を使用する
  • 不要になったモジュール・プラグインはg_module_close()で開放する
  • 本体側はリンク時に “pkg-config --libs gmodule-2.0” の出力をリンカオプションとして渡す必要がある
  • 処理に失敗した際にはg_module_error()で直近のエラーに関する文字列が得られる

となる。

更に、GLibのこの機能では、モジュール側に特定の名前の関数を実装することで、読み込んだときの初期化処理や破棄時の後始末処理を自動的に行われるようにできる。これらはモジュールをC++コンパイラでコンパイルすると内部で名前が変わってしまう(修飾・マングルされる)ことにより呼ばれなくなるため、これを防ぐためにモジュールの仕様を定めたヘッダファイルの中で “G_BEGIN_DECLS[1] と “G_END_DECLS[2] の間にこれらの関数のプロトタイプ宣言を記述するか、これらの関数を実装する代わりにC++言語のクラスでコンストラクタやデストラクタを用いる(例は後述)。

  • g_module_check_init()関数(戻り値の型は “const gchar *” で、初期化時にエラーが発生した場合にその文字列を指し示すポインタを返し、正常に初期化処理が終了した場合は “NULL” を返すようにする): g_module_open()の呼び出し時に自動的に呼ばれる
  • g_module_unload()関数(戻り値の型はvoidreturn文は記述しない): g_module_close()の呼び出し時に自動的に呼ばれる

これらの関数は “GModule *” 型の引数を1つ取るようにする必要がある。この引数を関数内で使用しない場合は “G_GNUC_UNUSED” を引数の部分に付けるとGCCや互換コンパイラで警告を出さないようにできる。

モジュール・プラグインの実装と使用例

モジュールやプラグインと呼ばれるものの主な中身は “引数や戻り値の型が決められている関数の集まり” の形をとっているのだが、それを利用する側のプログラム(本体)の仕様に合ったものでなければそれを読み込んでも正しく動作しない。

モジュール・プラグイン内の関数の引数や戻り値の型が正しい(本体側が想定している)ものでなければモジュールのコンパイルが通らないようになっていたり、読み込み時にチェックを行うことで仕様通りでないモジュールを読み込ませないようにするのが望ましいが、C言語を想定したものとC++言語のクラスを使用したものとでこれを実現する形は異なり、それぞれコード例を扱う。

モジュールの実装の形は色々考えられるが、以下のコードで用いている形が特に優れているというわけではない(本体のプログラムによって適する形は異なる)。

動作を確認するための小さな例なので、モジュール側には本体プログラムの機能を呼び出すような関数は用意していない上、本体プログラム側にはモジュールの管理を行うような仕組みは実装していない。また、読み込み時の初期化処理と破棄時の後始末処理を記述する部分は設けているが、本体からオン/オフを切り替える[3]ことができるようにしたい場合などはこれらとは別に(本体の作成者側が)それぞれの切り替え時に呼ばれる関数についての記述を仕様のヘッダファイル内に入れる必要がある。

なお、既存のプログラムに対するモジュールやプラグインを作成したい場合は、それぞれのプログラムの作成者などが用意している作成手順に従って実装を行うか、ソースの公開されている既存のモジュールやプラグイン(サンプル含む)を参考にするなどする。

C言語

  • モジュールとして最低限必要な関数ごとの引数や戻り値の型に合った関数ポインタをメンバとして並べたものを最低限含んだ構造体の定義をヘッダファイル内で行う
  • その構造体の実体はモジュール内に1つずつ宣言し、モジュールの中身に合わせて初期化しておく
  • 同構造体の場所を返すための関数を、モジュール内に1つずつ用意する

という形でモジュールを実装した例となる。

ファイル名:myapp-module-interface.h
#ifndef __MYAPP_MODULE_INTERFACE_H__
#define __MYAPP_MODULE_INTERFACE_H__

#include <gmodule.h>

G_BEGIN_DECLS

/* モジュール内の呼び出し可能関数の引数や戻り値の型を定める */
typedef gint (* ModuleFunc1) (gint a, gint b);
typedef void (* ModuleFunc2) (const gchar *s);

/* アクセスに使用される構造体とその取得用関数 */
typedef struct
{
  const gchar *name;
  const gchar *version;
  const ModuleFunc1 func1;
  const ModuleFunc2 func2;
} MyappModule;

const MyappModule *myapp_module (void);

/* 自動的に呼ばれる関数をC++で正しく呼び出されるようにする */
/* これらの関数を実装しない場合やCのみでモジュールを記述する場合は記述不要 */
const gchar *g_module_check_init (GModule *module);
void g_module_unload (GModule *module);

G_END_DECLS

#endif /* __MYAPP_MODULE_INTERFACE_H__ */
ファイル名:libmyapp-foomodule.c
/*
 * C言語モジュールのコード例1
 *
 * gcc -shared -fPIC -O2 -Wall -Wextra -Werror=incompatible-pointer-types $(pkg-config --cflags gmodule-2.0) libmyapp-foomodule.c -o libmyapp-foomodule.so $(pkg-config --libs gmodule-2.0)
 */

#define NAME    "foomodule"
#define VERSION "0.1"

#include <gmodule.h>
#include "myapp-module-interface.h"

/* モジュール内関数1: 引数がgint型2つ・戻り値がgint型 */
static gint
foomodule_func1 (gint a, gint b)
{
  g_print ("foomodule> func1 (%d, %d)\n", a, b);
  return a + b;
}

/* モジュール内関数2: 引数がconst gchar *型1つ */
static void
foomodule_func2 (const gchar *s)
{
  g_print ("foomodule> func2 (\"%s\")\n", s);
}

/* g_module_check_init()を実装すると読み込み時に自動的に呼ばれる */
const gchar *
g_module_check_init (G_GNUC_UNUSED GModule *module)
{
  g_print ("foomodule> g_module_check_init ()\n");
  return NULL;
}

/* g_module_unload()を実装すると破棄時に自動的に呼ばれる */
void
g_module_unload (G_GNUC_UNUSED GModule *module)
{
  g_print ("foomodule> g_module_unload ()\n");
}

/* モジュール内の関数や変数にアクセスするための構造体 */
static const MyappModule foomodule = {NAME,
                                      VERSION,
                                      foomodule_func1,
                                      foomodule_func2};

/* 構造体の場所を返す関数 */
const MyappModule *
myapp_module (void)
{
  return &foomodule;
}
ファイル名:libmyapp-barmodule.c
/*
 * C言語モジュールのコード例2
 *
 * gcc -shared -fPIC -O2 -Wall -Wextra -Werror=incompatible-pointer-types $(pkg-config --cflags gmodule-2.0) libmyapp-barmodule.c -o libmyapp-barmodule.so $(pkg-config --libs gmodule-2.0)
 */

#define NAME    "barmodule"
#define VERSION "0.2"

#include <gmodule.h>
#include "myapp-module-interface.h"

static gint
barmodule_func1 (gint a, gint b)
{
  g_print ("barmodule> func1 (%d, %d)\n", a, b);
  return a * b;
}

static void
barmodule_func2 (const gchar *s)
{
  g_print ("barmodule> func2 (\"%s\")\n", s);
}

const gchar *
g_module_check_init (G_GNUC_UNUSED GModule *module)
{
  g_print ("barmodule> g_module_check_init ()\n");
  return NULL;
}

void
g_module_unload (G_GNUC_UNUSED GModule *module)
{
  g_print ("barmodule> g_module_unload ()\n");
}

static const MyappModule barmodule = {NAME,
                                      VERSION,
                                      barmodule_func1,
                                      barmodule_func2};

const MyappModule *
myapp_module (void)
{
  return &barmodule;
}
ファイル名:gmodule-load-test.c
/*
 * C言語モジュールをGLibの機能で読み込む例
 *
 * gcc -O2 -Wall -Wextra $(pkg-config --cflags gmodule-2.0) gmodule-load-test.c -o gmodule-load-test $(pkg-config --libs gmodule-2.0)
 */

#include <gmodule.h>
#include <stdlib.h>

#include "myapp-module-interface.h"

/* モジュール内の関数や変数にアクセスする構造体と、モジュールを開いた際の */
/* GModule型へのポインタとをまとめて管理するための構造体                  */
typedef struct
{
  MyappModule *mod;  /* myapp-module-interface.h で定義 */
  GModule *gmod;
} LoadedMyappModule;

/* モジュールの中身にアクセスする構造体を取得する関数へのポインタ */
typedef MyappModule *(* GetModuleFunc) (void);

/* モジュール読み込み処理を行う関数 */
gboolean
load_module (const gchar *infile, LoadedMyappModule *module)
{
  /* モジュールファイルを開く */
  GModule *gmod = g_module_open (infile, G_MODULE_BIND_LAZY);
  if (! gmod)
  {
    g_printerr ("Could not load module \"%s\": %s\n", infile, g_module_error ());
    return FALSE;
  }

  /* モジュールの中身にアクセスする構造体を取得する関数の場所を取得 */
  GetModuleFunc myapp_module;
  if (! g_module_symbol (gmod, "myapp_module", (gpointer *) &myapp_module))
  {
    g_printerr ("Could not load symbol \"myapp_module\": %s\n", g_module_error ());
    g_module_close (gmod);
    module->mod = NULL;
    module->gmod = NULL;
    return FALSE;
  }

  /* 取得した場所を用いてmyapp_module()を呼び、構造体の場所を取得 */
  module->mod = myapp_module ();
  /* 後で開放するときのためにGModule構造体の場所をメンバに保存 */
  module->gmod = gmod;
  return TRUE;
}

int
main ()
{
  /* GLibでモジュールがサポートされているかのチェック */
  if (! g_module_supported ())
  {
    g_printerr ("GModule is not supported\n");
    return EXIT_FAILURE;
  }

  /* モジュールを読み込む */
  LoadedMyappModule foomodule;
  /* Windows以外: ./libmyapp-foomodule.so  */
  /*     Windows: .\libmyapp-foomodule.dll */
  gchar *infile = g_module_build_path (".", "myapp-foomodule");
  g_print ("main> load_module (\"%s\", &foomodule)\n", infile);
  gboolean result = load_module (infile, &foomodule);
  g_free (infile);
  if (! result)
    return EXIT_FAILURE;

  /* 構造体(foomodule.mod)を通してモジュール内の変数や関数を利用する */
  g_print ("main> name:%s version:%s\n", foomodule.mod->name, foomodule.mod->version);
  gint retval = foomodule.mod->func1 (8383, 9659);
  g_print ("main> foomodule.mod->func1 (8383, 9659) = %d\n", retval);
  foomodule.mod->func2 ("TEST");

  /* 構造体のメンバを渡してモジュールの破棄を行う */
  g_print ("main> g_module_close (foomodule.gmod)\n");
  g_module_close (foomodule.gmod);

  g_print ("-----\n");

  /* 同様に別のモジュールを処理 */
  LoadedMyappModule barmodule;
  infile = g_module_build_path (".", "myapp-barmodule");
  g_print ("main> load_module (\"%s\", &barmodule)\n", infile);
  result = load_module (infile, &barmodule);
  if (! result)
    return EXIT_FAILURE;
  g_free (infile);
  g_print ("main> name:%s version:%s\n", barmodule.mod->name, barmodule.mod->version);
  retval = barmodule.mod->func1 (8383, 9659);
  g_print ("main> barmodule.mod->func1 (8383, 9659) = %d\n", retval);
  barmodule.mod->func2 ("TEST");
  /* 引数付きマクロを用いて、構造体を引数に取る形で後始末処理 */
#define CLOSE_MODULE(mod) g_module_close (mod.gmod)
  g_print ("main> %s\n", "CLOSE_MODULE (barmodule)");
  CLOSE_MODULE (barmodule);

  g_print ("-----\n");
  g_print ("main> end\n");

  return EXIT_SUCCESS;
}
実行結果
main> load_module ("./libmyapp-foomodule.so", &foomodule)
foomodule> g_module_check_init ()
main> name:foomodule version:0.1
foomodule> func1 (8383, 9659)
main> foomodule.mod->func1 (8383, 9659) = 18042
foomodule> func2 ("TEST")
main> g_module_close (foomodule.gmod)
foomodule> g_module_unload ()
-----
main> load_module ("./libmyapp-barmodule.so", &barmodule)
barmodule> g_module_check_init ()
main> name:barmodule version:0.2
barmodule> func1 (8383, 9659)
main> barmodule.mod->func1 (8383, 9659) = 80971397
barmodule> func2 ("TEST")
main> CLOSE_MODULE (barmodule)
barmodule> g_module_unload ()
-----
main> end

C++言語(クラス使用)

  • 本体プログラム側でモジュールとしての最低限必要な仕様をクラスとしてヘッダファイルに定義[4]しておき、実装を行う部分のメンバ関数は純粋仮想関数とする
  • モジュールのコードを記述する側はモジュールのクラスを継承し、 “virtual” の付いているメンバ関数を実装する形で、モジュールとして必須の処理を実装する
  • モジュールのコード内には継承した各モジュールごとのクラスのオブジェクト(インスタンス)を1つずつ用意する
  • 必要であれば、モジュールの対象プログラムを識別するための固有の値をクラスのメンバに入れておき、読み込んだ後にチェックを行い、更に必要であればモジュールAPIのバージョンなども持たせてチェックを行うなどする

という形でモジュールを実装した例となる。C++11を使用しているので、GCCや互換コンパイラでは-std=c++11もしくはそれよりも新しい仕様向けのオプション指定が必要。

ファイル名:myapp-module-interface-class.h
// -*- mode: c++ -*-

#ifndef __MYAPP_MODULE_INTERFACE_CLASS_H__
#define __MYAPP_MODULE_INTERFACE_CLASS_H__

#include <glib.h>

// モジュールがこのプログラム向けのものであるかを判断するための値を決める
// この例は "M" "Y" "A" "P" の並びから取っているが識別目的なので深い意味はない
#define MYAPP_MODULE_MAGIC ((gint32) 0x4d594150)

class MyappModule
{
public:
  // このクラスから派生したクラスでは初期化時に
  // magic(値は固定)と引数からのname,versionがメンバに入る
  MyappModule (const gchar *name, const gchar *version) :
    name (name),
    version (version)
    {}
  virtual ~MyappModule () {}

public:
  // 値が格納されるメンバ
  const gint32 magic = MYAPP_MODULE_MAGIC;
  const gchar *name;
  const gchar *version;

  // 各モジュール内で継承して実装するメンバ関数は純粋仮想関数とする必要がある
  // ここが仮想関数でないと継承後のクラスの(以下の名前の)メンバ関数が呼ばれない
  virtual gint func1 (G_GNUC_UNUSED gint a, G_GNUC_UNUSED gint b) = 0;
  virtual void func2 (G_GNUC_UNUSED const gchar *s) = 0;
};

#endif // __MYAPP_MODULE_INTERFACE_CLASS_H__
ファイル名:libmyapp-foomodule-class.cc
/*
 * C++言語モジュール(クラス使用)のコード例1
 *
 * g++ -std=c++11 -shared -fPIC -O2 -Wall -Wextra $(pkg-config --cflags glib-2.0) libmyapp-foomodule-class.cc -o libmyapp-foomodule-class.so $(pkg-config --libs glib-2.0)
 */

#define NAME    "foomodule"
#define VERSION "0.1"

#include <glib.h>
#include "myapp-module-interface-class.h"

class FooModule : public MyappModule
{
public:
  FooModule ();
  ~FooModule ();

public:
  gint func1 (gint a, gint b) override;
  void func2 (const gchar *s) override;
};

// モジュール内関数1: 引数がgint型2つ・戻り値がgint型
gint
FooModule::func1 (gint a, gint b)
{
  g_print ("foomodule> func1 (%d, %d)\n", a, b);
  return a + b;
}

// モジュール内関数2: 引数がconst gchar *型1つ
void
FooModule::func2 (const gchar *s)
{
  g_print ("foomodule> func2 (\"%s\")\n", s);
}

// コンストラクタを実装すると読み込み時に自動的に呼ばれる
// 名前とバージョンは親クラスのコンストラクタで扱われるようにしている
FooModule::FooModule () : MyappModule (NAME, VERSION)
{
  g_print ("foomodule> FooModule ()\n");
}

// デストラクタを実装すると破棄時に自動的に呼ばれる
FooModule::~FooModule ()
{
  g_print ("foomodule> ~FooModule ()\n");
}

// このモジュールのオブジェクト(インスタンス)
FooModule myapp_module;
ファイル名:libmyapp-barmodule-class.cc
/*
 * C++言語モジュール(クラス使用)のコード例2
 *
 * g++ -std=c++11 -shared -fPIC -O2 -Wall -Wextra $(pkg-config --cflags glib-2.0) libmyapp-barmodule-class.cc -o libmyapp-barmodule-class.so $(pkg-config --libs glib-2.0)
 */

#define NAME    "barmodule"
#define VERSION "0.2"

#include <glib.h>
#include "myapp-module-interface-class.h"

class BarModule : public MyappModule
{
public:
  BarModule ();
  ~BarModule ();

public:
  gint func1 (gint a, gint b) override;
  void func2 (const gchar *s) override;
};

gint
BarModule::func1 (gint a, gint b)
{
  g_print ("barmodule> func1 (%d, %d)\n", a, b);
  return a * b;
}

void
BarModule::func2 (const gchar *s)
{
  g_print ("barmodule> func2 (\"%s\")\n", s);
}

BarModule::BarModule () : MyappModule (NAME, VERSION)
{
  g_print ("barmodule> BarModule ()\n");
}

BarModule::~BarModule ()
{
  g_print ("barmodule> ~BarModule ()\n");
}

BarModule myapp_module;
ファイル名:gmodule-load-test-class.cc
/*
 * C++言語(クラス使用)モジュールをGLibの機能で読み込む例
 *
 * g++ -std=c++11 -O2 -Wall -Wextra $(pkg-config --cflags gmodule-2.0) gmodule-load-test-class.cc -o gmodule-load-test-class $(pkg-config --libs gmodule-2.0)
 */

#include <memory>
#include <cstdlib>
#include <gmodule.h>

#include "myapp-module-interface-class.h"

// モジュールのオブジェクトと、モジュールを開いた際の
// GModule構造体へのポインタとをまとめて管理するためのクラス
class LoadedMyappModule
{
public:
  LoadedMyappModule (MyappModule *obj, GModule *gmod) :
    obj (obj),
    gmod (gmod)
    {}
  ~LoadedMyappModule () {g_module_close (this->gmod);}

public:
  MyappModule *obj;
  GModule *gmod;
};

// モジュールの読み込みを試みて成功したら上のクラスのオブジェクトポインタを返し
// 失敗したらnullptr(ぬるぽインタ)を返す
LoadedMyappModule *
load_module (const gchar *infile)
{
  GModule *gmod = g_module_open (infile, G_MODULE_BIND_LAZY);
  if (! gmod)
  {
    g_printerr ("Could not load module \"%s\": %s\n", infile, g_module_error ());
    return nullptr;
  }

  MyappModule *myapp_module;
  if (! g_module_symbol (gmod, "myapp_module", (gpointer *) &myapp_module))
  {
    g_printerr ("Could not load symbol \"myapp_module\": %s\n", g_module_error ());
    g_module_close (gmod);
    return nullptr;
  }
  // モジュールがこのプログラム向けのものであるかを確認
  if (myapp_module->magic != MYAPP_MODULE_MAGIC)
  {
    g_printerr ("Magic \"%x\" is invalid.\n", myapp_module->magic);
    g_module_close (gmod);
    return nullptr;
  }

  return new LoadedMyappModule (myapp_module, gmod);  // deleteで破棄する必要がある
}

int
main ()
{
  if (! g_module_supported ())
  {
    g_printerr ("GModule is not supported\n");
    return EXIT_FAILURE;
  }

  gchar *infile = g_module_build_path (".", "myapp-foomodule-class");
  g_print ("main> load_module (\"%s\")\n", infile);
  LoadedMyappModule *foomodule = load_module (infile);
  g_free (infile);
  if (! foomodule)
    return EXIT_FAILURE;

  g_print ("main> name:%s version:%s\n", foomodule->obj->name, foomodule->obj->version);
  gint retval = foomodule->obj->func1 (8383, 9659);
  g_print ("main> foomodule->obj->func1 (8383, 9659) = %d\n", retval);
  foomodule->obj->func2 ("TEST");

  // モジュールの手動破棄
  g_print ("main> delete foomodule\n");
  delete foomodule;

  g_print ("-----\n");

  // 同様に別のモジュールを処理
  infile = g_module_build_path (".", "myapp-barmodule-class");
  g_print ("main> load_module (\"%s\")\n", infile);
  LoadedMyappModule *_barmodule = load_module (infile);
  g_free (infile);
  if (! _barmodule)
    return EXIT_FAILURE;

  {
    // このbarmoduleが寿命になると_barmoduleが破棄されるようにする(C++11以上)
    // スマートポインタと呼ばれるものの1つ
    std::unique_ptr<LoadedMyappModule> barmodule (_barmodule);
    g_print ("main> name:%s version:%s\n", barmodule->obj->name, barmodule->obj->version);
    retval = barmodule->obj->func1 (8383, 9659);
    g_print ("main> barmodule->obj->func1 (8383, 9659) = %d\n", retval);
    barmodule->obj->func2 ("TEST");
    g_print ("unique_ptr> delete _barmodule\n");
    // barmoduleはここで破棄され、モジュールも破棄される
  }

  g_print ("-----\n");
  g_print ("main> end\n");

  return EXIT_SUCCESS;
}
実行結果
main> load_module ("./libmyapp-foomodule-class.so")
foomodule> FooModule ()
main> name:foomodule version:0.1
foomodule> func1 (8383, 9659)
main> foomodule->obj->func1 (8383, 9659) = 18042
foomodule> func2 ("TEST")
main> delete foomodule
foomodule> ~FooModule ()
-----
main> load_module ("./libmyapp-barmodule-class.so")
barmodule> BarModule ()
main> name:barmodule version:0.2
barmodule> func1 (8383, 9659)
main> barmodule->obj->func1 (8383, 9659) = 80971397
barmodule> func2 ("TEST")
unique_ptr> delete _barmodule
barmodule> ~BarModule ()
-----
main> end
[1]: extern "C" {に展開される
[2]: } に展開される
[3]: 例えば、GUIアプリケーションでモジュール一覧内の個別のチェックボックスの状態を変更するといった形
[4]: アプリケーションによってはモジュールの種類ごとに追加の仕様を付け加えるために種類ごとのクラスに派生したものを用意する形も考えられる