CからC++のライブラリを呼び出す
大昔からある話だが,復習のためにやってみた.STLのlist
- C++ でCから呼び出すためのラッパを書く
- ラッパ関数のプロトタイプ宣言は extern "C" で宣言する
- このとき,C++で読まれる場合とCから読まれる場合を__cplusplusで切り分ける.
- リンクはC++のコンパイルドライバを使う
ラッパのヘッダファイル
まず,ラッパのヘッダファイルlist_wrapper.hを書く.list_int_t は void * としている.C++のコンパイラでは__cplusplusが定義されているので,extern "C" が有効になる.これはC++での関数名のマングリングを行わないようにする仕掛け.これをいれることでC言語から同じ関数名で呼び出すことができるようになる.
typedef void * list_int_t; #ifdef __cplusplus extern "C" { #endif list_int_t list_int_create(); void list_int_push_back(list_int_t, int); int list_int_pop_front(list_int_t); void list_int_delete(list_int_t); #ifdef __cplusplus }; #endif
ラッパを実装
定義したwrapperをlist_wrapper.cppで実装する.キャストしてlist
#include <list> #include "list_wrapper.h" using namespace std; list_int_t list_int_create(){ return (list_int_t) (new list<int>()); } void list_int_push_back(list_int_t listPtr, int item){ ((list<int> *)listPtr)->push_back(item); } int list_int_pop_front(list_int_t listPtr){ int tmp = ((list<int> *)listPtr)->front(); ((list<int> *)listPtr)->pop_front(); return tmp; } void list_int_delete(list_int_t listPtr){ delete ((list<int> *)listPtr); }
Cプログラムを書く
作ったラッパを利用するCプログラムtest.cを書く.listを作って,1,2,3とプッシュ,とりだしてprintfし,listを破棄している.
#include <stdio.h> #include "list_wrapper.h" main(){ list_int_t l = list_int_create(); list_int_push_back(l, 1); list_int_push_back(l, 2); list_int_push_back(l, 3); printf("%d\n", list_int_pop_front(l)); printf("%d\n", list_int_pop_front(l)); printf("%d\n", list_int_pop_front(l)); list_int_delete(l); }
コンパイルと実行
ラッパはg++で,テストプログラムはgccでコンパイルする.リンクはg++で.実行してみるとちゃんと動くの確認できる.
$ g++ -c list_wrapper.cpp $ gcc -c test.c $ g++ -o test test.o list_wrapper.o $ ./test 1 2 3
extern "C" の効果
extern "C" の効果をテストしてみる.こんなファイルを作ってテスト.
#ifdef EXTERN_C extern "C" { #endif int test(int); #ifdef EXTERN_C }; #endif int test(int a){}
まずはEXTERN_Cを定義せずに,つまりextern "C" なしでコンパイル
し,nmコマンドで定義されているラベルを見る.
$ g++ -c test2.cpp $ nm test2.o 00000000 T __Z4testi 00000000 A __Z4testi.eh
関数testが__Z4testi という名前で定義されていることが分かる.このように関数名に余分な情報が追加されるのは,引数が異なる同名関数を許すためだが,この名前だとC言語からは参照できない.
つぎにextern "C"つきでコンパイルしてラベルを見てみよう.
$ g++ -DEXTERN_C -c test2.cpp $ nm test2.o 00000000 T _test 00000000 A _test.eh
こんどは_testという名前になっている.これでC言語から呼び出せる,というわけ.
gccでリンクすると
最後のリンクをg++でなくgccでやるとこんなエラーがでる.これは必要なライブラリがリンクされないため.またmainが呼ばれる前の初期化処理を行うcrt0もCとC++では異なるので,C++用のコンパイルドライバでリンクしないと動かない.
$ gcc -o test test.o list_wrapper.o Undefined symbols: "operator new(unsigned long)", referenced from: _list_int_create in list_wrapper.o __gnu_cxx::new_allocator<std::_List_node<int> >::allocate(unsigned long, void const*)in list_wrapper.o "___gxx_personality_v0", referenced from: ___gxx_personality_v0$non_lazy_ptr in list_wrapper.o "operator delete(void*)", referenced from: _list_int_delete in list_wrapper.o __gnu_cxx::new_allocator<std::_List_node<int> >::deallocate(std::_List_node<int>*, unsigned long)in list_wrapper.o "std::_List_node_base::hook(std::_List_node_base*)", referenced from: std::list<int, std::allocator<int> >::_M_insert(std::_List_iterator<int>, int const&)in list_wrapper.o "std::__throw_bad_alloc()", referenced from: __gnu_cxx::new_allocator<std::_List_node<int> >::allocate(unsigned long, void const*)in list_wrapper.o "std::_List_node_base::unhook()", referenced from: std::list<int, std::allocator<int> >::_M_erase(std::_List_iterator<int>)in list_wrapper.o ld: symbol(s) not found collect2: ld returned 1 exit status