English Русский Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
如何交换数据:10 分钟为 MQL5 创建 DLL

如何交换数据:10 分钟为 MQL5 创建 DLL

MetaTrader 5示例 | 20 十一月 2013, 06:18
5 041 3
MetaQuotes
Renat Fatkhullin

事实上,很少有开发人员确切知道如何编写简单的 DLL 库,他们也不清楚绑定不同系统的特性。

通过多个示例,我将展示在 10 分钟内创建简单 DLL 的整个过程,并讨论我们绑定实施的一些技术细节。我们将使用 Visual Studio 2005/2008;其 Express 版本可免费从 Microsoft 的网站上下载。

1. 使用 C++ 在 Visual Studio 2005/2008 中创建 DLL 项目

使用 "File -> New"(文件 -> 新建)菜单运行 Win32 应用程序向导,选择 "Visual C++" 作为项目类型,然后选择 "Win32 Console Application"(Win32 控制台应用程序)模板并定义项目名称(如 "MQL5DLLSamples")。在 "Location"(位置)中选择存储项目的根目录,而不是使用提供的默认选项,取消选择 "Create directory for solution"(创建解决方案的目录)复选框并单击 "OK"(确定):

图 1. Win32 应用程序向导,DLL 项目创建

在下一步骤中按 "Next"(下一步)转到设置页面:

图 2. Win32 应用程序向导,项目设置

在最后一页,选择 "DLL" 应用程序类型,保持其他字段为空,然后单击 "Finish"。如果您不希望删除自动添加的演示代码,不要勾选 "Export symbols"(导出交易品种)选项:

图 3. Win32 应用程序向导,应用程序设置

最后您将得到一个空项目:

图 4. 向导生成的空 DLL 项目

要简化测试,最好在 "Output Directory"(输出目录)选项中将 DLL 文件的输出直接指定至客户端的 "...\MQL5\Libraries" - 接下来,这会节省您大量的时间:

图 5. DLL 输出目录


2. 准备添加函数

stdafx.h 文件的末尾添加 "_DLLAPI" 宏,这样您便可以轻松方便地说明导出的函数:

//+------------------------------------------------------------------+
//|                                                 MQL5 DLL Samples |
//|                   Copyright 2001-2010, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net |
//+------------------------------------------------------------------+
#pragma once

#include "targetver.h"

#define WIN32_LEAN_AND_MEAN             // 从Windows头中排除极少使用的
#include <windows.h>

//---
#define _DLLAPI extern "C" __declspec(dllexport)
//+------------------------------------------------------------------+

在 MQL5 中调用的 DLL 导入函数应具有 stdcall 和 cdecl 调用约定。即使 stdcall 和 cdecl 从堆栈提取参数的方式不同,由于 DLL 调用的特殊包装程序,MQL5 运行时环境可安全地使用两种版本。

C++ 编译器默认使用 __cdecl 调用,但我建议对于导出函数明确指定 __stdcall 模式

正确编写的导出函数必须具有以下形式:

_DLLAPI int __stdcall fnCalculateSpeed(int &res1,double &res2)
  {
   return(0);
  
}

在 MQL5 程序中,函数必须如下定义和调用:

#import "MQL5DLLSamples.dll"
int  fnCalculateSpeed(int &res1,double &res2);
#import

//--- 调用
   speed=fnCalculateSpeed(res_int,res_double);

项目编译后,该 stdcall 将在导出表中显示为 _fnCalculateSpeed@8,在此编译器添加下划线以及通过堆栈传递的字节数。这一修饰使我们可以更好地控制 DLL 函数调用的安全性,因为调用方确切知道应在堆栈中放置的数据数量(但不是类型)。

如果参数块的最终大小在 DLL 函数导入说明中存在错误,函数不会被调用,且日志中将出现新消息:" Cannot find 'fnCrashTestParametersStdCall' in 'MQL5DLLSamples.dll"(在 MQL5DLLSamples.dll 中找不到 "fnCrashTestParametersStdCall")。在这种情况下,需要仔细检查函数原型和 DLL 源中的所有参数。

无修饰的简化说明的搜索用于导出表未包含完整函数名情形下的兼容性。如果函数以 __cdecl 格式定义,则类似 fnCalculateSpeed 的名称将被创建。
_DLLAPI int fnCalculateSpeed(int &res1,double &res2)
  {
   return(0);
  
}


3. 传递参数和交换数据的方法

接下来我们讨论传递参数的几个变体:

  1. 接收和传递简单变量
    简单变量的情形十分简单 - 它们可以通过值或使用 & 通过引用传递。
    _DLLAPI int __stdcall fnCalculateSpeed(int &res1,double &res2)
      {
       int    res_int=0;
       double res_double=0.0;
       int    start=GetTickCount();
    //--- 简单的算数运算
       for(int i=0;i<=10000000;i++)
         {
          res_int+=i*i;
          res_int++;
          res_double+=i*i;
          res_double++;
         
    }
    //--- 设置计算结果
       res1=res_int;
       res2=res_double;
    //--- 返回计算时间 
       return(GetTickCount()-start);
      
    }
        
    
    从 MQL5 调用:
    #import "MQL5DLLSamples.dll"
    int  fnCalculateSpeed(int &res1,double &res2);
    #import
    
    //--- 调用函数进行计算
       int    speed=0;
       int    res_int=0;
       double res_double=0.0;
    
       speed=fnCalculateSpeed(res_int,res_double);
       Print("Time ",speed," msec, int: ",res_int," double: ",res_double);
    
    
    输出如下所示:
    MQL5DLL Test (GBPUSD,M1) 19:56:42 Time  16  msec, int:  -752584127  double:  17247836076609
  2. 使用元素填充接收和传递数组

    与其他 MQL5 程序不同,数组传递通过直接引用数据缓冲区执行,无需访问有关维度和大小的专有信息。这正是数组维度和大小应分别传递的原因。

    _DLLAPI void __stdcall fnFillArray(int *arr,const int arr_size)
      {
    //--- 检查输入参数
       if(arr==NULL || arr_size<1) return;
    //--- 用值填充数组
       for(int i=0;i<arr_size;i++) arr[i]=i;
      
    }
        
    
    从 MQL5 调用:
    #import "MQL5DLLSamples.dll"
    void fnFillArray(int &arr[],int arr_size);
    #import
    
    //--- 填充数组
       int    arr[];
       string result="Array: "; 
       ArrayResize(arr,10);
       
       fnFillArray(arr,ArraySize(arr));
       for(int i=0;i<ArraySize(arr);i++) result=result+IntegerToString(arr[i])+" ";
       Print(result);
    
    
    输出如下所示:
    MQL5DLL Test (GBPUSD,M1) 20:31:12 Array: 0 1 2 3 4 5 6 7 8 9 
  3. 传递和修改字符串
    Unicode 字符串使用其缓冲区地址的直接引用传递,无需传递任何其他信息。
    _DLLAPI void fnReplaceString(wchar_t *text,wchar_t *from,wchar_t *to)
      {
       wchar_t *cp;
    //--- 参数检查
       if(text==NULL || from==NULL || to==NULL) return;
       if(wcslen(from)!=wcslen(to))             return;
    //--- 搜索字串
       if((cp=wcsstr(text,from))==NULL)         return;
    //--- 替换
       memcpy(cp,to,wcslen(to)*sizeof(wchar_t));
      
    }
    
    
    从 MQL5 调用:
    #import "MQL5DLLSamples.dll"
    void fnReplaceString(string text,string from,string to);
    #import
    
    //--- 改变字符串
       string text="A quick brown fox jumps over the lazy dog"; 
       
       fnReplaceString(text,"fox","cat");
       Print("Replace: ",text);
    
    结果如下所示:
    MQL5DLL Test (GBPUSD,M1) 19:56:42 Replace:  A quick brown fox jumps over the lazy dog
    结果表明线并未改变!这是初学者传送而不是引用对象(字符串是对象)的副本时常见的错误。系统自动创建字符串 "text" 的副本并在 DLL 中对其进行修改,然后自动删除副本而不会影响原字符串。

    为了改变这种状况,需要通过引用传递一个字符串。为此,我们可通过将 & 添加至 "text" 参数来简单地修改导入代码块:
    #import "MQL5DLLSamples.dll"
    void fnReplaceString(string &text,string from,string to);
    #import
    
    编译并运行后,我们将得到正确的结果:
    MQL5DLL Test (GBPUSD,M1) 19:58:31 Replace:  A quick brown cat jumps over the lazy dog

4. 捕获 DLL 函数中的异常

为防止终端崩溃,每个 DLL 调用自动受到“未处理异常包装”的保护。这一机制可保护调用免受大多数标准错误的影响(如内存访问错误、除数为零等)。

要查看该机制如何工作,让我们创建以下代码:

_DLLAPI void __stdcall fnCrashTest(int *arr)
  {
//--- 等待接收一个零值引用来调用异常处理
   *arr=0;
  
}

并从客户端对其进行调用:

#import "MQL5DLLSamples.dll"
void fnCrashTest(int arr);
#import

//--- 调用crash(执行环境将抓住异常并防止客户端崩溃)
   fnCrashTest(NULL);
   Print("You won't see this text!");
//---

结果,它将试图写入零地址并生成一个异常。客户端将捕获它,将其记录到日志并继续它的工作:

MQL5DLL Test (GBPUSD,M1) 20:31:12 Access violation write to 0x00000000

5. DLL 调用包装程序和调用速度损失

如上文所述,DLL 函数的每个调用都包装于特殊的包装程序中,以确保安全性。该结合掩盖基本代码、替换堆栈、支持 stdcall / cdecl 协议并在调用函数内监控异常。

这一工作量不会导致函数调用有明显的延迟。

6. 最终构建

我们将上述所有 DLL 函数示例收录在 "MQL5DLLSamples.cpp" 文件中,MQL5 示例收录在脚本 "MQL5DLL Test.mq5" 中。Visual Studio 2008 的最终项目和 MQL5 的脚本已附于本文。

//+------------------------------------------------------------------+
//|                                                 MQL5 DLL Samples |
//|                   Copyright 2001-2010, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net |
//+------------------------------------------------------------------+
#include "stdafx.h"

//+------------------------------------------------------------------+
//| 传输并接收简单的变量                                                |
//+------------------------------------------------------------------+
_DLLAPI int __stdcall fnCalculateSpeed(int &res1,double &res2)
  {
   int    res_int=0;
   double res_double=0.0;
   int    start=GetTickCount();
//--- 简单的算数运算
   for(int i=0;i<=10000000;i++)
     {
      res_int+=i*i;
      res_int++;
      res_double+=i*i;
      res_double++;
     
}
//--- 设置计算结果
   res1=res_int;
   res2=res_double;
//--- 返回计算时间
   return(GetTickCount()-start);
  
}
//+------------------------------------------------------------------+
//| 用值填充数组                                                       |
//+------------------------------------------------------------------+
_DLLAPI void __stdcall fnFillArray(int *arr,const int arr_size)
  {
//--- 检查输入参数
   if(arr==NULL || arr_size<1) return;
//--- 用值填充数组
   for(int i=0;i<arr_size;i++) arr[i]=i;
  
}
//+------------------------------------------------------------------+
//| 子串替换文本字符串                                                  |
//| 该子串作为字符串内容的直接引用被传输                                  |
//+------------------------------------------------------------------+
_DLLAPI void fnReplaceString(wchar_t *text,wchar_t *from,wchar_t *to)
  {
   wchar_t *cp;
//--- 检查参数
   if(text==NULL || from==NULL || to==NULL) return;
   if(wcslen(from)!=wcslen(to))             return;
//--- 搜索字串
   if((cp=wcsstr(text,from))==NULL)         return;
//--- 替换 
   memcpy(cp,to,wcslen(to)*sizeof(wchar_t));
  
}
//+------------------------------------------------------------------+
//| 调用崩溃检测函数                                                   |
//+------------------------------------------------------------------+
_DLLAPI void __stdcall fnCrashTest(int *arr)
  {
//--- 等待接收一个零值引用来调用异常处理
   *arr=0;
  
}
//+------------------------------------------------------------------+


//+------------------------------------------------------------------+
//|                                                 MQL5DLL Test.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2010, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
//---
#import "MQL5DLLSamples.dll"
int  fnCalculateSpeed(int &res1,double &res2);
void fnFillArray(int &arr[],int arr_size);
void fnReplaceString(string text,string from,string to);
void fnCrashTest(int arr);
#import

//+------------------------------------------------------------------+
//| 脚本程序开始函数                                                   |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- 调用函数进行计算
   int    speed=0;
   int    res_int=0;
   double res_double=0.0;

   speed=fnCalculateSpeed(res_int,res_double);
   Print("Time ",speed," msec, int: ",res_int," double: ",res_double);
//--- 调用填充数组的函数
   int    arr[];
   string result="Array: "; 
   ArrayResize(arr,10);
   
   fnFillArray(arr,ArraySize(arr));
   for(int i=0;i<ArraySize(arr);i++) result=result+IntegerToString(arr[i])+" ";
   Print(result);
//--- 修改字符串
   string text="A quick brown fox jumps over the lazy dog"; 
   
   fnReplaceString(text,"fox","cat");
   Print("Replace: ",text);
//--- 最后调用崩溃检测函数
//--- (执行环境将抓住异常并防止客户端崩溃)
   fnCrashTest(NULL);
   Print("You won't see this text!");
//---
  
}
//+------------------------------------------------------------------+

感谢您阅读拙作!我乐意回答你们的任何问题。

本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/18

附加的文件 |
mql5dllsamples.zip (5.27 KB)
mql5dll_test.mq5 (3.35 KB)
最近评论 | 前往讨论 (3)
Warrior of the LORD
Warrior of the LORD | 22 7月 2017 在 05:49

nice code ,very useful! 

THK! Brother!

Hu Zhao
Hu Zhao | 11 12月 2017 在 04:24

Nice article.

But one question : 


How can I call a MQ5 function from DLL ?  Besides, I created a thread via CreateThread and run a window in DLL , it runs ok but when unload MQ5, dll window can be closed but MT4 crush...

What should I do next ?


Thanks in advance.

jimmywen
jimmywen | 8 4月 2020 在 11:38
Why in the DLL can call functions in MQL5: GetTickCount ()

用 MQL5 绘制指标的喷发 用 MQL5 绘制指标的喷发
在本文中,我们将讨论指标的喷发 - 一种市场研究的新方法。喷发的计算基于不同指标的相交:在每次价格跳动后,将出现越来越多的带不同颜色和各种形状的点。它们形成了众多的集群,如星云、云团、轨迹、直线、弧线等。这些形状有助于发现影响市场价格变动的无形的跳跃力和驱动力。
价格直方图(市场概况)及其在 MQL5 中的实施 价格直方图(市场概况)及其在 MQL5 中的实施
“市场概况”由真正才华横溢的思想家 Peter Steidlmayer 所提出。他建议使用有关“水平”和“垂直”市场动态信息的替代表示法,从而给出一套完全不同的模型。他认为存在市场深层次的摆动或称之为平衡和失衡周期的基本模式。在本文中,我将会探讨价格直方图(市场概况的一种简化模型)以及它在 MQL5 中的实施。
使用 WCF 服务将报价从 MetaTrader 5 导出至 .NET 应用程序的方法 使用 WCF 服务将报价从 MetaTrader 5 导出至 .NET 应用程序的方法
想要从 MetaTrader 5 导出报价到您自己的应用程序?MQL5-DLL 组合可给出这样的解决方案!本文介绍将报价从 MetaTrader 5 导出至以 .NET 编写的应用程序的方法。对我而言,使用该平台实施报价的导出要更为有趣、合理和容易。遗憾的是版本 5 仍然不支持 .NET,因此和以往一样,我们将使用 .NET 支持的 win32 dll 作为中间层。
以经济方式计算指标的原则 以经济方式计算指标的原则
调用用户指标和技术指标在自动交易系统的程序代码中只占很少的空间。通常它仅仅只是行代码。但是经常出现正是这几行代码占用测试 EA 交易程序所需的大部分时间的情况。因此,与指标内的数据计算有关的所有一切都需要更加彻底地考虑,而不能随意决定。本文正是探讨这一点。