関数呼び出しの内部を理解する(3)

関数呼び出しを理解する最後のポイントはextern "C"です。

externは通常はリンケージ宣言子ですが、C++の仕様では、"C"などのリテラルがついた場合、他の言語の宣言を変換して利用できるようにする、と書かれています(つまり、リンケージ変換宣言子?)。

結果としてC++からCで作られた関数を呼ぶにはexternが必要になります。また、CからC++で作られた関数は呼べないのです。



実装例を1つみてみましょう。

int Func1(int a);

という関数をVisual C++でCとC++の二種類でコンパイルします。コンパイルした結果を前にも利用したdumpbinで確認してみました。

SECT3  notype () External  | _Func1
SECT3  notype () External  | ?Func1@@YAHH@Z (int __cdecl Func1(int))

上がC、下がC++です。同じFunc1でも表現が違うのが確認できます。たとえ、CでFunc1を呼び出してもリンカは「_Func1」を探し、「?Func1@@YAHH@Z」が見つかりません。

そこでC++の方のコードの先頭に以下のコードを追加します。

extern "C" int Func1(int a);

そして、同様の実験を行います。

SECT3  notype () External  | _Func1
SECT3  notype () External  | _Func1

C++でコンパイルした関数がCで利用できるように変換されている事が確認できます。

以上、ここまで理解できれば、関数呼び出しの(リンカ)エラーに悩むことはなくなります。当然、リンカとコンパイラの違いも理解できている事になります。

最後になりましたが、DLLから関数を呼び出せない人のもう1つの原因を紹介します。
前述のように、装飾子が不一致だと同じ名前の関数が存在しても、リンカからは探せないのです。紹介した方法で、名前が一致しているか、確認してください。

蛇足ですが、DLLはLoadLibrary関数によって装飾名を指定する必要性がある事から、通常は「extern "C"」付きで関数を公開する場合が殆どです。加えて、C++の関数名の装飾には厳密なルールがないため、C形式で関数を出力しないとコンパイラに依存してしまうDLLが作成されてしまいます。

つまり、DLLは「extern "C"」付きの「stdcall」や「cdecl」で作成してください。そうしないと特定のC++コンパイラの装飾名付きのDLLが作成できてしまい、非常に利用しにくくなります。

長くなりましたが、関数呼び出しについては以上です。

C++には装飾名に引数の情報も含まれるなど、言い残した点も多少ありますが、所詮コンパイラ依存の話になりますし、読者の課題としておきます。

Leave a Reply

最初のコメントを頂けますか?

更新通知を受け取る »
avatar
wpDiscuz