FFI — одна из стандартных библиотек luajit.
Позволяет из Lua кода вызывать внешние C-функции и использовать структуры данных C.
В данной теме задаём вопросы по использованию FFI
Shmuel wrote: ↑Fri 08 Sep, 2017 20:42К сожалению, этого одного нуля недостаточно для стрингов в UTF-16. В своё время я предложил в форуме Lua, чтобы резервировалось место для двух нулей (\0\0), но предложение принято не было.
Shmuel wrote: ↑Fri 08 Sep, 2017 20:57Этот уже существующий \0 не является частью стринга - он лежит в его внутреннем представлении. Поэтому, если добавить в Utf8ToUtf16 к результату \0, мы нарушим корректность преобразования.
Мне тоже казалось, что результат должен содержать в итоге два завершающих нуля, но сейчас усомнился (может получиться и ни одного, в зависимости от реализации).
Получение привилегий backup/restore
Posted: Fri 08 Sep, 2017 21:06
by Shmuel
John Doe wrote: ↑Fri 08 Sep, 2017 21:01Мне тоже казалось, что результат должен содержать в итоге два завершающих нуля
Вам правильно казалось. Результат будет содержать \0\0 в конце (и ещё один \0 внутренней репрезентации).
Получение привилегий backup/restore
Posted: Fri 08 Sep, 2017 21:36
by Smitis
Shmuel wrote: ↑Fri 08 Sep, 2017 20:57Этот уже существующий \0 не является частью стринга - он лежит в его внутреннем представлении.
Кажется понял. Ноль есть в памяти, но он не считается частью строки, поэтому строковые функции, в том числе Utf8ToUtf16, его игнорируют. Что бы получить ноль в UTF-16 его надо ЯВНО добавить в строку. Можно добавить 0 в win.Utf8ToUtf16(str.."\0"), тогда в конце буфера будет ТРИ нулевых байта или win.Utf8ToUtf16(str).."\0", тогда будет ДВА нулевых байта, что в любом случае для работы юникодных функций достаточно.
Вопросы по использованию FFI
Posted: Sat 09 Sep, 2017 09:23
by DrKnS
Shmuel wrote: ↑Fri 08 Sep, 2017 20:57Этот уже существующий \0 не является частью стринга - он лежит в его внутреннем представлении. Поэтому, если добавить в Utf8ToUtf16 к результату \0, мы нарушим корректность преобразования.
Так вроде бы \0 уже добавляется к новому внутреннему представлению?
Вопросы по использованию FFI
Posted: Sat 09 Sep, 2017 10:37
by Shmuel
DrKnS wrote: ↑Sat 09 Sep, 2017 09:23Так вроде бы \0 уже добавляется к новому внутреннему представлению?
Это сделано для удобства внутренних операций LuaFAR. Там же двумя строчками ниже выставляется выходной размер, и он используется при формировании строки, выдаваемой "наружу". Другими словами, в функциях Lua-интерфейса этот \0 появляться не должен.
Вопросы по использованию FFI
Posted: Sat 09 Sep, 2017 14:57
by DrKnS
Shmuel wrote: ↑Sat 09 Sep, 2017 10:37 Другими словами, в функциях Lua-интерфейса этот \0 появляться не должен.
Да, действительно - по документации lua_pushlstring "makes (or reuses) an internal copy of the given string", теряя этот \0. Печально.
Вопросы по использованию FFI
Posted: Sat 09 Sep, 2017 15:19
by Shmuel
Печально, что главный автор Lua не согласился добавлять два "служебных" \0 вместо одного. (Причина, полагаю в том, что для него UTF-16 - экзотика, не стоящая внимания).
Добавить один (или сколько угодно) нулевых байтов к результату в LuaFAR совсем не проблема, но это будет некорректное преобразование.
Как вариант, можно было бы сделать дополнительные функции c Z ("zero") в конце имени, например Utf8ToUtf16Z.
Вопросы по использованию FFI
Posted: Sat 09 Sep, 2017 15:47
by John Doe
К сожалению это мало чем отличается от того, чтобы добавить ноль конкатенацией с аргументом функции..
Вопросы по использованию FFI
Posted: Tue 12 Sep, 2017 22:33
by John Doe
John Doe wrote: ↑Tue 25 Feb, 2014 12:56
Ещё вопрос.
В буфере из примера выше находится C-строка неизвестной длины.
Как лучше всего перевести её в lua-строку?
Очевидно надо использовать ffi.string, но поскольку длина заранее неизвестна, то возможны варианты
- объявить и использовать C.lstrlenW
- перевести в строку весь буфер, и использовать match"(.-)%z"
Давно было, и тогда мне почему то не пришло в голову решение на Lua, типа такого:
local ffi = require"ffi"
local ptr = ffi.new("char[?]",100,win.Utf8ToUtf16"test".."\0")
local wchar = ffi.cast("wchar_t*",ptr)
local len = 0
while wchar[len]~=0 do len = len+1 end
far.Show(win.Utf16ToUtf8(ffi.string(wchar,len*ffi.sizeof("wchar_t"))))
local ptr = ffi.new("char[?]",100,win.Utf8ToUtf16"test")
Похоже ffi.new тут работает не так, как я ожидал.
Из документации вроде бы следует, что ffi.new заполняет созданый массив нулями.
The elements of an array are initialized, starting at index zero. If a single initializer is given for an array, it's repeated for all remaining elements. This doesn't happen if two or more initializers are given: all remaining uninitialized elements are filled with zero bytes.
Однако в случае, когда в качестве инициализатора выступает строка, меньшая по длине, остаток видимо не обнуляется.
В документации этот частный случай не рассматривается, сказано только в общем:
Byte arrays may also be initialized with a Lua string. This copies the whole string plus a terminating zero-byte. The copy stops early only if the array has a known, fixed size.
Вопросы по использованию FFI
Posted: Wed 13 Sep, 2017 17:36
by Shmuel
John Doe wrote: ↑Wed 13 Sep, 2017 17:28В документации этот частный случай не рассматривается, сказано только в общем
На мой взгляд, сказано исчерпывающе ("This copies the whole string plus a terminating zero-byte").
Вопросы по использованию FFI
Posted: Wed 13 Sep, 2017 17:43
by John Doe
Меня ввело в заблужнение то, что непосредственно перед этим сказано: all remaining uninitialized elements are filled with zero bytes.
А так да, не спорю.
Вопросы по использованию FFI
Posted: Wed 29 Nov, 2017 09:30
by John Doe
Заготовка для документации:
0. Использование ffi (помимо усвоения документации библиотеки) в целом требует определённой квалификации, понимания работы вызываемых функций, осторожного обращения с памятью, и т.п.
Ниже следуют только дополнения, описывающие особенности использования ffi в фаре
1. Любое определение, сделанное посредством ffi.cdef, действует до конца сеанса.
Переопределить его невозможно: будет действовать определение, сделанное первым.
1.1. Определение, сделанное в одном скрипте будет действовать для всех, поэтому важно чтобы определения были корректными.
1.2. В случае изменения определения на несовместимое потребуется перезагрузить плагин (или фар).
2. В отличии от переопределения функций, попытка переопределения констант и именованных(?) структур ещё и вызывает ошибку времени выполнения, поэтому следует оборачивать вызов в pcall:
local function safe_def(def) return pcall(ffi.cdef,def) end
safe_def[[
static const ULONG SEE_MASK_NO_CONSOLE = 0x00008000"
]]
safe_def[[
//https://docs.microsoft.com/en-us/windows/console/console-selection-info-str
typedef struct _CONSOLE_SELECTION_INFO {
DWORD dwFlags;
COORD dwSelectionAnchor;
SMALL_RECT srSelection;
} CONSOLE_SELECTION_INFO, *PCONSOLE_SELECTION_INFO;
]]
2.1. Следует учитывать, что "переопределение" неизбежно, это вполне штатная ситуация: например при перезагрузке макросов, или при использовании определений в независимых скриптах.
3. Для удобства luamacro при старте загружает файл winapi.lua, содержащий определения типов, часто используемых в winapi.
4. ???
Работа со строками, с юникодом, передача параметра по ссылке или значению (_OUT_/_IN_ в msdn) это то, что абсолютно необходимо понимать при работе с ffi.
Прочитав тему с начала можно найти ответы на множество вопросов.
Если же вы не готовы тратить своё время на то чтобы во всём разобраться, то и начинать не стоит.
Todo: задокументировать нюансы, имеющие значение для распространённых задач:
- работа со строками, завершающий \0
- получение указателей/хэндлов, ...
- ограничение типов на 32бита: viewtopic.php?p=119951#p119951
Вопросы по использованию FFI
Posted: Wed 29 Nov, 2017 10:03
by Shmuel
John Doe wrote: ↑Wed 29 Nov, 2017 09:301.2. В случае изменения определения на несовместимое потребуется перезагрузить плагин (или фар).
Или выполнить _G.RecreateLuaState = true.
Вопросы по использованию FFI
Posted: Thu 30 Nov, 2017 08:25
by Smitis
John Doe wrote: ↑Wed 29 Nov, 2017 09:302. В отличии от переопределения функций, попытка переопределения констант и именованных(?) структур ещё и вызывает ошибку времени выполнения, поэтому следует оборачивать вызов в pcall:
В подавляющем большинстве случаев достаточно убрать имя структуры