Очень простенький модуль. Содержит единственную функцию, которая проверяет результат выполнения последней функции, вызванной с помощью FFI, получает расшифровку кода завершения и возвращает то и другое. Разумеется, проверять результат следует в том случае, если результат работы функции отличен от нормального (возратила признак ошибки, не возвратила корректный результат и т. п.), иначе результат, возвращаемый модулем, не имеет практического смысла.
Можно также использовать с аналогичной целью far.Message с параметром "e" (far. Message(<базовый текст сообщения>, <заголовок>, <кнопки>, "e")). Отличия данного модуля:
не выводит на экран ничего, что позволяет реагировать более гибко;
кроме текста ошибки возвращает его код, который удобен для программного анализа.
В качестве бонуса выкладываю первоначальную (пока у меня были сложности с дрессировкой FormatMessage) версию. Она содержит все коды ошибок внутри, текст ошибок только английский, зато возвращает три параметра вместо двух: код завершения, наименование кода и его расшифровку. В принципе, можно использовать как справочник.
Требуемая версия Far 3.0.3001+.
0.1 - первая разработка, только английский текст, возврат наименований кодов.
1.0 - первый релиз.
1.0.1 - Добавлено определение malloc() и free(). Дополнено Info.
IgorZ wrote: ↑Fri 28 Jul, 2017 10:52 проверяет результат выполнения последней функции, вызванной с помощью FFI
Не совсем корректная формулировка. Можно показаться будто (1) GetLastError только от ffi зависит и (2) любой вызов ffi должен установить какой-то код ошибки.
Поэтому хочу уточнить, что ожидать код ошибки стоит только в тех случаях, когда об этом явно сказано в MSDN.
Причём не всегда в случае отсутствия ошибки код будет 0, иногда успех надо определять по возвращаемому значению функции, и только в случае ошибки обращаться к GetLastError (в противном случае мы прочитаем код какой-то ошибки, оставшийся от какого-то прошлого вызова совершенно другой функции, не нами даже вызванной).
Собственно, я только что пересказал описание из MSDN
GetLastError wrote:The Return Value section of the documentation for each function that sets the last-error code notes the conditions under which the function sets the last-error code. Most functions that set the thread's last-error code set it when they fail. However, some functions also set the last-error code when they succeed. If the function is not documented to set the last-error code, the value returned by this function is simply the most recent last-error code to have been set; some functions set the last-error code to 0 on success and others do not.
Также хочу обратить внимание, что подобная функциональность уже есть, в функции Message (FMSG_ERRORTYPE)
local function AddConsoleAlias(Source,Target,ExeName)
Source,Target,ExeName = _utf16(Source),_utf16(Target),_utf16(ExeName)
local res = C.AddConsoleAliasW(_ptr(Source),_ptr(Target),_ptr(ExeName))
if res>0 then return true end
far.Message("",nil,nil,"ew") --debug
end
*грустно* Про такую функциональность far.Message я и забыл... Куча времени потрачена практически впустую... Эх!
Ладно, спасибо за подсказку. Описание тоже подправлю, для порядка.
Last edited by IgorZ on Mon 31 Jul, 2017 06:15, edited 1 time in total.
Подумав, решил, что раз уж выложил, то надо поправить в соответствии с. Так что Версия 1.0.1 - Добавлено определение malloc() и free(). Дополнено Info.
Возникло несколько замечаний по оптимальному использованию ffi.
Функция C.wcslen в коде не определена, работает только благодаря тому, её определение по случайности уже произошло "где-то там".
Впрочем, она и не нужна, ведь длину возвращает FormatMessageW (кстати в условии у вас ошибка, надо сравнивать с 0)
local buf = ffi.gc(ffi.C.malloc(2048), ffi.C.free)
Необходимости в malloc/free нет, ведь есть ffi.new.
И если уж использовать malloc, то незачем дожидаться сборщика мусора, ведь в данном случае мы чётко знаем, когда буфер нам уже не нужен, и можем освободить его сразу.
Также не уверен, что размер буфера выбран правильно (2048).
Требуемый размер заранее неизвестен, зато есть флаг FORMAT_MESSAGE_ALLOCATE_BUFFER, почему его не использовать?
(Впрочем, не знаю деталей реализации FORMAT_MESSAGE_ALLOCATE_BUFFER, возможно в этом случае тупо выделяется 64 кБ)
Last edited by John Doe on Tue 07 Nov, 2017 10:44, edited 2 times in total.
На самом деле, всё просто. Я раньше самостоятельно не писал ничего с использованием FFI, пользуясь чужими образцами и подсказками, так что этот модуль - по большей части тренировочный. Отсюда и все возникшие вопросы (скажем, со сборщиком мусора мне было просто интересно сделать именно так). За замечания спасибо, буду посвободней - поразбираюсь.
Грозит тем, что где-то в другом скрипте будет другое определение, и один из двух отвалится (тот что загружен вторым).
К сожалению, пока я этого не сделал, FormatMessageW с FORMAT_MESSAGE_ALLOCATE_BUFFER отказывался работать. Дело в том, что без FORMAT_MESSAGE_ALLOCATE_BUFFER lpBuffer это адрес буфера для выходной строки, а с FORMAT_MESSAGE_ALLOCATE_BUFFER это адрес ячейки, в которую пишется адрес буфера для выходной строки. И если не отразить это в описании, то при запуске получаем
╔═══════════════════════════════════════════════ LuaMacro ═══════════════════════════════════════════════╗
║ C:\BIN\Far3Common\Profile\Macros\modules\ffierror.lua:38: bad argument #5 to 'FormatMessageW' (cannot ║
║ convert 'unsigned short *[1]' to 'unsigned short *') ║
║ Stack Traceback ║
║ =============== ║
║ (1) Lua local 'ffierror' at file 'C:\BIN\Far3Common\Profile\Macros\modules\ffierror.lua:38' ║
║ Local variables: ║
║ err = number: 0 ║
║ (2) main chunk of file '\\?\C:\BIN\Far3Common\Profile\Macros\ffierror.sample.lua' at line 9 ║
║ ║
╟────────────────────────────────────────────────────────────────────────────────────────────────────────╢
║ { OK } ║
╚════════════════════════════════════════════════════════════════════════════════════════════════════════╝
Возможно, это и можно как-то обойти, но в таком случае моих знаний FFI для этого не хватает.
P. S. Описание в документации относится к варианту без флага FORMAT_MESSAGE_ALLOCATE_BUFFER.
P. P. S. Ох, и намучался же я, прежде чем понял, что недостаточно перегрузить макросы и выгрузить модуль ffierror для того, чтобы изменения в ffi.cdef подействовали, и надо перезапустить Far. Это я просто так жалуюсь...
local buf_ptr = ffi.new("LPTSTR[1]")
local len = C.FormatMessageW(dwFlags,nil,errCode,0,ffi.cast("LPTSTR",buf_ptr),0,nil)
if len~=0 then
local msg = win.Utf16ToUtf8(ffi.string(buf_ptr[0],len*wsize))
-- ...
IgorZ wrote: ↑Wed 29 Nov, 2017 05:18P. S. Описание в документации относится к варианту без флага FORMAT_MESSAGE_ALLOCATE_BUFFER.
Декларацию всё равно изменять нельзя. А приводить типы - сколько угодно.
IgorZ wrote: ↑Wed 29 Nov, 2017 05:18Ох, и намучался же я, прежде чем понял, что недостаточно перегрузить макросы и выгрузить модуль ffierror для того, чтобы изменения в ffi.cdef подействовали, и надо перезапустить Far.
Справедливое замечание. Стоит отразить в документации особенности работы с ffi (safe_cdef и всё такое).
local buf_ptr = ffi.new("LPTSTR[1]")
local len = C.FormatMessageW(dwFlags,nil,errCode,0,ffi.cast("LPTSTR",buf_ptr),0,nil)
if len~=0 then
local msg = win.Utf16ToUtf8(ffi.string(buf_ptr[0],len*wsize))
-- ...
Ага, работает, спасибо. Блин, написано же для ffi.cast:
This functions is mainly useful to override the pointer compatibility checks or to convert pointers to addresses or vice versa.
Но нет, несколько раз перечитывал документацию, а это в упор не увидел!
Это, скорее, философское замечание. Также как можно ремонтировать электропроводку, не отключая предохранители, заплывать за буйки и кормить с рук хищников в зоопарке.
P. S. В любом из перечисленных случаев неприятных последствий может и не быть. Но если случились - винить некого, кроме себя.
Last edited by IgorZ on Wed 29 Nov, 2017 11:02, edited 2 times in total.