1

Тема: Создание собственной DLL на C++ для LUA (в QUIK)

(Обновлено 09.01.2021 - добавлен вариант сборки для Quik 8.11, x64, Lua5.4)

Ссылки на проект, который можно использовать как основу и "болванку" для вашей библиотеки. Уже все настроено как надо, собрано в кучку. Проект для VS2015. Также можно открыть в более новой версии VS.

В приведенном готовом проекте по ссылкам выше предусмотрены следующие варианты сборки в зависимости от требуемой версии QUIK (указано в формате: Solution Configurations | Solution Platforms):

  • Release / Debug | Win32 - для QUIK 6.x / 7.x (x32, Lua5.1)

  • Release / Debug | x64 - для QUIK 8.0...8.4 (x64, Lua5.1)

  • Release-Lua53 / Debug-Lua53 | x64 - для QUIK 8.5 и далее (x64, Lua5.3)

  • Release-Lua54 / Debug-Lua54 | x64 - для QUIK 8.11 и далее (x64, Lua5.4)

Есть вопросы? что-то не понятно? Вэлком в комментарии.

2

Re: Создание собственной DLL на C++ для LUA (в QUIK)

Написание собственных .dll-библиотек на С++ для подключения их к коду Lua (QLua) и вызов своих функций из этих библиотек

Здесь примеры будут рассматриваться на C++ (т.к. именно этот и только этот язык рассмотрен в документации LUA), но если у кого-то будет острое желание делать внешние модули на другом языке - пишите в комментариях. (Есть отдельная тема про Delphi / Lazarus.)

Скажу сразу: здесь не планируется и не будет исчерпывающего описания взаимодействия LUA и Си. Просто потому, что такого материала в интернете просто завались, ничего нового я не открою.
Однако хотелось бы привести описание шагов, позволяющих сделать «быстрый старт» в написании своей библиотеки, попутно указав на некоторые неочевидные нюансы настройки проекта.

Ссылки: официальная документация по сращиванию LUA и C, эта документация также доступна по-русски.

Начну в каком-то смысле «с конца».
Чтобы подключить внешнюю DLL библиотеку к LUA, в скрипте необходимо необходимо вписать строчку:

luacdll = require("luacdll")

В результате выполнения этой строчки в LUA будет закружена библиотека с именем luacdll.dll (причем расширение в require не указывается), и из этой библиотеки будет прочитана информация об имеющихся в ней функциях, доступных из LUA (позже мы увидим каким именно образом).
Для простоты и надежности я очень советую положить скомпилированный dll-файл в тот же каталог, где расположен сам терминал QUIK, это проще и надежнее всего. Хотя можно настроить в LUA-скрипте содержимое переменной package.cpath до строки с require, если очень хочется.

Уточнение для QUIK 8.11 и более новых версий по поводу расположения файла скомпилированного dll-файла. Начиная с этой версии, для выполнения скриптов доступен выбор версии интерпретатора Lua: 5.3 или 5.4. Это добавляет проблем с выбором места, где расположить изготовленную нами библиотеку. Если вы гарантированно используете лишь одну версию (либо 5.3, либо 5.4) для выполнения всех своих скриптов - то можете также положить библиотеку в корень терминала QUIK. Это будет просто и надёжно. Однако, если вы планируете запускать скрипты с разными версиями интерпретатора, что придется держать 2 версии библиотеки (скомпилированные для Lua5.3 и Lua5.4) и подгружать в скрипт соответствующую версию библиотеки (при использовании неподходящей сборки dll-библиотеки непременно будут проблемы в работе библиотеки). Подробнее этот момент рассмотрен в отдельной ветке форума.

Настройка C++ проекта

Итак, собственно переходим к C++.
Для его успешной сборки нам обязательно понадобятся следующие файлы из поставки LUA (полный собранный дистрибутив доступен на lua.org)

  • lauxlib.h

  • lua.h

  • luaconf.h

  • lua5.1.lib

Первые три файла содержат описание типов и прототипы интерфейсных функций LUA, четвертый - библиотека для статический линковки с внешней библиотекой интерпретатора lua5.1.dll.

Открываем MS Visual Studio, создаем новый проект DLL.
В свойствах проекта для всех конфигураций, какие мы будем собирать (обычно это Release и Debug), необходимо добавить библиотеку lua5.1.lib в дополнительные библиотеки. В приложенном примере она лежит в подпапке contrib:

Настройка свойств C++ проекта для сборки библиотеки, доступной из LUA

Открываем cpp-файл нашего проекта. Вначале добавляем в него заголовочные файлы LUA, причем перед их включением (это важно!) добавляем определение двух переменных препроцессора: они необходимы для случая сборки DLL, доступной из LUA. Если бы мы собирали наоборот LUA-интерпретатор, запускающий из себя LUA-скрипты, то необходимо было бы сделать другие определения.

#define LUA_LIB
#define LUA_BUILD_AS_DLL

#include "lua.hpp"

Подключаем единый файл lua.hpp, в котором:

  • подключаются все необходимые заголовочные файлы Lua;

  • т.к. наш проект C++, то заголовочные файлы Lua необходимо подключать под extern "C", что уже сделано в этом едином файле.

В приведённом здесь демонстрационном примере #define для определения LUA_LIB и LUA_BUILD_AS_DLL написан непосредственно в исходном коде просто для наглядности.
Однако в большом проекте удобнее определения LUA_LIB и LUA_BUILD_AS_DLL сделать параметрах проекта, настройка Preprocessor Definitions (удобно, если проект содержит множество файлов). Тогда не придется вставлять соотв. #define в каждый файл исходного кода.
Настройка свойств C++ проекта для сборки библиотеки, доступной из LUA

Еще одно соображение.
Чтобы у вас не возникло проблем с переносом и использованием вашей библиотеки на других компьютерах (если такая надобность есть), то есть смысл скомпилировать её со статическим run-time, т.е. весь код собственно библиотек C++ будет полностью внутри вашей DLL. Это несколько увеличит её размер, но кого сейчас волнует размер файла? Отвечу: никого. Зато вам не придётся дополнительно к вашей библиотеке таскать несколько системных DLL, или заставлять пользователей вашей устанавливать C++ Redistributable Package, причем определённой версии. Вы ведь не хотите создавать себе и другим лишние сложности? и правильно, поэтому статический run-time - это наш путь.
Чтобы его включить, необходимо сделать следующие настройки в свойствах проекта. Причем в зависимости от конфигурации следует выбрать верный тип run-time'а, иначе и debug будет не debug, и release не пойми что.

Для release-конфигураций выбираем:
https://quik2dde.ru/static-img/cpp_lua-2-03.png

Для debug-конфигураций выбираем:
https://quik2dde.ru/static-img/cpp_lua-2-04.png

(Немного подробнее про run-time библиотеки вот в этом сообщении.)

Теперь собственно код библиотеки на C++

Т.к. библиотеку мы назвали luacdll и именно это имя указываем в Lua-скрипте в require, то при загрузке нашей библиотеки LUA-интерпретатор будет искать экспортируемую из нее функцию с определенным именем, состоящем из "двух частей". В нашем случае это имя будет luaopen_luacdll(). Здесь  luaopen_ это предопределенный префикс (см. документацию), а luacdll собственно имя нашей библиотеки. Разумеется, тип и аргументы этой функции тоже предопределены.

Для Lua 5.1 эта функция должна содержать следующие строки:

 extern "C" LUALIB_API int luaopen_luacdll(lua_State *L) {
    luaL_openlib(L, "luacdll", ls_lib, 0);
    return 1;
}

Здесь мы регистрируем в LUA-интерпретаторе (путем вызова luaL_openlib) те функции, которые мы предоставляем из нашей библиотеки, что делает их доступными для вызова из LUA-скриптов. Вторым параметром функции передается namespace (имя глобальной переменной), в котором будут доступны функции нашей библиотеки при вызове; чтобы не запутаться, namespace делаем совпадающим с именем нашей библиотеки.

Для Lua 5.2 и более новых версий эта функция должна содержать следующие строки:

 extern "C" LUALIB_API int luaopen_luacdll(lua_State *L) {
    luaL_newlib(L, ls_lib);
    return 1;
}

Здесь мы тоже регистрируем в LUA-интерпретаторе (путем вызова luaL_newlib) те функции, которые мы предоставляем из нашей библиотеки, что делает их доступными для вызова из LUA-скриптов. В отличии от варианта для Lua 5.1 никакая глобальная переменная не задаётся, функции библиотеки становятся доступными в той переменной, которой присвоим результат выполнения require() в коде Lua-скрипта.
luaL_newlib() - это удобный макрос, который сразу включает в себя вызов трех интерфейсных функций Lua:

  • luaL_checkversion - для проверки версии интерпретатора Lua, особенно актуально для QUIK 8.11;

  • luaL_newlibtable и luaL_setfuncs - функции, последовательно вызываемые для регистрации функций, реализованных в библиотеке.

(Функция luaL_openlib() удалена из Lua 5.2 как устаревшая.)

В нашей простейшей библиотеке будут реализованы 3 функции, доступные из LUA:

  • GetCurrentThreadId - получить ID текущего потока

  • MultTwoNumbers - перемножает 2 числа, заданных в качестве аргументов

  • MultAllNumbers - перемножает все числа, встретившиеся в аргументах

Сам список функций (имя и указатель на соответствующую Си-функцию) описан в константном массиве:

 static struct luaL_reg ls_lib[] = {
    {"GetCurrentThreadId", forLua_GetCurrentThreadId},
    {"MultTwoNumbers", forLua_MultTwoNumbers},
    {"MultAllNumbers", forLua_MultAllNumbers},
    {nullptr, nullptr}
}; 

Реализация функции, возвращающей ID текущего потока:

 static int forLua_GetCurrentThreadId(lua_State *L) {
    // возвращаем одно целочисленное значение, полученное от Win API функции
    lua_pushinteger(L, GetCurrentThreadId());
    return(1);
}

Ее прототип предопределен и един для всех интерфейсных функций: принимает единственный параметр L - указатель на стек LUA (см. документацию).
В данном случае функция не подразумевает никаких аргументов и возвращает единственное целочисленное значение.

Исходный код остальных функций можно посмотреть в приложенном архиве, на мой взгляд, в особых комментариях он не нуждается. В реализации MultAllNumbers можно посмотреть как производится обработка вызова функции из LUA с произвольным числом и типом параметров.

После компиляции получившийся DLL-файл (как было сказано) копируем в тот же каталог, где расположен терминал QUIK (обязательно перепишите туда же файл lua5.1.dll!), и запускаем в нем следующий LUA-скрипт, предварительно сохраним его в виде файла:

require("luacdll")

message(tostring(luacdll.GetCurrentThreadId()), 1)

r = luacdll.MultTwoNumbers(5.6, 2.17)
message (tostring(r), 1)

r = luacdll.MultAllNumbers(6, 3, "23423", {2.17}, 1.1)
message (tostring(r), 1)

function main()
end

В результате его выполнения будет выведено 3 сообщения: с ID основного потока терминала, число 12.152 и число 1.1.

Полностью исходные тексты и скомпилированную dll можно скачать по ссылкам в первом посте этой темы.

3

Re: Создание собственной DLL на C++ для LUA (в QUIK)

Куда поместить файл с DLL-библиотекой

Я настоятельно рекомендую созданную библиотеку DLL поместить в каталог с QUIK, т.е. в ту же папку, где лежит файл info.exe.
Да, в принципе можно играться с указанием путей в скрипте и это даже работает при должном навыке. Вот только зачем? есть ли хоть один здравый аргумент? Эстетика - это, быть может, и хорошо, но надёжность - она, на мой взгляд, перевешивает любую эстетику на 3 порядка минимум.
Так что без стеснений размещайте свои DLL-библиотеки в каталог с QUIK - и всё у вас будет работать просто и надёжно.

QUIK 5.11: одновременное присутствие Lua 5.3 и Lua 5.4

Увы, QUIK 5.11 внес изменения в описанную выше простую парадигму про расположение внешних DLL-библиотек в каталоге QUIK. Теперь для исполнения каждого скрипта можно выбрать одну из версий интерпретатора Lua 5.3 или Lua 5.4. В то же время скомпилированная DLL-библиотека слинкована либо на использование lua53.dll, либо на использование lua54.dll библиотек. И если для в скрипте, который выполняется интерпретатором Lua 5.4 (т.е. фактически скрипт выполняется библиотекой lua54.dll) мы загрузим DLL-библиотеку, которая использует lua53.dll - то работать такая связка будет уже некорректно по многим моментам.
Как тут быть и куда теперь складывать библиотеки?
Этот вопрос рассмотрен в отдельной теме на этом форуме.

4

Re: Создание собственной DLL на C++ для LUA (в QUIK)

< reserved >

5

Re: Создание собственной DLL на C++ для LUA (в QUIK)

подскажите как сверстать  dll на delphi. А то у меня по опционам по БШ много написано

6

Re: Создание собственной DLL на C++ для LUA (в QUIK)

Достаточно взять заголовочные pas-файлы для Lua библиотеки из любого дельфового проекта интерграции с Lua (коих много) - а лальше API все ровно такое же )

Описания в таких же пределах, как здесь (т.е. самые азы), будет достаточно?

PS
Про Delphi - отдельное подробное описание.

7

Re: Создание собственной DLL на C++ для LUA (в QUIK)

Можно пример.

8

Re: Создание собственной DLL на C++ для LUA (в QUIK)

admin, если можно, исходники проекта dll приложите.
С С++ много лет не работал. Основательно подзабыл.

9

Re: Создание собственной DLL на C++ для LUA (в QUIK)

ubase пишет:

admin, если можно, исходники проекта dll приложите.

Все, кто дочитывает, в конце второго поста видят ссылку на скачивание архива с исходниками wink

PS
Перенес ссылки выше, в начало темы, так удобнее будет, пожалуй.

10

Re: Создание собственной DLL на C++ для LUA (в QUIK)

admin пишет:

Все, кто дочитывает, в конце второго поста видят ссылку на скачивание архива с исходниками )

Нашел, спасибо. Я просто 2-й раз читаю, уже по диагонали. smile Первый раз меня исходники не интересовали.

11

Re: Создание собственной DLL на C++ для LUA (в QUIK)

Подскажите как сделать все-таки dll на Delphi?

12

Re: Создание собственной DLL на C++ для LUA (в QUIK)

d@ncer пишет:

Подскажите как сделать все-таки dll на Delphi?

Пожалуйста, наслаждайтесь wink

Как сделать свою библиотеку для Lua на Delphi

13 (2013-02-18 02:02:29 отредактировано ubase)

Re: Создание собственной DLL на C++ для LUA (в QUIK)

В инете видел вызов С-функции (библиотеки) require("kernel32"). Удивило. Это как это, напрямую, без танцев с бубном? (подробно, не разбирался, просто отметил)
Честно, не понял. Сайт потерял, к сожалению.
Вообще, авторы Луа могли бы это все эти танцы непосредственно в ДЛЛ внести, чтобы просто, не думая вызывать С-функции. Впрочем, это их дело. smile

14

Re: Создание собственной DLL на C++ для LUA (в QUIK)

День добрый.
Очень бы хотелось вас попросить сделать пример создания C# dll под луа.

15

Re: Создание собственной DLL на C++ для LUA (в QUIK)

akuzn, кому хочется - пусть разбирается с Luanet )
Я посмотрел - в чистом виде это делать довольно занудно, мне не очень хотелось бы связываться

16 (2013-04-17 10:27:24 отредактировано akuzn)

Re: Создание собственной DLL на C++ для LUA (в QUIK)

Luanet не dll, тем более, как и многое из предполагаемого, не работает под КВИКом.
Ну если бы в начале не было разбега - обращайтесь, то уж не стал бы писать.
Если настроение будет, посмотрите. Тем более прикладное применение есть.
Я в том смысле что по сравнению с "роботом по волнам ..иота" и прочей шелухой от данного решения можно добиться гораздо больше пользы.

17

Re: Создание собственной DLL на C++ для LUA (в QUIK)

Хорошо бы вы озвучили это " прикладное применение".

18

Re: Создание собственной DLL на C++ для LUA (в QUIK)

Формулирую. За КЛУА остается работка с заявками и событиями терминала, логика и интерфейс уходит в Си#.
Плюсы Си# - качественные интерфейсы, удобно оттестироваться в WealthLab.

19

Re: Создание собственной DLL на C++ для LUA (в QUIK)

Т.е. фактически для решения задачи вполне подойдет отдельное приложение на C#, которое будет взаимодействовать со скриптом Lua,верно? Как таковая dll на C# получается не нужна.

А что касается "разбега",
Друзья! Бесплатно я делаю то, что мне интересно. Поимейте уже совесть.

20

Re: Создание собственной DLL на C++ для LUA (в QUIK)

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

21

Re: Создание собственной DLL на C++ для LUA (в QUIK)

Вы забыли ответить на вопрос.

22 (2013-04-18 20:43:21 отредактировано akuzn)

Re: Создание собственной DLL на C++ для LUA (в QUIK)

Да, любая форма взаимодействия с C#.
Конечно, может быть, в новой версии терминала что-то улучшилось - уж столько товарищам было высказано. Но из документации пока ничего не ясно. Ровно как они ничего не сказали о перечне изменений и дополнений.
В любом раскладе, считаю C# с логикой и интерфейсом, КЛУА - терминал - это супер решение.

23

Re: Создание собственной DLL на C++ для LUA (в QUIK)

В качестве итога. Решение сводится к сборке, сочетающей управляемый и неуправляемый код. Все решается в рамках студии.

24

Re: Создание собственной DLL на C++ для LUA (в QUIK)

akuzn,
итог - это пример законченного кода и настроек студии, иллюстрирующие описанные концепции. Спасибо.

25 (2013-05-05 14:34:30 отредактировано akuzn)

Re: Создание собственной DLL на C++ для LUA (в QUIK)

Я вам написал исключительно по-товарищески. Такой пример мною оплачен и очень дорого.
Единственное что можно сказать - количество сил не соответствует. Вы, наверное, правы, в итоге лучше без квика и луи.