资源变量

#resource 指令有一种特殊形式,可以将外部文件声明为资源变量,并在程序中作为相应类型的普通变量进行访问。声明格式为:

#resource "path_file_name" as resource_variable_type resource_variable_name

下面是一些声明示例:

#resource "data.bin" as int Data[]           //array of int type with data from the file data.bin 
#resource "rates.dat" as MqlRates Rates[]    // array of MqlRates structures from the file rates.dat
#resource "data.txt" as string Message       // line with the contents of the file data.txt
#resource "image.bmp" as bitmap Bitmap1[]    // one-dimensional array with image pixels
                                             // from file image.bmp
#resource "image.bmp" as bitmap Bitmap2[][]  // two-dimensional array with the same image

我们来解释一下。资源变量是常量(不能在 MQL5 代码中修改)。例如,要在显示在屏幕上之前编辑图像,应创建资源数组变量的副本。

对于文本文件(string 类型的资源),编码会根据是否存在 BOM 头。如果没有 BOM,则编码由文件内容决定。支持 ANSI、UTF-8 和 UTF-16 编码。从文件读取数据时,所有字符串都会转换为 Unicode。

使用资源字符串变量不仅能极大地方便基于纯 MQL5 的程序编写,还能极大地方便基于其他技术的程序编写。例如,可以在单独文件中编写 OpenCL 代码(MQL5 支持其作为扩展),然后将其作为字符串包含在 MQL 程序的资源中。在 大型智能交易系统示例的中,我们已经使用资源字符串包含 HTML 模板。

对于图像,我们引入了一种特殊的 bitmap 类型;这种类型有几个特点。

bitmap 类型描述图像中的单个点或像素,用 4 字节无符号整数 (uint) 表示。像素包含 4 个字节,分别对应 ARGB 或 XRGB 格式的颜色分量(一个字母 = 一个字节),其中 R 代表红色,G 代表绿色,B 代表蓝色,A 代表透明度(alpha 通道),X 代表忽略字节(无透明度)。当在图表上叠加图像或图像之间相互叠加时,透明度可用于产生各种效果。

我们将在有关动态创建图形资源一节中研究 ARGB 和 XRGB 格式的定义(参见 ResourceCreate)。例如,对于 ARGB,十六进制数 0xFFFF0000 表示完全不透明的红色像素(最高字节为 0xFF)(下一个字节也是 0xFF),绿色和蓝色分量的下一个字节为 0。

需要注意的是,像素彩色编码不同于 color。我们回顾一下,color 类型的值可以用十六进制写成如下形式:0x00BBGGRR,其中 BB、GG、RR 分别表示蓝色、绿色和红色分量(在每个字节中,数值 255 表示分量的最大强度)。对于类似的像素记录,字节顺序则相反:0xAARRGGBB。当高字节(此处表示 AA)为 0,数值 255 为纯色时,就会实现完全透明。 ColorToARGB 函数可用于将 color 转换为 ARGB。

BMP 文件可以有多种编码方式(如果使用任何编辑器创建或编辑 BMP 文件,请在本程序的文档中查看此问题)。MQL5 资源不支持所有现有的编码方法。可以使用 ResourceCreate 函数检查是否支持特定文件。在指令中指定不支持的 BMP 格式文件将导致编译错误。

加载 24 位颜色编码的文件时,alpha 通道组件的所有像素都会设置为 255(不透明)。加载不含 alpha 通道的 32 位彩色编码文件时,也意味着不透明,即所有图像像素的 alpha 通道分量都设置为 255。加载含有 alpha 通道的 32 位彩色编码文件时,不会对像素进行任何操作。

图像可以用一维数组和二维数组来描述。这只影响寻址方法,而占用的内存量将保持不变。在这两种情况下,数组的大小都是根据 BMP 文件的数据自动设置的。一维数组的大小等于图像高度和宽度的乘积 (height * width),,二维数组则会获得独立的维度 [height][width]:第一个索引是行号,第二个索引是行中的点。

注意!在声明链接到资源变量的资源时,访问该资源的唯一方式是通过该变量,而通过名称 "::resource_name"(或更一般的 "path_file_name.ex5::resource_name")读取的标准方式不再有效。这也意味着此类资源不能作为其他程序的共享资源使用。

我们以两个指标为例;这两个指标都是无缓冲区的。选择这种 MQL 程序类型只是为了方便,因为除了其他指标外,它还可以应用于图表而不会发生冲突,而智能交易系统则需要一个没有其他智能交易系统的图表。此外,与脚本不同的是,它们保留在图表上,可用于后续设置更改。

BmpOwner.mq5 指标包含三个资源的说明:

  • "search1.bmp" 图像,带有简单的 #resource 指令,可从其他程序访问
  • "search2.bmp" 图像,作为 bitmap 类型的资源数组变量,无法从外部访问
  • "message.txt" 文本文件,作为向用户显示警告的资源字符串

这两个图像在该指标中均未以任何方式使用。在调用 AlertOnInit 函数中需要使用警告行,因为该指标并非旨在独立使用,而只是作为图像资源的提供者。

如果在源代码中没有使用资源变量,编译器可能根本不会在程序的二进制代码中包含该资源,但这不适用于图像。

#resource "search1.bmp"
#resource "search2.bmp" as bitmap image[]
#resource "message.txt" as string Message

这三个文件都位于指标源代码所在的同一目录中:MQL5/Indicators/MQL5Book/p7/

如果用户尝试运行该指标,它会显示警告并立即停止工作。该警告包含在 Message 资源字符串变量中。

int OnInit()
{
   Alert(Message); // equivalent to the following line of the code
   // Alert("This indicator is not intended to run, it holds a bitmap resource");
   
   // remove the indicator explicitly, because otherwise it remains "hanging" on the chart uninitialized
   ChartIndicatorDelete(00MQLInfoString(MQL_PROGRAM_NAME));
   return INIT_FAILED;
}

在第二个 BmpUser.mq5 指标中,我们将尝试使用输入变量 ResourceOffResourceOn 中指定的外部资源,以在 OBJ_BITMAP_LABEL 对象中显示。

input string ResourceOff = "BmpOwner.ex5::search1.bmp";
input string ResourceOn = "BmpOwner.ex5::search2.bmp";

默认情况下,该对象的状态为禁用/释放 ("Off"),其图像取自先前的指标 "BmpOwner.ex5::search1.bmp"。该路径和资源名称类似于完整符号 "\\Indicators\\MQL5Book\\p7\\BmpOwner.ex5::search1.bmp"。在这里,简短的形式是可以接受的,因为这些指标彼此相邻。如果随后打开对象特性对话框,就会在 Bitmap file (On/Off) 字段中看到完整的符号。

对于按下状态,我们应在 ResourceOn 中读取资源 "BmpOwner.ex5::search2.bmp"(我们看看会发生什么)。

在其他输入变量中,可以选择图表的角,相对于该角设置图像的定位,以及水平和垂直缩进。

input int X = 25;
input int Y = 25;
input ENUM_BASE_CORNER Corner = CORNER_RIGHT_LOWER;

OBJ_BITMAP_LABEL 对象的创建及其特性的设置(包括作为 OBJPROP_BMPFILE 图片的资源名称)均在 OnInit 中执行。

const string Prefix = "BMP_";
const ENUM_ANCHOR_POINT Anchors[] =
{
   ANCHOR_LEFT_UPPER,
   ANCHOR_LEFT_LOWER,
   ANCHOR_RIGHT_LOWER,
   ANCHOR_RIGHT_UPPER
};
   
void OnInit()
{
   const string name = Prefix + "search";
   ObjectCreate(0nameOBJ_BITMAP_LABEL000);
   
   ObjectSetString(0nameOBJPROP_BMPFILE0ResourceOn);
   ObjectSetString(0nameOBJPROP_BMPFILE1ResourceOff);
   ObjectSetInteger(0nameOBJPROP_XDISTANCEX);
   ObjectSetInteger(0nameOBJPROP_YDISTANCEY);
   ObjectSetInteger(0nameOBJPROP_CORNERCorner);
   ObjectSetInteger(0nameOBJPROP_ANCHORAnchors[(int)Corner]);
}

请注意,在 OBJPROP_BMPFILE 中指定图片时,按下状态用修饰符 0 表示,而释放(未按下)状态(默认情况下)用修饰符 1 表示,这有点出乎意料。

卸载指标时,OnDeinit 处理程序会删除对象。

void OnDeinit(const int)
{
   ObjectsDeleteAll(0Prefix);
}

我们来编译这两个指标,并以默认设置运行 BmpUser.ex5。图形文件 search1.bmp 的图像应出现在图表上(见左图)。

图表上对象中图形资源的正常显示(左)和错误显示(右)

图表上对象中图形资源的正常显示(左)和不正确显示(右)

如果点击图片,也就是将其切换到按下状态,程序将尝试访问 "BmpOwner.ex5::search2.bmp" 资源(由于资源数组 bitmap 附加在该资源上,因此该资源不可用)。结果,我们将看到一个红色方块,表示一个没有图片的空对象(见右上图)。如果输入参数指定了一个已知不存在或未共享资源的路径或名称,那么总是会发生类似的情形。可以创建自己的程序,在程序中描述指向某个现有 bmp 文件的资源,然后在指标输入参数 BmpUser 中指定。在这种情况下,指标将能够在图表上显示图片。