自作 DLL の排除
まだご自身で DLL を書いていますか?
本稿はそんな方向けです!
はじめに
MQL5 言語機能がタスク遂行に十分ではない瞬間はなんどきでも訪れます。そのような場合、MQL5 プログラマーは別のツールを使用する必要があります。たとえば、データベースと連携する、コミュニケーションソケットを使用する、オペレーションシステムの関数を利用するなどが可能です。MQL5 プログラマーはまた各人が使用するMQL5 の機能を拡げるため多様な API を扱う必要があります。ただ、いくつかの理由によりプログラマーは MQL5から直接必要な関数にアクセスすることができません。それはプログラマーに以下の知識がないからです。
- 複雑なデータタイプ(たとえばストラクチャ)を API 関数に変換する方法
- API 関数から返されるポインタと連携する方法
よってプログラマーは別のプログラム言語を使わざるを得ず、そのため必要な機能性と連携するために仲介 DLL を作成することとなるのです。MQL5 に多様なデータタイプを表現しそれらを APIに転送する機能があったとしても、残念ながら MQL5 は受け付けられたポインタからデータを抽出することに関する問題を解決することはできません。
本稿ではすべての "i" にドットを打ち、複雑なデータタイプを転送し受け取り、返されたインデックスに連携するメカニズムを示していきます。
コンテンツ
- インデックスの取得
- メモリ領域のコピー
- MQL5 を使用したストラクチャの転送
- ソケットへのストラクチャ転送例
- メモリマッピングファイル例
- MySQL 例
1. メモリがすべて
ご存知かと思いますが、あらゆる変数(複雑なデータタイプ変数を含む)には特定のアドレスがあり、そこからその変数はメモリに格納されます。このアドレスは 4 バイトの整数値(int タイプの)で、この変数の最初のバイトアドレスに等しくなっています。
すべてがうまく定義されると、それはこのメモリ領域と連携することができます。C 言語ライブラリ(msvcrt.dll)には memcpy 関数が含まれます。その目的要素がないことに気づくことです。それは MQL5 と多様な API ライブラリをバインドし、プログラマーに大きな可能性を提供します。
先人の知識を参照しよう
Memcpy 関数はあるバッファから別のバッファに指定のバイト数をコピーし、レシーババッファにポインタを返します。
void *memcpy(void *dst, const void *src, int cnt); dst - pointer to the receiver buffer src - pointer to the source buffer cnt - number of bytes for copying
すなわち、src アドレスから始まるcnt バイトサイズのメモリ領域が dst アドレスから始まるメモリ領域にコピーされるのです。
src アドレスにあるデータタイプは多様です。char タイプの1バイト変数、double タイプの8バイトの数、配列、あらゆるストラクチャ、メモリボリュームなどです。そのことはアドレスとサイズがわかっていれば、データをある領域から別の領域に自由に送信できることを意味しています。
どのように動作するか
ダイアグラム 1 はいくつかのデータタイプの比較サイズを示しています。
Memcpy 関数はあるメモリ領域から別のメモリ領域にデータコピーをするのに必要です。
図2は4バイトデータのコピーを示しています。
MQL5 では以下のように表記されます。
例 1 memcpy の使用 #import "msvcrt.dll" int memcpy(int &dst, int &src, int cnt); #import //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { int dst, src=4, cnt=sizeof(int); int adr=memcpy(dst, src, cnt); Print("dst value="+string(dst)+" Address dst="+string(adr)); }
多様なデータタイプ(同じcntサイズ)が指定のメモリ領域 dst およびsrcとして使用可能であることに注意が必要です。たとえば src ポインタは double 変数(cnt=8 バイト)を参照することができ、 dst はサイズ char[8] または int[2]の配列を参照可能です。
そのときプログラマーがメモリに対してどのような考え方をしているかは問題ではありません。配列 char[8] か、または単にひとつの長い変数か、はたまたストラクチャ { int a1; int a2; }であるかは問いません。
メモリデータは多様なタイプのデータとみなすことができますたとえば5バイトの配列を {int i; char c;} ストラクチャに転送したり、その逆も可能です。この関係により API 関数と直接連動するチャンスを得るのです。
確定順で memcpy アプリケーションバージョンを検証します。
インデックスの取得
例1でmemcpy 関数が dst 変数アドレスを返すことを示しました。
このプロパティはあらゆる変数(その他の複雑タイプ配列を含み)を取得するのに使用可能です。そのためには、同じ変数をソースパラメータおよびレシーバパラメータとして指定するだけです。cnt では 0 を転送することが可能です。というのも実際のコピーは必要ないからです。
たとえば double 変数と short 配列のアドレスを取得します。
例2 変数に対するポインター取得 #import "msvcrt.dll" int memcpy(short &dst[], short &src[], int cnt); int memcpy(double &dst, double &src, int cnt); #import //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { short src[5]; //--- getting src array address (i.е., the address of the first element) int adr=memcpy(src, src, 0); double var; //--- getting var variable address adr=memcpy(var, var, 0); }
受け取られたアドレスは必要な API 関数に転送することができます。またはストラクチャパラメータや同じ memcpy 関数のパラメータとしても転送することができます。
配列のコピー
ご存じのように配列は専用メモリのかたまりです。専用メモリのサイズはエレメントタイプとその量に依存します。たとえば短い配列タイプでエレメント数が10であれば、 そのような配列はメモリに 20 バイトをコピーします(ショート サイズは 2 バイトです)。
ただこれら 20 バイトは 20 の char タイプデータまたは 5 のintタイプデータで構成される配列として示されています。いずれにせよ、それらはメモリ内で 20 バイトを取ります。
配列をコピーするには以下を行う必要があります。
- エレメントの必要量(結果の cnt より小さくない)を dst メモリに割り当てます。
- src からコピーされるバイト数を cnt に指定します。
例3 配列のコピー #import "msvcrt.dll" int memcpy(double &dst[], double &src[], int cnt); #import //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { double src[5]; //--- calculating the number of bytes!!! int cnt=sizeof(double)*ArraySize(src); double dst[]; ArrayResize(dst, 5); //--- the array has been copied from src to dst memcpy(dst, src, cnt); }
2. ストラクチャの API 関数への転送
書かれたストラクチャポインタを API に転送する必要があるとします。MQL5 言語はストラクチャ送信の限度を設定します。本稿の冒頭でメモリは別に存在することができると述べました。それは必要なストラクチャが MQL5 によってサポートされるデータタイプにコピーされることを意味しています。通常配列はストラクチャに適したタイプとなっています。そのためストラクチャから配列を取得し、配列を API 関数に転送するのです。
ストラクチャを使用してメモリをコピーするオプションは ドキュメンテーション項で述べます。ここではmemcpy 関数を使用することはできません。というのもストラクチャをパラメータとして転送することが不可能で、ストラクチャのコピーがここでは唯一の方法だからです。
図3は異なるタイプの5個の変数で構成されるストラクチャの表現とchar 配列として示されるそれと等価のものを表示しています。
例4 MQL5 によるストラクチャのコピー struct str1 { double d; // 8 bytes long l; // 8 bytes int i[3]; // 3*4=12 bytes }; struct str2 { uchar c[8+8+12]; // str1 structure size }; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { str1 src; src.d=-1; src.l=20; //--- filling the structure parameters ArrayInitialize(src.i, 0); str2 dst; //--- turning the structure into the byte array dst=src; }
そのようなシンプルな方法でストラクチャをバイトデータにコピーしました。
この例をより実用的なものとするためソケット作成関数を考察します。
int connect(SOCKET s, const struct sockaddr *name, int namelen);
この関数では二番目のパラメータが問題です。ストラクチャに対するポインタを受け入れてしまうからです。ですがそれにどう対処するかすでにわかっています。というわけで始めましょう。
1. MQL5で許容されるメソッドを用いてインポートのためのconnect 関数を書きます。
int connect(int s, uchar &name[], int namelen);
2. ドキュメンテーションで必要なストラクチャをよく見ます。
struct sockaddr_in { short sin_family; u_short sin_port; in_addr sin_addr; // additional 8 byte structure char sin_zero[8]; };
3. 同じようなサイズの配列を持つストラクチャを作成します。
struct ref_sockaddr_in { uchar c[2+2+8+8]; };
4. 必要な sockaddr_in ストラクチャを書いたら、それをバイト 配列に転送し、connect パラメータとしてサブミットします。
以下は前述の手順に従って作成したコードセクションです。
例5 サーバーへのクライアントソケット指示 #import "Ws2_32.dll" ushort htons(ushort hostshort); ulong inet_addr(char &cp[]); int connect(int s, char &name[], int namelen); #import //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- connecting the host after the socket initialization char ch[]; StringToCharArray("127.0.0.1", ch); //--- preparing the structure sockaddr_in addrin; addrin.sin_family=AF_INET; addrin.sin_addr=inet_addr(ch); addrin.sin_port=htons(1000); //--- copying the structure to the array ref_sockaddr_in ref=addrin; //--- connecting the host res=connect(asock, ref.c, sizeof(addrin)); //--- further work with the socket }
ご覧のようにご自身の DLL を作成する必要はまったくありません。ストラクチャは API に直接転送されます。
3. API 関数ポインタとの連携
ほとんどの場合、API 関数はデータにポインタを返します。それはストラクチャおよび配列です。MQL5 はデータ抽出には適していません。よってここでは memcpy 関数を使用します。
「メモリマッピングファイル(MMF)」からのメモリ配列との連携例
MMF と連携する場合、その関数が使用されます。それは専用メモリ配列にポインタを返します。
int MapViewOfFile(int hFile, int DesiredAccess, int OffsetHigh, int OffsetLow, int NumOfBytesToMap);
この配列からのデータ 読み出しは memcpy関数によって必要なバイト量をコピーするだけで実行されます。
配列にデータを書き込むのも memcpy を用いて行います。
例6 レコードと MMF メモリからのデータ読み出し #import "kernel32.dll" int OpenFileMappingW(int dwDesiredAccess, int bInheritHandle, string lpName); int MapViewOfFile(int hFileMappingObject, int dwDesiredAccess, int dwFileOffsetHigh, int dwFileOffsetLow, int dwNumberOfBytesToMap); int UnmapViewOfFile(int lpBaseAddress); int CloseHandle(int hObject); #import "msvcrt.dll" int memcpy(uchar &Destination[], int Source, int Length); int memcpy(int Destination, int &Source, int Length); int memcpy(int Destination, uchar &Source[], int Length); #import #define FILE_MAP_ALL_ACCESS 0x000F001F //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- opening the memory object int hmem=OpenFileMappingW(FILE_MAP_ALL_ACCESS, 0, "Local\\file"); //--- getting pointer to the memory int view=MapViewOfFile(hmem, FILE_MAP_ALL_ACCESS, 0, 0, 0); //--- reading the first 10 bytes from the memory uchar src[10]; memcpy(src, view, 10); int num=10; //--- recording the 4 byte int number to the memory beginning memcpy(view, num, 4); //--- unmapping the view UnmapViewOfFile(view); //--- closing the object CloseHandle(hmem); }
見てのとおりメモリ配列用のポインタと連携するのはそれほど難しいことではありません。もっと重要なことは、そのためにご自身でそれ以外の DLL を作成する必要がないということです。
MySQL に返されるストラクチャとの連携例
MySQL と連携する場合の緊急課題の一つはそこからのデータ取得です。mysql_fetch_row 関数はストリング配列を返します。各ストリングはフィールド配列です。よってこの関数はポインタにポインタを返します。われわれのタスクは返されたポインタからこれらデータをすべて抽出することです。
フィールドがバイナリデータを含む多様なデータタイプであることでタスクはやや込み入っています。そういったデータをストリング配列として示すことはできないということです。関数 mysql_num_rows、mysql_num_fields、 mysql_fetch_lengths はストリングおよびフィールドサイズに関する情報を取得するのに使用されます。
図4はメモリ内に結果を示すトラクチャを表示しています。
これらストリングの冒頭のアドレスは配列に集められます。そして配列冒頭のアドレス(例では= 94)は mysql_fetch_row 関数が返すものです。
次はデータベースリクエストから取得するデータに対するコード例です。
例7 MySQL からのデータ取得 #import "libmysql.dll" int mysql_real_query(int mysql, uchar &query[], int length); int mysql_store_result(int mysql); int mysql_field_count(int mysql); uint mysql_num_rows(int result); int mysql_num_fields(int result); int mysql_fetch_lengths(int result); int mysql_fetch_row(int result); #import //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- ... preliminarily initialized mysql data base //--- request for getting all the strings from the table string query="SELECT * FROM table"; uchar aquery[]; StringToCharArray(query, aquery); //--- sending the request err=mysql_real_query(mysql, aquery, StringLen(query)); int result=mysql_store_result(mysql); //--- in case it contains the strings if (result>0) { ulong num_rows=mysql_num_rows(result); int num_fields=mysql_num_fields(result); //--- getting the first string pointer int r=0, row_ptr=mysql_fetch_row(result); while(row_ptr>0) { //--- getting the pointer to the current string columns lengths int len_ptr=mysql_fetch_lengths(result); int lens[]; ArrayResize(lens, num_fields); //--- getting the sizes of the string fields memcpy(lens, len_ptr, num_fields*sizeof(int)); //--- getting the data fields int field_ptr[]; ArrayResize(field_ptr, num_fields); ArrayInitialize(field_ptr, 0); //--- getting the pointers to the fields memcpy(field_ptr, row_ptr, num_fields*sizeof(int)); for (int f=0; f<num_fields; f++) { ArrayResize(byte, lens[f]); ArrayInitialize(byte, 0); //--- copy the field to the byte array if (field_ptr[f]>0 && lens[f]>0) memcpy(byte, field_ptr[f], lens[f]); } r++; //--- getting the pointer to the pointer to the next string row_ptr=mysql_fetch_row(result); } } }
4. API 関数からのNULL 終端ストリング読み出し
API 関数の中にはストリングにポインタを返すものもありますが、このストリングの長さは表示しません。ここでは、ゼロで終わるストリングを論じます。このゼロはストリングの終了部を判断するのに役立ちます。これはストリングサイズが指定可能であるということです。
C (msvcrt.dll) 言語ライブラリはすでに適切なポインタから別のストリングに NULL終端ストリングのコンテンツをコピーする関数を備えています。ストリングサイズは関数によって決定されます。レシーバとしてバイト配列を使用することが望まれます。 API はUnicodeではなくマルチバイトストリングを返すことがよくあるからです。
strcpy -NULL で終わっているストリングをコピーします。
char *strcpy(char *dst, const char *src); dst - the pointer to the destination string src - the pointer to the Null-terminated source string
実際、それは memcpy 関数の特殊な例です。ストリングにゼロを見つけるとシステムはコピーを停止します。この関数はつねにそのようなポインタと連携するのに使用されます。
たとえばストリングにポインタを返す MySQL からの API には複数の関数があります。そして strcpy を使用してそれらからデータを取得するのは些末なタスクです。
例8 ポインターからのストリング取得 #import "libmysql.dll" int mysql_init(int mysql); int mysql_real_connect(int mysql, uchar &host[], uchar &user[], uchar &password[], uchar &DB[], uint port, uchar &socket[], int clientflag); int mysql_get_client_info(); int mysql_get_host_info(int mysql); int mysql_get_server_info(int mysql); int mysql_character_set_name(int mysql); int mysql_stat(int mysql); #import "msvcrt.dll" int strcpy(uchar &dst[], int src); #import //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { uchar byte[]; ArrayResize(byte, 300); int ptr; string st; //--- pointer to the string ptr=mysql_get_client_info(); if (ptr>0) strcpy(byte, ptr); Print("client_info="+CharArrayToString(byte)); //--- initializing the base int mysql=mysql_init(mysql); //--- transferring the strings to the byte arrays uchar ahost[]; StringToCharArray("localhost", ahost); uchar auser[]; StringToCharArray("root", auser); uchar apwd[]; StringToCharArray("", apwd); uchar adb[]; StringToCharArray("some_db", adb); uchar asocket[]; StringToCharArray("", asocket); //--- connecting the base int rez=mysql_real_connect(mysql, ahost, auser, apwd, adb, port, asocket, 0); //--- determining the connection and the base status ptr=mysql_get_host_info(mysql); if (ptr>0) strcpy(byte, ptr); Print("mysql_host_info="+CharArrayToString(byte)); ptr=mysql_get_server_info(mysql); if (ptr>0) strcpy(byte, ptr); Print("mysql_server_info="+CharArrayToString(byte)); ptr=mysql_character_set_name(mysql); if (ptr>0) strcpy(byte, ptr); Print("mysql_character_set_name="+CharArrayToString(byte)); ptr=mysql_stat(mysql); if (ptr>0) strcpy(byte, ptr); Print("mysql_stat="+CharArrayToString(byte)); }
おわりに
これまで述べてきたように、多様な API 関数と連携する場合、メモリとの連携(ストラクチャのコピー、memcpyにおけるポインタおよびそのデータ取得、strcpyストリング取得)に関する3つの基本メカニズムを使用することで実質的にすべてのタスクに応じるのです。
警告レシーババッファに十分なデータ量が割り当てられていない限り memcpy および strcpy と連携するのは安全ではありません。よって受け取られるデータに対する割当てサイズには注意が必要です。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/364
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索