用非托管导出在 MQL5 中调用 C# 代码
一、托管与非托管代码的鸿沟
MetaTrader 的 MQL 可用 DLL 动态链接已有库,但只能直接调用“非托管”原生 DLL。C# 编译的程序集靠 CLR/CIL 运行,是“托管”的,MT5 无法用 LoadLibrary 直接调用,中间必须有一层导出包装。
二、解决方案:非托管导出
通过“非托管导出”(Unmanaged Export)或 C++/CLI 包装,把托管方法暴露为普通 C 函数,MT5 就能像调用普通 DLL 一样调用 C# 逻辑。
三、C# 侧 Calculator 类
先在 C# 里实现业务逻辑(如 Calculator.Add),再用 [DllExport] 或 C++/CLI 桥把它导出为 NativeExport_xxx_Add 这样的 C 接口。
// C# 计算逻辑
public class Calculator
{
public int Add(int a, int b) { return a + b; }
}
// 通过 [DllExport] 或 C++/CLI 包装导出
extern "C" __declspec(dllexport)
int NativeExport_CppCliWrapper_Calculator_Add(int a, int b)
{ return Calculator_Add(a, b); }
四、MQL5 侧调用
MQL5 用 #import 声明 DLL 函数并调用,就像调用任何原生库。返回值是普通 int/double,无托管痕迹。
#import "CppCliWrapper.dll" int NativeExport_CppCliWrapper_Calculator_Add(int a, int b); #import // 在 EA 中 int r = NativeExport_CppCliWrapper_Calculator_Add(3, 4);
五、关键注意
六、非托管导出机制
MQL5 通过「非托管导出」(unmanaged export)调用外部 DLL 里的函数:C# 侧用 [DllExport] 或类似手段把方法导出为 C 风格函数,MQL5 用 #import 声明后直接调用,绕过了托管运行时的封送开销。
这让你复用 C#/.NET 生态里现成的库(数学、机器学习、网络),把重计算或复杂逻辑放在 DLL 里,MQL5 只做调度,能力和性能都上一个台阶。
七、数据类型映射(封送)
跨边界传数据要显式映射:MQL5 的 double/int 对应 C 的 double/int,字符串要用 const string& 或 char* 约定长度,结构体要内存布局一致。错一处就会崩溃或静默传错值。
// C# 导出
[DllExport("Calc")]
public static double Calc(double x) { return Math.Tanh(x); }
// MQL5 导入
#import "MyLib.dll"
double Calc(double x);
#import
八、调用与错误处理
调用 DLL 函数要try-catch,DLL 崩了不能拖垮终端;且 DLL 要先放到终端能找到的目录(如 MQL5/Libraries)。另一个坑是 32/64 位匹配——终端和 DLL 位数必须一致,否则加载失败。
用 DLL 扩展能力很强,但部署时要连同 DLL 一起发、并确认位数与权限,比纯 MQL5 多一层运维。
九、位数匹配与部署陷阱
最常被坑的是 32/64 位:MT5 终端是 64 位,DLL 必须同样 64 位编译,否则加载失败且无明确报错。部署前在干净机器测一遍加载,别等用户报错才发现。
另一陷阱是 DLL 依赖的运行库(如 VC++ Redistributable)没装,导致加载即崩。把依赖写进部署说明,或静态链接,能省大量支持工单。
十、DLL 调用的可观测性
DLL 是黑盒,必须让它的行为可观测:每次调用记录入参、返回、耗时,异常时写日志。否则 DLL 内部静默出错,MQL5 侧只会看到「信号不对」却查不到根因,调试形如盲人摸象。
另一个好习惯是给 DLL 函数加版本号,MQL5 侧校验版本匹配才调用,避免「DLL 更新了但 EA 还按旧接口调」导致的诡异崩溃。版本契约让跨边界协作稳得住。
导出函数必须用 `__stdcall` 调用约定并声明 `extern "C"` 防止名字改编,否则 MQL5 按名调用会找不到入口直接报错。两侧数据类型要严格对齐——double 与 int 的字节长度、数组内存布局一致,否则栈损坏会让整个终端崩溃且难定位。先在最小样例验证调用通了,再接复杂逻辑。