Lấy danh sách chung các thuộc tính của terminal và chương trình
Các hàm tích hợp có sẵn để lấy các thuộc tính môi trường sử dụng cách tiếp cận chung: các thuộc tính của mỗi loại cụ thể được nhóm lại thành một hàm riêng biệt với một tham số duy nhất chỉ định thuộc tính được yêu cầu. Có các liệt kê được định nghĩa để xác định các thuộc tính: mỗi phần tử mô tả một thuộc tính.
Như chúng ta sẽ thấy dưới đây, cách tiếp cận này thường được sử dụng trong API MQL5 và trong các lĩnh vực khác, bao gồm cả các lĩnh vực ứng dụng. Cụ thể, các tập hợp hàm tương tự được sử dụng để lấy các thuộc tính của tài khoản giao dịch và công cụ tài chính.
Các thuộc tính của ba loại đơn giản, int
, double
, và string
, là đủ để mô tả môi trường. Tuy nhiên, không chỉ các thuộc tính kiểu số nguyên được biểu diễn bằng các giá trị kiểu int
, mà còn cả các cờ logic (đặc biệt là quyền/cấm, sự hiện diện của kết nối mạng, v.v.), cũng như các liệt kê tích hợp khác (ví dụ, các loại chương trình MQL và các loại giấy phép).
Với sự phân chia có điều kiện thành các thuộc tính terminal và thuộc tính của một chương trình MQL cụ thể, có các hàm sau để mô tả môi trường:
int MQLInfoInteger(ENUM_MQL_INFO_INTEGER p)
int TerminalInfoInteger(ENUM_TERMINAL_INFO_INTEGER p)
double TerminalInfoDouble(ENUM_TERMINAL_INFO_DOUBLE p)
string MQLInfoString(ENUM_MQL_INFO_STRING p)
string TerminalInfoString(ENUM_TERMINAL_INFO_STRING p)
Các nguyên mẫu này ánh xạ các loại giá trị tới các loại liệt kê. Ví dụ, các thuộc tính terminal kiểu int
được tổng hợp trong ENUM_TERMINAL_INFO_INTEGER
, và các thuộc tính kiểu double
của nó được liệt kê trong ENUM_TERMINAL_INFO_DOUBLE
, v.v. Danh sách các liệt kê có sẵn và các phần tử của chúng có thể được tìm thấy trong tài liệu, trong các phần về Thuộc tính Terminal và Chương trình MQL.
Trong các phần tiếp theo, chúng ta sẽ xem xét tất cả các thuộc tính, được nhóm lại dựa trên mục đích của chúng. Nhưng ở đây, chúng ta chuyển sang vấn đề lấy danh sách chung của tất cả các thuộc tính hiện có và giá trị của chúng. Điều này thường cần thiết để xác định "điểm nghẽn" hoặc các đặc điểm hoạt động của chương trình MQL trên các phiên bản cụ thể của terminal. Một tình huống khá phổ biến là khi một chương trình MQL hoạt động trên một máy tính, nhưng không hoạt động hoặc gặp một số vấn đề trên máy tính khác.
Danh sách các thuộc tính được cập nhật liên tục khi nền tảng phát triển, vì vậy nên thực hiện yêu cầu của chúng không dựa trên danh sách cố định trong mã nguồn, mà tự động.
Trong phần Liệt kê, chúng ta đã tạo một hàm mẫu EnumToArray
để lấy danh sách đầy đủ các phần tử liệt kê (tệp EnumToArray.mqh
). Cũng trong phần đó, chúng ta đã giới thiệu tập lệnh ConversionEnum.mq5
, sử dụng tệp tiêu đề đã chỉ định. Trong tập lệnh, một hàm hỗ trợ process
đã được triển khai, nhận một mảng với các mã phần tử liệt kê và xuất chúng ra nhật ký. Chúng ta sẽ lấy những phát triển này làm điểm xuất phát để cải tiến thêm.
Chúng ta cần sửa đổi hàm process
sao cho không chỉ lấy danh sách các phần tử của một liệt kê cụ thể mà còn truy vấn các thuộc tính tương ứng bằng một trong các hàm thuộc tính tích hợp.
Hãy đặt tên cho phiên bản mới của tập lệnh là Environment.mq5
.
Vì các thuộc tính của môi trường được phân bố trên nhiều hàm khác nhau (trong trường hợp này là năm hàm), bạn cần học cách truyền vào phiên bản mới của hàm process
một con trỏ đến hàm tích hợp cần thiết (xem phần Con trỏ hàm (typedef)). Tuy nhiên, MQL5 không cho phép gán địa chỉ của một hàm tích hợp vào một con trỏ hàm. Điều này chỉ có thể thực hiện với một hàm ứng dụng được triển khai trong MQL5. Do đó, chúng ta sẽ tạo các hàm bao bọc. Ví dụ:
int _MQLInfoInteger(const ENUM_MQL_INFO_INTEGER p)
{
return MQLInfoInteger(p);
}
// ví dụ về mô tả kiểu con trỏ
typedef int (*IntFuncPtr)(const ENUM_MQL_INFO_INTEGER property);
// khởi tạo biến con trỏ
IntFuncPtr ptr1 = _MQLInfoInteger; // ok
IntFuncPtr ptr2 = MQLInfoInteger; // lỗi biên dịch
2
3
4
5
6
7
8
9
Một "bản sao" cho MQLInfoInteger
được hiển thị ở trên (rõ ràng, nó nên có một tên khác, nhưng tốt nhất là tương tự). Các hàm khác được "đóng gói" theo cách tương tự. Tổng cộng sẽ có năm hàm.
Nếu trong phiên bản cũ của process
chỉ có một tham số mẫu chỉ định một liệt kê, thì trong phiên bản mới, chúng ta cũng cần truyền kiểu của giá trị trả về (vì MQL5 không "hiểu" các từ trong tên của liệt kê): mặc dù phần cuối "INTEGER" có trong tên ENUM_MQL_INFO_INTEGER
, trình biên dịch không thể liên kết nó với kiểu int
).
Tuy nhiên, ngoài việc liên kết các kiểu của giá trị trả về và liệt kê, chúng ta cần truyền vào hàm process
một con trỏ đến hàm bao bọc thích hợp (một trong năm hàm chúng ta đã định nghĩa trước đó). Sau cùng, trình biên dịch không thể tự xác định từ một đối số, ví dụ, kiểu ENUM_MQL_INFO_INTEGER
, rằng cần gọi MQLInfoInteger
.
Để giải quyết vấn đề này, một cấu trúc mẫu đặc biệt đã được tạo ra, kết hợp cả ba yếu tố lại với nhau.
template<typename E, typename R>
struct Binding
{
public:
typedef R (*FuncPtr)(const E property);
const FuncPtr f;
Binding(FuncPtr p): f(p) { }
};
2
3
4
5
6
7
8
Hai tham số mẫu cho phép bạn chỉ định kiểu của con trỏ hàm (FuncPtr
) với sự kết hợp mong muốn của kết quả và tham số đầu vào. Thực thể cấu trúc có trường f
cho một con trỏ đến kiểu mới được định nghĩa đó.
Bây giờ, phiên bản mới của hàm process
có thể được mô tả như sau.
template<typename E, typename R>
void process(Binding<E, R> &b)
{
E e = (E)0; // tắt cảnh báo về việc thiếu khởi tạo
int array[];
// lấy danh sách các phần tử liệt kê vào một mảng
int n = EnumToArray(e, array, 0, USHORT_MAX);
Print(typename(E), " Count=", n);
ResetLastError();
// hiển thị tên và giá trị cho mỗi phần tử,
// thu được bằng cách gọi một con trỏ trong cấu trúc Binding
for(int i = 0; i < n; ++i)
{
e = (E)array[i];
R r = b.f(e); // gọi hàm, sau đó phân tích _LastError
const int snapshot = _LastError;
PrintFormat("% 3d %s=%s", i, EnumToString(e), (string)r +
(snapshot != 0 ? E2S(snapshot) + " (" + (string)snapshot + ")" : ""));
ResetLastError();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Đối số đầu vào là cấu trúc Binding
. Nó chứa một con trỏ đến một hàm cụ thể để lấy các thuộc tính (trường này sẽ được điền bởi mã gọi).
Phiên bản này của thuật toán ghi lại số thứ tự, định danh thuộc tính và giá trị của nó. Một lần nữa, lưu ý rằng số đầu tiên trong mỗi mục sẽ chứa thứ tự của phần tử trong liệt kê, không phải giá trị (các giá trị có thể được gán cho các phần tử với khoảng cách). Tùy chọn, bạn có thể thêm đầu ra của biến e
"dưới dạng nguyên bản" bên trong các lệnh print format
.
Ngoài ra, bạn có thể sửa đổi quá trình để nó thu thập các giá trị thuộc tính kết quả vào một mảng (hoặc một bộ chứa khác, như một bản đồ) và trả chúng ra "bên ngoài".
Sẽ là một lỗi tiềm ẩn nếu tham chiếu trực tiếp đến con trỏ hàm trong lệnh print format
cùng với phân tích mã lỗi _LastError
. Vấn đề là thứ tự đánh giá các đối số hàm (xem phần Tham số và Đối số) và các toán hạng trong một biểu thức (xem phần Khái niệm cơ bản) không được xác định trong trường hợp này. Do đó, khi một con trỏ được gọi trên cùng một dòng nơi _LastError
được đọc, trình biên dịch có thể quyết định thực thi phần thứ hai trước phần đầu tiên. Kết quả là, chúng ta sẽ thấy một mã lỗi không liên quan (ví dụ, từ một lời gọi hàm trước đó).
Nhưng đó chưa phải là tất cả. Biến tích hợp _LastError
có thể thay đổi giá trị của nó gần như ở bất kỳ đâu trong quá trình đánh giá một biểu thức nếu bất kỳ thao tác nào thất bại. Đặc biệt, hàm EnumToString
có thể gây ra mã lỗi nếu một giá trị được truyền dưới dạng đối số không có trong liệt kê. Trong đoạn mã này, chúng ta miễn nhiễm với vấn đề này vì hàm EnumToArray
của chúng ta trả về một mảng chỉ chứa các phần tử liệt kê đã được kiểm tra (hợp lệ). Tuy nhiên, trong các trường hợp chung, trong bất kỳ lệnh "phức hợp" nào, có thể có nhiều nơi mà _LastError
sẽ bị thay đổi. Về mặt này, nên cố định mã lỗi ngay sau hành động mà chúng ta quan tâm (ở đây là lời gọi hàm bằng con trỏ), lưu nó vào một biến trung gian snapshot
.
Nhưng hãy quay lại vấn đề chính. Cuối cùng, chúng ta có thể tổ chức một lời gọi của hàm process
mới để lấy các thuộc tính khác nhau của môi trường phần mềm.
void OnStart()
{
process(Binding<ENUM_MQL_INFO_INTEGER, int>(_MQLInfoInteger));
process(Binding<ENUM_TERMINAL_INFO_INTEGER, int>(_TerminalInfoInteger));
process(Binding<ENUM_TERMINAL_INFO_DOUBLE, double>(_TerminalInfoDouble));
process(Binding<ENUM_MQL_INFO_STRING, string>(_MQLInfoString));
process(Binding<ENUM_TERMINAL_INFO_STRING, string>(_TerminalInfoString));
}
2
3
4
5
6
7
8
Dưới đây là một đoạn trích của các mục nhật ký được tạo ra.
ENUM_MQL_INFO_INTEGER Count=15
0 MQL_PROGRAM_TYPE=1
1 MQL_DLLS_ALLOWED=0
2 MQL_TRADE_ALLOWED=0
3 MQL_DEBUG=1
...
7 MQL_LICENSE_TYPE=0
...
ENUM_TERMINAL_INFO_INTEGER Count=50
0 TERMINAL_BUILD=2988
1 TERMINAL_CONNECTED=1
2 TERMINAL_DLLS_ALLOWED=0
3 TERMINAL_TRADE_ALLOWED=0
...
6 TERMINAL_MAXBARS=100000
7 TERMINAL_CODEPAGE=1251
8 TERMINAL_MEMORY_PHYSICAL=4095
9 TERMINAL_MEMORY_TOTAL=8190
10 TERMINAL_MEMORY_AVAILABLE=7813
11 TERMINAL_MEMORY_USED=377
12 TERMINAL_X64=1
...
ENUM_TERMINAL_INFO_DOUBLE Count=2
0 TERMINAL_COMMUNITY_BALANCE=0.0 (MQL5_WRONG_PROPERTY,4512)
1 TERMINAL_RETRANSMISSION=0.0
ENUM_MQL_INFO_STRING Count=2
0 MQL_PROGRAM_NAME=Environment
1 MQL_PROGRAM_PATH=C:\Program Files\MT5East\MQL5\Scripts\MQL5Book\p4\Environment.ex5
ENUM_TERMINAL_INFO_STRING Count=6
0 TERMINAL_COMPANY=MetaQuotes Software Corp.
1 TERMINAL_NAME=MetaTrader 5
2 TERMINAL_PATH=C:\Program Files\MT5East
3 TERMINAL_DATA_PATH=C:\Program Files\MT5East
4 TERMINAL_COMMONDATA_PATH=C:\Users\User\AppData\Roaming\MetaQuotes\Terminal\Common
5 TERMINAL_LANGUAGE=Russian
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Những thuộc tính này và các thuộc tính khác sẽ được mô tả trong các phần tiếp theo.
Đáng chú ý là một số thuộc tính được thừa hưởng từ các giai đoạn phát triển trước của nền tảng và chỉ được giữ lại để đảm bảo tính tương thích. Đặc biệt, thuộc tính TERMINAL_X64
trong TerminalInfoInteger
trả về chỉ báo liệu terminal có phải là 64-bit hay không. Ngày nay, việc phát triển các phiên bản 32-bit đã bị ngừng, và do đó thuộc tính này luôn bằng 1 (true
).