外汇EA编写教程:使用非托管导出将C代码应用于MQL5

简介

很长一段时间以来,我一直在寻找一个简单的解决方案,使我能够在MQL5中使用C DLL和托管模式。在阅读了很多文章之后,我准备为托管DLL实现一个C++包装器,并且我想出了一个超级解决方案,节省了大量的工作时间。

此解决方案是为非托管应用程序导出托管C代码的简单示例。在本文中,我将提供关于托管模式DLL的背景,解释为什么不能直接从MetaTrader访问它们,并介绍我发现的可以使用MetaTrader托管代码的解决方案。

我将提供一个使用非托管导出模板的简单示例,并继续我的所有发现。这应该为任何试图在MetaTrader 5中使用C DLL代码的人提供一个良好的背景。

1。比较托管代码与非托管代码

因为大多数读者可能不知道托管代码和非托管代码之间的区别,所以我将用几句话来解释它。基本上,MetaTrader使用MQL语言来实现事务规则、度量、EA事务和脚本。但是,它可以在运行时动态链接已经用其他语言实现的库。这些库也称为DLL或动态链接库。

实际上,库是包含编译源代码的二进制文件,可以由几个外部程序调用以执行特定的操作。例如,神经网络库可以导出神经网络训练和测试的函数,派生库可以导出不同派生的计算,矩阵库可以导出相关矩阵的操作。元交易者的DLL变得越来越流行,因为它们使隐藏指示器或实现EA事务的一部分成为可能。使用库的一个主要原因是重用现有代码而不必反复实现它。

在…出现之前。NET中,由Visual Basic、Delphi和VC++编写的所有DLL,无论是COM、Win32还是普通C++,都可以由操作系统直接执行。我们称此代码为非托管代码或本机代码。后来,。网络的出现,带来了一个非常不同的环境。

代码由控制(或管理)。NET公共语言运行库-clr(公共语言运行库)。需要使用CLR编译器从用几种不同语言编写的源代码生成元数据和公共中间语言nbsp;-cil(公共中间语言)。

CIL是一种独立于机器的高级语言。元数据根据“公共类型规范”CTS完全描述CIL描述的对象类型。因为clr知道所有关于类型的信息,所以它可以为我们提供一个托管的执行环境。管理可以被视为垃圾收集(自动内存管理、对象删除和提供安全性),以防止本机语言中的常见错误,如代码执行或内存覆盖,从而导致与管理员权限冲突。

必须指出的是,永远不会直接执行CIL代码——它总是通过JIT(及时)编译转换为本机代码,或者通过预编译的CIL转换为程序集。对于那些第一次阅读本文的人,托管模式代码的概念可能会令人困惑,因此我将在下面的clr中介绍一般过程:

nbsp;

图1。公共语言运行时-nbsp;

2。从MQL5访问托管代码的可能实现

在下一节中,我将向您展示如何从非托管代码访问托管代码。

我认为有必要指出所有这些方法,因为可能有人更愿意使用其他方法而不是我正在使用的方法。所使用的方法包括COM互操作、反向P/JooCK、C++ IJW、C++/CLI包装类和非托管导出。

COM互操作

组件对象模型(COM)是微软在20世纪90年代初提出的一种二进制接口标准,其核心思想是用不同的编程语言创建的对象可以被任何其他COM对象使用,而不必知道其内部实现。这需要一个严格定义的COM接口,该接口完全独立于实现。

事实上,COM已被替换。网络技术,和微软的推广使用。NET而不是COM。为了提供与旧代码的向后兼容性,。网络可以在两个方向上与COM合作,也就是说。NET可以调用COM方法,COM对象也可以使用。NET托管代码。

此函数称为COM interop。COM Interop API位于宿主系统中。运行时。InteropServices命名空间。

nbsp;

图 2. COM 互操作模型

图2。COM互操作性模型-nbsp;

以下COM互操作代码调用函数raw_factorial。

请注意coInitialize()、coCreateInstance()和coUninitialize()函数以及用于调用函数的接口:

#include "windows.h"
#include <stdio.h>
#import "CSDll.tlb" named_guids

int main(int argc, char* argv[])
{
    HRESULT hRes = S_OK;
    CoInitialize(NULL);
    CSDll::IMyManagedInterface *pManagedInterface = NULL;

    hRes = CoCreateInstance(CSDll::CLSID_Class1, NULL, CLSCTX_INPROC_SERVER, 
     CSDll::IID_IMyManagedInterface, reinterpret_cast<void**> (&pManagedInterface));

    if (S_OK == hRes)
    {
        long retVal =0;
        hRes = pManagedInterface->raw_factorial(4, &retVal);
        printf("The value returned by the dll is %ld/n",retVal);
        pManagedInterface->Release();
    }

    CoUninitialize();
    return 0;
}

为了进一步理解COM互操作,请阅读介绍COM互操作的详细文档和我在MSDN博客上找到的应用实例:如何从托管调用C++代码,反之亦然(互操作)。(如何从托管代码调用C++代码或从C++调用托管代码)。

反向P/Invoke

平台调用(英文缩写为p/invoke)启用。NET调用用任何非托管语言编写的任何函数,只要重新定义这些函数的签名。这是通过从执行本机函数指针来实现的。NET。这个应用程序在平台调用教程中详细介绍。

基本用途是使用dllimport属性标记导入的函数:

// PInvokeTest.cs
using System;
using System.Runtime.InteropServices;

class PlatformInvokeTest
{
    [DllImport("msvcrt.dll")]
    public static extern int puts(string c);
    [DllImport("msvcrt.dll")]
    internal static extern int _flushall();

    public static void Main() 
    {
        puts("Test");
        _flushall();
    }
}

反转操作可以描述为向非托管代码提供托管分配回调。

这称为反向P/Invoke,通过在托管环境中实现一个公共委托函数并导入在本地dll中实现的调用函数来实现:

#include <stdio.h>
#include <string.h>
typedef void (__stdcall *callback)(wchar_t * str);
extern "C" __declspec(dllexport) void __stdcall caller(wchar_t * input, int count, callback call)
{
      for(int i = 0; i < count; i++)
      {
            call(input);
      }
}

以下是托管代码的示例:

using System.Runtime.InteropServices;
public class foo
{
    public delegate void callback(string str);
    public static void callee(string str)
    {
        System.Console.WriteLine("Managed: " +str);
    }
    public static int Main()
    {
        caller("Hello World!", 10, new callback(foo.callee));
        return 0;
    }
    [DllImport("nat.dll",CallingConvention=CallingConvention.StdCall)]
    public static extern void caller(string str, int count, callback call);
}

这个解决方案的关键点是要求受信者开始交互。

有关详细信息,请阅读具有反向pinvoke(非托管到托管代码回调)、[平台调用陷阱反转(非托管到托管代码回调)]和反向pinvoke和stdcall cdecl(平台调用反转平台调用和stdcall cdecl)的gotchas。

2.3。C++ IJW

C++互操作,称为WorkWork(ijw),是C++托管扩展提供的C++的一个特定特性:

#using <mscorlib.dll>
using namespace System;
using namespace System::Runtime::InteropServices;

#include <stdio.h>

int main()
{
   String * pStr = S"Hello World!";
   char* pChars = (char*)Marshal::StringToHGlobalAnsi(pStr).ToPointer(); 
   
   puts(pChars);
   
   Marshal::FreeHGlobal(pChars);
} 

对于那些希望在非托管应用程序中使用托管C++的人来说,这个解决方案可能是有用的。要获得完整的引用,请阅读“C++托管扩展的互操作性”(C++托管扩展中的互操作性)和“在托管C++中使用IJW”(在托管C++中使用IJW)。

C++/CLI包装类

C++/CLI包装器类实现通过嵌入在C++/CLI模式中的另一个类从嵌入式或包管理类中获得它的名称。编写包装DLL的第一步是编写C++类,这些类包括了原始托管类的方法。

包装类必须包含的句柄。NET对象使用GCROOT&lt;gt;模板,并且必须分配来自原始类的所有调用。包装类被编译成IL(中间语言)格式,因此它是一个托管类。

下一步是编写包含TracMMA非托管指令、BACK IL类的本地C++类,并使用DeCSPEC(DLLISTUM)和NBSP指令来分配所有调用。这些步骤使任何非托管应用程序都使用本机C++ DLL。

请参见实现示例。第一步是实现C代码。

示例计算器类包含两个常用方法:

public class Calculator
{
    public int Add(int first, int second)
    {
        return first + second;
    }
    public string FormatAsString(float i)
    {
        return i.ToString();
    }
}

下一步是编写一个托管包装器,它从计算器类中分配所有方法:

#pragma once
#pragma managed

#include <vcclr.h>

class ILBridge_CppCliWrapper_Calculator {
private:
    //Aggregating the managed class
    gcroot<CppCliWrapper::Calculator^> __Impl;
public:
    ILBridge_CppCliWrapper_Calculator() {
        __Impl = gcnew CppCliWrapper::Calculator;
    }
    int Add(int first, int second) {
        System::Int32 __Param_first = first;
        System::Int32 __Param_second = second;
        System::Int32 __ReturnVal = __Impl->Add(__Param_first, __Param_second);
        return __ReturnVal;
    }
    wchar_t* FormatAsString(float i) {
        System::Single __Param_i = i;
        System::String __ReturnVal = __Impl->FormatAsString(__Param_i);
        wchar_t* __MarshaledReturnVal = marshal_to<wchar_t*>(__ReturnVal);
        return __MarshaledReturnVal;
    }
};

请注意,您使用gcnew指令来存储对原始计算器类的引用,并将其存储为gcRoot&lt;gt;模板。所有包装的方法都可以与原始方法具有相同的名称,参数和返回值的名称分别以_param和_returnval作为前缀。

现在,必须实现非托管C++来封装C++/CLI并导出本机C++ DLL方法。

头文件应该包含带有_declspec(dllexport)指令的类定义,并将指针存储在包装类中。

#pragma once
#pragma unmanaged

#ifdef THISDLL_EXPORTS
#define THISDLL_API __declspec(dllexport)
#else
#define THISDLL_API __declspec(dllimport)
#endif

//Forward declaration for the bridge
class ILBridge_CppCliWrapper_Calculator;

class THISDLL_API NativeExport_CppCliWrapper_Calculator {
private:
    //Aggregating the bridge
    ILBridge_CppCliWrapper_Calculator* __Impl;
public:
    NativeExport_CppCliWrapper_Calculator();
    ~NativeExport_CppCliWrapper_Calculator();
    int Add(int first, int second);
    wchar_t* FormatAsString(float i);
};

具体实施如下:

#pragma managed
#include "ILBridge_CppCliWrapper_Calculator.h"
#pragma unmanaged
#include "NativeExport_CppCliWrapper_Calculator.h"

NativeExport_CppCliWrapper_Calculator::NativeExport_CppCliWrapper_Calculator() {
    __Impl = new ILBridge_CppCliWrapper_Calculator;
}
NativeExport_CppCliWrapper_Calculator::~NativeExport_CppCliWrapper_Calculator()
{
    delete __Impl;
}
int NativeExport_CppCliWrapper_Calculator::Add(int first, int second) {
    int __ReturnVal = __Impl->Add(first, second);
    return __ReturnVal;
}
wchar_t* NativeExport_CppCliWrapper_Calculator::FormatAsString(float i) {
    wchar_t* __ReturnVal = __Impl->FormatAsString(i);
    return __ReturnVal;
}

本文描述了创建这个包装类的分步指南。NET到C++桥(桥)。NET到C++。

混合中提供了创建包装器的完整参考。网络和本机代码(.NET和本机代码集成);有关如何在本机类型中声明句柄的一般信息,请阅读如何:在本机类型中声明句柄(如何在本机类型中声明句柄)。

2.5。非托管导出

专家。NET 2.0IL汇编程序详细介绍了这项技术,我将它推荐给所有想了解详细信息的人。NET编译器。其主要思想是使用ildasm将编译后的模块解压成IL代码,改变模块的vtable和vtable fixup表,然后使用ilasm重新编译dll,从而使托管方法成为托管dll的非托管导出。

此任务可能看起来令人望而生畏,但此操作将创建一个可以从任何非托管应用程序使用的DLL。必须记住,它仍然是一个托管组件,因此。必须安装NET Framework。在将托管代码导出为非托管代码(将托管代码导出为非托管代码)中提供了分步教程。

在用ildasm解压了dll之后,我们得到了IL语言的源代码。请参见下面粘贴了非托管导出的IL代码的简单示例:

assembly extern mscorlib {}
..assembly UnmExports {}
..module UnmExports.dll
..corflags 0x00000002
..vtfixup [1] int32 fromunmanaged at VT_01
..data VT_01 = int32(0)
..method public static void foo()
{
..vtentry 1:1
..export [1] as foo
ldstr "Hello from managed world"
call void [mscorlib]System.Console::WriteLine(string)
ret
}

负责实现导出的非托管IL源代码的行为:

..vtfixup [1] int32 fromunmanaged at VT_01
..data VT_01 = int32(0)

以及

..vtentry 1:1
..export [1] as foo

第一部分负责在vtable fixup表中添加函数项,并将vt_01虚拟地址设置为函数。第二部分指定此函数将使用哪个vEntry,以及要导出的函数的导出别名。nbsp;

此解决方案的优点是,在DLL实现期间,我们不需要实现普通托管C DLL以外的任何其他代码,并且如本书中所述,此方法完全向非托管客户机打开具有所有安全性和类库的托管世界。

缺点是不是每个人都适合理解。NET程序集语言。我相信我会编写一个C++包装器,直到找到Robert Giesecke编写的非托管导出模板:https://SITS.GoGoLe.COM/SITE/RoBurtGieSeKe/,它可以在不知道IL代码的情况下使用非托管输出。

三。非托管导出C模板

r.giesecke编写的非托管导出c项目的模板使用msbuild任务,并在创建后自动添加相应的vt fixup,因此根本不需要更改IL代码。只需将模板包下载为zip文件,并将其复制到Visual Studio的“项目模板”文件夹中。

编译项目后,生成的dll文件可以导入到metatrader,而不会有任何缺陷;我将在下面的部分中提供示例。

4。例子

指出如何使用正确的编组方法在MetaTrader和C_之间传递变量、数组和结构是一项具有挑战性的任务,我认为这里提供的信息可以节省您很多时间。所有示例都是用Windows Vista操作系统编译的。已安装Net 4.0和Visual C_uuExpress 2010。我还将一个示例dll附加到文本中,其中包含从C dll调用函数的MQL5代码。

4.1。例1。将两个整数、一个双精度变量和一个浮点变量添加到dll函数,并将结果返回给metatrader。

using System;
using System.Text;
using RGiesecke.DllExport;
using System.Runtime.InteropServices;

namespace Testme
{
    class Test
    {

        [DllExport("Add", CallingConvention = CallingConvention.StdCall)]
        public static int Add(int left, int right)
        {
            return left + right;
        }

        [DllExport("Sub", CallingConvention = CallingConvention.StdCall)]
        public static int Sub(int left, int right)
        {
            return left - right;
        }

        [DllExport("AddDouble", CallingConvention = CallingConvention.StdCall)]
        public static double AddDouble(double left, double right)
        {
            return left + right;
        }

        [DllExport("AddFloat", CallingConvention = CallingConvention.StdCall)]
        public static float AddFloat(float left, float right)
        {
            return left + right;
        }

    }
}

您可能会注意到,每个导出的函数前面都有dllexport指令。第一个参数描述派生函数的别名,第二个参数描述调用约定。对于MetaTrader,我们必须使用CallingConvention。STDCALL。

导入和使用来自DLL的函数的MQL5代码是简单的,与在本机C++中编写的任何其他DLL都没有区别。首先,您必须在导入块中声明导入的函数,并指示将来可以从MQL5代码使用dll中的哪些函数:

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample1.mq5 |
//|                                      Copyright 2010, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"
   int Add(int left,int right);
   int Sub(int left,int right);
   float AddFloat(float left,float right);
   double AddDouble(double left,double right);
#import

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   for(int i=0; i<3; i++)
     {
      Print(Add(i,666));
      Print(Sub(666,i));
      Print(AddDouble(666.5,i));
      Print(AddFloat(666.5,-i));
     }
  }
//+------------------------------------------------------------------+

结果:

2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 664.50000
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 668.5
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 664
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 668
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 665.50000
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 667.5
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 665
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 667
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 666.50000
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 666.5
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 666
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 666

4.2。例2。一维数组访问

        [DllExport("Get1DInt", CallingConvention = CallingConvention.StdCall)]
        public static int Get1DInt([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]  int[] tab, int i, int idx)
        {
            return tab[idx];
        }

        [DllExport("Get1DFloat", CallingConvention = CallingConvention.StdCall)]
        public static float Get1DFloat([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]  float[] tab, int i, int idx)
        {
            return tab[idx];
        }

        [DllExport("Get1DDouble", CallingConvention = CallingConvention.StdCall)]
        public static double Get1DDouble([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]  double[] tab, int i, int idx)
        {
            return tab[idx];
        }

为了封送一维数组,Marshalas指令必须传递UnmanagedType。lparray作为第一个参数,sizeparamindex作为第二个参数。sizeParamIndex指示哪个参数(从零开始计数)包含数组的大小。

在上面的示例中,i是数组的大小,idx是要返回的元素的索引。

以下是使用数组访问的MQL5示例代码:

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample2.mq5 |
//|                                      Copyright 2010, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"  
   int Get1DInt(int &t[],int i,int idx);
   float Get1DFloat(float &t[],int i,int idx);
   double Get1DDouble(double &t[],int i,int idx);
#import
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   int tab[3];
   tab[0] = 11;
   tab[1] = 22;
   tab[2] = 33;

   float tfloat[3]={0.5,1.0,1.5};
   double tdouble[3]={0.5,1.0,1.5};

   for(int i=0; i<3; i++)
     {
      Print(tab[i]);
      Print(Get1DInt(tab,3,i));
      Print(Get1DFloat(tfloat,3,i));
      Print(Get1DDouble(tdouble,3,i));

     }
  }
//+------------------------------------------------------------------+

结果

2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 1.5
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 1.50000
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 33
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 33
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 1
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 1.00000
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 22
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 22
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 0.5
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 0.50000
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 11
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 11

nbsp;

4.3。例3。填写一维数组并将其返回给MetaTrader

        [DllExport("SetFiboArray", CallingConvention = CallingConvention.StdCall)]
        public static int SetFiboArray([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
        int[] tab, int len, [In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] int[] res)
        {
            res[0] = 0;
            res[1] = 1;
            
            if (len < 3) return -1;
            for (int i=2; i<len; i++)
                res[i] = res[i-1] + res[i-2];
            return 0;
        }

此示例使用两个输入数组比较输入参数协议。如果要将更改后的元素返回给metatrader(通过引用传递),则只需在marshalas属性之前推送[in,out,]属性即可。

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample3.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"  
    int SetFiboArray(int& t[], int i, int& o[]);
#import


//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
int fibo[10];
static int o[10];

   for (int i=0; i<4; i++)
   { fibo[i]=i; o[i] = i; }
   
   SetFiboArray(fibo, 6, o);
   
   for (int i=0; i<6; i++)
      Print(IntegerToString(fibo[i])+":"+IntegerToString(o[i]));
      
  }
//+------------------------------------------------------------------+

结果

2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 0:5
2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 0:3
2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 3:2
2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 2:1
2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 1:1
2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 0:0

4.4。例4。二维阵列接入

        public static int idx(int a, int b) {int cols = 2; return a * cols + b; }
 
        [DllExport("Set2DArray", CallingConvention = CallingConvention.StdCall)]
        public static int Set2DArray([In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] int[] tab, int len)
        {
            tab[idx(0, 0)] = 0;
            tab[idx(0, 1)] = 1;
            tab[idx(1, 0)] = 2;
            tab[idx(1, 1)] = 3;
            tab[idx(2, 0)] = 4;
            tab[idx(2, 1)] = 5;
            
            return 0;
        }

对于编组,二维数组并不是那么简单,但我使用的技术是将二维数组作为一维数组传递,并通过辅助IDX函数访问数组元素。

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample4.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"
   int Set2DArray(int &t[][2],int i);
#import
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   int t2[3][2];

   Set2DArray(t2,6);

   for(int row=0; row<3; row++)
      for(int col=0; col<2; col++)
         Print("t2["+IntegerToString(row)+"]["+IntegerToString(col)+"]="+IntegerToString(t2[row][col]));

  }
//+------------------------------------------------------------------+

结果

2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[2][1]=5
2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[2][0]=4
2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[1][1]=3
2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[1][0]=2
2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[0][1]=1
2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[0][0]=0

4.5。例5。替换字符串内容

  	 [DllExport("ReplaceString", CallingConvention = CallingConvention.StdCall)]
        public static int ReplaceString([In, Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder str,
        [MarshalAs(UnmanagedType.LPWStr)]string a, [MarshalAs(UnmanagedType.LPWStr)]string b)
        {
            str.Replace(a, b);

            if (str.ToString().Contains(a)) return 1;
            else  return 0;
        }

这个例子很短,但是我花了相当长的时间来实现,因为我尝试使用[in,out]属性或带有ref或out关键字的字符串参数,但是没有成功。

解决方案是使用StringBuilder而不是字符串变量。

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample5.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"   
   int ReplaceString(string &str,string a,string b);
#import
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   string str="A quick brown fox jumps over the lazy dog";
   string stra = "fox";
   string strb = "cat";


   Print(str);
   Print(ReplaceString(str,stra,strb));
   Print(str);

  }
//+------------------------------------------------------------------+

结果

2011.01.30 22:18:36     UnmanagedExportsDLLExample5 (EURUSD,M1) A quick brown cat jumps over the lazy dog
2011.01.30 22:18:36     UnmanagedExportsDLLExample5 (EURUSD,M1) 0
2011.01.30 22:18:36     UnmanagedExportsDLLExample5 (EURUSD,M1) A quick brown fox jumps over the lazy dog

4.6。例6。发送和更改mqltick结构

	 private static List<MqlTick> list;

	 [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct MqlTick
        {
            public Int64 Time;
            public Double Bid;
            public Double Ask;
            public Double Last;
            public UInt64 Volume;
        }

        [DllExport("AddTick", CallingConvention = CallingConvention.StdCall)]
        public static int AddTick(ref MqlTick tick, ref double bidsum)
        {
            bidsum = 0.0;

            if (list == null) list = new List<MqlTick>();

            tick.Volume = 666;
            list.Add(tick);

            foreach (MqlTick t in list) bidsum += t.Ask;

            return list.Count;
        }

mqltick结构作为引用传递,并用ref关键字标记。[结构布局(布局类型)。sequential,pack=1)]mqltick结构本身之前必须添加属性。

pack参数描述结构中的数据对齐。有关详细信息,请参见structlayoutattribute。pack字段(structlayoutattribute.包字段)。

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample6.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"
   int AddTick(MqlTick &tick, double& bidsum);
#import
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])
  {
//---
   MqlTick newTick;
   double bidsum;
   
   SymbolInfoTick(Symbol(), newTick);
   
   Print("before = " + IntegerToString(newTick.volume));
   
   Print(AddTick(newTick, bidsum));
   
   Print("after = " + IntegerToString(newTick.volume) + " : " + DoubleToString(bidsum));
   
   
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

结果

2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 8.167199999999999
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 6
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) before = 0
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 6.806
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 5
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) before = 0
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 5.4448
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 4
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) before = 0
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 4.0836
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 3
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) before = 0
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 2.7224
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 2
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) before = 0
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 1.3612
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 1
2011.01.30 23:59:04     TickDLLSend (EURUSD,M1) before = 0

nbsp;

总结

在本文中,我介绍了在MQL5代码和托管C代码之间进行交互的几种方法。

我还提供了几个示例,说明如何根据C封送MQL5结构,以及如何在MQL5脚本中调用导出的DLL函数。我相信所提供的示例可以作为将来在托管代码中编写DLL的研究的基础。

本文还为MetaTrader打开了一扇门,使其能够使用已经在C中实现的多个库。有关详细信息,请阅读“参考”部分中的链接。

要测试它,请将文件放在以下文件夹中:

MQL5/Libraries/testme.dll
MQL5/Scripts/unmanagedexportsdllexample1.mq5
MQL5/Scripts/unmanagedexportsdllexample2.mq5
MQL5/Scripts/unmanagedexportsdllexample3.mq5
MQL5/Scripts/unmanagedexportsdllexample4.mq5
MQL5/Scripts/unmanagedexportsdllexample5.mq5
MQL5/Experts/unmanagedexportsdllexample6.mq5

参考文献

  1. 使用Visual Studio 2005导出.NET DLL以供本机应用程序使用(使用Visual Studio 2005为本机应用程序导出.NET DLL)
  2. 与未编码代码的互操作(与非托管代码的互操作)
  3. COM Interop简介(COM Interop简介)
  4. 组件对象模型(COM)
  5. 使用u declspec(dllexport)从dll导出(使用u declspec(dllexport)
    从dll导出
  6. 如何:在本机类型中声明句柄
  7. 如何调用托管代码中的C++代码(反之亦然)((如何调用C++代码从托管代码或C++托管代码))
  8. 反向P/Invoke和异常(反向P/Invoke和异常)
  9. 如何在Visual Studio.NET或Visual Studio 2005中从本地VisualC++代码调用托管DLL(如何在Visual Studio.NET或Visual Studio 2005中编写的本地Visual C++代码中调用托管的DLL)
  10. 平台调用教程
  11. pinvoke反向pinvoke和_stdcall-_cdecl
  12. 具有反向pinvoke的gotchas(非托管到托管代码回调)[平台调用陷阱的反转(非托管到托管代码回调)]
  13. 混合。网络和本机代码(.网络和本机代码集成)
  14. 将托管代码导出为非托管代码(将托管代码导出为非托管代码)
  15. 了解经典的COM互操作性。净应用
  16. C++编程的托管扩展(用于C++编程的托管扩展)
  17. 罗伯特·吉塞克的网站
  18. msbuild任务(msbuild任务)
  19. 公共语言运行时

由MetaQuotes Software Corp.从英文翻译为
原文。https://www.mql5.com/en/articles/249

附加文件下载zip非托管dexportsdlleexample 1.mq5(1.13 kb),非托管dexportsdlleexample 2.mq5(1.27 kb)非托管dexportsdlleexample 3.mq5(1.1 kb)非托管dexportsdlleexample 4.mq5(1.07 kb)非托管dexportsdlleexample 5.mq5(1.05 kb)非托管dexportsdlleexample 6.mq5(1.89 kb)unmanagedexports.zip(1.02 kb),testme.zip(13.02 kb),testmell.zip(3.22 kb)

 

 


MyFxtop迈投(www.myfxtop.com)-靠谱的外汇跟单社区,免费跟随高手做交易!

 

免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经(www.myfxtop.cn)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。

風險提示

MyFxtops邁投所列信息僅供參考,不構成投資建議,也不代表任何形式的推薦或者誘導行為。MyFxtops邁投非外匯經紀商,不接觸妳的任何資金。 MYFXTOPS不保證客戶盈利,不承擔任何責任。從事外彙和差價合約等金融產品的槓桿交易具有高風險,損失有可能超過本金,請量力而行,入市前需充分了解潛在的風險。過去的交易成績並不代表以後的交易成績。依據各地區法律法規,MyFxtops邁投不向中國大陸、美國、加拿大、朝鮮居民提供服務。

邁投公眾號

聯繫我們

客服QQ:981617007
Email: service@myfxtop.com

MyFxtops 邁投