1

Тема: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

Так как Delphi и Lazarus во многом очень схожи (а уж в плане написания dll и вовсе практически идентичны), рассказ будет разбит на 3 части:

  • подготовительная часть Delphi

  • подготовительная часть Lazarus

  • общая содержательная часть

Скачать общий архив с полным исходным кодом и собранными dll.

2

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

Delphi

Для начала скачиваем заголовочные pas-файлы с описанием интерфейсов Lua. Взять их можно из разных мест, в чем-то они будут немного отличаться, например из-за того, что сконвертированы из разных версий h-файлов, поставляемых с Lua. Кроме того, трансляция C-прототипов в общем случае тоже может быть выполнена в разной семантике при идентичном фактическом представлении скомпилированного кода.

Для конкретности изложения возьмем интерфейсные pas-файлы Lua из поставки FreePascal. Если у вас установлен FreePascal или Lazarus, то вам повезло: такие файлы у вас уже есть в каталоге source\packages\lua внутри установленного FPC. У кого нет, или имеющиеся неподходящей версии (должны быть для Lua 5.1) - качаем отсюда https://github.com/graemeg/freepascal/t … es/lua/src три файла, которые потом добавим к нашему проекту:

  • lauxlib.pas

  • lua.pas

  • lualib.pas

Теперь собственно создание библиотеки. Запускаем Delphi, выбираем File -> New в случае Delphi5 или File -> New -> Other в случае Delphi 7. В других версия – что-то аналогичное.

https://quik2dde.ru/static-img/40/2web-pic-1.png

На вкладке New выбираем тип проекта Dll, жмем Ok.

https://quik2dde.ru/static-img/40/2web-pic-2.png

Добавим в проект интерфейсные файлы Lua: Project -> Add to project...

https://quik2dde.ru/static-img/40/2web-pic-3.png

и в открывшемся диалоге выбираем скачанные ранее файлы, жмем «Открыть»:

https://quik2dde.ru/static-img/40/2web-pic-4.png

Несколько забегая вперед скажу, что для компилябельности в Delphi пришлось немного модифицировать заголовочные файлы, в частности добавив определение не объявленных в Delphi производных типов (которые присутствуют в библиотеках FreePascal/Lazarus), и отключив (под условными директивами) интерфейсные функции Lua, которые содержат в определении слово varargs, т.к. не все версии Delphi поддерживают эту директиву; надеюсь, вам вполне удастся обойтись без этих функций, таких функций всего 3 или 4. Добавлено объявление типов:

{ Delphi compatible }
{$IFNDEF PtrInt}   type PtrInt = ^Integer; {$ENDIF}
{$IFNDEF PPointer} type PPointer = ^Pointer; {$ENDIF}
{$IFNDEF PPChar}   type PPChar = ^PChar; {$ENDIF}

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

Теперь сохраним проект File -> Save, в качестве имени проекта укажем SimpleDelphiLua.

( перейти к продолжению )

3

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

Lazarus

Т.к. в поставку Lazarus (вернее сказать, FreePascal) уже включены заголовочные pas-файлы с описанием интерфейсов для Lua, то достаточно просто прописать их в модулях проекта в uses там, где это необходимо. Правда, если вы захотите их модифицировать (чтобы, например, использовать qlua.dll, о чем написано в конце общей части), то лучше скопировать эти файлы в папку с проектом и уже там изменять их.

У меня русифицированный Lazarus, поэтому картинки такие. Надеюсь, с вашей вы версией разберетесь.

Создадим новый проект

https://quik2dde.ru/static-img/40/2web-laz-1.png

в открывшемся диалоге выберем тип проекта «Библотека» («Library»)

https://quik2dde.ru/static-img/40/2web-laz-2.png

и сохраним наш проект под именем SimpleDelphiLua (в названии Delphi просто для удобства написания общей части).

Подготовительная часть для Lazarus на этом закончена.

4

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

Общая часть

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

(Строго говоря, написанное про единственно возможное имя функции не совсем верно; функция может называться и иначе, в этом случае надо будет изменить формат подключения нашего модуля в Lua. Но для простоты делаем так, как написано.)

var
  ls_lib: array[1..3] of luaL_reg =
    (
      (name: 'MultAllNumbers'; func: forLua_MultAllNumbers),
      (name: 'GetHostAppPath'; func: forLua_GetHostAppPath),
      (name: nil; func: nil)
    );

function luaopen_SimpleDelphiLua(L: Plua_State): Integer; cdecl;
begin
    luaL_openlib(L, PChar('SimpleDelphiLua'), @ls_lib, 0);
    lua_pop(L, 1);
    Result := 0;
end; 

Не забываем для интерфейсных функций добавлять модификатор соглашений о вызовах cdecl! Для остальных функции, которые используются только внутри DLL-библиотеки, добавлять модификаторы соглашений о вызовах не обязательно.

Т.к. функция инициализации должна экспортироваться из dll-ки, необходимо где-то после ее объявления добавить строчку:

exports luaopen_SimpleDelphiLua; 

Теперь добавим в библиотеку собственно те функции, которые будут вызываться из скриптов Lua. Все такие функции должны объявляться одинаково вот по такому шаблону:

function FuncName(L: Plua_State): Integer; cdecl; 

На вход они принимают указатель на Lua стек, а возвращать должны количество результирующих значений. Т.к. это интерфейсные функции, которые будут вызываться из Lua-машины, не забывайте указывать для них модификатор cdecl!

Как видно из кода выше, в библиотеке мы реализуем две функции:

  • MultAllNumbers - перемножает все переданные в качестве аргументов числа и возвращает результат перемножения;

  • GetHostAppPath - возвращает полный путь, где расположено загрузившее эту DLL приложение; в нашем случае это будет папка, где расположен QUIK.

Сами функции я вынес в отдельный модуль LuaFuncImpl.pas, добавленный в проект. В него подключаем интерфейсы из заголовочных файлов Lua:

uses  lua, lauxlib, lualib;

Функция MultAllNumbers представляет из себя «дословное переложение» аналогичной функции из примера Lua-библиотек на C. Вот она:

function forLua_MultAllNumbers(L: Plua_State): Integer; cdecl;
var
    n,i: Integer;
    res: Double;
    isNumberFound: Boolean;
begin
    n := lua_gettop(L);  // количество переданных аргументов
    res := 1;
    isNumberFound := false;
    for i := 1 to n do
      begin
        if lua_type(L,i) = LUA_TNUMBER then
          begin
            res := res * lua_tonumber(L,i);
            isNumberFound := true;
          end;
      end;

    if isNumberFound then
        lua_pushnumber(L, res)  // если найдено хоть одно число в аргументах - возвращаем произведение
    else
        lua_pushnil(L);         // если список аргументов пуст или в нем нет ни одного числа - функция вернет nil

    Result := 1;  // возвращаемое количество результатов
end; 

Реализацию функции GetHostAppPath() можете посмотреть в приложенных исходниках в самом первом посте темы, она совсем простая.

Скомпилируем проект, полученную библиотеку SipmleDelphiLua.dll скопируем в каталог, где установлен QUIK, и создадим тестовый скрипт на Lua:

require "SimpleDelphiLua"

res = SimpleDelphiLua.MultAllNumbers(2, 3, "A", 5.3)

message("MultAllNumbers=" .. tostring(res, 1), 1)
message("GetHostAppPath=" .. SimpleDelphiLua.GetHostAppPath(), 1)

function main()
end

Запустив его в QUIK, увидим результат в виде 2-х сообщений: результат перемножения трех чисел (2, 3 и 5.3) и полный путь к каталогу, откуда запущен терминал QUIK.

https://quik2dde.ru/static-img/40/2web-pic-5.png

Вот у нас и готова библиотека на Delphi или Lazarus для Lua!

Если вдруг захочется использовать визуальные компоненты Delphi для построения GUI - то пожалуйста, конечно, но есть разные нюансы. Проще взять готовую библиотеку, например VCLua, которая как раз сделана в Delphi-подобной, только бесплатной среде разработки Lazarus.

В финале хотелось бы добавить еще один штрих. Начиная с версии 6.5.1.19 терминала QUIK, библиотека qlua.dll стала экспортировать из себя все функции поддержки Lua. Это значит, что вместо Lua5.1.dll можно использовать напрямую qlua.dll, т.е. никакая Lua5.1.dll для наших библиотек больше не нужна. Отличная новость!

Для поддержки такого варианта в файле Lua.pas я добавил возможность задания подключения интерфейсных Lua-функций непосредственно из qlua.dll в случае, если определена директива USEQLUA, и сразу включил именно такой режим сборки:

{$DEFINE USEQLUA}

const
{$IFDEF UNIX}
  LUA_NAME = 'liblua.5.1.so';
  LUA_LIB_NAME = 'liblua.5.1.so';
{$ELSE}
{$IFDEF USEQLUA}
  LUA_NAME = 'qlua.dll';
  LUA_LIB_NAME = 'qlua.dll';
{$ELSE}
  LUA_NAME = 'lua5.1.dll';
  LUA_LIB_NAME = 'lua5.1.dll';
{$ENDIF}
{$ENDIF}

Можете проверить и убедиться, что библиотека SipmleDelphiLua.dll работает и при отсутствии файла lua5.1.dll.

5

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

< reserved >

6

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

< reserved >

7

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

< reserved >

8

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

Добавил описание для Lazarus.

9

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

Благодарю за Ваш труд. Достойно уважения. Сэкономило немало времени и нервов… Не понятно с какой стороны было подступиться.
Интересует Ваше мнение как лучше реализовать связку Delphi + QLua.

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

C# читать как Delphi.
Отсюда возникает вопрос: как лучше всего организовать данное взаимодействие делфи программы со скриптом, а именно вызов функций скрипта из делфи приложения и наоборот (как показанно в вашем посте).

Еще раз спасибо. С Уважением Владимир.

10

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

biznesman,
Если вам нужно именно взаимодействие с отдельным приложением - то просто набрать в гугле "межпроцессное взаимодействие". Вариантов много: TCP-коннект, PIPE, выделенная общая область памяти между процессами (маппинг), WM_COPYDATA вроде все еще поддерживается, опять же COM, или даже пресловутый DDE-обмен... Выбирайте что вам знакомо, или хотя бы ближе. (Здесь явно не полный список вариантов)

С ходу пример накидать не возьмусь, надо самому повспоминать что и с чем.

11

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

Доброго времени суток!

Спасибо за ответ, но хотелось бы узнить как реализовать связку через lua api.
На данный момент реализовал Application -> dll -> QLua. Пытался просто передать адрес стека из dll, но при попытке работать с ним из своего приложения получаю AV.
Как от сюда убрать dll, как из прложения получить адрес стека?

12

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

Из другого приложения - никак, это разные адресные пространства.

13

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

Спасибо автору, материал очень помог.
Lazarus собирает нормально DLL, а под Delphi 2010 собирается DLL, которая Quik не нравится. Скрипт в Quik выдает "attempt to index global 'SimpleDelhpiLua' (a nil value). Выскакивает, если в скрипте есть вызов функции из DLL.

  ls_lib: array[1..3] of luaL_reg =
    (
      (name: 'MultAllNumbers'; func: forLua_MultAllNumbers),
      (name: 'GetHostAppPath'; func: forLua_GetHostAppPath),
      (name: nil; func: nil)
    );

Скрипт не хочет находить функции.
Кто-нибудь решал эту проблему?

14

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

merabn пишет:

Lazarus собирает нормально DLL, а под Delphi 2010 собирается DLL, которая Quik не нравится. Скрипт в Quik выдает "attempt to index global 'SimpleDelhpiLua' (a nil value).

У вас dll совсем не загружается. Проверьте:
1) положите собранную dll в каталог с QUIK
2) проверьте в свойствах проекта, может вы собираете её под x64 платформу? Надо x86 собирать, т.е. 32-х битную библиотеку

15 (2015-04-05 14:52:29 отредактировано merabn)

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

admin пишет:

У вас dll совсем не загружается. Проверьте:
1) положите собранную dll в каталог с QUIK
2) проверьте в свойствах проекта, может вы собираете её под x64 платформу? Надо x86 собирать, т.е. 32-х битную библиотеку

dll загружается, если в скрипте кроме require нет ничего, то выполняется без ошибок. Как я понимаю, как раз require и подключает dll. Значит скрипт и находит dll и, по всей видимости загружает список функций. Но указатели на функции в последствии оказываются недействительными. Это мои предположения, я не специалист по lua и плохо знаком.
1. dll в каталоге с QUIK
2. У меня стоит Delphi 2010. Она сама 32 битная. В свойствах компилятора стоит 32 (можно еще выбрать 16 и windows 3.1 smile )

Уже столько времени на все это убил, лучше поставить Delphi 7 и сделать нужную DLL.

16 (2016-09-29 11:30:53 отредактировано sam063rus)

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

-

17

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

sam063rus пишет:

потому что для того, чтобы написать "обратный порт" с лазаруса на дельфи - надо малость подшаманить проект.

Это никак не связано с описываемой  проблемой.
Потому как, если почитать внимательно, человек делает простейшую DLL на дельфи.

"Проблемы" же при портированием из Lazarus - сугубо косметические и связаны лишь с этапом компиляции, а не выполнения.

18

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

merabn,
Попробуйте еще такой вариант:

SimpleDelphiLua = require "SimpleDelphiLua"

19 (2015-04-05 22:47:19 отредактировано merabn)

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

admin пишет:

Попробуйте еще такой вариант:

SimpleDelphiLua = require "SimpleDelphiLua"

В этом случае пишет attempt to index global 'SimpleDelphiLua' (a boolean value) для строки с вызовом функции из DLL.

20 (2015-04-05 23:05:28 отредактировано merabn)

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

sam063rus пишет:

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

----
p.s. в свою очередь, для перевода с дельфи в лазарус - ничего делать не надо - т.к. в "лазаре" уже есть необходимая опция импорта проекта.

К сожалению, информации не нашел, которая помогла бы "подшаманить".
С Delphi в Lazarus много чего есть, хоть и в самом Lazarus есть возможность импорта из Delphi.

Но тут все-таки дело не в этом. На Delphi 7 DLL собирается и она работает в Quik. На Embarcadero Delhpi 2010 и Delphi XE7 - собранная DLL не работает. В самом деле, в проекте простейшая DLL, там практически все - объявления типов и функций. Самая важная вещь - регистрация функций в lua. По поводу изменений в объявлении указателей на функции в Embarcadero по сравнению с Delphi 7 ничего не нашел, относящегося к делу.
Склоняюсь к мысли, что дело в опциях компилятора и компоновщика. Причем, на Delphi 7 - размер DLL 90kb, Delphi 2010 - 250kb, XE7 - 900kb.
Пробовал и разные опции компиляции, но не помогло.  И мой уже чисто спортивный интерес к проблеме компоновки DLL в продуктах Embarcadero (или проблеме регистрации DLL в lua) угасает smile Рано выкидывать Delphi 7 smile

21 (2016-09-29 11:31:02 отредактировано sam063rus)

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

-

22

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

sam063rus пишет:

выложите здесь скомпилированную под delphi 2010 версию, которая не работает - я её "чуть по-внимательнее посмотрю".

я снес delphi 2010, под delphi xe7 не пойдет? там та же проблема.

23 (2016-09-29 11:31:10 отредактировано sam063rus)

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

-

24

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

DLL

25 (2016-09-29 11:31:18 отредактировано sam063rus)

Re: Создание своей DLL на Delphi или Lazarus для Lua (в QUIK)

-