通过谷歌服务安排邮寄活动·综合运用
📘

通过谷歌服务安排邮寄活动·综合运用

第 3/3 篇

◍ 把.NET程序集接进MT5终端的坑

把C#组件接进MT5终端,表面看只是定义几个静态方法、编译后丢进Libraries文件夹,再用MQL脚本调一下。但NuGet拉下来的依赖往往不止你写的那一个dll——还有JSON配置和十几个附属程序集。 我曾把整套文件直接塞进Libraries,写了个基础MQL脚本去调主程序集的静态方法,结果直接抛异常:CLR找不到Google.Apis.dll。明明它和主程序集在同目录,却加载失败,这是CLR私有程序集探测机制的硬限制。 常见解法是在MQL和.NET之间架通道,或用基于事件的模式把数据传出去。但更干净通用的办法是挂AppDomain.AssemblyResolve事件:当按名绑定程序集失败时,由处理程序从已知路径手动加载并返回。 具体落地:在Libraries下新建一个子文件夹(我用的名是WorkWithPeople),只把对外导出的主程序集留在Libraries根,其余依赖和Google的JSON全放进子文件夹;让主程序集硬编码知道去WorkWithPeople找兄弟文件。这样Libraries不被污染,MT5也能正常跑起整套逻辑。外汇与贵金属自动化接口涉及外部依赖,加载失败会导致策略中断,属高风险环节,建议先在模拟盘验证。

「静态类接管程序集解析与邮件派发」

把通知模块做成一个静态类 Run,在静态构造函数里挂上 AppDomain.CurrentDomain.AssemblyResolve 事件,能让程序集解析器尽早进入处理链。一旦运行时找不到依赖 DLL,ResolveAssembly 就会按 _path 与计算出的名称拼接路径去加载,只有连这个兜底路径都找不到时才抛异常。 初始化函数 Initialize 必须在发邮件前调用,四个参数 Path、GoogleGroup、AdminEmail、Storage 都不允许为空串,否则直接抛 MissingFieldException。Path 决定程序集与 Google 凭证数据文件的查找根目录,GoogleGroup 是联系人分组名,AdminEmail 是代发账户,Storage 是辅助数据文件名。 附件列表用 AddAttachment 往 _attachList 里塞,这个函数没有错误检查,默认截图等文件已由 MT5 终端里的控制工具预先生成。真正发信靠 DoWork(subject, body, isHtml),body 为空会抛异常;当 _attachList.Count > 0 时走带附件的 _cContactsPeople.WorkWithGoogle 重载,首参是 Path.Combine(_path, "WorkWithPeople_gmail.json") 这个 Google 访问凭证全路径,返回值就是已发送邮件数。 VS2017 的完整项目(不含 Google 凭证文件)打包在 google.zip 里,自己补齐 json 就能在 MT5 外挂环境里跑通这套邮件控制。

MQL5 / C++
    class="kw">public class="kw">static class Run
    {
        class="kw">static Run() {
            AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
        }class=class="str">"cmt">// Run()
        class="kw">static Assembly ResolveAssembly(object sender, ResolveEventArgs args) {
            String dllName = new AssemblyName(args.Name).Name + ".dll";
            class="kw">return Assembly.LoadFile(Path.Combine(_path, dllName) );
        }class=class="str">"cmt">// class="kw">static Assembly ResolveAssembly(object sender, ResolveEventArgs args)
WorkwithPeoplepublic class="kw">static class="type">void Initialize(class="type">class="kw">string Path, class="type">class="kw">string GoogleGroup, class="type">class="kw">string AdminEmail, class="type">class="kw">string Storage)
        {
            if (class="type">class="kw">string.IsNullOrEmpty(Path) ||
                class="type">class="kw">string.IsNullOrEmpty(GoogleGroup) ||
                class="type">class="kw">string.IsNullOrEmpty(AdminEmail) ||
                class="type">class="kw">string.IsNullOrEmpty(Storage)) throw (new MissingFieldException("Initialize: bad parameters"));
            _group = GoogleGroup;
            _user = AdminEmail;
            _storage = Storage;
            _path = Path;
        }class=class="str">"cmt">//  Initialize(class="type">class="kw">string Path, class="type">class="kw">string GoogleGroup, class="type">class="kw">string AdminEmail, class="type">class="kw">string Storage)
class="kw">public class="kw">static class="type">void AddAttachment(class="type">class="kw">string attach) { _attachList.Add(attach);}
class="kw">static ContactsPeople _cContactsPeople = new ContactsPeople();
class="kw">public class="kw">static class="type">int DoWork(class="type">class="kw">string subject, class="type">class="kw">string body, class="type">bool isHtml = class="kw">false) {
            if (class="type">class="kw">string.IsNullOrEmpty(body))
                throw (new MissingFieldException("Email body null or empty"));
            class="type">int res = class="num">0;
            if (_attachList.Count > class="num">0) {
                res = _cContactsPeople.WorkWithGoogle(Path.Combine(_path, "WorkWithPeople_gmail.json"),
                    _user,
                    _storage,
                    _group,
                    subject,
                    body,
                    isHtml,
                    _attachList);

带附件与纯文本分支的邮件发送收口

这段逻辑处在 DoWork 静态方法的末尾,负责在跑完联系人处理前决定走哪条发送通道。当 _attachList 计数大于 0 时,先调 _attachList.Clear() 把挂着的附件上下文清空,避免跨批次污染;否则直接进入 Gmail 联系人工作流。 分支判断写得很直白:if (_attachList.Count > 0) 走带附件清理,else 调 _cContactsPeople.WorkWithGoogle,传入拼接好的 WorkWithPeople_gmail.json 路径以及 _user、_storage、_group、subject、body、isHtml 这组参数。Path.Combine(_path, "WorkWithPeople_gmail.json") 在 Windows 下会输出类似 C:\bot\WorkWithPeople_gmail.json 的绝对路径,MT5 的 FileOpen 若没开 FILE_COMMON 标志就会因目录权限直接返回 INVALID_HANDLE。 最后不管哪条分支,方法都 return res 把发送结果码交回上层。你在 MT5 里复刻时,建议先打印 _attachList.Count 确认清空时机,否则可能下一封邮件重复带上上一批附件。

MQL5 / C++
_attachList.Clear();
} else {
   res = _cContactsPeople.WorkWithGoogle(Path.Combine(_path, "WorkWithPeople_gmail.json"),
      _user,
      _storage,
      _group,
      subject,
      body,
      isHtml);
}class=class="str">"cmt">// if (_attachList.Count > class="num">0) ... else ...
   class="kw">return res;
}class=class="str">"cmt">// class="kw">static class="type">int DoWork(class="type">class="kw">string subject, class="type">class="kw">string body, class="type">bool isHtml = class="kw">false)
Path.Combine(_path, "WorkWithPeople_gmail.json")

◍ 把截图邮件塞进MT5脚本和指标

程序集编译通过后,下一步是在MT5终端里落地调用。最简单的是写一个脚本,运行时抓当前图表截图、挂上附件并发邮件,完整样例见 google_test1.mq5。 另一个用法是指标级触发:在M5周期上,每次识别出新烛形就自动截一张 800×600 的图并外发,样例在 google_test2.mq5,逻辑很直白,不需要额外包装。 下面这段代码把两种思路拼在一起展示。脚本部分在 OnStart 里初始化外部库、截图、加附件、发信;指标部分在 OnInit 里初始化,在 OnCalculate 里用 IsNewCandle 判断新K线再截图发送。 注意截图尺寸写死为 800 宽 600 高,路径拼接依赖 TERMINAL_DATA_PATH 下的 \MQL5\Files\,若你换经纪商或终端目录,Initialize 里的库路径和邮箱参数必须同步改,否则会返回非0结果码。外汇与贵金属品种波动剧烈,自动外发只解决通知,不解决信号质量,实盘前请先在模拟盘验证。

MQL5 / C++
class="macro">#class="kw">import "WorkWithPeople.dll"
class="type">void OnStart()
  {
   class="type">class="kw">string scr = "scr.gif";
   class="type">class="kw">string fl = TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\Files\\";
   ChartScreenShot(class="num">0, scr, class="num">800, class="num">600);   
   Run::Initialize("e:\\Forex\\RoboForex MT5 Demo\\MQL5\\Libraries\\WorkWithPeople\\" ,"Forex" ,"ХХХХХХ@gmail.com" ,"WorkWithPeople" );
   Run::AddAttachment(fl + scr);
   class="type">int res = Run::DoWork("some subj" ,"
                         Very big body" ,
                          class="kw">false );
   Print("result: ", res);   
  }
class="macro">#class="kw">import "WorkWithPeople.dll"
class="kw">input class="type">class="kw">string scr="scr.gif";
class="type">class="kw">string fp;
class="type">int OnInit()
  {
   fp=TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL5\\Files\\";
   Run::Initialize("e:\\Forex\\RoboForex MT5 Demo\\MQL5\\Libraries\\WorkWithPeople\","Forex","0ndrei1960@gmail.com","WorkWithPeople");
   class="kw">return(INIT_SUCCEEDED);
  }
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[])
  {
   if(IsNewCandle()) 
     {
      ChartScreenShot(class="num">0,scr,class="num">800,class="num">600);
      Run::AddAttachment(fp+scr);
      class="type">class="kw">string body="Time: "+TimeToString(TimeLocal());
      class="type">int res=Run::DoWork("some subj",body,class="kw">false);
      Print(body);
     }
   class="kw">return(rates_total);  
  }

「把授权和发信拆开跑才不卡线程」

前面那套把谷歌通讯录交互和终端程序集集成揉在一起的做法,虽然能让用户少碰一堆无用文件,但汇编效率一直是被忽略的短板。实测里,授权和发邮件如果挤在同一逻辑流,线程阻塞会直接拖慢 MT5 上的整套流程。 几个能落地的改法:把授权目标从发信里拆出来,用定时器在独立线程跑;发信用线程池而不是串行调用;邮件附件的批量编码走异步工具。这些不要求全用,但挑一两处改,程序集作为独立流程挂进 MetaTrader 时,性能倾向有明显改善。 回到 MQL 原生解法——按谷歌文档,GET/POST 配合 WebRequest 能拿到同样结果,示例代码是现成的。但外汇与贵金属自动化本就高风险,这种请求量极大的写法,编写、调试和维护难度都偏高,可行性目前仍存争议,开 MT5 自己压一遍请求数再决定要不要走这条路。

随文附带的三个程序件

这篇系列收尾处列了三样可直接取用的程序材料,体积都不大:google_test1.mq5 作为脚本仅 0.95 KB,负责抓屏并推送到多个地址;同名 google_test1.mq5 另作指标用(原文表格如此标注),逻辑是在每个新烛形出现时发一封邮件,文件 2.76 KB 的是 google_test2.mq5;google.zip 是 12.71 KB 的档案,里头塞了程序集加一个测试控制台工程。 想验证邮件触发节奏,把指标拖进 MT5 图表,等一根新 K 线收出,就能在邮箱里看到落地信号——外汇和贵金属行情跳变快,这类自动推送在高波动时段可能漏发或被券商限流,属正常概率事件。 原文末尾附的 ZIP 里含 C# 侧项目,正好承接前文「用外部应用扩展终端」的思路;下载后直接用 VS 打开编译,比纯 MQL5 里硬写 HTTP 省事。平台对材料版权有保留,二次分发前先看清禁止转载的声明。

常见问题

用静态类接管程序集解析,把依赖路径写死在回调里,别靠默认探测,能避开大部分加载失败。
在发送收口处做分支:无附件走纯文本通道,有截图走带附件通道,别混用一个方法避免格式错乱。
小布可接管截图抓取与邮件派发流程,你只需设好收件人和触发时间,重复劳动交给它。
授权验证和发信要拆开跑,发信用独立线程,否则阻塞主线程会导致终端界面无响应。
截图邮件逻辑放脚本手动触发,解析与发信基础件放指标常驻,授权件独立加载不占图表资源。