C++で作成したDLLを他の言語で利用できるようにする



C++でDLLを作成していざVisual Basicから呼ぼうとすると関数が呼べない。

そんな問題を解決した経験があるならば、関数の装飾名呼び出し規約についてご存じでしょう。実は、1年ほど前に同等の記事を長々と書きましたが、重要な部分だけを再びおさらい、ちょっと補足。

意味もなくDEFファイルを作っている人は理解が足りないかもしれません。


そもそも、言語による関数呼び出しとは、呼び出し先と呼び出し元で呼び出し規約を一致させる必要がある。呼び出し規約とは一緒に決めた約束事と思ってもらって構いません。

そして、旧Visual BasicがWindows APIと同じstdcall規約しかサポートしない事から、出来るだけ多言語から使えるようにするためにはstdcall規約を使うのが望ましいです。

さて、Visual C++でstdcall規約を使うと不幸な内部装飾が生成されるため、DLLの関数呼び出しに失敗します。これから解決策を順序よく説明していこうと思いますが、十分なC++の知識がある場合、項番3までは読み飛ばしてください。

1 CとC++、どちらでコンパイルしているか?

この区別が出来ない人は、<cstdio>と<stdio.h>の区別が付かないと判断して良い。つまりDLL以前の問題。ちゃんと「コンパイラオプション」やCやC++の仕様を良く読む事。Visual C++なら「/TP」「/TC」あたり。CとC++では装飾名が違うため、DLL作成時のトラブルの一因になりうる。

2 extern "C"の効果を理解してますか?

C++で書かれた関数を、C言語から呼べるようにするってことは分かるはず。

つまり、コンパイラが内部で使っている名前を変化させ、C言語のように関数名だけで関数を区別するようになります。結果として、以下のソースコードはextern "C"をつけた時点でコンパイルが通らなくなる。

// extern.cpp
void func(char* a) {};
void func(int a) {}; // オーバーロード有効

// extern.cpp
extern "C" void func(char* a) {};
extern "C" void func(int a) {}; // エラー

C++では引数の型情報を持ったりするため、内部装飾名は複雑であり、コンパイラ依存。呼び出し元が同一バージョンのC++でない限り、複雑な装飾名はゴミ情報でしかない。よってDLLの公開関数はextern "C"をつけることが好まれる。とりあえず、extern "C"をつけたら、C++からC並の機能に落とすんだな、くらいの理解で良い。extern "C"した関数が例外を投げるなんてのは論外。

★3 コンパイラが生成する装飾名を知ってますか?

知らなくてもDLLは作れるが、トラブルに対応出来ないため覚えておこう。宣言のみを書いて、実体を書かなければ、リンカに教えてもらえる。

void func(int a); // 宣言のみ

int main() {
  func(5); // リンカエラー、「?func@@YGHH@Z」が見つからないとか・・・
  return 0;
}

赤字で書いた部分が装飾名。装飾する事自体をC++ name manglingと呼ぶ。

★4 DLLの関数としてエクスポートする方法を2種類知ってますか?

VC++を使ってるならば、大抵は前者でよい。

1 .コンパイラ拡張によるエクスポート
__declspec(dllexport)。装飾名がそのままDLLに書き込まれる。stdcall規約を使うと、_func@4みたいになる。ヘルプに書かれている通り、今後長くサポートしていくつもりのライブラリならば、面倒でもDEFファイルを作るべき。

そこで、外から見える名前を変えてあげるのだが、そのためだけにDEFファイルを使うようなら、本末転倒。リンカオプションをソースコードに書き込むのが良いと思う。
#pragma comment(linker, "/export:func=_func@4")
 
また、C++の関数装飾をC言語風にみせかけること、つまり

/export:func=?func@@YGHH@Z

とする事が、貴方の人生を不幸にするだけな事は理解しておきたい。また、悪意をもって人のmakefileに

/export:func1=func2 /export:func2=func1

と勝手に追記することは、貴方の人生がクラッシュされる恐れがあり、非推奨。

2. DEFファイルによるエクスポート
関数を公開する事が出来る便利なファイル。内部装飾名をリネームする事も可能で、上記にない機能もあり。

Leave a Reply

2 Comments on "C++で作成したDLLを他の言語で利用できるようにする"

Notify of
avatar
Sort by:   newest | oldest | most voted
ueda
Guest

VC++(VisualStudio2003)で作ったDLLをVB(VisualStudio2010)で使おうとして、まさにこの辺の問題で引っかかってました。
DllInportに毎回「CallingConvention:=CallingConvention.Cdecl」と書くのもいかがなものかと思って、DLLソースのほうに__stdcallをつけたら使えなくなってしまいました。
#pragma comment(linker,・・・で解決できました。
「extern "C"」さえつけておけば、名前装飾の呪いからは逃れられると信じてたのに・・。
有難う御座いました!

konuma
Guest

> #pragma comment(linker,・・・で解決できました。
VB2010を持っていないのですが、VC2003のstdcall呼び出し名をVB2010のstdcallが見つけられなかったという事でしょうか。
ならば、VB2010からCallingCoversion, Entrypointオプションを指定する、つまりVC側の再リンクをせずに呼び出す方が安全と考えます。
元の装飾名を弄って、かつソースを無くした場合、推測が出来なくなりますから・・・。

wpDiscuz