繁簡切換您正在訪問的是FX168財經網,本網站所提供的內容及信息均遵守中華人民共和國香港特別行政區當地法律法規。

FX168财经网>人物频道>帖子

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

作者/你在说什么呢 2020-02-25 01:00 0 来源: FX168财经网人物频道

事实上,很少有开发人员确切知道如何编写简单的 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!");
//---
  
}
//+------------------------------------------------------------------+

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

分享到:
举报财经168客户端下载

全部回复

0/140

投稿 您想发表你的观点和看法?

更多人气分析师

  • 张亦巧

    人气2184文章4145粉丝45

    暂无个人简介信息

  • 梁孟梵

    人气2176文章3177粉丝39

    qq:2294906466 了解群指导添加微信mfmacd

  • 指导老师

    人气1864文章4423粉丝52

    暂无个人简介信息

  • 李冉晴

    人气2320文章3821粉丝34

    李冉晴,专业现贷实盘分析师。

  • 王启蒙现货黄金

    人气296文章3134粉丝8

    本人做分析师以来,并专注于贵金属投资市场,尤其是在现货黄金...

  • 张迎妤

    人气1896文章3305粉丝34

    个人专注于行情技术分析,消息面解读剖析,给予您第一时间方向...

  • 金泰铬J

    人气2328文章3925粉丝51

    投资问答解咨询金泰铬V/信tgtg67即可获取每日的实时资讯、行情...

  • 金算盘

    人气2696文章7761粉丝125

    高级分析师,混过名校,厮杀于股市和期货、证券市场多年,专注...

  • 金帝财神

    人气4760文章8329粉丝119

    本文由资深分析师金帝财神微信:934295330,指导黄金,白银,...

FX168财经

FX168财经学院

FX168财经

FX168北美