事实上,很少有开发人员确切知道如何编写简单的 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 输出目录
在 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); }
接下来我们讨论传递参数的几个变体:
_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
与其他 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
_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 中对其进行修改,然后自动删除副本而不会影响原字符串。
#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
为防止终端崩溃,每个 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
如上文所述,DLL 函数的每个调用都包装于特殊的包装程序中,以确保安全性。该结合掩盖基本代码、替换堆栈、支持 stdcall / cdecl 协议并在调用函数内监控异常。
这一工作量不会导致函数调用有明显的延迟。
我们将上述所有 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!"); //--- } //+------------------------------------------------------------------+
感谢您阅读拙作!我乐意回答你们的任何问题。
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程