Для начала объясню, почему я выбрал для связки с QUIK'ом именно QT и зачем нужен этот "велосипед". Дело в том, что захотелось побольше графических возможностей, а если использовать Visual Studio С++ (а не C#), то готовых компонентов для "рисования" графиков практически нет. А C# или java в сравнению с QT С++ будут уступать в скорости, которая необходима для тестирования стратегий. (Последнее спорно, но, по моему мнению, все же C++ "ближе" к процессорному языку, и код тестов QT в будущем можно будет перенести на unix)
И тут начались грабли... Если заходил со стороны прямой компиляции LUA для QT, не хотел подключаться QUIK (написанный на MVSC). Затем я попробовал создать DLL на MVSC и подключиться динамически к ней из QT, но export'ы DLL просто не виделась в QT. И тут я наткнулся на замечательную статью Обмен данными между DLL (C/C++) и приложением C# посредством Memory Mapped File (MMF). Ниже привожу мою адаптацию данной статьи на QT Ctreator. При этом использовалась среда Visual Studio 2017 С++ для написания DLL и среда QT Creator 5.3 с компилятором MinGW 4.8.
LUA и DLL пишутся так же:
QLua(Lua)
- Подключает библиотеку DLL
- Запускает функцию отправки сообщений в QT
- Останавливает функцию отправки сообщений в QT
Код LUA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package.cpath = "c:\\SBERBANK\\QUIK_RSA\\testdll\\Release\\testdll.dll"
-- Подключает библиотеку DLL
require("QluaCConnector");
-- Флаг для поддержания работы функции main
IsRun = true;
function main()
-- Запускает функцию отправки сообщений в C#
QluaCConnector.StartSendHi();
-- Обеспечивает работу скрипта и библиотеки до остановки скрипта пользователем
while IsRun do
sleep(1000);
end;
end;
function OnStop()
-- Останавливает функцию отправки сообщений в C#
QluaCConnector.StopSendHi();
-- Останавливает цикл в функции main
IsRun = false;
end; |
package.cpath = "c:\\SBERBANK\\QUIK_RSA\\testdll\\Release\\testdll.dll"
-- Подключает библиотеку DLL
require("QluaCConnector");
-- Флаг для поддержания работы функции main
IsRun = true;
function main()
-- Запускает функцию отправки сообщений в C#
QluaCConnector.StartSendHi();
-- Обеспечивает работу скрипта и библиотеки до остановки скрипта пользователем
while IsRun do
sleep(1000);
end;
end;
function OnStop()
-- Останавливает функцию отправки сообщений в C#
QluaCConnector.StopSendHi();
-- Останавливает цикл в функции main
IsRun = false;
end;
DLL C/C++, написанная на Visual Studio
- Библиотека DLL создает/подключается к именованной памяти.
- Отправляет (записывает в память) текстовое сообщение: "Привет из C/C++".
- Читает память с периодичностью в 1 секунду, если память стала чиста, сообщение отправляется вновь.
Код DLL
(Если Вы не знаете как создавать библиотеки DLL, которые можно использовать в скриптах QLua(Lua), ознакомьтесь, пожалуйста, с данной статьей).
1
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
#include "stdafx.h"
#include < Windows.h>;
#define LUA_LIB
#define LUA_BUILD_AS_DLL
extern "C" {
#include "../contrib/lauxlib.h"
#include "../contrib/lua.h"
}
//=== Получает указатель на выделенную именованную память =====================================================//
// Имя для выделенной памяти
TCHAR Name[] = TEXT("MyMemory");
// Создаст, или подключится к уже созданной памяти с таким именем
HANDLE hFileMapMyMemory = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 256, Name);
// Флаг необходимости отправлять сообщение
bool Run = true;
//=== Реализация функций, вызываемых из LUA ====================================================================//
static int forLua_StartSendHi(lua_State *L) // Отправляет сообщения для C#
{
// Если указатель на память получен
if (hFileMapMyMemory)
{
// Получает доступ (представление) непосредственно к чтению/записи байт
PBYTE pb = (PBYTE)(MapViewOfFile(hFileMapMyMemory, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 256));
// Если доступ получен
if (pb != NULL)
{
// Очищает память при первом обращении
for (int i = 0; i < 256; i++)pb[i] = '\0';
// Бесконечный цикл пока Run == true
while (Run)
{
// Если запись пустая (либо первая отправка, либо C# очистил память, подтвердив получение)
if (pb[0] == 0)
{
// Записывает текст сообщения в память
char *Str = "hello from DLL C/C++";
memcpy(pb, Str, strlen(Str));
}
// Пауза в 1 секунду
Sleep(1000);
}
// Закрывает представление
UnmapViewOfFile(pb);
// Закрывает указатель на память
CloseHandle(hFileMapMyMemory);
}
}
return(0);
}
static int forLua_StopSendHi(lua_State *L) // Прекращает отправку сообщений для C#
{
Run = false; // Выключает флаг
return(0);
}
//=== Регистрация реализованных в dll функций, чтобы они стали "видимы" для Lua ================================//
static struct luaL_reg ls_lib[] = {
{ "StartSendHi", forLua_StartSendHi }, // из скрипта Lua эту функцию можно будет вызывать так: QluaCSharpConnector.StartSendHi(); здесь можно указать любое другое название
{ "StopSendHi", forLua_StopSendHi }, // соответственно
{ NULL, NULL }
};
//=== Регистрация названия библиотеки, видимого в скрипте Lua ==================================================//
extern "C" LUALIB_API int luaopen_QluaCConnector(lua_State *L) {
luaL_openlib(L, "QluaCConnector", ls_lib, 0);
return 0;
} |
#include "stdafx.h"
#include < Windows.h>;
#define LUA_LIB
#define LUA_BUILD_AS_DLL
extern "C" {
#include "../contrib/lauxlib.h"
#include "../contrib/lua.h"
}
//=== Получает указатель на выделенную именованную память =====================================================//
// Имя для выделенной памяти
TCHAR Name[] = TEXT("MyMemory");
// Создаст, или подключится к уже созданной памяти с таким именем
HANDLE hFileMapMyMemory = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 256, Name);
// Флаг необходимости отправлять сообщение
bool Run = true;
//=== Реализация функций, вызываемых из LUA ====================================================================//
static int forLua_StartSendHi(lua_State *L) // Отправляет сообщения для C#
{
// Если указатель на память получен
if (hFileMapMyMemory)
{
// Получает доступ (представление) непосредственно к чтению/записи байт
PBYTE pb = (PBYTE)(MapViewOfFile(hFileMapMyMemory, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 256));
// Если доступ получен
if (pb != NULL)
{
// Очищает память при первом обращении
for (int i = 0; i < 256; i++)pb[i] = '\0';
// Бесконечный цикл пока Run == true
while (Run)
{
// Если запись пустая (либо первая отправка, либо C# очистил память, подтвердив получение)
if (pb[0] == 0)
{
// Записывает текст сообщения в память
char *Str = "hello from DLL C/C++";
memcpy(pb, Str, strlen(Str));
}
// Пауза в 1 секунду
Sleep(1000);
}
// Закрывает представление
UnmapViewOfFile(pb);
// Закрывает указатель на память
CloseHandle(hFileMapMyMemory);
}
}
return(0);
}
static int forLua_StopSendHi(lua_State *L) // Прекращает отправку сообщений для C#
{
Run = false; // Выключает флаг
return(0);
}
//=== Регистрация реализованных в dll функций, чтобы они стали "видимы" для Lua ================================//
static struct luaL_reg ls_lib[] = {
{ "StartSendHi", forLua_StartSendHi }, // из скрипта Lua эту функцию можно будет вызывать так: QluaCSharpConnector.StartSendHi(); здесь можно указать любое другое название
{ "StopSendHi", forLua_StopSendHi }, // соответственно
{ NULL, NULL }
};
//=== Регистрация названия библиотеки, видимого в скрипте Lua ==================================================//
extern "C" LUALIB_API int luaopen_QluaCConnector(lua_State *L) {
luaL_openlib(L, "QluaCConnector", ls_lib, 0);
return 0;
}
QT Creator
- Приложение на C++ создает/подключается к именованной памяти.
- Читает память с периодичностью в 1 секунду, если в памяти появилось текстовое сообщение: "hello from DLL C/C++", выводит его в текстовое поле и очищает память, сообщая тем самым DLL что сообщение получено.
Создание приложения на QT
Создаем Приложения -> Приложения QT Widgets. Размещаем на форме TextBrowser:
mainwindow.h
1
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
| #ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
// добавляем QTimer и QTime
#include
#include
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
QTimer *tmr; //Адресная переменная таймера
private slots:
void GetMessageFromDLL(); //Слот для принятия сообщений
};
#endif // MAINWINDOW_H |
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
// добавляем QTimer и QTime
#include
#include
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
QTimer *tmr; //Адресная переменная таймера
private slots:
void GetMessageFromDLL(); //Слот для принятия сообщений
};
#endif // MAINWINDOW_H
mainwindow.cpp
1
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
| #include "mainwindow.h"
#include "ui_mainwindow.h"
//добавляем для winapi
#include "windows.h"
//имя MMF, задаваемое в dll
#define FILEMAP_NAME "MyMemory"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
tmr = new QTimer(this); // Создаем объект класса QTimer и передаем адрес переменной
tmr->setInterval(1000); // Задаем интервал таймера
connect(tmr, SIGNAL(timeout()), this, SLOT(GetMessageFromDLL())); // Подключаем сигнал таймера к нашему слоту
tmr->start(); // Запускаем таймер
}
MainWindow::~MainWindow()
{
delete ui;
delete tmr;
}
void MainWindow::GetMessageFromDLL()
{
HANDLE hFileMap = OpenFileMappingA(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, FILEMAP_NAME);
if(hFileMap){
//если такой mmf существует, тогда считываем начало памяти
PBYTE pbMapView = (PBYTE)MapViewOfFile(hFileMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 256);
if(pbMapView == NULL)
ui->textBrowser->append( "невозможно открыть MMF. код ошибки "+ GetLastError());
else {
//преобразуем память в строку
QString myString((char*)pbMapView);
if (myString == "hello from DLL C/C++"){
//выводим строку в textbrowser
ui->textBrowser->append(myString);
//очищаем память
memset(pbMapView,'\0',256);
}
//закрываем
UnmapViewOfFile(pbMapView);
}
CloseHandle(hFileMap);
}
} |
#include "mainwindow.h"
#include "ui_mainwindow.h"
//добавляем для winapi
#include "windows.h"
//имя MMF, задаваемое в dll
#define FILEMAP_NAME "MyMemory"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
tmr = new QTimer(this); // Создаем объект класса QTimer и передаем адрес переменной
tmr->setInterval(1000); // Задаем интервал таймера
connect(tmr, SIGNAL(timeout()), this, SLOT(GetMessageFromDLL())); // Подключаем сигнал таймера к нашему слоту
tmr->start(); // Запускаем таймер
}
MainWindow::~MainWindow()
{
delete ui;
delete tmr;
}
void MainWindow::GetMessageFromDLL()
{
HANDLE hFileMap = OpenFileMappingA(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, FILEMAP_NAME);
if(hFileMap){
//если такой mmf существует, тогда считываем начало памяти
PBYTE pbMapView = (PBYTE)MapViewOfFile(hFileMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 256);
if(pbMapView == NULL)
ui->textBrowser->append( "невозможно открыть MMF. код ошибки "+ GetLastError());
else {
//преобразуем память в строку
QString myString((char*)pbMapView);
if (myString == "hello from DLL C/C++"){
//выводим строку в textbrowser
ui->textBrowser->append(myString);
//очищаем память
memset(pbMapView,'\0',256);
}
//закрываем
UnmapViewOfFile(pbMapView);
}
CloseHandle(hFileMap);
}
}
Стоит отметить, что были использованы стандартные средства WINAPI, а не инструментарий QT для MMF, поскольку в QT именование MMF идет по-другому.
Если все правильно сделано, то в запущенном приложении QT будут появляться строки hello from DLL C/C++, если при этом будет запущен скрипт LUA:
Надеюсь, моя "балалайка" кому-нибудь пригодиться. Если я в чем-то ошибся и можно проще, прошу поделиться своими наработками.