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

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

Qlua-csharp-connector-dll
Для обмена данными между библиотекой DLL, написанной на языке C/C++ и приложением, написанном на языке C#, удобно и эффективно использовать Отображаемые в Памяти Файлы (MemoryMappedFile). По сути, это выделенный участок оперативной памяти компьютера (скорость!), который имеет свое уникальное имя и размер в байтах. Оба эти параметра задаются программистом. В дальнейшем можно, как читать из этой памяти, так и писать в нее, подключившись к ней в библиотеке DLL и в приложении C#.

Был проведен тест скорости обмена сообщениями, в тесте принимали участие следующие технологии: MemoryMappedFile, NamedPipes и Socket. Создавались по два отдельных приложения на C#, сервер и клиент, которые должны были обменяться друг с другом текстовыми сообщениями размером 120 символов 500 000 раз. На все это у них ушло следующее количество времени:
MemoryMappedFile: 1,5 секунды
NamedPipes: 12,5 секунд
Socket: 14 секунд

При этом, количество требуемого кода, так же, было меньше всего у MemoryMappedFile, по моему, выбор очевиден!

Пример создания и использования именованной памяти с именем "MyMemory" и размером 256 байт. В примере реализован следующий алгоритм:

 

QLua(Lua)

  • Подключает библиотеку DLL
  • Запускает функцию отправки сообщений в C#
  • Останавливает функцию отправки сообщений в C#
Код скрипта QLua:
C/C++

  • Библиотека DLL создает/подключается к именованной памяти.
  • Отправляет (записывает в память) текстовое сообщение: "Привет из C/C++".
  • Читает память с периодичностью в 1 секунду, если память стала чиста, сообщение отправляется вновь.
Код библиотеки DLL (C/C++):
C#

  • Приложение на C# создает/подключается к именованной памяти.
  • Читает память с периодичностью в 1 секунду, если в памяти появилось текстовое сообщение: "Привет из C/C++", выводит его в текстовое поле и очищает память, сообщая тем самым DLL что сообщение получено.
Код C#:
Теперь, после добавления библиотеки DLL в каталог терминала QUIK (туда, где файл "info.exe"), запуска скрипта QLua и запуска приложения C#, Вы увидите как на форме C#, с периодичностью в 1 секунду, появляются сообщения "Привет из C/C++":
Привет-из-C
Если у Вас появились какие-то вопросы, задайте их в комментариях под статьей !!!

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

Обмен данными между DLL (C/C++) и приложением C#: 94 комментария

  1. После плясок с бубном вокруг ДЛЛ, пришел к выводу, что MMF, ну и еще Named Pipes, наверное действительно самые простые и достаточно эффективные решения взаимодействия приложения с Луа-окружением. Наверное уйду в одну из этих технологий.
    Но здесь все просто пока передаешь один сигнал и нет дуплексного взаимодействия. Как только сигналов становится много (стакан, лента сделок, OHLSV, инфа о заявках, сделках и счетах) + запросы из приложения к Луа - начинаются проблемы. Т.е. нужно определяться интерфейсами и протоколами обмена, что, в общем, тоже нетривиальная задача.

    1. Я вообще для каждого канала данных отдельную память просто выделяю и все, никаких конфликтов. Например, выделяю память "all_trades", в DLL зарегистрирована функция обратного вызова OnAllTrade, которая пишет новые сделки в стек FIFO, читает из этого стека отдельный фоновый поток и пишет их в память, если она ожидает записи, в C#, так же, висит отдельный поток

      1
      2
      3
      
      new Thread(()=>{
         ...
      }).Start();

      который постоянно читает память, когда она ожидает чтения и сохраняет сделки в соответствующий массив, в котором любой другой поток всегда может найти актуальную информацию.
      А протокол простой 🙂 :
      DLL

      1
      2
      3
      4
      5
      
      //КОНСТАНТЫ СОСТОЯНИЯ ПАМЯТИ
      const char *QR = "_QR_"; // Память ожидает чтения Qlua (Qlua Read)
      const char *QW = "_QW_"; // Память ожидает записи Qlua (Qlua Write)
      const char *CR = "_CR_"; // Память ожидает чтения C# (C# Read)
      const char *CW = "_CW_"; // Память ожидает записи C# (C# Write)

      C#

      1
      2
      3
      4
      5
      
      //КОНСТАНТЫ СОСТОЯНИЯ ПАМЯТИ
      public string QR = "_QR_"; // Память ожидает чтения Qlua (Qlua Read)
      public string QW = "_QW_"; // Память ожидает записи Qlua (Qlua Write)
      public string CR = "_CR_"; // Память ожидает чтения C# (C# Read)
      public string CW = "_CW_"; // Память ожидает записи C# (C# Write)

      Прочитал первые 4 байта и понятно что можно, что нельзя, для примера памяти "all_trade" вообще 2-х констант достаточно.

  2. Здравствуйте. Спасибо Вам за этот раздел сайта. Книга Иерузалимского +Ваш сайт очень помогают в освоении. Книга - теория, а ваш сайт прикладные экземплы.
    Вот только с MemoryMappedFile не могу смириться.)) Поэтому хочу уже в ДЛЛ уйти в managed код, и его цеплять к приложению С#. А в приложении С# уже делать обратные вызовы и получать события QLua.
    В результате должны получить как бы C# API.
    Первый вопрос - передача событий и данных ДЛЛ в поток managed класса С++. Пока пытаюсь разобраться с генерацией событий в С++. Не сильно получается, с С++ уже давно не работал.

    1. Здравствуйте! Всегда пожалуйста. К сожалению, я не профессиональный программист, а самоучка, изучаю что-то по мере необходимости, потому только отдаленно понимаю что такое "поток managed класса С++". Вод здесь один хороший человек выложил пример как вообще без C++ обходиться, может быть это Вам поможет.
      https://quikluacsharp.ru/stati-uchastnikov/lua-c-bez-s-dll/

      1. Добрый день. Спасибо, я конечно читал эту статью, но мне хочется все-таки все взаимодействие с Луа сделать на С++. Я тоже не проф программист, но по работе всегда требовалось что-то сделать.)) Managed и unmanaged -это неуправляемый (чистый С++) и управляемый (NET) коды, и в С++ их можно совмещать в одном проекте, и даже в рамках одного срр файла. Уже пробовал, и это работает. Т.е., все можно делать в рамках VS, не используя сторонний компилятор (как в статье). Есть простенький экземпл, как это делается, могу прислать весь проект, если заинтересует.

        1. Если Вы данную DLL пробовали подгружать в скрипт и все прошло хорошо, то пришлите пример, буду рад, просто, насколько я знаю, для использования Net в DLL нужно включать поддержку CLR, а с ней Lua не дружит, хотя, скорее всего, я что-то не понимаю 🙂

          1. Пока это просто пример использования NET совместно с неуправляемым кодом с MSDN -иллюстрация возможности.
            В вашей (нашей) ДЛЛ уже включил CLR и все прошло штатно, но NET-к од пока не писал. Как только что-то получится - пришлю.

              1. https://msdn.microsoft.com/en-us/library/0adb9zxe.aspx
                В общем, не получилось. Net-код нормально работает внутри Lua dll (при работоспособности коннекта с Квик) и вся функциональность Net видна снаружи. DLL цепляется к Net приложению и компилируется. Но... ошибка исполнения при цепляниии ДЛЛ к приложению.
                Подумываю об организации callback из ДЛЛ.

                    1. Я, честно говоря, еще не могу одного понять, если бы DLL C++ подключалась к C#, то проблем бы не было, но она подключается к квику, и я не могу понять как запущенный экземпляр DLL увидит C# и как C# увидит этот экземпляр DLL.

                    2. Даже если бы мы к квику подключили DLL на C# написанную, то как из нее вызвать функцию приложения C# запущенного, это же два разных процесса, никак друг с другом не связанных.

                    3. Есть мысль, что нужно как-то создавать экземпляр какого-то класса в DLL и регистрировать его в глобальном пространстве, потом находить его из приложения C# и обращаться к его методам. Но это только мои догадки пока что 🙂

                    4. Хотя, вроде бы, экземпляры классов и так видны из вне, раньше что-то такое помню тестил, но даже примерно не помню уже как это все работает.

                    5. Отчего-то в Ваших постах отсутствует -"Ответить". Потому отвечаю как-бы сам себе.))
                      1. Объекты с# (или С++) в ДЛЛ запущенные из Луа и объекты ДЛЛ, запущенные из приложения - разные потоки. Т.е., внутри ДЛЛ Луа прекрасно общается с с#, но даже если мы его умудримся вывести наружу, то они все равно будет в потоке Луа.
                      Если вызываем ДЛЛ из приложения, то вызов и все колбэки уже будут в потоке приложения, если напрямую не работают со стеком Луа по указателю.
                      Т.е., взаимодействие (дуплекс) при вызовах м.б. только через события и делегаты.
                      Я такие ДЛЛки видел и работал с ними (там полный дуплекс), но как они устроены представления не имею.

                    1. По настройкам сайта допускается максимум 10 вложенных комментариев, так что, либо пишите новый комментарий как комментарий к самой статье, т.е. в новой ветке, либо как ответ, где есть кнопка ответить, я, в любом случае, их увижу.

  3. Дмитрий, подскажите пожалуйста как бы выглядела передача из DLL CPP в C# передача инфо о сделке (с помощью выделенной памяти) не через строку символов, а напрямую через представление инфо байтами? Просто плохо знаю CPP
    Николай

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

  4. Да )) это не те компы)))) с которых начинал в 85 году, на ассемблере )))))) делали всякие контроллеры- сейчас программирование просто хобби
    Тогда еще вопрос по созданию оконного приложения на С#, наверно если уж изучать и писать (сугубо для себя) то наверно окна на WPF. В интернете огромное кол-во примеров но как дилетанту трудно отличить качественные примеры от ....
    Дмитрий, может у Вас есть пару сайтов на примете на эту тему, которые Вы могли бы по рекомендовать, для начального старта.
    С уважением, Александр

    1. К сожалению, Александр, в данном вопросе ничего толкового не могу Вам подсказать, потому что сам хочу WPF изучить, но времени не хватает, т.к. хочу сильно расширить функциональные возможности данного сайта, добавить много нового и нужного, для этого пишу его с нуля на C# ASP.NET MVC 5 и все время уходит на изучение данной технологии и создание элементов:) По-этому, по старинке, накидываю на форму стандартные элементы управления, навешиваю на них обработчики событий, а графики и т.п. отрисовываю в PictureBox из примитивов, делаю, например, функцию, которая рисует свечу в нужной координате, а потом вызываю ее в цикле с нужными координатами и получаю график и т.д. и т.п.

  5. Безусловно - "Проще всего из чисел создавать строку с разделителями «;», типа: «1;2;3;4;5;6;7;8;9;10;11;12;13;14;15″ и ее ..." и это первое что приходит в голову.
    Сомнение было в скорости выполнения. Каждый раз кодируешь в текст потом обратно.
    Но если Вы таким образом передаете стакан и получаются малые временные в задержки.
    Тогда сделаю также. Спасибо за совет.
    Оставлю все как сейчас в текстовом формате.

  6. Дмитрий, добрый день. Спасибо Вам за сайт -большая проделана работа.
    Набирал примеры все сразу работало.
    Решил используя Вашу идею обмена через CreateFileMapping в своем приводе но записать/прочитать структуру из 15 чисел не получилось. Хочу организовать обмен между С++ и C#
    Помогите пожалуйста, если Вам это не трудно .
    С уважением, Александр

    1. Здравствуйте, Александр! Спасибо за добрые слова! Всегда рад помочь. Проще всего из чисел создавать строку с разделителями ";", типа: "1;2;3;4;5;6;7;8;9;10;11;12;13;14;15" и ее передавать из C/C++ в C# посредством FileMapping. А на стороне C# посредством Split(';') получать из строки массив элементов. О том, как передавать массив из QLua в C/C++ я добавлял недавно пример в конце статьи "ВЗАИМОДЕЙСТВИЕ LUA И БИБЛИОТЕКИ DLL, НАПИСАННОЙ НА C/C++". Но еще проще решить такую задачу по аналогии с примером, описанным в статье "ОТПРАВКА СТАКАНА ИЗ QUIK (QLUA) В ПРИЛОЖЕНИЕ C#". Что именно у Вас не получается?

    1. Здравствуйте, Игорь! Благодарю за вопрос! Я использую этот механизм, как для получения данных из QUIK в C#, так и для отправки торговых приказов в QUIK. А вообще, спектр применения очень широк и зависит только от Вашей фантазии, ведь можно передать 1 байт информации, а можно и целый файл.

      Если Вас заинтересовал данный вопрос, то рекомендую ознакомиться со следующими материалами: 1, 2, 3.

      P.S. Еще, данная технология применяется когда нужно обрабатывать объемный файл и скорость обработки критична. Тогда файл размещается в оперативной памяти. Работа с ним ведется так же, как, если бы он был на диске, но скорость работы с оперативной памятью компьютера намного выше.

      1. Спасибо за подробный ответ.
        Скажите, а применим подобный метод передачи данных при передачи больших объемов? например если речь идет о Таблице всех сделок, стакана и некоторых других данных?
        1) Я понимаю, что технически это реализуемо, но какова скорость такого взаимодействия?
        2) Для передачи разных данных нужно будет создавать свою область данных? Например одна для Таблицы всех сделок, другая для стакана, третья для чего-либо еще ... Не очень удобно.
        3) Если нужно также передавать команды в QUIK, то как я понимаю примерно так:
        function main()
        -- Запускает функцию отправки сообщений в C#
        QluaCSharpConnector.StartSendHi();
        -- Запускает функцию приема команд из C#
        cmd = QluaCSharpConnector.RecieveCmd();
        .....
        -- Обеспечивает работу скрипта и библиотеки до остановки скрипта пользователем
        while IsRun do
        sleep(1000);
        end;
        end;
        При этом отправка команд и получения будут обрабатываться последовательно, пока не отработает функция отправки команды, не запуститься функция получения команды.
        4) Почему вы выбрали такой способ взаимодействия, а не, к примеру взаимодействие через COM интерфейс на LUA?

        Старался излагать доходчиво вопросы, извиняюсь если где-то не получилось. Мои знания пока что по большей теоретические.

        1. MemoryMappedFile технология специально создана для работы с большими объемами данных, тем более, таблица всех сделок, стакан и т.п. не таких большого объемов, как может показаться на первый взгляд.
          1) Вы правы, технически это довольно легко реализуемо, скорость работы с оперативной памятью зависит от конфигурации Вашего компьютера. В любом случае, сам QUIK тоже работает из оперативной памяти и обмениваться данными на скорости большей, чем позволяет оперативная память, не сможет.
          2) Теоретически, можно использовать и один экземпляр MemoryMappedFile, но вряд ли это будет удобно. Мне, лично, конструкция, когда для каждого потока данных выделен свой канал памяти показалась очень удобной. Тем более, создав один канал и поняв как он работает, не сложно будет размножить его простым копированием кода с изменением имен каналов и функций обращения.
          3) Не совсем так. Вот, специально для Вас, небольшую статью с примерами написал (надеюсь и другим пригодится).
          4) Мне этот способ показался более простым и понятным. У меня не очень приятные ощущения остались от использования ранее COM, честно говоря. Еще для использования COM в Lua-скрипте нужно больше строк кода писать, а во всей этой конструкции: «QUIK-Lua-C#», QUIK является самым слабым звеном, как по скорости, так и по надежности, а соответственно и Lua. По этой причине, я для себя принял решение максимально абстрагироваться от терминала и использовать его только как инструмент подключения к бирже, а всю логику и обработку/хранение данных перенести в C#, как, на мой взгляд, наилучшее на сегодня программное решение, как для понимания, так и по функциональности.

          P.S. Рад был ответить на Ваши вопросы, надеюсь, что немного помог разобраться!

          1. Позвольте задам еще один вопрос 🙂
            В случае обмена данными через MemoryMappedFile возникнет такая проблема проблему:
            допустим нужно передать какие-то часто меняющиеся данные из Lua в С#, пускай это будет стакан. В Lua мы реализуем метод OnQuote, в котором получаем данные стакана и передаем их в функцию из DLL, назовем ее QluaCSharpConnector.TakeSS(). В ней, в цикле, полученные из LUA данные заносятся в файл памяти, если там не ноль. На C# мы реализуем цикл, который проверяет наличие данных в памяти - если там не 0, значит данные есть, забираем их и записываем 0.
            Но на самом может случится так (и случится), что функция из DLL QluaCSharpConnector.TakeSS() получит из Lua новую порцию данных, а программа на C# еще не успела забрать/обработать предыдущие данные в файле памяти. В этом случае новые данные будут либо утеряны, либо DLL функция QluaCSharpConnector.TakeSS() будет ждать когда файл памяти очистится и будет готов записи новой порции данных. А что если в момент ожидания в LUA снова сработает OnQuote?