MT4 向け DLL の関数エクスポート

執筆者: ソフトゲート  日時: 2014-01-09  カテゴリ: MT4

昨年後半はブログをサボっていたくせに、最近になってやたらと更新しまくっておりましたが、ぼちぼち開発で忙しくなってきましたので、そろそろペースダウンの予感がします。

 

今回は、お題の通り、MT4 から呼び出す DLL の作り方についてご説明したいと思います。

といっても、Visual Studio の使い方をステップバイステップで丁寧に解説するわけではありません。

どちらかと言えば、関数エクスポートに関するトリビア的な内容が多いのですが、「見よう見まねで DLL を作るくらいはできるけど、__declspec 等のおまじないがどういう意味かきっちりは理解していない」 といった方々が理解を深める参考になれば幸いです。

 

まず、MT4 から DLL を呼び出す時の構文を確認しておきます。

MyLib.dll という DLL の中の MyFunc という関数を呼び出す場合、

#import "MyLib.dll"
string MyFunc(int, double);
#import

こんな感じで DLL 関数の名前や引数/返値の情報を MT4 に宣言しておく必要があります。

宣言さえ済んでしまえば

string retVal = MyFunc(123, 456.789);

のようにして、普通の MQL 関数と同様の構文で呼び出すことが出来ます。 

MT4 は宣言に書かれた「名前」に基づいて、呼び出すべき DLL 関数を探すので、当然のことながら DLL 関数は その「名前」エクスポートされていなければいけません。

 

また、MT4 から呼び出す DLL 関数は __stdcall という呼び出し規約であることが要求されます。

呼び出し規約の説明を始めると長くなるので、これについてはまた別の機会に取り上げたいと思います。

 

さて、ある関数をエクスポートするというのは、簡単に言ってしまえば、その関数の名前とアドレスを DLL のエクスポートテーブルに追加することに他なりません。

関数をエクスポートする方法としては、以下の三つがあります:

  1. 関数に __declspec(dllexport) 指定子を付ける
  2. DEF ファイルの EXPORTS セクションで指定する
  3. #pragma comment でリンカに EXPORT オプションを渡す

おそらく、MT4 向けの DLL を作ったことがある方は、一番目の __declspec(dllexport) という指定子に見覚えがあることでしょう。

 

実は、結論から言うと、MT4 向けの DLL を作る際に __declspec(dllexport) は必要ありません。

それはなぜか。実例を見てみましょう。

2014-01-09-Source01

このように記述して DLL をビルドすると、エクスポートテーブルは次のような感じになります。

2014-01-09-Exports01

赤く塗られた二つの関数が __declspec(dllexport) を指定してエクスポートしたものです。

C++ の mangling の影響で、えらく分かりにくい関数名でエクスポートされています。

 

えっ、こういうときは extern "C" を付ければいいって?

2014-01-09-Source02

では、こんな感じに extern "C" を付けてみると、エクスポートテーブルはどうなるかというと・・・

2014-01-09-Exports02

extern "C" を付けたにも関わらず、MyFunc1c は呼び出し規約による name decoration で修飾されています。

 

結局、__declspec(dllexport) を指定してエクスポートしても、所望の関数名ではエクスポートできないようです。

 

えっ、それなら DEF ファイルを書いてエクスポート名を指定すればいいって?

2014-01-09-Source03

では、DEF ファイルの EXPORTS セクションに MyFunc1d を付け足してみましょう:

2014-01-09-DEF-file

エクスポートテーブルを確認してみると・・・

2014-01-09-Exports03

ようやく、こちらの希望通りの名前でエクスポートされました!

 

 

 

・・・・・ちょっと待ってください。

先ほど、関数をエクスポートする方法は三つあると言いました:

  1. 関数に __declspec(dllexport) 指定子を付ける
  2. DEF ファイルの EXPORTS セクションで指定する
  3. #pragma comment でリンカに EXPORT オプションを渡す

結局、MyFunc1d のケースでは、__declspec(dllexport) だけでは足りずに、DEF ファイルの EXPORTS セクションにエクスポート名を書いたわけですから、1 および 2 を両方実施していることになりませんか。

 

じゃあ、そもそも、__declspec(dllexport) なんて不要だったのでしょうか?

実際に確認してみましょう。

2014-01-09-Source04

このとき、エクスポートテーブルは・・・

2014-01-09-Exports04

ちゃんと期待したとおりの名前でエクスポートされているようですね。

 

結論としては、MT4 用の DLL を作る場合、関数に __declspec(dllexport) 指定子を付ける必要はありません。

どうせエクスポート名を指定するための DEF ファイルを書く必要があるわけだし、DEF ファイルを書けば自然に関数はエクスポートされるのですから。

__declspec(dllexport) を付けておいても害はありませんよ。書いても意味が無いケースが多いというだけのことです。

ちなみに、MT4 に付属している DLL サンプルのソースを見ても、__declspec(dllexport) を指定した上で、DEF ファイルを利用しています。MetaQuotes のプログラマも、エクスポートの仕組みについて、ちゃんと理解していないんでしょうね。

 

おまけ 1

もうひとつ、DEF ファイルを書かなくても、#pragma ディレクティブで同じような効果を得ることもできます。

2014-01-09-Source05

こんな風に #pragma ディレクティブでリンカにオプションを渡してやると・・・

2014-01-09-Exports05

ちゃんと修飾名無しでエクスポートすることが出来ます。

まあ、個人的には、素直に DEF ファイルを書く方が楽だと思いますけどね。

 

おまけ 2

DEF ファイルの EXPORTS セクションに記述された名前と一致する関数があれば、(修飾名にかかわらず) リンカはそれをエクスポートしようとします。

そのため、たとえば引数の数を変えただけのオーバーロードを用意してやると・・・

2014-01-09-Source07

一致する関数が複数あって、どれをエクスポートすれば良いか分からなくなるので、エラーを吐きます。

2014-01-09-MatchFailure

 

おまけ 3

MT4 ベータ版では、string が Unicode になっているというので、こんなスクリプトを実行してみました:

2014-01-09-Script

MyFuncW は Unicode 文字列を返す DLL 関数です。

2014-01-09-Source06

現行のビルド 509 では MyFuncW 以外は正しく文字列を取得できます。

2014-01-09-MBCS

これをベータ版 569 で実行すると・・・

2014-01-09-Unicode

Unicode 文字列を返す MyFuncW はちゃんと動作しますが、ほかはめちゃくちゃな結果になっていますね。

 

 

 

執筆者について

ソフトゲート

ソフトゲート

MT4 支援ソフトウェアパッケージ Forex Studio を開発/販売しています。