😽

C++で書いたコードをCで使う

2024/10/23に公開

はじめに

組み込み系などでは言語はCで書かれることがよくありますが、幾つかのライブラリなどはC++APIしか提供していない場合があります(openCVなど)。
そんな場合でもプログラム全体をC++で書きたくないという場合もあるでしょう(例えばもうCで書いちゃったプログラムへ追加する場合や、以下のCのメリットを極力享受したい場合など)。

そういう場合の対応方法をまとめます。

方法

全体として以下のようにします

  1. ライブラリのCラッパファイル(拡張子cpp)を作る
  2. 上記ライブラリをCから呼び出す

1. ライブラリのラッパを作る

extern 'C'で囲まれた部分は、c++で書かれていてもCから呼び出すことができます。
これを利用して、ライブラリのC++関数をCから呼び出せるようにします。

ヘッダファイル

ヘッダファイルでの各関数の宣言をextern 'C'で囲ってやるのが簡単でしょう。

関数宣言


#ifdef __cplusplus
extern "C" {
#endif

void wrapperFun(int);

#ifdef __cplusplus
}
#endif

ヘッダファイルはc++(関数の実体の定義)とC(関数の呼び出し)の両方でインクルードされるので、Cの場合のみextern 'C'が付与されるよう、__cplusplusを利用します。
関数宣言はC++、Cの双方で参照されるので、両方で有効な部分に記述します。

構造体、enum

引数でライブラリで定義された構造体やenumが必要な場合もヘッダファイルで定義します。
基本的にはコピペになると思います。
なお、これらはCでの参照され、C++に含まれると二重定義になるので、__cplusplusを利用してCでのみ有効になるようにします。

#ifndef __cplusplus
enum LineTypes {
    FILLED  = -1,
    LINE_4  = 4, //!< 4-connected line
    LINE_8  = 8, //!< 8-connected line
    LINE_AA = 16 //!< antialiased line
};
#endif

拡張子

ヘッダファイルの拡張子は.h(.hppではない)にします。
Cでインクルードされるからです(.hをC++でインクルードするのは問題ありません)。

関数定義

基本的には同じ引数をもらって、API本体に渡し、返り値をそのまま返すという実装になるかと思います。
ライブラリで定義された特殊なクラス(ライブラリ提供のコンストラクタを使わないとインスタンスを作れない、openCVのMatなど)が引数の場合は、void *で渡し、ラッパ関数内で変換(返り値の場合は逆)するなどの方法があります。

2. ラッパ関数をCから呼び出し

上記ヘッダファイルをインクルードし、他のC関数と同様に呼び出せばOKです。

コンパイル

cppファイルはg++で、cファイルはgccでコンパイルします。
ラッパ関数を呼び出しているcファイルをコンパイルする際には、-lstdc++コマンドをつける必要があります。これは、C++の標準ライブラリをリンクしています。g++の場合は自動でリンクしてくれますが、gccの場合は自動でやってくれないので手動でリンクしなければなりません。
これをやらないと'undefined reference to symbol '_ZNSt8ios_base4InitD1Ev@@GLIBCXX_3.4’'といったエラーが出ると思います。

補足

C++でなくC++を使うメリット

  • Cの方が単純かつブラックボックスが少ない
  • メモリ管理などがより自由度が高い
  • ハードウェアの操作などがより自由度が高い
  • Cの方がdeterministic 特にC++のオブジェクト指向関連の機能(オーバライドなど)

最後に

couseraの課題でopenCVを使ったプログラムを作る際、上記の課題に直面しました。(ハードウェアの操作はCで行いたいが、openCVのC APIはC++のみ(あるにはあるがかなり古く、保守もされていない))
そもそも全部C++で書くのか、ラッパを作るのか、という選定の部分が一番悩ましい気がします。

Discussion