c++のextern"C"

7942 ワード

たとえば、CでDLLライブラリを開発し、C++言語でもDLL出力(Export)の関数を呼び出すことができるようにするには、extern「C」でコンパイラに関数名を変更しないように強制する必要があります.
通常、C言語のヘッダファイルには、次のような形式のコードがよく見られます.
#ifdef __cplusplus
extern "C" {
#endif

/**** some declaration or so *****/

#ifdef __cplusplus
}
#endif

では、この書き方は何に使いますか.実際,CPPとCインタフェースを可能にするための構文形式である.この方式を採用したのは、二つの言語の違いによるものだ.CPPは多態性をサポートし、すなわち同じ関数名を有する関数は異なる機能を達成することができるため、CPPは通常、パラメータによって特定の呼び出しがどの関数であるかを区別する.コンパイル時、CPPコンパイラはパラメータタイプと関数名を結合し、プログラムがターゲットファイルにコンパイルされた後、CPPコンパイラはターゲットファイルのシンボル名に基づいて複数のターゲットファイルを直接1つのターゲットファイルまたは実行可能ファイルに接続することができる.しかしC言語では、多態性の概念が全くないため、Cコンパイラはコンパイル時に関数名の前に下線を追加する以外は、何もできません(少なくとも多くのコンパイラがそうしています).そのため、CPPとCを混合してプログラミングすると、問題が発生する可能性があります.あるヘッダファイルにこのような関数が定義されているとします.
int foo(int a, int b); この関数の実現は1つにある.cファイル中、同時に、.cppファイルでこの関数が呼び出されました.では、CPPコンパイラがこの関数をコンパイルすると、この関数名を_に変更する可能性があります.fooii,ここでiiは関数の第1パラメータと第2パラメータが整数であることを示す.Cコンパイラはこの関数名を_にコンパイルする可能性があります.foo .つまり、CPPコンパイラで得られたターゲットファイルでは、foo()関数は_fooiiシンボルで参照されますが、Cコンパイラで生成されたターゲットファイルではfoo()関数は_fooは代を指す.しかし、コネクタが動作している場合は、上位レベルがどの言語を採用しているかにかかわらず、ターゲットファイルの記号のみを認識できます.すると、コネクタが発見する.cppでfoo()関数が呼び出されましたが、他のターゲットファイルでは見つかりません.fooiiという記号は、接続プロセスにエラーがあることを示します.extern「C」{}という文法形式はこの問題を解決するために用いられる.この問題について例として説明する.
まず、次の3つのファイルがあると仮定します.
/* file: test_extern_c.h */

#ifndef __TEST_EXTERN_C_H__
#define __TEST_EXTERN_C_H__

#ifdef __cplusplus
extern "C" {
#endif

/*
* this is a test function, which calculate
* the multiply of a and b.
*/

extern int ThisIsTest(int a, int b);

#ifdef __cplusplus
}
#endif

#endif 

このヘッダファイルには関数が1つしか定義されていません.ThisIsTest()です.この関数は外部関数として定義され、他のプログラムファイルに含めることができます.ThisIsTest()関数の実装がtest_にあると仮定するextern_c.cファイル:
/* test_extern_c.c */

#include "test_extern_c.h"

int ThisIsTest(int a, int b)
{
  return (a + b);
} 

ThisIsTest()関数の実装は非常に簡単であり,2つのパラメータの加算結果を返すだけであることがわかる.次に、CPPからThisIsTest()関数を呼び出すとします.
/* main.cpp */

#include "test_extern_c.h"

#include <stdio.h>
#include <stdlib.h>

class FOO {

public:

  int bar(int a, int b)

    {

        printf("result=%i
", ThisIsTest(a, b)); } }; int main(int argc, char **argv) { int a = atoi(argv[1]); int b = atoi(argv[2]); FOO *foo = new FOO(); foo->bar(a, b); return(0); }

このCPPソースファイルでは、メンバー関数bar()でThisIsTest()関数を呼び出す簡単なクラスFOOが定義されている.次にgccコンパイルtestを採用すればextern_c.c、g++コンパイルmainを採用する.cppとtest_extern_c.o接続で何が起こるか:[cyc@cyc src]$ gcc -c test_extern_c.c [cyc@cyc src]$ g++ main.cpp test_extern_c.o [cyc@cyc src]$ ./a.out 4 5 result=9は、プログラムに異常がなく、予想通りに動作していることがわかります.ではtest_extern_c.hのextern「C」{}のいる数行の注釈を落とすとどうなるのでしょうか.コメント後のtest_extern_c.hファイルの内容は以下の通りである.
/* test_extern_c.h */

#ifndef __TEST_EXTERN_C_H__
#define __TEST_EXTERN_C_H__

//#ifdef   __cplusplus
//extern "C" {
//#endif

/*
/* this is a test function, which calculate
* the multiply of a and b.
*/

extern int ThisIsTest(int a, int b);

//#ifdef   __cplusplus
// }
//#endif

#endif 

また、他のファイルは変更されず、test_を同じ方法でコンパイルしています.extern_c.cとmain.cppファイル:[cyc@cyc src]$ gcc -c test_extern_c.c [cyc@cyc src]$ g++ main.cpp test_extern_c.o /tmp/cca4EtJJ.o(.gnu.linkonce.t._ZN3 FOO 3 barEii+0 x 10):In function`FOO::bar(int,int)'::undefined reference to`ThisIsTest(int,int)'collect 2:ld returned 1 exit status main.cppのときにエラーが発生し、コネクタldプロンプトで関数ThisIsTest()への参照が見つかりません.問題の原因をより明確に説明するために、ターゲットファイルをコンパイルし、ターゲットファイルにどのような記号があるかを見てみましょう.[cyc@cyc src]$ gcc -c test_extern_c.c   [cyc@cyc src]$ objdump -t test_extern_c.o test_extern_c.o:   file format elf32-i386 SYMBOL TABLE: 00000000 l   df *ABS* 00000000 test_extern_c.c 00000000 l   d .text 00000000 00000000 l   d .data 00000000 00000000 l   d .bss   00000000 00000000 l   d .comment     00000000 00000000 g   F .text 0000000b ThisIsTest [cyc@cyc src]$ g++ -c main.cpp       [cyc@cyc src]$ objdump -t main.o       main.o:   file format elf32-i386 MYMBOL TABLE: 00000000 l   df *ABS* 00000000 main.cpp 00000000 l   d .text 00000000 00000000 l   d .data 00000000 00000000 l   d .bss   00000000 00000000 l   d .rodata     00000000 00000000 l   d .gnu.linkonce.t._ZN3FOO3barEii 00000000 00000000 l   d .eh_frame     00000000 00000000 l   d .comment     00000000 00000000 g   F .text 00000081 main 00000000       *UND* 00000000 atoi 00000000       *UND* 00000000 _Znwj 00000000       *UND* 00000000 _ZdlPv 00000000 w   F .gnu.linkonce.t._ZN3FOO3barEii 00000027 _ZN3FOO3barEii 00000000       *UND* 00000000 _Z10ThisIsTestii 00000000       *UND* 00000000 printf 00000000       *UND* 00000000 __gxx_personality_v 0はgccでtest_をコンパイルしていることがわかりますextern_c.c以降、そのターゲットファイルtest_extern_c.oにはソースファイルで定義されているThisIsTest()関数であるThisIsTest記号があります.g++を用いてmainをコンパイル.cppの後、そのターゲットファイルmain.oの中に一つ_Z 10 ThisTestii記号、これがIsg++コンパイラによって「粉砕」された関数名です.最後の2文字iは、第1パラメータと第2パラメータの両方が整数であることを示す.なぜ接頭辞を付けるのかZ 10私はよく知らないが、ここでは私たちの議論に影響を与えないので、気にしない.明らかに、これが原因であり、その原理は本稿の冒頭で説明した.では、なぜextern"C"{}形式を採用すればこの問題は起こらないのか、test_extern_c.h extern"C"{}の形式でコンパイルされたターゲットファイルにはどのような記号がありますか:[cyc@cyc src]$ gcc -c test_extern_c.c [cyc@cyc src]$ objdump -t test_extern_c.o test_extern_c.o:   file format elf32-i386 SYMBOL TABLE: 00000000 l   df *ABS* 00000000 test_extern_c.c 00000000 l   d .text 00000000 00000000 l   d .data 00000000 00000000 l   d .bss   00000000 00000000 l   d .comment     00000000 00000000 g   F .text 0000000b ThisIsTest [cyc@cyc src]$ g++ -c main.cpp [cyc@cyc src]$ objdump -t main.o main.o:   file format elf32-i386 SYMBOL TABLE: 00000000 l   df *ABS* 00000000 main.cpp 00000000 l   d .text 00000000 00000000 l   d .data 00000000 00000000 l   d .bss   00000000 00000000 l   d .rodata     00000000 00000000 l   d .gnu.linkonce.t._ZN3FOO3barEii 00000000 00000000 l   d .eh_frame     00000000 00000000 l   d .comment     00000000 00000000 g   F .text 00000081 main 00000000       *UND* 00000000 atoi 00000000       *UND* 00000000 _Znwj 00000000       *UND* 00000000 _ZdlPv 00000000 w   F .gnu.linkonce.t._ZN3FOO3barEii 00000027 _ZN3FOO3barEii 00000000       *UND* 00000000 ThisIsTest 00000000       *UND* 00000000 printf 00000000       *UND* 00000000 __gxx_personality_v 0ここで前と何か違いがあることに気づいたかどうかは、2つのターゲットファイルに1つのシンボルThisIsTestがあり、このシンボルがThisIsTest()関数を参照していることがわかります.明らかに、両方のターゲットファイルに同じThisIsTest記号が存在するため、それらが参照しているのは実際に同じ関数であると考えられ、2つのターゲットファイルを接続し、プログラムコードセグメントにThisIsTest記号がある場所はThisIsTest()関数の実際のアドレスで置き換えられる.また、extern"C"{}のみに囲まれた関数は、mainに対してこのようなターゲットシンボル形式をとることも分かる.cppにおけるFOOクラスのメンバ関数は,両方のコンパイル方式後のシンボル名が「粉砕」されている.したがって,上記の解析を総合すると,extern"C"{}という形式の宣言を用いることで,CPPとC間のインタフェースの相互接続性が得られ,言語内部のメカニズムによりターゲットファイルに接続する際にエラーが発生しないと結論できる.説明しなければならないのは、私の試験結果に基づいた結論にすぎない.CPPについてはあまり使われておらず、あまり知られていないため、内部処理メカニズムがよく分からないので、この問題の詳細を深く理解する必要がある場合は関連資料を参照してください.
注:cppプログラムをg++でコンパイルすると、コンパイラはマクロ__を定義します.cplusplus,根拠_cplusplusがextern「C」が必要かどうかを定義するかどうかを決定します.
まとめ:上で述べたのはすべて理論で、いくつかのプログラムで、それでは実際に使用する時以下の集中状況があります:1.次に、後で使用するためのc言語のモジュールを書きます(以降のプロジェクトはcのものでもc++のものでも構いません).ソースファイルは事前にコンパイルされており、.soまたは.oにコンパイルされていても構いません.ヘッダファイルに関数を宣言する際には、条件コンパイルで含めて、以下のようにします.
#ifdef __cpluscplus
extern "C" {
#endif

//some code

#ifdef __cplusplus
}
#endif

すなわち,すべての関数宣言をsome codeの位置に置く.2.もしこのモジュールがすでに存在するならば、会社の先辈が书いたのかもしれなくて、どうせすでに存在して、モジュールの.hファイルにextern「C」のキーワードがなく、このモジュールが変更されたくない場合は、c++ファイルにモジュールのヘッダファイルが含まれている場合にextern「C」を付けると、以下のようになります.
extern "C" {
#include "test_extern_c.h"
}

 
3.上記の例では、モジュール内の1つの関数のみを使用し、モジュール全体をincludeする必要がない場合は、includeヘッダファイルを使用せずに、関数を個別に宣言することができます.
extern "C" {
int ThisIsTest(int, int);
}

モジュール内のこのThisIsTest関数を使用することができます.