摆脱自制的 DLL(基础篇)
◍ 放下你手里的自制 DLL
不少 MT5 老用户习惯把核心算法塞进自制 DLL,再让 EA 调用。这么做在 2014 年前后很普遍,但维护成本极高:每次 MT5 构建更新,DLL 都可能因运行时或位数问题失效。 官方在 2014 年 3 月 10 日放出过一篇示例,明确演示了不依赖外部 DLL 的纯 MQL5 实现路径。该帖当时获得 4211 次浏览、36 条讨论,说明社区对「去 DLL 化」需求很实。 对外汇、贵金属这类高杠杆品种,少一层外部依赖就少一层崩盘风险。你可以现在打开 MT5 的「文件→新建→EA」,试着把原先 DLL 里的逻辑用 MQL5 原生函数重写,大概率能跑通且更易迁移。
为什么 MQL5 要借道 DLL
MQL5 本身能定义结构、数组等复杂类型,也能把它们塞进 API 调用,但有一个硬缺口:拿到 API 返回的指针后,语言层面没法直接按结构布局还原出里面的字段。这就逼着不少交易者兼程序员转去写 C++ 中间 DLL,只为了补上“指针→可读数据”这一环。 具体卡在哪?一是怎么把 MQL5 里的 struct 原样丢给外部 API;二是 API 回传的指针(比如 void*)MQL5 收下了却读不出内容。官方文档没给现成解法,社区里多数人靠手写 CopyMemory 式封装绕路。 本文要做的,是把“传结构进去、拿指针出来再拆”这套机制拆开讲清,让你在 MT5 里少写一层黑盒。外汇与贵金属杠杆高,任何涉及外部调用的 EA 都可能因内存越界引发闪崩,验证前请先用模拟盘。
「把内存当字节流来搬」
MT5 里每个变量都占一块内存,地址本身是 4 字节 int,指向变量第一个字节。只要拿到地址和字节数,就能把那片内存原样搬到别处——这和变量本来叫 double 还是 char[8] 无关,编译器不关心你脑子里把它当什么类型。 msvcrt.dll 里的 memcpy 就是干这个的:void *memcpy(void *dst, const void *src, int cnt),dst 是接收区指针,src 是源区指针,cnt 是要复制的字节数。它返回接收缓冲区地址,这个返回值在 MQL5 里可以直接拿来当变量地址用。 一个很实用的 trick:把同一个变量同时填进 src 和 dst,cnt 传 0,等于没复制但拿回了它的地址。例 2 里 short[5] 数组和 double 变量都这么取地址,后面要传给 API 或别的内存操作就方便多了。 搬数组时先算字节数再 resize。例 3 里 double src[5] 占 sizeof(double)*ArraySize(src) = 8*5 = 40 字节,dst 先 ArrayResize 到 5 个元素,再 memcpy 过去。注意 cnt 填的是字节不是元素个数,填错就越界了。 外汇和贵金属 EA 里直接玩内存属于高风险操作,越界可能让终端崩掉,验证前先在脚本里跑通再上实盘。
class="type">void *memcpy(class="type">void *dst, const class="type">void *src, class="type">int cnt); dst - pointer to the receiver buffer src - pointer to the source buffer cnt - number of bytes for copying Example class="num">1. Using memcpy class="macro">#class="kw">import "msvcrt.dll" class="type">int memcpy(class="type">int &dst, class="type">int &src, class="type">int cnt); class="macro">#class="kw">import class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class="type">int dst, src=class="num">4, cnt=class="kw">sizeof(class="type">int); class="type">int adr=memcpy(dst, src, cnt); Print("dst value="+class="type">class="kw">string(dst)+" Address dst="+class="type">class="kw">string(adr)); } Example class="num">2. Getting pointers to the variable class="macro">#class="kw">import "msvcrt.dll" class="type">int memcpy(class="type">short &dst[], class="type">short &src[], class="type">int cnt); class="type">int memcpy(class="type">class="kw">double &dst, class="type">class="kw">double &src, class="type">int cnt); class="macro">#class="kw">import class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class="type">short src[class="num">5]; class=class="str">"cmt">//--- getting src array address(i.е., the address of the first element) class="type">int adr=memcpy(src, src, class="num">0); class="type">class="kw">double var; class=class="str">"cmt">//--- getting var variable address adr=memcpy(var, var, class="num">0); } Example class="num">3. Copying the arrays class="macro">#class="kw">import "msvcrt.dll" class="type">int memcpy(class="type">class="kw">double &dst[], class="type">class="kw">double &src[], class="type">int cnt); class="macro">#class="kw">import class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class="type">class="kw">double src[class="num">5]; class=class="str">"cmt">//--- calculating the number of bytes!!! class="type">int cnt=class="kw">sizeof(class="type">class="kw">double)*ArraySize(src); class="type">class="kw">double dst[]; ArrayResize(dst, class="num">5); class=class="str">"cmt">//--- the array has been copied from src to dst memcpy(dst, src, cnt); }
◍ 把结构塞进 API 而不写 DLL
MQL5 对结构作为参数传递有硬性限制,memcpy 这种常规手段用不了,因为它不接受结构入参。唯一干净的路子是把结构按内存布局原样搬进一个 uchar 数组,再把数组喂给 API 函数。 图里那个含 5 个异型变量的结构,等价的 char 数组表示长度就是各字段字节数之和,比如 double(8)+long(8)+int[3](12)=28 字节,照此开数组就能无损承接。 以 Winsock 的 connect 为例,第二个参数要吃 sockaddr 结构指针。我们在 MQL5 侧用同尺寸数组结构 sockaddr_in(2+2+8+8=20 字节) 建好并填字段,再整体赋给 ref_sockaddr_in,就能直接传参,全程不用自己编译一个 DLL。 下面代码块里 Example 4 演示了 str1 到 str2 的字节数组拷贝,Example 5 是客户端 socket 绑定本机 127.0.0.1:1000 的实际引用写法,开 MT5 建脚本粘进去就能跑通。外汇与贵金属API接入涉及网络与平台底层调用,存在连接失败与高风险,实盘前务必在模拟环境验证。
Example class="num">4. Copying the structures by means of MQL5 class="kw">struct str1 { class="type">class="kw">double d; class=class="str">"cmt">// class="num">8 bytes class="type">long l; class=class="str">"cmt">// class="num">8 bytes class="type">int i[class="num">3]; class=class="str">"cmt">// class="num">3*class="num">4=class="num">12 bytes }; class="kw">struct str2 { class="type">uchar c[class="num">8+class="num">8+class="num">12]; class=class="str">"cmt">// str1 structure size }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { str1 src; src.d=-class="num">1; src.l=class="num">20; class=class="str">"cmt">//--- filling the structure parameters ArrayInitialize(src.i, class="num">0); str2 dst; class=class="str">"cmt">//--- turning the structure into the byte array dst=src; } class="type">int connect(SOCKET s, const class="kw">struct sockaddr *name, class="type">int namelen); class="type">int connect(class="type">int s, class="type">uchar &name[], class="type">int namelen); class="kw">struct sockaddr_in { class="type">short sin_family; u_short sin_port; in_addr sin_addr; class=class="str">"cmt">// additional class="num">8 byte structure class="type">char sin_zero[class="num">8]; }; class="kw">struct ref_sockaddr_in { class="type">uchar c[class="num">2+class="num">2+class="num">8+class="num">8]; }; Example class="num">5. Referring of the client socket to the server class="macro">#class="kw">import "Ws2_32.dll" class="type">class="kw">ushort htons(class="type">class="kw">ushort hostshort); class="type">ulong inet_addr(class="type">char &cp[]); class="type">int connect(class="type">int s, class="type">char &name[], class="type">int namelen); class="macro">#class="kw">import class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- connecting the host after the socket initialization class="type">char ch[]; StringToCharArray("class="num">127.0.class="num">0.1", ch); class=class="str">"cmt">//--- preparing the structure sockaddr_in addrin; addrin.sin_family=AF_INET; addrin.sin_addr=inet_addr(ch); addrin.sin_port=htons(class="num">1000); class=class="str">"cmt">//--- copying the structure to the array ref_sockaddr_in ref=addrin; class=class="str">"cmt">//--- connecting the host res=connect(asock, ref.c, class="kw">sizeof(addrin)); class=class="str">"cmt">//--- further work with the socket }