Таким образом можно любую встроенную в QLua функцию заменить функцией в DLL, в результате скрипт QLua может содержать только одну строку подключения библиотеки, а всю обработку можно вынести в DLL, а из нее в C#.
Код QLua(Lua)
1 2 3 4 5 6 7 8 9 10 11 12 13 | require("QLuaCallbacks"); Run = true; function main() while Run do sleep(1); end; end; function OnStop() Run = false; end; |
Код DLL
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 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | #include <windows.h> #include <string> //=== Необходимые для Lua константы ============================================================================// #define LUA_LIB #define LUA_BUILD_AS_DLL //=== Заголовочные файлы LUA ===================================================================================// extern "C" { #include "Lua\lauxlib.h" #include "Lua\lua.h" } //=== Стандартная точка входа для DLL ==========================================================================// BOOL APIENTRY DllMain(HANDLE hModule, DWORD fdwReason, LPVOID lpReserved) { return TRUE; } struct DateTime { int mcs; //Микросекунды int ms; //Миллисекунды int sec; //Секунды int min; //Минуты int hour; //Часы int day; //День int week_day; //Номер дня недели int month; //Месяц int year; //Год }; std::string getFieldValue(lua_State *L, const char *key) { std::string result; lua_pushstring(L, key); /* поместить ключ на стек */ lua_gettable(L, -2); /* получить значение */ result = lua_tostring(L, -1); lua_pop(L, 1); return result; } DateTime getDateTimeField(lua_State *L) { DateTime datetime; lua_pushstring(L, "datetime"); /* поместить ключ на стек */ lua_gettable(L, -2); /* получить таблицу datetime */ lua_pushstring(L, "mcs"); //Микросекунды lua_gettable(L, -2); datetime.mcs = lua_tonumber(L, -1); lua_pop(L, 1); lua_pushstring(L, "ms"); //Миллисекунды lua_gettable(L, -2); datetime.ms = lua_tonumber(L, -1); lua_pop(L, 1); lua_pushstring(L, "sec"); //Секунды lua_gettable(L, -2); datetime.sec = lua_tonumber(L, -1); lua_pop(L, 1); lua_pushstring(L, "min"); //Минуты lua_gettable(L, -2); datetime.min = lua_tonumber(L, -1); lua_pop(L, 1); lua_pushstring(L, "hour"); //Часы lua_gettable(L, -2); datetime.hour = lua_tonumber(L, -1); lua_pop(L, 1); lua_pushstring(L, "day"); //День lua_gettable(L, -2); datetime.day = lua_tonumber(L, -1); lua_pop(L, 1); lua_pushstring(L, "week_day"); //Номер дня недели lua_gettable(L, -2); datetime.week_day = lua_tonumber(L, -1); lua_pop(L, 1); lua_pushstring(L, "month"); //Месяц lua_gettable(L, -2); datetime.month = lua_tonumber(L, -1); lua_pop(L, 1); lua_pushstring(L, "year"); //Год lua_gettable(L, -2); datetime.year = lua_tonumber(L, -1); lua_pop(L, 1); lua_pop(L, 1); return datetime; } //=== Реализация функций обратного вызова ====================================================================// static int forLua_OnAllTrade(lua_State *L) // { // Проверяет, является-ли первый элемент стека массивом (таблицей Lua) if (lua_istable(L, 1)) { std::string trade_num = getFieldValue(L, "trade_num"); // Номер сделки в торговой системе std::string flags = getFieldValue(L, "flags"); // Набор битовых флагов std::string price = getFieldValue(L, "price"); // Цена std::string qty = getFieldValue(L, "qty"); // Количество бумаг в последней сделке в лотах std::string value = getFieldValue(L, "value"); // Объем в денежных средствах std::string accruedint = getFieldValue(L, "accruedint"); // Накопленный купонный доход std::string yield = getFieldValue(L, "yield"); // Доходность std::string settlecode = getFieldValue(L, "settlecode"); // Код расчетов std::string reporate = getFieldValue(L, "reporate"); // Ставка РЕПО(%) std::string repovalue = getFieldValue(L, "repovalue"); // Сумма РЕПО std::string repo2value = getFieldValue(L, "repo2value"); // Объем выкупа РЕПО std::string repoterm = getFieldValue(L, "repoterm"); // Срок РЕПО в днях std::string sec_code = getFieldValue(L, "sec_code"); // Код бумаги заявки std::string class_code = getFieldValue(L, "class_code"); // Код класса DateTime datetime = getDateTimeField(L); // Дата и время std::string period = getFieldValue(L, "period"); // Период торговой сессии.Возможные значения : «0» – Открытие; «1» – Нормальный; «2» – Закрытие if (sec_code == "SBER") { std::string TradeInfo = "trade_num=" + trade_num + " " + "flags=" + flags + " " + "price=" + price + " " + "qty=" + qty + " " + "value=" + value + " " + "accruedint=" + accruedint + " " + "yield=" + yield + " " + "settlecode=" + settlecode + " " + "reporate=" + reporate + " " + "repovalue=" + repovalue + " " + "repo2value=" + repo2value + " " + "repoterm=" + repoterm + " " + "sec_code=" + sec_code + " " + "class_code=" + class_code + " " + "time=" + std::to_string(datetime.hour) + ":" + std::to_string(datetime.min) + ":" + std::to_string(datetime.sec) + "." + std::to_string(datetime.mcs) + " " + "trade_num=" + trade_num + " " + "period=" + period; //Выводит сообщение в терминале с информацией по сделке lua_getglobal(L, "message"); lua_pushfstring(L, TradeInfo.c_str()); lua_pcall(L, 1, 0, 0); } } return 0; } //=== Регистрация реализованных в dll функций, чтобы они стали "видимы" для Lua ================================// static struct luaL_reg ls_lib[] = { { NULL, NULL } }; //=== Регистрация названия библиотеки, видимого в скрипте Lua ==================================================// extern "C" LUALIB_API int luaopen_QLuaCallbacks(lua_State *L) { luaL_openlib(L, "QLuaCallbacks", ls_lib, 0); //Добавляет функцию в стек lua_pushcclosure(L, forLua_OnAllTrade, 0); //Регистрирует её как коллбэк на OnAllTrade lua_setglobal(L, "OnAllTrade"); return 0; } |
Если у Вас появились какие-то вопросы, задайте их в комментариях под статьей !!!
Здравствуйте!
В блоке кода:
строка: luaL_openlib(L, "QLuaCallbacks", ls_lib, 0); выдает ошибку: luaL_openlib - Идентификатор не определен.
Использую lua54.lib. Как исправить если ошибка из-за перехода на следующую версию луа?
Здравствуйте, могу поделиться ссылкой на решение VS, правд там 5.3.5 используется
https://1drv.ms/u/s!AsAAhw-6vq0mjsYcg5hJs65jNH_xyg?e=ufe1WS
Здравствуйте! Не могу понять. Все собралось без ошибок, а при запуске скрипта в Квике нет сообщений, жму остановить и терминал виснет наглухо бледнея. Может кто в курсе, что не так?
Здравствуйте, столкнулся с проблемой main и прочих коллбеков, я так понимаю это два разных потока с одним стеком? Порой они начинают одновременно обращаться к стеку плодя исключения. Как можно справиться с этим?
Здравствуйте, я, честно говоря, не сталкивался с такой проблемой, даже не знаю что подсказать, а там точно один стек на оба потока? Если завести 2 переменных, в одной хранить стек, который получили из main, во второй хранить и постоянно обновлять стек из коллбеков. Возможно ерунду предложил, но других мыслей нет 🙂
Ну вот к примеру строка из кода выше "extern "C" LUALIB_API int luaopen_QLuaCallbacks(lua_State *L)" Мы получаем только один lua_State *L я всегда думал что это один общий. Возможно ли вытянуть еще какой?
Когда вызывается коллбек, в него же тоже передается стек:
static int forLua_OnAllTrade(lua_State *L)
с ним и пробуйте работать
Ай-яй-яй, сколько боли и страданий, а все оказалось так просто! Спасибо огромное!
Всегда пожалуйста 🙂
Добрый день!
Загрузил в QUICK свою библиотеку (см. код ниже) из lua скрипта. В лог файл печатаются сообщения
"LuaOnParam", но не "LuaOnParam: table".
Подскажите пожалуйста, что не так, Почему на стеке не таблица?
Здравствуйте! Потому что в OnParam не таблица передается, а 2 значения: код класса и код бумаги. Посмотрите руководство.
Дмитрий, спасибо!
Да, действительно передается два значения. Мне удалось их прочитать со стека, но вот вопрос... как получить данные стакана по произвольному инструменту в контексте OnQuote коллбэка реализованного в DLL?
Или нет альтернативы Вашему методу:
- В QLua скрипте организуем буфер (стек), в который будут заноситься котировки (в виде строки) из OnQuote коллбэк.
- Основной цикл QLua скрипта берет стакан из стека и вызывает функцию DLL.
Пожалуйста! getQuoteLevel2 вызывайте в OnQuote, так же, как в скрипте и все.
В общем, нашел решение как передать в функцию обработки события ДЛЛ ссылку на постоянный канал связи из-за пределов области видимости. Пока не пробовал, так что решения пока не привожу, может и не прокатить.
Поговоришь с людьми, глядишь и что-то в голову придет.))
Уверен, что Вы справитесь 🙂
В общем, сделал. Теперь инфа из main и инфа событий идут на сервер-приложение каждый по своему каналу связи. Все сводится к достаточно специфической работе с указателями из за смеси неуправляемого и управляемого кодов в ДЛЛ.
А зачем Вам в DLL управляемый код?
Мы с вами это уже обсуждали пару недель назад, в другой теме.) https://quikluacsharp.ru/qlua-c-cpp-csharp/obmen-dannymi-mezhdu-dll-c-c-i-prilozheniem-c/
А если вкратце, то несмотря на некоторую специфику этой смеси, в целом, многое становится гораздо проще и компактней в реализации.
Т.е. Вы над какими-то частями кода ставите unmanaged, а над другими managed и от этого становиться код проще ? 🙂
Простите за дилетантский вопрос, я в этой теме, можно сказать, ноль. Или Вы под управляемым кодом имеете в виду использование какой-то библиотеки, типа boost ?)
Под управляемым кодом в терминологии Майкрософт (и общепринятой) понимается использование функциональности NET Framework. Скажем С# и VB - это чистый управляемый код. На С++ можно писать как на управляемом так и неуправляемом коде, и даже комбинировать их в одном флаконе. Только надо помнить, что это несколько разные с++ с разными правилами.
ЗЫ Вообще-то, Java и Lua - тоже управляемый код.
И какую функциональность .NET Вы используете в DLL, если не секрет?
DLL-ка еще только делается. Планируется все, кроме непосредственного взаимодействия с Луа делать на NET. Хотя и в функциях взаимодействия с Луа уже есть куски. Пока реализована вся связь с приложением и используется работа со строками.
Вообще, есть возможность гнать по каналу не строки, а сразу бинарные структуры, но это не в обозримом будущем.))
Ладно, удачи в начинаниях 🙂
На предмет событий Qlua, последовал совету документации и пишу события в таблицы, затем копаю таблицы из ДЛЛ в поиске новых строк для передачи.
Понятно, вызов ДЛЛ из событий так или иначе возможен (предложенный Дмитрием вариант оч неплох), но что с этим делать дальше, куда лошадь запрягать мне абсолютно не ясно - функцию вызвали, таблицу получили, поток ушел, и "всепропало". А передать все куда-либо как? Создавать заново при каждом вызове клиента для канала передачи? - можно, но накладно.
Другой вариант, создавать события и отлавливать их из другого потока, но тоже вопрос - а сможем ли мы их отловить? Потоки ведь не наши, т.е. опять межпроцессное взаимодействие.
Есть еще варианты, но, похоже, по замыслу простая ДЛЛ разрастается до угрожающих размеров.))
Что-то я не понял, что Вы имеете в виду под: "функцию вызвали, таблицу получили, поток ушел, и "всепропало"" ? Нужно Вам получать из квика обезличенные сделки, подписываетесь на OnAllTradeи получаете информацию по каждой новой сделке в DLL, а дальше можете все, что угодно делать с ней. Хоть прямо в DLL как-то ее обрабатывайте и отправляйте транзакции, хоть в C# ее переправляйте и там что-то с ней делайте. Вы со всеми статьями из данного раздела ознакомились? https://quikluacsharp.ru/category/qlua-c-cpp-csharp/
Объясните что пропало-то? 🙂
В основном, да, ознакомился.
Вот что я не понимаю. Функции ДЛЛ из событий вызываются из другого потока Qlua. Канал передачи из ДЛЛ в приложение определен и работает в потоки main(). Каким образом функция из потока событий Qlua может добраться до канала передачи не создавая свой канал? Либо опять она должна общаться с потоком main() через дупло в виде стека Луа, и мы опять приходим к варианту, что должны отлавливать изменения в стеке Луа.
Я вижу разницу с вариантом, предложенным в документации Qlua, только в том, что мы перекладываем преобразование таблицы из потока main() на поток функции обработки события.
Или я действительно что-то не понял.)
ЗЫ напомню вариант документации.
В общем, вопрос решен в пользу создания событийной функцией клиента и передачи инфы о событии сразу на сервер приложения. Появляется второй канал связи, который создается при каждом вызове события, но по времени получается быстрее, чем передача через стек.
Я не знаю что это за документацию такую Вы в пример приводите, но, как я понял, Вам не нравится на стороне DLL выуживать значения из переданного стека и Вы ищите другой вариант, по проще, или я ошибаюсь? 🙂
Это документация АРКА по КЛуа -"Использование Lua в Рабочем месте QUIK", с. 9.
По стеку Луа, Здесь Вы ошибаетесь, дело не в нем. Дело в концепции передачи событий из ДЛЛ в приложение. Вы получаете строку с результатом и запихиваете ее обратно в стек
Я так понимаю, не ради "message", а с целью последующей отправки приложению через поток main(), который, когда очередь дойдет, выудит эту строку и отправит приложению. (что по идеологии соответствует предложенному АРКА). Само событие при этом задерживается на величину цикла опроса в ДЛЛ + время перекладывания из стека обратно в стек и повторного извлечения.
Я же, сейчас, думаю о передаче события непосредственно приложению, минуя повторное запихивание в стек результата. Получается вроде быстрее, да и main освобождается. И, кстати, структура ДЛЛ вроде упрощается.
message в данном примере используется только для того, чтобы запустив данный пример, Вы увидели, что он работает, никакого другого смысла в message здесь нет. Если Вы уже получили данные в DLL, зачем их снова получать?
Дайте, пожалуйста, ссылку на эту документацию.
Документация Quik - http://arqatech.com/ru/support/files/ Раздел - KeyGen, прочие утилиты и документация.
Я уже объяснял (пытался)) в первом посте, что потоки событий и main в ДЛЛ - разные потоки. И поток событий не имеет доступа к функциям main обмена данных с приложением. И, стало быть, мы при каждом вызове событий вынуждены заново создавать канал связи.
Либо передавать события в поток main и оттуда уже передавать приложению - что, косвенно, и предлагается в документации.
Спасибо за ссылку. По поводу каналов связи все просто, объявляете в DLL переменную
А потом получаете в нее указатель на стек:
И используете уже её, там, где Вам нужно.
Эт понятно. Я о другом, или вас не понял.
Нужно как-то при каждом вызове в ДЛЛ события, не создавать заново канал связи между ДЛЛ и нашей торговой системой. Нужна ссылка в этих функциях на постоянный канал связи (в вашем случае MMF).
Имеем
Не грузитесь этим, я просто излагаю вопрос.
Можно, кстати эту ссылку получить через стек, но она не переживет уборщика мусора.)
Канал связи (MMF) создается ведь один раз при загрузке DLL, Вы же его не внутри функций обратного вызова создаете. Или Вы хотите, чтобы у Вас несколько экземпляров DLL одновременно по одному каналу связи общались с C# ? А если Вы о подключении, так там так и сделано, если уже есть открытый канал с таким именем, то подключаемся, если нет, то создаем.
А в документации предлагается решение, чтобы как можно меньше тормозить квик. Я такое же решение в примере простого движка пропагандировал 🙂 https://quikluacsharp.ru/quik-qlua/primer-prostogo-torgovogo-dvizhka-simple-engine-qlua-lua/
Но это никакого отношения к DLL не имеет. Просто нужно запомнить одно: стараться как можно быстрее "отпустить" основной поток, не задерживая его на выполнение кода внутри функций обратного вызова, хоть в DLL, хоть в самом скрипте. Для выполнения всего алгоритма нужно в DLL так же подписаться на main и так же, как в скрипте, внутри этой функции выполнять всю работу.
Собственно, так и делаю.
QLUA не загружает DLL выдает сообщение The specified procedure could not be found Что это значит?
Функцию какую-то найти не может, Вы что-то меняли в приведенном примере?
Не менял, более того даже если закомментировать весь код на c++ сообщение от квика тоже
Версии VS и QUIK могут как-то влиять на процесс?
Как понять закомментировали весь код на C++ ? Т.е. Вы закомментировали весть код в исходном файле DLL, а потом ее скомпилировали, и VS ничего не сказала, что у Вас нет кода в файле?
Ну там остались только Заголовочные файлы и точка входа, а все функции закомментированы, а что она скажет это же не выполняемый файл а DLL,
я просто думал Вы и точку входа закомментировали 🙂 А у Вас проект DLL для x86 сделан? Нужно x86
да
Вы не могли бы послать мне скомпилированную DLL я бы проверил загрузку ее в QLUA, потому как DLL в C# через ImportDLL загружается и функцию C# видит
а у Вас какая версия и разрядность OC ?
10-ка 64
у меня такая же, тогда сейчас попробую
попробовал, все работает:
DLL: https://cloud.mail.ru/public/91tQ/wAMaM5PQN
все решение VS: https://cloud.mail.ru/public/6Sh9/J38VZKGBa
Да у вас все работает, даже когда ваш проект у себя перекомпилирую, спасибо огромное буду пользоваться вашим )))
Выложите, пожалуйста, еще раз проекты для VS
Сейчас восстановлю проект и выложу. Тех файлов у меня уже нет просто.
У Вас по такой ссылке получится скачать?
https://1drv.ms/f/s!AsAAhw-6vq0mhbUfvimEevRUUUKw0A
Спасибо скачать удалось. Но выдается ошибка при запуске .\QLuaCallbacks.lua:1: loop or previous error loading module 'QLuaCallbacks'
Попробуйте перезапустить терминал
Та же ошибка
Судя по ошибке, у Вас где-то в процессах уже висит загруженная копия этой DLL, попробуйте windows перезагрузить.Я у себя запускал и этот скрипт и эту длл, все отлично работает. Если и перезагрузка не поможет, то откройте решение в Visual Studio и пересоберите DLL и замените ее на ту, что у что поместили в папку с терминалом до этого. Вы ведь поместили скачанную DLL в папку с терминалом?
У Вас получилось?
Да, но пересборка dll ничего не дала. Буду разбираться. Подскажите для добавления обработки других событий нужно выполнять эти две функции с другими именами события и функции обработки?
//Добавляет функцию в стек
lua_pushcclosure(L, forLua_OnAllTrade, 0);
//Регистрирует её как коллбэк на OnAllTrade
lua_setglobal(L, "OnAllTrade");
Да, и саму функцию обработки новую тоже в код добавить, так же, как forLua_OnAllTrade добавлена.
Все заработало, я новичок в QUIK забыл включить внешние транзакции и открыть таблицу обезличенных заявок. Спасибо за ответы.
Пожалуйста, кстати, внешние транзакции можете отключить, они для этого не нужны, вроде бы как.
Всегда пожалуйста 🙂
Дмитрий, вы в функции forLua_OnAllTrade фактически парсите присланную Таблицу обезличенных сделок, зная ее структуру (т.е. зная какие у неё есть ключи). Как быть если мы не знаем структуру пришедшей таблицы? Можно ли как-нибудь добраться до количества и значения её ключей, а потом уже в цикле прочитать всю таблицу?
Я, честно говоря, не пробовал, но думаю можно! Нужно почитать, потестировать, тогда станет ясно. Будет время, попробую. Думаю должен быть в Lua C API аналог цикла for key, value in pairs(table) do
Есть книжка такая хорошая "Иерузалимски Р. Программирование на языке Lua. 3-е издание" там можно покопаться по данному вопросу (я с торрента ее какого-то брал в pdf формате 🙂 )
Да, Роберту я сейчас плотно почитываю, но пока по этому вопросу ничего не нашел. И гугл предательски молчит 🙂 .
Спасибо, за тест!
Всегда пожалуйста!
При такой реализации метод ONALLTRADE будет выполняться в основном потоке, как и в обычном случае? Или что-то меняется?
Проверил, в том же потоке, что и другие функции обратного вызова (в основном), запускал такой скрипт:
получил такой результат:

перед этим, конечно, добавив в DLL такую функцию:
и изменив в функции forLua_OnAllTrade строку lua_pushfstring(L, TradeInfo.c_str());
на lua_pushfstring(L, ("OnAllTrade: " + std::to_string(GetCurrentThreadId())).c_str());
разумеется, как и остальные коллбеки квика. Всё дело в том, что вы создаёте функцию OnAllTrade и регистрируете её в глобальной таблице. Далее, она подхватывается самой qlua
А Вы случайно не знаете как сделать так, чтобы DLL выполнялась в отдельном потоке, как и main?
"Дмитрий (Admin)
28.11.2015 в 05:07
А Вы случайно не знаете как сделать так, чтобы DLL выполнялась в отдельном потоке, как и main?
"
как много раз говорил на различных форумах:
функция main - в глазах QLUA - такой же коллбек, как и другие. Только под неё вдобавок ещё создаётся отдельный поток в QLUA. Поэтому, если Вы создадите в своей dll эту функцию то, весь код, который будет внутри этой функции - будет выполняться в отдельном потоке, равно как и все функции, которые Вы внутри main будете вызывать.
Именно так делают в некоторых коммерческих проектах роботорговцы. Авторов называть не буду, чтобы нескомпрометировать их творения.
Спасибо за ответ!
to Admin,
если есть вопросы - задавайте их здесь: vk.com/sam063rus
Хорошо, спасибо!