本 Hack では、CからC++の関数を呼び出す方法、逆にC++からCの関数を呼び出す方法を紹介します。
C++からCで書かれた関数を呼び出したくなることはよくあります。あるいは逆に、CからC++で書かれた関数を呼び出したくなることもたまにはあるでしょう。本Hackでは、それらを実現する方法と、その際の注意点を解説します。基礎的なHackですが、一から順に確認していきましょう。
次の dbg 関数を、C コンパイラ、C++ コンパイラの両方でコンパイルしてみます。
// // dbg.c // #include <stdio.h> void dbg(const char *s) { printf("Log: %s\n", s); } |
すると、生成されたオブジェクトに含まれるシンボル名は(例えば)次のようになるでしょう。
コンパイラ | シンボル名 |
C | dbg |
C++ | _Z3dbgPKc |
Cコンパイラで関数をコンパイルすると、基本的には関数名がそのままシンボル名になります。環境によってはシンボル名が "dbg" ではなく、"_dbg" になる場合もありますが、その程度の変化です。一方、C++コンパイラで関数をコンパイルした場合、[Hack #16] で解説しているように、関数の所属する名前空間の情報や、関数の引数の型情報がシンボルに含まれるようになります。
さて、dbg.c を C コンパイラでコンパイルし、それを C++ で書かれた関数から呼び出して見ましょう。次の sample.cpp を用意します。
// // sample.cpp // extern "C" void dbg(const char *s): int main() { dbg("foo"); return 0; } |
これを、次のようにコンパイル、リンクすると、正常に実行ファイルが生成されます。無事、C++の関数からCの関数を呼び出すことができました。
% gcc -Wall -c dbg.c % g++ -Wall -c sample.cpp % g++ -o sample dbg.o sample.o % ./sample Log: foo |
sample.cpp の4行目、extern "C" がポイントです。extern "C" の有無で sampole.o の内容がどう変化するか見てみましょう (nmコマンドの使い方は、「[Hack #12] nmでオブジェクトファイルに含まれるシンボルをチェックする」を参照してください)。
(1) extern "C" あり
% nm sample.o U __gxx_personality_v0 00000000 T main U dbg |
(2) extern "C" なし
% nm sample.o U __gxx_personality_v0 00000000 T main U _Z3dbgPKc |
(2) では、sample.o は "_Z3dbgPKc" というシンボルを参照していますが、dbg.o に含まれるシンボルは単なる "dbg" であり、"_Z3dbgPKc"はどこにも存在しません。これにより、extern "C" をつけないと、次のようにリンクに失敗する結果となります。
% g++ -o sample dbg.o sample.o sample.o(.text+0x25): In function `main': : undefined reference to `dbg(char const*)' |
このように、C++からCの関数を呼び出す際には、extern "C" が重要な役割を果たします。
Yusuke Sato / BINARY HACKS (2006)