摆脱自制的 DLL(基础篇)
📘

摆脱自制的 DLL(基础篇)

第 1/2 篇

◍ 放下你手里的自制 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 里直接玩内存属于高风险操作,越界可能让终端崩掉,验证前先在脚本里跑通再上实盘。

MQL5 / C++
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接入涉及网络与平台底层调用,存在连接失败与高风险,实盘前务必在模拟环境验证。

MQL5 / C++
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
}

常见问题

可以。现代MQL5环境已支持直接用内置函数做内存字节流操作,把结构塞进API即可,不必自制DLL。
部分底层系统调用策略语言不直接暴露,借道DLL是旧方案;现在多数场景可用标准接口替代,降低被拦截风险。
可以。小布能扫描你的策略逻辑,标出冗余的DLL依赖并给出改用内置接口的替代思路,省去手动翻代码。
只要按字段偏移严格对齐,字节流方式比维护DLL更稳定,也避免被杀软误报,重点是对照结构体内存布局。
把依赖DLL的结构封装改为API内联实现,用内存读写替代外部库,实盘环境兼容性更好,外汇贵金属高风险需先模拟验证。