dllexport と__stdcallを同時に宣言すると関数名が変わる

VsiaulStudioのC++のプロジェクトで、DLLを作成し、関数を外部公開するため、 extern "C" と dllexport を指定したところ予期しない名前がついていて関数呼び出しが失敗してしまいました。

以下のように、外部公開される名前の先頭にアンダースコア、末尾にアットマークと引数のサイズが追加されています。

// 公開したい関数名
void hoge(int a);

// 実際に外部に公開されている名前
_hoge@4

実装したコード

以下のように、公開したい関数に対する宣言を行いました。

// 公開したい関数名
void hoge(int a);

// DLLをエクスポートする指定 dllexport を追加
__declspec(dllexport) void hoge(int a);

// マングル(name mangling{名前装飾})を回避するのに extern を追加
extern "C" __declspec(dllexport) void hoge(int a);

// 呼び出し規約をstdcallにするために __stdcall を追加
extern "C" __declspec(dllexport) void __stdcall hoge(int a);

解決方法

DEFファイルを使用しましょう。(存在を忘れていました。

declspec(dllexport) と stdcall を併用するとこの状況発生するみたいです。StackOverflowでの言及箇所

なので、VSのプロジェクトから以下手順でDEFを追加します。(こうするとリンカーのオプションに自動的に /DEF ${ファイル名} が追加されます。

追加 > 新しい項目 > モジュール定義ファイル(.def)

f:id:Takachan:20180708184415p:plain

追加したDEFファイルの書式は以下のような感じです。

LIBRARY ${DLLの名前}

EXPORTS
  ${関数名}
  ${関数名}
  ...

今回はこんな感じです。

LIBRARY MtLib

EXPORTS
   hoge

関数の公開の指定をDEFファイルで行ったのでdllexportを削除するようコードを修正します。

// dllexport の記述を削除
extern "C" void __stdcall hoge(int a);

これで、hoge という名前で関数が公開されました。

C#で呼び出す場合は、上記宣言でも大丈夫

余談ですが、C#のからC++のDLLを呼び出す場合、以下のように先頭にアンダースコアが付いていて、後ろにアットマーク+数字でもDLLImportで問題は発生しません。

// C++のDLLで公開している名前
_hoge@4

// C#側の宣言
[DllImport("myLib.dll", EntryPoint="hoge")] // 名前が違うけど問題なく呼び出せる
public static extern void Hoge(int a);