10 分钟掌握 MQL5 的 DLL(第二部分):使用 Visual Studio 2017 创建·综合运用
📘

10 分钟掌握 MQL5 的 DLL(第二部分):使用 Visual Studio 2017 创建·综合运用

第 3/3 篇

把复杂对象塞进 DLL 的两条路

在 MT5 里往外部 DLL 传对象是个硬限制:含动态数组、嵌套对象的结构体无法直接丢进导出函数。若你拿不到函数库源码,常规改数据结构的方法就走不通,只能绕道。 第一条路是 StructToCharArray()。示例里先把 Str 结构转成 uchar 数组再传指针,但这个函数只认 POD 结构(纯旧数据,无构造/虚表),遇到带动态对象的封装就失效。作者本人也没在真实代码上验证过它的边界行为,实用性存疑。 更稳的思路是在脚本侧把对象序列化成 JSON 字符串,DLL 内再反解成原生结构,反向同理。MQL5、C++、C# 都有现成解析器可用。代价是封装/解封费时间,但能处理极复杂嵌套,且封装器可后续优化复用,不必重写。 外汇与贵金属 EA 调用第三方库本就高风险,DLL 崩溃可能直接拖垮终端;动手前建议在脚本里先跑通最小结构体再逐步加压。

MQL5 / C++
class="kw">struct Str 
  {
    ...
  };
Str s;
class="type">uchar ch[];
StructToCharArray(s,ch);
SomeExportFunc(ch);

class="macro">#pragma pack(class="num">1)
typedef class="kw">struct D_a {
...
}Da, *PDa;
class="macro">#pragma pack()
class="type">void SomeExportFunc(class="type">char* pA)
  {
        PDa = (PDa)pA;
        ......
  }

◍ 用 DLL 绕开终端限制发邮件

想在 MT5 里让 EA 或脚本主动推送邮件,又不想被终端的邮箱设置绑死,可以自己编一个 DLL 接管发送逻辑。这个思路的核心是把 SMTP 收发做成独立函数库,调用方只传地址、账号和正文,不依赖 MT5 选项里的邮件配置,哪怕终端里邮件功能关了也不影响外发。 导出函数只有一个 SendSomeMail,参数顺序很直白:addr_from 和 addr_to 是收发件人;subject 与 text_body 是主题和正文;smtp_server、smtp_user、smtp_password 对应服务器地址、登录名和密码。注意发件人必须实名,因为函数库不会做匿名中继,你要在邮件服务器上有真实账号。 端口在函数库里写死了标准 25,一次调用只能发到一个收件人地址;要群发就用不同 addr_to 反复调。底层借用了微软论坛流传已久的 CDO 示例(C++ 部分不在此贴出,原工程包里有完整项目),稳定性经过社区反复验证。 下面的 MQL5 脚本演示怎么接这个 DLL:把 X 占位符换成你自己的邮箱和 SMTP 信息,跑 OnStart 就能看到 Print 回来的发送结果布尔值。外汇与贵金属交易信号推送涉及高风险,任何自动通知都只是辅助,不构成方向建议。

MQL5 / C++
SENDSOMEMAIL_API <span class="keyword">class="type">bool</span>&nbsp;&nbsp;SendSomeMail(LPCWSTR addr_from,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LPCWSTR addr_to,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LPCWSTR subject,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LPCWSTR text_body,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LPCWSTR smtp_server,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LPCWSTR smtp_user,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LPCWSTR smtp_password);
<span class="preprocessor">class="macro">#class="kw">import </span><span class="class="type">class="kw">string">"SendSomeMail.dll"</span>
<span class="keyword">class="type">bool</span>&nbsp;&nbsp;SendSomeMail(<span class="keyword">class="type">class="kw">string</span> addr_from,<span class="keyword">class="type">class="kw">string</span> addr_to,<span class="keyword">class="type">class="kw">string</span> subject,<span class="keyword">class="type">class="kw">string</span> text_body,<span class="keyword">class="type">class="kw">string</span> smtp_server,<span class="keyword">class="type">class="kw">string</span> smtp_user,<span class="keyword">class="type">class="kw">string</span> smtp_password);
<span class="preprocessor">class="macro">#class="kw">import
</span><span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| 脚本程序开始函数              &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;   &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">void</span> <span class="functions">OnStart</span>()
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="keyword">class="type">bool</span> b = SendSomeMail(<span class="class="type">class="kw">string">"XXX@XXX.XX"</span>, <span class="class="type">class="kw">string">"XXXXXX@XXXXX.XX"</span>, <span class="class="type">class="kw">string">"hello"</span>, <span class="class="type">class="kw">string">"hello from me to you"</span>,<span class="class="type">class="kw">string">"smtp.XXX.XX"</span>, <span class="class="type">class="kw">string">"XXXX@XXXX.XXX"</span>, <span class="class="type">class="kw">string">"XXXXXXXXX"</span>);
&nbsp;&nbsp; <span class="functions">Print</span>(<span class="class="type">class="kw">string">"Send mail: "</span>, b);
&nbsp; }

「DLL 封装保护的现实边界」

顺带提一个实战里容易踩坑的点:编译出来的 DLL 如果怕被反编译,常规做法是套封装器。我手头实测过两个——Themida 2.4.6.0 和 VMProtect Ultimate 3.0.9,把最早的 Project2.dll 各封了一遍,在 MT5 终端里用现成脚本调导出函数,都能正常跑起来。 但这不意味着换别的封装工具也稳。封装后的函数库能不能被终端加载、调用会不会崩,和封装器的实现强相关,目前没有统一保证。Project2_Pack.zip 里放了上述两种封装好的版本,想验证的可以直接丢进终端试。 外汇和贵金属 EA 开发本身高风险,封装只是代码层防护,不改变策略盈亏概率,别把保护强度误当成稳定性背书。

随文附带的可复用工程包

这一节把全文用到的工程文件列清了,方便直接在 MT5 里跑通 DLL 调用链路。核心就两个项目:Project2 是简单 DLL 的示范,Project2.mq5 作为脚本去调它;SendSomeMail 则是发邮件的 DLL 项目,配套 SendSomeMail.mq5 脚本调用。 附件体积差异很大,Project2.zip 仅 18.65 KB,而用 Themida 和 VMProtect 加壳后的 Project2_Pack.zip 达到 4645.25 KB,说明保护后的二进制膨胀了约 249 倍。做外汇或贵金属自动化时,这种加壳会增加被杀软误报的概率,实盘前先在隔离环境验一遍。 想验证就下载 Project2.mq5(3.42 KB)和 Project2.zip,按原文步骤在 VS 编译 DLL 后丢进 MT5 的 MQL5/Libraries,跑脚本看能否正常加载。加密壳那版建议只用于研究,别直接挂实盘账户。

常见问题

可用结构体打包或序列化字符串两种方式把复杂对象塞进 DLL;结构体适合固定字段,字符串适合灵活扩展,按需求选。
在 DLL 里调用系统或第三方邮件接口,由 EA 触发 DLL 函数发信,可绕开终端内置邮件的频率与配置限制。
小布可基于你贴出的调用逻辑和工程结构,提示哪些明文参数易被反编译读取,并给出混淆或校验的建议点。
现实边界是 DLL 只提高逆向成本,native 层仍可被调试还原,敏感逻辑别只靠封装,需配合服务端校验。
直接替换工程里的导出函数体与头文件声明,保留编译配置,改完用示例 EA 联调即可验证调用通路。