Đặc điểm kết nối DLL
Các thực thể sau không thể được truyền làm tham số vào các hàm được nhập từ DLL:
- Lớp (đối tượng và con trỏ đến chúng)
- Cấu trúc chứa mảng động, chuỗi, lớp và các cấu trúc phức tạp khác
- Mảng chuỗi hoặc các đối tượng phức tạp nói trên
Tất cả các tham số loại đơn giản được truyền theo giá trị trừ khi được chỉ rõ rằng chúng được truyền theo tham chiếu. Khi truyền một chuỗi, địa chỉ bộ đệm của chuỗi được sao chép sẽ được truyền; nếu chuỗi được truyền theo tham chiếu, thì địa chỉ bộ đệm của chuỗi cụ thể này được truyền đến hàm nhập từ DLL mà không cần sao chép.
Khi truyền một mảng đến DLL, địa chỉ bắt đầu của bộ đệm dữ liệu luôn được truyền (bất kể cờ AS_SERIES). Hàm bên trong DLL không biết gì về cờ AS_SERIES, mảng được truyền là một mảng có độ dài không xác định, và cần một tham số bổ sung để chỉ định kích thước của nó.
Khi mô tả nguyên mẫu của một hàm được nhập, bạn có thể sử dụng các tham số với giá trị mặc định.
Khi nhập DLL, bạn nên cấp phép sử dụng chúng trong thuộc tính của một chương trình MQL cụ thể hoặc trong cài đặt chung của terminal. Về vấn đề này, trong phần Quyền truy cập, chúng ta đã trình bày script EnvPermissions.mq5
, trong đó đặc biệt có một hàm để đọc nội dung của clipboard hệ thống Windows bằng cách sử dụng các DLL hệ thống. Hàm này được cung cấp tùy chọn: lệnh gọi của nó đã bị comment vì chúng ta không biết cách làm việc với thư viện. Bây giờ, chúng ta sẽ chuyển nó sang một script riêng biệt LibClipboard.mq5
.
Việc chạy script có thể yêu cầu người dùng xác nhận (vì DLL bị vô hiệu hóa mặc định vì lý do bảo mật). Nếu cần, hãy bật tùy chọn trong hộp thoại, trên tab có các phụ thuộc.
Các tệp tiêu đề được cung cấp trong thư mục MQL5/Include/WinApi
, bao gồm cả các chỉ thị #import
cho nhiều hàm hệ thống cần thiết như quản lý clipboard (openclipboard
, GetClipboardData
, và CloseClipboard
), quản lý bộ nhớ (GlobalLock
và GlobalUnlock
), cửa sổ Windows, và nhiều hàm khác. Chúng ta sẽ chỉ bao gồm hai tệp: winuser.mqh
và winbase.mqh
. Chúng chứa các chỉ thị nhập cần thiết và, gián tiếp, thông qua kết nối với windef.mqh
, các macro thuật ngữ Windows (HANDLE và PVOID):
#define HANDLE long
#define PVOID long
#import "user32.dll"
...
int OpenClipboard(HANDLE wnd_new_owner);
HANDLE GetClipboardData(uint format);
int CloseClipboard(void);
...
#import
#import "kernel32.dll"
...
PVOID GlobalLock(HANDLE mem);
int GlobalUnlock(HANDLE mem);
...
#import
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Ngoài ra, chúng ta nhập hàm lstrcatW
từ thư viện kernel32.dll
vì chúng ta không hài lòng với mô tả của nó trong winbase.mqh
được cung cấp mặc định: điều này cung cấp cho hàm một nguyên mẫu thứ hai, phù hợp để truyền giá trị PVOID trong tham số đầu tiên.
#include <WinApi/winuser.mqh>
#include <WinApi/winbase.mqh>
#define CF_UNICODETEXT 13 // một trong những định dạng trao đổi chuẩn - văn bản Unicode
#import "kernel32.dll"
string lstrcatW(PVOID string1, const string string2);
#import
2
3
4
5
6
7
Bản chất của việc làm việc với clipboard là "bắt" quyền truy cập vào nó bằng OpenClipboard
, sau đó bạn nên lấy một tay cầm dữ liệu (GetClipboardData
), chuyển đổi nó thành địa chỉ bộ nhớ (GlobalLock
), và cuối cùng sao chép dữ liệu từ bộ nhớ hệ thống vào biến của bạn (lstrcatW
). Tiếp theo, các tài nguyên bị chiếm dụng được giải phóng theo thứ tự ngược lại (GlobalUnlock
và CloseClipboard
).
void ReadClipboard()
{
if(OpenClipboard(NULL))
{
HANDLE h = GetClipboardData(CF_UNICODETEXT);
PVOID p = GlobalLock(h);
if(p != 0)
{
const string text = lstrcatW(p, "");
Print("Clipboard: ", text);
GlobalUnlock(h);
}
CloseClipboard();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Hãy thử sao chép văn bản vào clipboard và sau đó chạy script: nội dung của clipboard sẽ được ghi lại. Nếu bộ đệm chứa hình ảnh hoặc dữ liệu khác không có biểu diễn văn bản, kết quả sẽ trống.
Các hàm được nhập từ DLL tuân theo quy ước liên kết thực thi nhị phân của các hàm Windows API. Để đảm bảo quy ước này, các từ khóa đặc thù của trình biên dịch được sử dụng trong văn bản nguồn của chương trình, chẳng hạn như ___stdcall
trong C hoặc C++. Những quy tắc liên kết này ngụ ý như sau:
- Hàm gọi (trong trường hợp của chúng ta là chương trình MQL) phải thấy nguyên mẫu của hàm được gọi (nhập từ DLL) để xếp các tham số lên stack một cách chính xác.
- Hàm gọi (trong trường hợp của chúng ta là chương trình MQL) xếp các tham số theo thứ tự ngược lại, từ phải sang trái — đây là thứ tự mà hàm được nhập đọc các tham số được truyền cho nó.
- Các tham số được truyền theo giá trị, ngoại trừ những tham số được truyền rõ ràng theo tham chiếu (trong trường hợp của chúng ta là chuỗi).
- Hàm được nhập đọc các tham số được truyền cho nó và dọn dẹp stack.
Dưới đây là một ví dụ khác về script sử dụng DLL — LibWindowTree.mq5
. Nhiệm vụ của nó là duyệt qua cây của tất cả các cửa sổ terminal và lấy tên lớp của chúng (theo đăng ký trong hệ thống bằng WinApi) và tiêu đề. Ở đây, cửa sổ có nghĩa là các yếu tố chuẩn của giao diện Windows, bao gồm cả các điều khiển. Thủ tục này có thể hữu ích cho việc tự động hóa công việc với terminal: mô phỏng việc nhấn nút trong các cửa sổ, chuyển đổi các chế độ không khả dụng qua MQL5, v.v.
Để nhập các hàm hệ thống cần thiết, chúng ta sẽ bao gồm tệp tiêu đề WinUser.mqh
sử dụng user32.dll
.
#include <WinAPI/WinUser.mqh>
Bạn có thể lấy tên của lớp cửa sổ và tiêu đề của nó bằng các hàm GetClassNameW
và GetWindowTextW
: chúng được gọi trong hàm GetWindowData
.
void GetWindowData(HANDLE w, string &clazz, string &title)
{
static ushort receiver[MAX_PATH];
if(GetWindowTextW(w, receiver, MAX_PATH))
{
title = ShortArrayToString(receiver);
}
if(GetClassNameW(w, receiver, MAX_PATH))
{
clazz = ShortArrayToString(receiver);
}
}
2
3
4
5
6
7
8
9
10
11
12
Hậu tố 'W' trong tên hàm có nghĩa là chúng được thiết kế cho các chuỗi định dạng Unicode (2 byte mỗi ký tự), đây là loại được sử dụng phổ biến nhất hiện nay (hậu tố 'A' cho chuỗi ANSI chỉ có ý nghĩa khi sử dụng để tương thích ngược với các thư viện cũ).
Với một tay cầm ban đầu đến một cửa sổ Windows, việc duyệt lên theo hệ thống phân cấp của các cửa sổ cha được cung cấp bởi hàm TraverseUp
: hoạt động của nó dựa trên hàm hệ thống GetParent
. Đối với mỗi cửa sổ được tìm thấy, TraverseUp
gọi GetWindowData
và xuất tên lớp và tiêu đề kết quả vào log.
HANDLE TraverseUp(HANDLE w)
{
HANDLE p = 0;
while(w != 0)
{
p = w;
string clazz, title;
GetWindowData(w, clazz, title);
Print("'", clazz, "' '", title, "'");
w = GetParent(w);
}
return p;
}
2
3
4
5
6
7
8
9
10
11
12
13
Việc duyệt sâu vào hệ thống phân cấp được thực hiện bởi hàm TraverseDown
: hàm hệ thống FindWindowExW
được sử dụng để liệt kê các cửa sổ con.
HANDLE TraverseDown(const HANDLE w, const int level = 0)
{
// yêu cầu cửa sổ con đầu tiên (nếu có)
HANDLE child = FindWindowExW(w, NULL, NULL, NULL);
while(child) // lặp trong khi còn cửa sổ con
{
string clazz, title;
GetWindowData(child, clazz, title);
Print(StringFormat("%*s", level * 2, ""), "'", clazz, "' '", title, "'");
TraverseDown(child, level + 1);
// yêu cầu cửa sổ con tiếp theo
child = FindWindowExW(w, child, NULL, NULL);
}
return child;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Trong hàm OnStart
, chúng ta tìm cửa sổ terminal chính bằng cách duyệt các cửa sổ lên từ tay cầm của biểu đồ hiện tại mà script đang chạy. Sau đó, chúng ta xây dựng toàn bộ cây cửa sổ terminal.
void OnStart()
{
HANDLE h = TraverseUp(ChartGetInteger(0, CHART_WINDOW_HANDLE));
Print("Main window handle: ", h);
TraverseDown(h, 1);
}
2
3
4
5
6
Chúng ta cũng có thể tìm kiếm các cửa sổ cần thiết theo tên lớp và/hoặc tiêu đề, do đó cửa sổ chính có thể được lấy ngay lập tức bằng cách gọi FindWindowW
, vì các thuộc tính của nó đã được biết.
h = FindWindowW("MetaQuotes::MetaTrader::5.00", NULL);
Dưới đây là một ví dụ log (đoạn trích):
'AfxFrameOrView140su' ''
'Afx:000000013F110000:b:0000000000010003:0000000000000006:00000000000306BA' 'EURUSD,H1'
'MDIClient' ''
'MetaQuotes::MetaTrader::5.00' '12345678 - MetaQuotes-Demo: Demo Account - Hedge - ...'
Main window handle: 263576
'msctls_statusbar32' 'For Help, press F1'
'AfxControlBar140su' 'Standard'
'ToolbarWindow32' 'Timeframes'
'ToolbarWindow32' 'Line Studies'
'ToolbarWindow32' 'Standard'
'AfxControlBar140su' 'Toolbox'
'Afx:000000013F110000:b:0000000000010003:0000000000000000:0000000000000000' 'Toolbox'
'AfxWnd140su' ''
'ToolbarWindow32' ''
...
'MDIClient' ''
'Afx:000000013F110000:b:0000000000010003:0000000000000006:00000000000306BA' 'EURUSD,H1'
'AfxFrameOrView140su' ''
'Edit' '0.00'
'Afx:000000013F110000:b:0000000000010003:0000000000000006:00000000000306BA' 'XAUUSD,Daily'
'AfxFrameOrView140su' ''
'Edit' '0.00'
'Afx:000000013F110000:b:0000000000010003:0000000000000006:00000000000306BA' 'EURUSD,M15'
'AfxFrameOrView140su' ''
'Edit' '0.00'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25