FFI — одна из стандартных библиотек luajit.
Позволяет из Lua кода вызывать внешние C-функции и использовать структуры данных C.
В данной теме задаём вопросы по использованию FFI
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:
В подавляющем большинстве случаев достаточно убрать имя структуры
Smitis wrote: ↑Thu 30 Nov, 2017 08:25В подавляющем большинстве случаев достаточно убрать имя структуры
Да, выше по теме было.
Но имя структуры используется в сообщениях об ошибках, поэтому я бы не стал его комментировать, safe_def предпочтительнее.
На самом деле любая попытка переопределения это действительно ошибка, и даже странно что её нет с безымянными структурами и функциями.
В идеале было бы, если бы повторное определение проверялось на соответствие с имеющимся, и ошибка выдавалась бы в случае несовместимых определений.
Но нет, повторные просто проглатываются, не выдавая ошибок, но и не переопределяя.
Вопросы по использованию FFI
Posted: Fri 11 May, 2018 01:35
by buniak_a_h
Shmuel wrote: ↑Thu 19 Jun, 2014 15:34
Это не проблема, если сторонние скрипты будут вызывать require "winapi" перед своими ffi-определениями. Ошибка сразу и вылезет.
╔════════════════════════════════════════════════════ LuaMacro ═════════════════════════════════════════════════════╗
║ ...ta\Roaming\Far Manager\Profile\Macros\modules\winapi.lua:319: attempt to redefine '_UNICODE_STRING' at line 52 ║
║ stack traceback: ║
║ [C]: in function 'require' ║
║ \\?\D:\ttt\www.lua:1: in main chunk ║
╟───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢
║ { OK } ║
╚═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
Два традиционных вопроса: кто виноват и что делать?
Вопросы по использованию FFI
Posted: Fri 11 May, 2018 02:09
by buniak_a_h
Господа! Помогите, кто может!
Есть маленький скрипт:
Описывается интерфейс к двум функциям из WINAPI: MessageBoxA и CopyFileA. У обеих функций есть параметры типа LPCTSTR (строка в кодировке ASCII).
Вызов MessageBoxA проходит ожидаемо (выводятся обе строки - в заголовке и на форм) А вот при вызове CopyFileA почему-то возникает ошибка: bad argument #1 to '?' (cannot convert 'string' to 'const unsigned short *')
При том, что параметры описаны абсолютно одинаково, аргумент-строка передается одна и та же... Что я делаю не так? И как надо? Уже голову сломал...
Вопросы по использованию FFI
Posted: Fri 11 May, 2018 06:41
by 2useven10
Если вы используете неюникодные ф-и (почему?), то описывайте строковые аргументы как LPCSTR.
LPCTSTR - условный тип, который в зависимости от дефайнов считается либо LPCSTR, либо в LPCWSTR.
Видимо дефайны у вас и у ffi разные.
Вопросы по использованию FFI
Posted: Fri 11 May, 2018 06:50
by 2useven10
Лучше пользоваться юникодными вариантами ф-й -- MessgeBoxW, CopyFileW и соответственно LPCWSTR.
Лишнее преобразование из юникода при этом надо выкинуть.
Юникодные строки должны закачиваться юникодным нулём. Lua сам такого не делает, поэтому
приходится в конце строки явно добавлять свой ноль -- "filename\0".
Вопросы по использованию FFI
Posted: Fri 11 May, 2018 06:54
by Shmuel
buniak_a_h wrote: ↑Fri 11 May, 2018 01:35
Два традиционных вопроса: кто виноват и что делать?
Аналогичный вопрос уже задавался в данной ветке.
Ответ: pcall(require,"winapi")
buniak_a_h wrote: ↑Fri 11 May, 2018 02:09
Что я делаю не так? И как надо?
Надо в определениях заменить LPCTSTR на const char*
И как верно заметили выше, желательно работать с юникодными версиями функций Windows API. Зачем возвращаться к давно преодолённым ограничениям ASCII.
Вопросы по использованию FFI
Posted: Fri 11 May, 2018 08:04
by 2useven10
buniak_a_h wrote: ↑Fri 11 May, 2018 02:09
Есть маленький скрипт:
local ffi = require("ffi")
ffi.cdef[[
BOOL CopyFileW(
LPCWSTR ExistingFileName,
LPCWSTR NewFileName,
BOOL bFailIfExists
);
int MessageBoxW(
HWND hWnd,
const LPCWSTR lpText,
const LPCWSTR lpCaption,
UINT uType
);
]]
local inPath=win.GetCurrentDir() .. '\\Test.lua'
local outPath=inPath .. '.out'
if mf.fexist(outPath) then win.DeleteFile(outPath) end -- На всякий случай
local inPathW, outPathW = ffi.cast("LPCWSTR",win.Utf8ToUtf16(inPath..'\0')), ffi.cast("LPCWSTR",win.Utf8ToUtf16(outPath..'\0'))
if not far.Show(ffi.C.MessageBoxW(nil, inPathW, outPathW, 0)) then return end
far.Show(ffi.C.CopyFileW(inPathW, outPathW, 1))
buniak_a_h wrote: ↑Fri 11 May, 2018 01:35
Два традиционных вопроса: кто виноват и что делать?
Аналогичный вопрос уже задавался в данной ветке.
Ответ: pcall(require,"winapi")
Это если б мне надо было, чтоб заработало. Но Вы пишете, что require"winapi" нужно для получения диагностики. «Ошибка сразу и вылезла». Вот она и вылезла, при том, что своих определений я не давал...
Впрочем, вопрос академический. У кого-то ошибка... Я просто убрал require"winapi".
buniak_a_h wrote: ↑Fri 11 May, 2018 02:09
Что я делаю не так? И как надо?
Надо в определениях заменить LPCTSTR на const char*
Это - первое, что я попробовал . Это - не помогает...
Shmuel wrote: ↑Fri 11 May, 2018 06:54
Зачем возвращаться к давно преодолённым ограничениям ASCII.
Пример с MessageBoxA взят из FFI Library. Просто я ничего не понимаю в кодировках, сделал как попугай, по образу и подобию примера. А там почему-то "A"-вариант.
local ffi = require("ffi")
ffi.cdef[[
BOOL CopyFileW(
LPCWSTR ExistingFileName,
LPCWSTR NewFileName,
BOOL bFailIfExists
);
int MessageBoxW(
HWND hWnd,
const LPCWSTR lpText,
const LPCWSTR lpCaption,
UINT uType
);
]]
local inPath=win.GetCurrentDir() .. '\\Test.lua' .. '\0'
local outPath=inPath .. '.out' .. '\0'
if mf.fexist(outPath) then win.DeleteFile(outPath) end -- На всякий случай
local inPathW, outPathW = ffi.cast("LPCWSTR",win.Utf8ToUtf16(inPath)), ffi.cast("LPCWSTR",win.Utf8ToUtf16(outPath))
if not far.Show(ffi.C.MessageBoxW(nil, inPathW, outPathW, 0)) then return end
far.Show(ffi.C.CopyFileW(inPathW, outPathWA, 1))
у меня работает.
У меня - нет. Т.е. ошибки не выдаёт, но и файл не копирует. Просто уничтожает. Может, дело в том, что \0 добавлен в inPath, а outPath получен дописыванием к InPath ещё чего-то (за финальным нулём!)?
Вопросы по использованию FFI
Posted: Fri 11 May, 2018 08:56
by buniak_a_h
2useven10 wrote: ↑Fri 11 May, 2018 08:04
у меня работает.
Добавочка.
Если из inPath сначала убрать .. '\0', а затем отдельной строкой написать inPath = inPath .. '\0'
то вылет следующий: