简介
很长一段时间以来,我一直在寻找一个简单的解决方案,使我能够在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互操作性模型-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<;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<;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编写的非托管导出模板:HTTP://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
参考文献
- 使用Visual Studio 2005导出.NET DLL以供本机应用程序使用(使用Visual Studio 2005为本机应用程序导出.NET DLL)
- 与未编码代码的互操作(与非托管代码的互操作)
- COM Interop简介(COM Interop简介)
- 组件对象模型(COM)
- 使用u declspec(dllexport)从dll导出(使用u declspec(dllexport)
从dll导出 - 如何:在本机类型中声明句柄
- 如何调用托管代码中的C++代码(反之亦然)((如何调用C++代码从托管代码或C++托管代码))
- 反向P/Invoke和异常(反向P/Invoke和异常)
- 如何在Visual Studio.NET或Visual Studio 2005中从本地VisualC++代码调用托管DLL(如何在Visual Studio.NET或Visual Studio 2005中编写的本地Visual C++代码中调用托管的DLL)
- 平台调用教程
- pinvoke反向pinvoke和_stdcall-_cdecl
- 具有反向pinvoke的gotchas(非托管到托管代码回调)[平台调用陷阱的反转(非托管到托管代码回调)]
- 混合。网络和本机代码(.网络和本机代码集成)
- 将托管代码导出为非托管代码(将托管代码导出为非托管代码)
- 了解经典的COM互操作性。净应用
- C++编程的托管扩展(用于C++编程的托管扩展)
- 罗伯特·吉塞克的网站
- msbuild任务(msbuild任务)
- 公共语言运行时
由MetaQuotes Software Corp.从英文翻译为
原文。https://www.mql5.com/en/articles/249
MyFxtop迈投(www.myfxtop.com)-靠谱的外汇跟单社区,免费跟随高手做交易!
免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经(www.myfxtop.cn)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。