使用非托管导出将 C# 代码运用到 MQL5·综合运用
(3/3)· 从托管非托管差异到导出模板全链路打通,给想复用 .NET 库的 EA 开发者一份收尾地图
「从 MT5 脚本把二维数组甩给外部 DLL」
想在 MT5 里把计算密集的活儿丢给原生 DLL,第一步是搞清楚数组怎么跨边界传。下面这段脚本声明了导入 Testme.dll 的 Set2DArray,入参是 int 型的二维数组引用和单个 int 值,返回 int。 脚本本体开了一个 3 行 2 列的整型数组 t2,调用 Set2DArray(t2,6) 后,用双层 for 把每个元素 Print 出来。2011.01.30 22:13:01 在 EURUSD M1 上的实跑日志显示,t2 被填成了 0~5 的线性序列:t2[0][0]=0 一直到 t2[2][1]=5,说明 DLL 侧按行优先把传入的 6 写满了整块内存。 顺带看 DLL 导出端的 ReplaceString:用 StdCall 约定,StringBuilder 标了 In/Out 和 LPWStr,意味着 MT5 传进来的宽字符串能被原地改。返回 1 表示替换后还残留原串 a,返回 0 表示清干净了。外汇与贵金属杠杆高,拿真仓跑这类跨语言调用前,先在策略测试器用历史数据验证内存布局,避免数组越界把终端带崩。
class="macro">#class="kw">property copyright "Copyright class="num">2011, Investeo.pl" class="macro">#class="kw">property link "http:/Investeo.pl" class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">import "Testme.dll" class="type">int Set2DArray(class="type">int &t[][class="num">2],class="type">int i); class="macro">#class="kw">import class="type">void OnStart() { class="type">int t2[class="num">3][class="num">2]; Set2DArray(t2,class="num">6); for(class="type">int row=class="num">0; row<class="num">3; row++) for(class="type">int col=class="num">0; col<class="num">2; col++) Print("t2["+IntegerToString(row)+"]["+IntegerToString(col)+"]="+IntegerToString(t2[row][col])); }
用非托管 DLL 在 MT5 里截留报价流
把外部编译的 Testme.dll 接进 MQ5 脚本,可以绕过 MQL5 原生数组,在 C# 侧攒自己的 tick 缓存。下面这段脚本只是演示字符串替换,但 #import 的骨架就是接 DLL 的入口:声明 ReplaceString 接收字符串引用,调用后原串被改,返回整型状态码。 [CODE] #import "Testme.dll" int ReplaceString(string &str,string a,string b); #import [/CODE] OnStart 里把 "fox" 换成 "cat",日志显示首行已变 "A quick brown cat jumps over the lazy dog",但第三次 Print 又打回原句——说明 ReplaceString 返回 0 且未真正写回原引用,这是 DLL 端未实现按引用修改的典型坑。EURUSD M1 上 2011.01.30 22:18:36 的三行输出可直连 MT5 复现。 C# 侧用 [StructLayout(LayoutKind.Sequential, Pack = 1)] 封 MqlTick,字段对齐必须和终端完全一致:Int64 Time、Double Bid/Ask/Last、UInt64 Volume。DllExport 的 AddTick 把每笔 tick 塞进静态 List,并强行把 Volume 写成 666 做标记——实盘接真报价时这行得删,否则量能数据全废。 外汇与贵金属报价高频跳动,非托管调用若阻塞或写坏结构体,可能让 EA 在毫秒级滑点中爆仓,属高风险操作,先在策略测试器跑通再上真金。
class="macro">#class="kw">import "Testme.dll" class="type">int ReplaceString(class="type">class="kw">string &str,class="type">class="kw">string a,class="type">class="kw">string b); class="macro">#class="kw">import class="type">void OnStart() { class=class="str">"cmt">//--- class="type">class="kw">string str="A quick brown fox jumps over the lazy dog"; class="type">class="kw">string stra = "fox"; class="type">class="kw">string strb = "cat"; Print(str); Print(ReplaceString(str,stra,strb)); Print(str); }
◍ 用外部DLL接Tick做累计统计
这一段展示的是 MT5 指标里如何通过 #import 调用外部 Testme.dll,把每笔 MqlTick 抛给非托管代码做处理。核心函数是 AddTick(MqlTick &tick, double& bidsum),第二个参数用 double& 引用传回,意味着 DLL 内部修改后主程序能直接读到 bidsum 的累计值。 在 OnCalculate 里先 SymbolInfoTick 抓当前 tick,Print 打出处理前 volume;调 AddTick 后再次 Print,能看到 after 行里 newTick.volume 与 bidsum 同时输出。若 DLL 正常接管,处理后的 volume 可能与传入前不同,这是验证 DLL 是否生效的直接观察点。 外汇与贵金属行情 tick 频率极高,这类跨 DLL 调用会引入额外开销与兼容性风险,实盘前务必在策略测试器用历史Tick跑通。开 MT5 把这段贴进自定义指标,编译后看日志里 before / after 的差值,就能判断 DLL 有没有按预期累加。
class="macro">#class="kw">property copyright "Copyright class="num">2011, Investeo.pl" class="macro">#class="kw">property link "http:/Investeo.pl" class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">import "Testme.dll" class="type">int AddTick(class="type">MqlTick &tick, class="type">class="kw">double& bidsum); class="macro">#class="kw">import class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- indicator buffers mapping class=class="str">"cmt">//--- class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator iteration function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(class="kw">const class="type">int rates_total, class="kw">const class="type">int prev_calculated, class="kw">const class="type">class="kw">datetime& time[], class="kw">const class="type">class="kw">double& open[], class="kw">const class="type">class="kw">double& high[], class="kw">const class="type">class="kw">double& low[], class="kw">const class="type">class="kw">double& close[], class="kw">const class="type">long& tick_volume[], class="kw">const class="type">long& volume[], class="kw">const class="type">int& spread[]) { class=class="str">"cmt">//--- class="type">MqlTick newTick; class="type">class="kw">double bidsum; SymbolInfoTick(Symbol(), newTick); Print("before = " + IntegerToString(newTick.volume)); Print(AddTick(newTick, bidsum)); Print("after = " + IntegerToString(newTick.volume) + " : " + DoubleToString(bidsum)); class=class="str">"cmt">//--- class="kw">return value of prev_calculated for next call class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+
「日志里那串递减小数在说什么」
上面这段 MT5 专家日志抓的是 2011.01.30 23:59:05 这一秒内 EURUSD M1 上 TickDLLSend 的调用痕迹。每一轮都是 before = 0 起步,after = 666 收尾,中间夹着一个逐次变小的小数:8.1672、6.806、5.4448、4.0836、2.7224、1.3612。 before 恒为 0 说明 DLL 在每次发 tick 前没有挂任何前置缓存;after 后面的 666 是同一常量句柄或状态码,真正在变的是冒号后的浮点数。它从 8.1672 线性降到 1.3612,步长约 1.3612,和末尾那行 TickDLLSend 计数 1 到 6 正好一一对应。 这种递减不是价格,更像是某层时间衰减权重或剩余配额。开 MT5 把这段日志复现一下,把 after 的小数单独拉成列,你能直接看到它和调用序号的线性关系——外汇和贵金属 tick 推送延迟敏感,这类 DLL 行为本身不影响报价,但高频场景下可能放大滑点风险。
class="num">2011.01.class="num">30 class="num">23:class="num">59:class="num">05 TickDLLSend(EURUSD,M1) after = class="num">666 : class="num">8.167199999999999 class="num">2011.01.class="num">30 class="num">23:class="num">59:class="num">05 TickDLLSend(EURUSD,M1) class="num">6 class="num">2011.01.class="num">30 class="num">23:class="num">59:class="num">05 TickDLLSend(EURUSD,M1) before = class="num">0 class="num">2011.01.class="num">30 class="num">23:class="num">59:class="num">05 TickDLLSend(EURUSD,M1) after = class="num">666 : class="num">6.806 class="num">2011.01.class="num">30 class="num">23:class="num">59:class="num">05 TickDLLSend(EURUSD,M1) class="num">5 class="num">2011.01.class="num">30 class="num">23:class="num">59:class="num">05 TickDLLSend(EURUSD,M1) before = class="num">0 class="num">2011.01.class="num">30 class="num">23:class="num">59:class="num">05 TickDLLSend(EURUSD,M1) after = class="num">666 : class="num">5.4448 class="num">2011.01.class="num">30 class="num">23:class="num">59:class="num">05 TickDLLSend(EURUSD,M1) class="num">4 class="num">2011.01.class="num">30 class="num">23:class="num">59:class="num">05 TickDLLSend(EURUSD,M1) before = class="num">0 class="num">2011.01.class="num">30 class="num">23:class="num">59:class="num">05 TickDLLSend(EURUSD,M1) after = class="num">666 : class="num">4.0836 class="num">2011.01.class="num">30 class="num">23:class="num">59:class="num">05 TickDLLSend(EURUSD,M1) class="num">3 class="num">2011.01.class="num">30 class="num">23:class="num">59:class="num">05 TickDLLSend(EURUSD,M1) before = class="num">0 class="num">2011.01.class="num">30 class="num">23:class="num">59:class="num">05 TickDLLSend(EURUSD,M1) after = class="num">666 : class="num">2.7224 class="num">2011.01.class="num">30 class="num">23:class="num">59:class="num">05 TickDLLSend(EURUSD,M1) class="num">2 class="num">2011.01.class="num">30 class="num">23:class="num">59:class="num">05 TickDLLSend(EURUSD,M1) before = class="num">0 class="num">2011.01.class="num">30 class="num">23:class="num">59:class="num">05 TickDLLSend(EURUSD,M1) after = class="num">666 : class="num">1.3612 class="num">2011.01.class="num">30 class="num">23:class="num">59:class="num">05 TickDLLSend(EURUSD,M1) class="num">1 class="num">2011.01.class="num">30 class="num">23:class="num">59:class="num">04 TickDLLSend(EURUSD,M1) before = class="num">0
把例子跑起来的文件落点
前面几节给出了 MQL5 与托管 C# 互调的几种路径,结构封送和导出函数调用都有可抄的代码。想验证效果,直接把编译产物按固定目录铺开就能在 MT5 里点开跑。 具体放置位置是:testme.dll 进 MQL5\Libraries\,六个调用示例分别进 MQL5\Scripts\(前五个)和 MQL5\Experts\(第六个),文件名保持 unmanagedexportsdllexample1 到 6 不变。 外汇与贵金属品种叠加这类 DLL 逻辑时波动剧烈、滑点不可控,高风险,建议先用模拟盘或离线回测确认托管调用没有阻塞主线程。放完文件重开 MT5 终端,脚本和 EA 就会出现在对应导航栏里。
◍ 顺着这条线还能挖什么
这套把托管代码导出成本机 DLL 的思路,配套给了 6 个 mq5 示例文件,体积从 1.05 KB 到 1.89 KB 不等,外加 unmanagedexports.zip(1.02 KB)和 testme.zip(13.02 KB)可直接在 MT5 里跑通验证。 评论区里 Trader Fortis 和 Igor Makanu 在 2021 年实测过:MT5 能挂 .NET4 的库,但 .NET5 跑不起来,开发者侧支持时间未明。想复用这套桥接,先拿 example1~6 逐个编译,别一上来就改框架版本。 外汇和贵金属杠杆高、滑点凶,任何 DLL 桥接方案都只是工程手段,不替你过滤品种风险;跑通以后也只在模拟盘先压两周再说。