Пример реализации функции обратного вызова OnAllTrade внутри DLL

Автор записи: Дмитрий (Admin)
1 звезда2 звезды3 звезды4 звезды5 звезд (Голосов 4, среднее: 5,00 из 5)
Загрузка...

Qlua-csharp-connector-dll
Таким образом можно любую встроенную в QLua функцию заменить функцией в DLL, в результате скрипт QLua может содержать только одну строку подключения библиотеки, а всю обработку можно вынести в DLL, а из нее в C#.

Код QLua(Lua)
Код DLL

Если у Вас появились какие-то вопросы, задайте их в комментариях под статьей !!!

Добавить комментарий

Пример реализации функции обратного вызова OnAllTrade внутри DLL: 68 комментариев

  1. Добрый день!

    Загрузил в QUICK свою библиотеку (см. код ниже) из lua скрипта. В лог файл печатаются сообщения
    "LuaOnParam", но не "LuaOnParam: table".
    Подскажите пожалуйста, что не так, Почему на стеке не таблица?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    static int Lua_OnParam(lua_State *L)
    {
        LOG_INFO << "LuaOnParam";
     
        if (lua_istable(L, 1))
        {
            LOG_INFO << "LuaOnParam: table";
            std::string secCode = getFieldValue(L, "sec_code");
            std::string classCode = getFieldValue(L, "class_code"); 
            LOG_INFO << secCode << " " << classCode;
        }
        return 1;
    }
    extern "C" LUALIB_API int luaopen_cppconnector(lua_State *L)
    {
        lua_register(L, "OnInit", Lua_OnInit);
        lua_register(L, "OnParam", Lua_OnParam);
        return 0;
    }
      1. Дмитрий, спасибо!

        Да, действительно передается два значения. Мне удалось их прочитать со стека, но вот вопрос... как получить данные стакана по произвольному инструменту в контексте OnQuote коллбэка реализованного в DLL?
        Или нет альтернативы Вашему методу:
        - В QLua скрипте организуем буфер (стек), в который будут заноситься котировки (в виде строки) из OnQuote коллбэк.
        - Основной цикл QLua скрипта берет стакан из стека и вызывает функцию DLL.

  2. В общем, нашел решение как передать в функцию обработки события ДЛЛ ссылку на постоянный канал связи из-за пределов области видимости. Пока не пробовал, так что решения пока не привожу, может и не прокатить.
    Поговоришь с людьми, глядишь и что-то в голову придет.))

      1. В общем, сделал. Теперь инфа из main и инфа событий идут на сервер-приложение каждый по своему каналу связи. Все сводится к достаточно специфической работе с указателями из за смеси неуправляемого и управляемого кодов в ДЛЛ.

          1. Мы с вами это уже обсуждали пару недель назад, в другой теме.) https://quikluacsharp.ru/qlua-c-cpp-csharp/obmen-dannymi-mezhdu-dll-c-c-i-prilozheniem-c/
            А если вкратце, то несмотря на некоторую специфику этой смеси, в целом, многое становится гораздо проще и компактней в реализации.

            1. Т.е. Вы над какими-то частями кода ставите unmanaged, а над другими managed и от этого становиться код проще ? 🙂
              Простите за дилетантский вопрос, я в этой теме, можно сказать, ноль. Или Вы под управляемым кодом имеете в виду использование какой-то библиотеки, типа boost ?)

              1. Под управляемым кодом в терминологии Майкрософт (и общепринятой) понимается использование функциональности NET Framework. Скажем С# и VB - это чистый управляемый код. На С++ можно писать как на управляемом так и неуправляемом коде, и даже комбинировать их в одном флаконе. Только надо помнить, что это несколько разные с++ с разными правилами.
                ЗЫ Вообще-то, Java и Lua - тоже управляемый код.

                  1. DLL-ка еще только делается. Планируется все, кроме непосредственного взаимодействия с Луа делать на NET. Хотя и в функциях взаимодействия с Луа уже есть куски. Пока реализована вся связь с приложением и используется работа со строками.
                    Вообще, есть возможность гнать по каналу не строки, а сразу бинарные структуры, но это не в обозримом будущем.))

  3. На предмет событий Qlua, последовал совету документации и пишу события в таблицы, затем копаю таблицы из ДЛЛ в поиске новых строк для передачи.
    Понятно, вызов ДЛЛ из событий так или иначе возможен (предложенный Дмитрием вариант оч неплох), но что с этим делать дальше, куда лошадь запрягать мне абсолютно не ясно - функцию вызвали, таблицу получили, поток ушел, и "всепропало". А передать все куда-либо как? Создавать заново при каждом вызове клиента для канала передачи? - можно, но накладно.
    Другой вариант, создавать события и отлавливать их из другого потока, но тоже вопрос - а сможем ли мы их отловить? Потоки ведь не наши, т.е. опять межпроцессное взаимодействие.
    Есть еще варианты, но, похоже, по замыслу простая ДЛЛ разрастается до угрожающих размеров.))

    1. Что-то я не понял, что Вы имеете в виду под: "функцию вызвали, таблицу получили, поток ушел, и "всепропало"" ? Нужно Вам получать из квика обезличенные сделки, подписываетесь на OnAllTradeи получаете информацию по каждой новой сделке в DLL, а дальше можете все, что угодно делать с ней. Хоть прямо в DLL как-то ее обрабатывайте и отправляйте транзакции, хоть в C# ее переправляйте и там что-то с ней делайте. Вы со всеми статьями из данного раздела ознакомились? https://quikluacsharp.ru/category/qlua-c-cpp-csharp/
      Объясните что пропало-то? 🙂

      1. В основном, да, ознакомился.
        Вот что я не понимаю. Функции ДЛЛ из событий вызываются из другого потока Qlua. Канал передачи из ДЛЛ в приложение определен и работает в потоки main(). Каким образом функция из потока событий Qlua может добраться до канала передачи не создавая свой канал? Либо опять она должна общаться с потоком main() через дупло в виде стека Луа, и мы опять приходим к варианту, что должны отлавливать изменения в стеке Луа.
        Я вижу разницу с вариантом, предложенным в документации Qlua, только в том, что мы перекладываем преобразование таблицы из потока main() на поток функции обработки события.
        Или я действительно что-то не понял.)

      2. ЗЫ напомню вариант документации.

        1
        2
        3
        4
        5
        6
        
         function OnTrade(trade)
        table.sinsert(MAIN_QUEUE, {callback = "OnTrade", value = trade})
        end
        function OnAllTrade(all_trade)
        table.sinsert(MAIN_QUEUE, {callback = "OnAllTrade", value = all_trade})
        end
      3. В общем, вопрос решен в пользу создания событийной функцией клиента и передачи инфы о событии сразу на сервер приложения. Появляется второй канал связи, который создается при каждом вызове события, но по времени получается быстрее, чем передача через стек.

        1. Я не знаю что это за документацию такую Вы в пример приводите, но, как я понял, Вам не нравится на стороне DLL выуживать значения из переданного стека и Вы ищите другой вариант, по проще, или я ошибаюсь? 🙂

          1. Это документация АРКА по КЛуа -"Использование Lua в Рабочем месте QUIK", с. 9.
            По стеку Луа, Здесь Вы ошибаетесь, дело не в нем. Дело в концепции передачи событий из ДЛЛ в приложение. Вы получаете строку с результатом и запихиваете ее обратно в стек

            1
            2
            3
            
            lua_getglobal(L, "message");
                     lua_pushfstring(L, TradeInfo.c_str());
                     lua_pcall(L, 1, 0, 0);

            Я так понимаю, не ради "message", а с целью последующей отправки приложению через поток main(), который, когда очередь дойдет, выудит эту строку и отправит приложению. (что по идеологии соответствует предложенному АРКА). Само событие при этом задерживается на величину цикла опроса в ДЛЛ + время перекладывания из стека обратно в стек и повторного извлечения.
            Я же, сейчас, думаю о передаче события непосредственно приложению, минуя повторное запихивание в стек результата. Получается вроде быстрее, да и main освобождается. И, кстати, структура ДЛЛ вроде упрощается.

            1. message в данном примере используется только для того, чтобы запустив данный пример, Вы увидели, что он работает, никакого другого смысла в message здесь нет. Если Вы уже получили данные в DLL, зачем их снова получать?
              Дайте, пожалуйста, ссылку на эту документацию.

              1. Документация Quik - http://arqatech.com/ru/support/files/ Раздел - KeyGen, прочие утилиты и документация.
                Я уже объяснял (пытался)) в первом посте, что потоки событий и main в ДЛЛ - разные потоки. И поток событий не имеет доступа к функциям main обмена данных с приложением. И, стало быть, мы при каждом вызове событий вынуждены заново создавать канал связи.
                Либо передавать события в поток main и оттуда уже передавать приложению - что, косвенно, и предлагается в документации.

                1. Спасибо за ссылку. По поводу каналов связи все просто, объявляете в DLL переменную

                  1
                  2
                  3
                  
                  ...
                  lua_State *LuaState;
                  ...

                  А потом получаете в нее указатель на стек:

                  1
                  2
                  3
                  
                  static int forLua_OnAllTrade(lua_State *L)  {
                     LuaState = L;
                     ...

                  И используете уже её, там, где Вам нужно.

                  1. Эт понятно. Я о другом, или вас не понял.
                    Нужно как-то при каждом вызове в ДЛЛ события, не создавать заново канал связи между ДЛЛ и нашей торговой системой. Нужна ссылка в этих функциях на постоянный канал связи (в вашем случае MMF).
                    Имеем

                    1
                    2
                    3
                    4
                    5
                    
                       static int forLua_OnAllTrade(lua_State *L) static int forLua_OnAllTrade(lua_State *L) {
                    ссылка на постоянный канал связи?  TransmitChannel
                    .........
                    TransmitChannel.Transmit(str); //передаем данные приложению
                    }

                    Не грузитесь этим, я просто излагаю вопрос.
                    Можно, кстати эту ссылку получить через стек, но она не переживет уборщика мусора.)

                    1. Канал связи (MMF) создается ведь один раз при загрузке DLL, Вы же его не внутри функций обратного вызова создаете. Или Вы хотите, чтобы у Вас несколько экземпляров DLL одновременно по одному каналу связи общались с C# ? А если Вы о подключении, так там так и сделано, если уже есть открытый канал с таким именем, то подключаемся, если нет, то создаем.

                2. А в документации предлагается решение, чтобы как можно меньше тормозить квик. Я такое же решение в примере простого движка пропагандировал 🙂 https://quikluacsharp.ru/quik-qlua/primer-prostogo-torgovogo-dvizhka-simple-engine-qlua-lua/
                  Но это никакого отношения к DLL не имеет. Просто нужно запомнить одно: стараться как можно быстрее "отпустить" основной поток, не задерживая его на выполнение кода внутри функций обратного вызова, хоть в DLL, хоть в самом скрипте. Для выполнения всего алгоритма нужно в DLL так же подписаться на main и так же, как в скрипте, внутри этой функции выполнять всю работу.

          1. Ну там остались только Заголовочные файлы и точка входа, а все функции закомментированы, а что она скажет это же не выполняемый файл а DLL,

            1. Вы не могли бы послать мне скомпилированную DLL я бы проверил загрузку ее в QLUA, потому как DLL в C# через ImportDLL загружается и функцию C# видит

                    1. Да у вас все работает, даже когда ваш проект у себя перекомпилирую, спасибо огромное буду пользоваться вашим )))

                    2. Выложите, пожалуйста, еще раз проекты для VS

                    3. Спасибо скачать удалось. Но выдается ошибка при запуске .\QLuaCallbacks.lua:1: loop or previous error loading module 'QLuaCallbacks'

                    4. Судя по ошибке, у Вас где-то в процессах уже висит загруженная копия этой DLL, попробуйте windows перезагрузить.Я у себя запускал и этот скрипт и эту длл, все отлично работает. Если и перезагрузка не поможет, то откройте решение в Visual Studio и пересоберите DLL и замените ее на ту, что у что поместили в папку с терминалом до этого. Вы ведь поместили скачанную DLL в папку с терминалом?

                    5. Да, но пересборка dll ничего не дала. Буду разбираться. Подскажите для добавления обработки других событий нужно выполнять эти две функции с другими именами события и функции обработки?

                      //Добавляет функцию в стек
                      lua_pushcclosure(L, forLua_OnAllTrade, 0);
                      //Регистрирует её как коллбэк на OnAllTrade
                      lua_setglobal(L, "OnAllTrade");

                    6. Все заработало, я новичок в QUIK забыл включить внешние транзакции и открыть таблицу обезличенных заявок. Спасибо за ответы.

                    7. Пожалуйста, кстати, внешние транзакции можете отключить, они для этого не нужны, вроде бы как.

  4. Дмитрий, вы в функции forLua_OnAllTrade фактически парсите присланную Таблицу обезличенных сделок, зная ее структуру (т.е. зная какие у неё есть ключи). Как быть если мы не знаем структуру пришедшей таблицы? Можно ли как-нибудь добраться до количества и значения её ключей, а потом уже в цикле прочитать всю таблицу?

    1. Я, честно говоря, не пробовал, но думаю можно! Нужно почитать, потестировать, тогда станет ясно. Будет время, попробую. Думаю должен быть в Lua C API аналог цикла for key, value in pairs(table) do
      Есть книжка такая хорошая "Иерузалимски Р. Программирование на языке Lua. 3-е издание" там можно покопаться по данному вопросу (я с торрента ее какого-то брал в pdf формате 🙂 )

    1. Проверил, в том же потоке, что и другие функции обратного вызова (в основном), запускал такой скрипт:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      
      require("QLuaCallbacks");
       
      Run = true;
       
      function main()
      	message("main: "..QLuaCallbacks.GetCurrentThreadId())
      	while Run do
      		sleep(1);
      	end;
      end;
       
      function OnQuote()
      	message("OnQuote: "..QLuaCallbacks.GetCurrentThreadId())
      end;
       
      function OnStop()
      	Run = false;
      end;

      получил такой результат:

      перед этим, конечно, добавив в DLL такую функцию:

      1
      2
      3
      4
      5
      6
      
      static int forLua_GetCurrentThreadId(lua_State *L)
      {
         lua_pushnumber(L, GetCurrentThreadId());
       
         return(1);
      }

      и изменив в функции forLua_OnAllTrade строку lua_pushfstring(L, TradeInfo.c_str());
      на lua_pushfstring(L, ("OnAllTrade: " + std::to_string(GetCurrentThreadId())).c_str());

    2. разумеется, как и остальные коллбеки квика. Всё дело в том, что вы создаёте функцию OnAllTrade и регистрируете её в глобальной таблице. Далее, она подхватывается самой qlua

        1. "Дмитрий (Admin)
          28.11.2015 в 05:07
          А Вы случайно не знаете как сделать так, чтобы DLL выполнялась в отдельном потоке, как и main?
          "
          как много раз говорил на различных форумах:
          функция main - в глазах QLUA - такой же коллбек, как и другие. Только под неё вдобавок ещё создаётся отдельный поток в QLUA. Поэтому, если Вы создадите в своей dll эту функцию то, весь код, который будет внутри этой функции - будет выполняться в отдельном потоке, равно как и все функции, которые Вы внутри main будете вызывать.
          Именно так делают в некоторых коммерческих проектах роботорговцы. Авторов называть не буду, чтобы нескомпрометировать их творения.