Обмен данными между DLL (C/C++) и приложением QT Creator (C++)

Автор записи: NewDracon

Для начала объясню, почему я выбрал для связки с 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;

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;
}

 

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

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);
    }
}

Стоит отметить, что были использованы стандартные средства WINAPI, а не инструментарий QT для MMF, поскольку в QT именование MMF идет по-другому.

Если все правильно сделано, то в запущенном приложении QT будут появляться строки hello from DLL C/C++, если при этом будет запущен скрипт LUA:

Надеюсь, моя "балалайка" кому-нибудь пригодиться. Если я в чем-то ошибся и можно проще, прошу поделиться своими наработками.