Для начала объясню, почему я выбрал для связки с 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
[su_spoiler title="Код LUA" style="fancy"]
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; |
[/su_spoiler]
DLL C/C++, написанная на Visual Studio
- Библиотека DLL создает/подключается к именованной памяти.
- Отправляет (записывает в память) текстовое сообщение: "Привет из C/C++".
- Читает память с периодичностью в 1 секунду, если память стала чиста, сообщение отправляется вновь.
[su_spoiler title="Код DLL" style="fancy"]
(Если Вы не знаете как создавать библиотеки 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; } |
[/su_spoiler]
QT Creator
- Приложение на C++ создает/подключается к именованной памяти.
- Читает память с периодичностью в 1 секунду, если в памяти появилось текстовое сообщение: "hello from DLL C/C++", выводит его в текстовое поле и очищает память, сообщая тем самым DLL что сообщение получено.
[su_spoiler title="Создание приложения на QT" style="fancy"]
Создаем Приложения -> Приложения 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 |
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); } } |
[/su_spoiler]
Стоит отметить, что были использованы стандартные средства WINAPI, а не инструментарий QT для MMF, поскольку в QT именование MMF идет по-другому.
Если все правильно сделано, то в запущенном приложении QT будут появляться строки hello from DLL C/C++, если при этом будет запущен скрипт LUA:
Надеюсь, моя "балалайка" кому-нибудь пригодиться. Если я в чем-то ошибся и можно проще, прошу поделиться своими наработками.