Информация Русская документация FFI

imring

чо тут
Администратор
Lampo Team
!!! WARNING !!! не доделал

FFI: Информация
Библиотека FFI позволяет вызывать внешние функции C и использование структур данных C от чистого кода Lua.
Библиотека FFI в значительном степени устраняет написания утомительной ручной привязки Lua/C в C. Нет необходимости изучать отдельный язык привязки — это разбирает простые декларации C! Они могут быть вырезаны (вставлены) из файлов заголовков C или справочных руководств. Это связано с необходимости связывания больших библиотек без необходимости иметь дело с хрупкими генераторами привязки.
Библиотека FFI тесно интегрирована в LuaJIT (он недоступен как отдельный модуль). Код, сгенерированный JIT-компилятором для доступа к структурам данных C из кода Lua, совпадает с кодом, создаваемым компилятором C. Вызовы функций C могут быть встроены в JIT-скомпилированный код, в отличие от вызовов функций, связанных через классический Lua/C API.
В этом пункте дается краткое введение в использование библиотеки FFI.

Пример мотивации: вызов внешних функций C
На самом деле легко вызвать внешнюю библиотечную функцию C:
Lua:
local ffi = require("ffi")
ffi.cdef[[
int printf(const char *fmt, ...);
]]
ffi.C.printf("Hello %s!", "world")
Итак, давайте рассмотрим это отдельно:
Lua:
local ffi = require("ffi") -- Загрузка библиотеки "FFI".
ffi.cdef[[ // Добавление объявлений C для функции. Часть внутри двойных скобок (в зеленом цвете) является стандартным синтаксисом C.
int printf(const char *fmt, ...);
]]
ffi.C.printf("Hello %s!", "world") -- Вызов по названной функции C - Да, это так просто!
На самом деле, что происходит за картиной, далеко не просто: 5 строчка использует стандартное пространство имен библиотеки C ffi.C. Индексирование этого пространства имен с именем символа ("printf") автоматически связывает его со стандартной библиотекой C. Результатом является особый вид объекта, который при вызове запускает функцию printf. Аргументы, переданные этой функции, автоматически преобразуются из объектов Lua в соответствующие типы C.
Хорошо, возможно, использование printf() не было таким впечатляющим примером. Вы могли бы сделать это с помощью io.write() и string.format(). Но вы получили идею...
Итак, вот что, чтобы открыть окно сообщений в Windows:
Lua:
local ffi = require("ffi")
ffi.cdef[[
int MessageBoxA(void *w, const char *txt, const char *cap, int type);
]]
ffi.C.MessageBoxA(nil, "Hello world!", "Test", 0)
Опять же, это было слишком легко, нет?
Сравните это с усилиями, необходимыми для связывания этой функции с использованием классического Lua/C API: создайте дополнительный файл C, добавьте функцию C, которая извлекает и проверяет типы аргументов, переданные из Lua, и вызывает фактическую функцию C, добавляет список модулей функции и их имена, добавить функцию luaopen_ * и зарегистрировать все функции модуля, скомпилировать и связать его с общей библиотекой (DLL), переместить ее на правильный путь, добавить код Lua, который загружает модуль иииии... наконец, вызовите привязку функция. Уф!

Пример мотивации: использование структур данных C
Библиотека FFI позволяет создавать и получать доступ к структурам данных C. Разумеется, основное использование этого - для взаимодействия с функциями C. Но они также могут использоваться автономно.
Lua построен на высокоуровневых типах данных. Они гибкие, расширяемые и динамичные. Вот почему мы все так любим Lua. Увы, это может быть неэффективным для определенных задач, где вам действительно нужен низкоуровневый тип данных. Например большой массив фиксированной структуры должен быть реализован с большой таблицей, содержащей множество крошечных таблиц. Это накладывает как существенные накладные расходы памяти, так и накладные расходы на производительность.
Вот эскиз библиотеки, которая работает с цветными изображениями и простым эталоном. Первое, простая версия Lua:
Lua:
local floor = math.floor

local function image_ramp_green(n)
  local img = {}
  local f = 255/(n-1)
  for i=1,n do
    img[i] = { red = 0, green = floor((i-1)*f), blue = 0, alpha = 255 }
  end
  return img
end

local function image_to_grey(img, n)
  for i=1,n do
    local y = floor(0.3*img[i].red + 0.59*img[i].green + 0.11*img[i].blue)
    img[i].red = y; img[i].green = y; img[i].blue = y
  end
end

local N = 400*400
local img = image_ramp_green(N)
for i=1,1000 do
  image_to_grey(img, N)
end
Это создает таблицу с 160 000 пикселей, каждая из которых представляет собой таблицу с четырьмя номерами в диапазоне 0-255. Сначала создается изображение с зеленой рампой (1D для простоты), затем изображение преобразуется в оттенки серого 1000 раз. Да, это глупо, но мне нужен простой пример...
И вот версия FFI:
Lua:
local ffi = require("ffi")
ffi.cdef[[
typedef struct { uint8_t red, green, blue, alpha; } rgba_pixel;
]]

local function image_ramp_green(n)
  local img = ffi.new("rgba_pixel[?]", n)
  local f = 255/(n-1)
  for i=0,n-1 do
    img[i].green = i*f
    img[i].alpha = 255
  end
  return img
end

local function image_to_grey(img, n)
  for i=0,n-1 do
    local y = 0.3*img[i].red + 0.59*img[i].green + 0.11*img[i].blue
    img[i].red = y; img[i].green = y; img[i].blue = y
  end
end

local N = 400*400
local img = image_ramp_green(N)
for i=1,1000 do
  image_to_grey(img, N)
end
Хорошо, так что это было не слишком сложно:
Сначала загрузите библиотеку FFI и объявите тип данных низкого уровня. Здесь мы выбираем структуру, которая содержит четыре байтовых поля, по одному для каждого компонента 4x8-битного RGBA-пикселя. Создание структуры данных с помощью ffi.new() просто — '?' является заполнителем для количества элементов массива переменной длины. C массивы основаны на нуле, поэтому индексы должны работать от 0 до n-1. Возможно, вам захочется выделить еще один элемент, чтобы упростить преобразование старого кода. Поскольку ffi.new() заполняет нулевой массив по умолчанию, нам нужно установить только зеленые и альфа-поля. Вызов math.floor() может быть пропущен здесь, поскольку числа с плавающей точкой уже обрезаны до нуля при преобразовании их в целое число. Это происходит безоговорочно, когда число хранится в полях каждого пикселя.
Теперь давайте посмотрим на влияние изменений: во-первых, потребление памяти для изображения снижается с 22 мегабайт до 640 килобайт (400 * 400 * 4 байта). Это в 35 раз меньше! Итак, да, таблицы имеют заметные накладные расходы. Кстати: оригинальная программа будет потреблять 40 мегабайт в простой Lua (на x64).
Затем производительность: чистая версия Lua работает за 9,57 секунды (52,9 секунды с интерпретатором Lua), а версия FFI работает на 0,48 секунды на моей машине (вычислительная техника). Это в 20 раз быстрее (в 110 раз быстрее, чем интерпретатор Lua).
Заядлый читатель может заметить, что преобразование чистой версии Lua для использования индексов массива для цветов ([1] вместо .red, [2] вместо .green и т.д.) должно быть более компактным и быстрым. Это, безусловно, верно (в ~1,7 раза). Также поможет переключение на структуру массивов.
Однако полученный код будет менее идиоматичным и скорее подверженным ошибкам. И это все еще не приближается к производительности версии кода FFI. Кроме того, высокоуровневые структуры данных не могут быть легко переданы другим функциям C, особенно функциям ввода / вывода, без чрезмерных штрафных санкций.
 

imring

чо тут
Администратор
Lampo Team
FFI: Руководство
Этот пункт предназначен для ознакомления с особенностями библиотеки FFI, представляя несколько вариантов использования и рекомендаций.
Однако этот пункт не пытается объяснить всю библиотеку FFI. Вам нужно взглянуть на
функции FFI и семантику FFI, чтобы узнать больше.

Загрузка библиотеки FFI
Библиотека FFI по умолчанию встроена в LuaJIT, но по умолчанию она не загружается и не инициализируется. Предлагаемый способ использования библиотеки FFI состоит в том, чтобы добавить следующее к началу каждого файла Lua, для которого требуется одна из его функций:
Lua:
local ffi = require("ffi")
Обратите внимание, что это не определяет переменную ffi в таблице глобальных переменных - вам действительно нужно использовать локальную переменную. Функция require гарантирует, что библиотека загружается только один раз.
Примечание: если вы хотите поэкспериментировать с FFI из интерактивного приглашения исполняемого файла командной строки, опустите локальный, поскольку он не сохраняет локальные переменные в строках.

Доступ к стандартным системным функциям
Следующий код объясняет, как получить доступ к стандартным системным функциям. Мы медленно печатаем две линии точек, спя в течение 10 миллисекунд после каждой точки:
Lua:
local ffi = require("ffi")
ffi.cdef[[ // ①
void Sleep(int ms);
int poll(struct pollfd *fds, unsigned long nfds, int timeout);
]]

local sleep
if ffi.os == "Windows" then -- ②
  function sleep(s) -- ③
    ffi.C.Sleep(s*1000) -- ④
  end
else
  function sleep(s)
    ffi.C.poll(nil, 0, s*1000) -- ⑤
  end
end

for i=1,160 do
  io.write("."); io.flush()
  sleep(0.01) -- ⑥
end
io.write("\n")
Вот пошаговое объяснение:
① Это определяет функции библиотеки C, которые мы собираемся использовать. Часть внутри двойных скобок (в зеленом цвете) является стандартным синтаксисом C. Обычно вы можете получить эту информацию из файлов заголовков C или документации, предоставляемой каждой библиотекой C или компилятором C.
② Трудность, с которой мы сталкиваемся здесь, заключается в том, что существуют разные стандарты на выбор. Windows имеет простую функцию Sleep(). В других системах существует множество функций, доступных для достижения второстепенных сон, но без четкого консенсуса. К счастью, для этой задачи также может быть использован poll(), и он присутствует на большинстве не-Windows-систем. Проверка ffi.os гарантирует, что мы будем использовать функцию, специфичную для Windows, только в системах Windows.
③ Здесь мы переносим вызов функции C в функции Lua. Это не является абсолютно необходимым, но полезно иметь дело с системными проблемами только в одной части кода. То, как мы его обертываем, гарантирует, что проверка для ОС выполняется только во время инициализации, а не для каждого вызова.
④ Еще более тонкой точкой является то, что мы определили функцию sleep() (для этого примера) как количество секунд, но принимаем дробные секунды. Умножая это на 1000, мы получаем миллисекунды, но это все еще оставляет ему номер Lua, который является значением с плавающей запятой. Увы, функция Sleep() принимает только целочисленное значение. К счастью для нас, библиотека FFI автоматически выполняет преобразование при вызове функции (усечение значения FP до нуля, как на C).
Некоторые читатели заметят, что Sleep() является частью KERNEL32.DLL и также является функцией stdcall. Так как же это может работать? Библиотека FFI предоставляет пространство имен библиотеки C по умолчанию ffi.C, которое позволяет вызывать функции из набора библиотек по умолчанию, например, компилятора C. Кроме того, библиотека FFI автоматически обнаруживает функции stdcall, поэтому вам не нужно объявлять их как таковые.
⑤ Функция poll() принимает еще несколько аргументов, которые мы не будем использовать. Вы можете просто использовать nil для передачи указателя NULL и 0 для параметра nfds. Обратите внимание, что число 0 не преобразуется в значение указателя, в отличие от C++. Вам действительно нужно передать указатели на аргументы и цифры указателя на числовые аргументы.
На странице в семантике FFI есть все подробности о конверсиях между объектами Lua и типами C. По большей части вам не придется иметь дело с этим, так как это выполняется автоматически, и оно тщательно разработано для преодоления семантических различий между Lua и C.
⑥ Теперь, когда мы определили нашу собственную функцию sleep(), мы можем просто называть ее простым кодом Lua. Это было не так плохо, да? Превращение этих скучных анимированных точек в увлекательную, пользующийся спросом игру оставляют в качестве упражнения для читателя. :)

Доступ к библиотеке сжатия zlib
В следующем коде показано, как получить доступ к библиотеке сжатия zlib из кода Lua. Мы определим две функции удобства удобства, которые берут строку и сжимают или распаковывают ее в другую строку:
Lua:
local ffi = require("ffi")
ffi.cdef[[ // ①
unsigned long compressBound(unsigned long sourceLen);
int compress2(uint8_t *dest, unsigned long *destLen,
          const uint8_t *source, unsigned long sourceLen, int level);
int uncompress(uint8_t *dest, unsigned long *destLen,
           const uint8_t *source, unsigned long sourceLen);
]]
local zlib = ffi.load(ffi.os == "Windows" and "zlib1" or "z") -- ②

local function compress(txt)
  local n = zlib.compressBound(#txt) -- ③
  local buf = ffi.new("uint8_t[?]", n)
  local buflen = ffi.new("unsigned long[1]", n) -- ④
  local res = zlib.compress2(buf, buflen, txt, #txt, 9)
  assert(res == 0)
  return ffi.string(buf, buflen[0]) -- ⑤
end

local function uncompress(comp, n) -- ⑥
  local buf = ffi.new("uint8_t[?]", n)
  local buflen = ffi.new("unsigned long[1]", n)
  local res = zlib.uncompress(buf, buflen, comp, #comp)
  assert(res == 0)
  return ffi.string(buf, buflen[0])
end

-- Simple test code. ⑦
local txt = string.rep("abcd", 1000)
print("Uncompressed size: ", #txt)
local c = compress(txt)
print("Compressed size: ", #c)
local txt2 = uncompress(c, #txt)
assert(txt2 == txt)
Вот пошаговое объяснение:
① Это определяет некоторые функции C, предоставляемые zlib. Для этого примера некоторые ограничения типа были уменьшены, и он использует предопределенные целочисленные типы фиксированного размера, все еще придерживаясь zlib API/ABI.
② Это загружает общую библиотеку zlib. В POSIX-системах он называется libz.so и обычно устанавливается заранее. Поскольку ffi.load() автоматически добавляет отсутствующие стандартные префиксы / суффиксы, мы можем просто загрузить библиотеку "z". В Windows это называется zlib1.dll, и вам придется сначала загрузить его с сайта zlib. Проверка ffi.os гарантирует, что мы передадим правильное имя ffi.load().
③ Во-первых, максимальный размер буфера сжатия получается путем вызова функции zlib.compressBound с длиной несжатой строки. Следующая строка выделяет байтовый буфер такого размера. [?] В спецификации типа указывает массив переменной длины (VLA). Фактическое количество элементов этого массива задается как 2-й аргумент ffi.new().
④ Сначала это может выглядеть странно, но посмотрите на объявление функции compress2 из zlib: длина адресата определена как указатель! Это связано с тем, что вы передаете максимальный размер буфера и возвращаете фактическую длину, которая была использована.
В C вы должны указать адрес локальной переменной (&buflen). Но поскольку в Lua нет адреса оператора, мы просто передадим одноэлементный массив. Удобно его можно инициализировать с максимальным размером буфера за один шаг. Вызов фактической функции zlib.compress2 является простым.
⑤ Мы хотим вернуть сжатые данные как строку Lua, поэтому будем использовать ffi.string(). Ему нужен указатель на начало данных и фактическую длину. Длина возвращена в массив buflen, поэтому мы просто получим ее оттуда.
Обратите внимание, что поскольку функция возвращается теперь, buf и buflen переменные в конечном итоге будут собраны мусором. Это прекрасно, потому что ffi.string() скопировал содержимое во вновь созданную (интернированную) строку Lua. Если вы планируете многократно вызывать эту функцию, подумайте о повторном использовании буферов и / или передаче результатов в буферах вместо строк. Это уменьшит накладные расходы на сбор мусора и интернирование строк.
⑥ Функции uncompress выполняют противоположную функцию сжатия. Сжатые данные не включают размер исходной строки, поэтому это нужно передать. В противном случае здесь нет сюрпризов.
⑦ Код, который использует только что определенные функции, является просто кодом Lua. Ему не нужно ничего знать о LuaJIT FFI - функции удобства обертки полностью скрывают его.
Одним из основных преимуществ LuaJIT FFI является то, что теперь вы можете написать эти обертки в Lua. И в какой-то момент времени вам будет необходимо создать дополнительный модуль C с использованием Lua/C API. Многие из более простых функций C, вероятно, могут использоваться непосредственно из вашего кода Lua без каких-либо оберток.
Замечание: zlib API использует длинный тип для прохождения длин и размеров. Но все эти функции zlib фактически имеют дело только с 32-битными значениями. Это неудачный выбор для публичного API, но может быть объяснено историей zlib - нам просто нужно будет с этим справиться.
Во-первых, вы должны знать, что длинный 64-разрядный тип, например. в системах POSIX/x64, но 32-разрядный тип для Windows/x64 и 32-разрядных систем. Таким образом, длинным результатом может быть либо простое Lua-число, либо 64-битный целочисленный объект cdata, в зависимости от целевой системы.
Итак, функции ffi.* Обычно принимают объекты cdata везде, где вы хотите использовать число. Вот почему мы получаем отступ с передачей n на ffi.string() выше. Но другие функции или модули Lua библиотеки не знают, как с этим справиться. Поэтому для максимальной переносимости вам нужно использовать tonumber () для возврата длинных результатов, прежде чем передавать их. В противном случае приложение может работать в некоторых системах, но не будет работать в среде POSIX / x64.


Определение метаметодов для типа C
Следующий код объясняет, как определить метаметоды для типа C. Мы определяем простой точечный тип и добавляем к нему некоторые операции:
Lua:
local ffi = require("ffi")
ffi.cdef[[ // ①
typedef struct { double x, y; } point_t;
]]

local point -- ②
local mt = {
  __add = function(a, b) return point(a.x+b.x, a.y+b.y) end, -- ③
  __len = function(a) return math.sqrt(a.x*a.x + a.y*a.y) end,
  __index = { -- ④
    area = function(a) return a.x*a.x + a.y*a.y end,
  },
}
point = ffi.metatype("point_t", mt) -- ⑤

local a = point(3, 4) -- ⑥
print(a.x, a.y)  --> 3  4
print(#a)        --> 5
print(a:area())  --> 25
local b = a + point(0.5, 8)
print(#b)        --> 12.5
Вот пошаговое объяснение:
① Это определяет тип C для двумерного точечного объекта.
② Мы должны объявить переменную, удерживающую конструктор точек, потому что она используется внутри метаметода.
③ Давайте определим метаметод __add, который добавит координаты двух точек и создаст новый точечный объект. Для простоты эта функция предполагает, что оба аргумента являются точками. Но это может быть любое сочетание объектов, если хотя бы один операнд имеет требуемый тип (например, добавление точки плюс число или наоборот). Наш метаметод __len возвращает расстояние от точки до начала координат.
④ Если у нас заканчиваются операторы, мы можем также определить именованные методы. Здесь таблица __index определяет функцию области. Для пользовательских запросов индексирования может потребоваться определить функции __index и __newindex.
⑤ Это связывает метаметоды с нашим типом C. Это нужно сделать только один раз. Для удобства конструктор возвращается ffi.metatype(). Однако мы не обязаны использовать его. Исходный тип C все еще можно использовать, например, для создания массива точек. Метаметоды автоматически применяются к любым и всем типам использования этого типа.
Обратите внимание, что связь с метатаблицей является постоянной, а метатаблица не может быть изменена после этого! То же самое для таблицы __index.
⑥ Вот несколько простых примеров использования для типа точки и ожидаемых результатов. Предварительно определенные операции (такие как a.x) могут быть свободно смешаны с новыми метаметодами. Обратите внимание, что область является методом и должна быть вызвана с синтаксисом Lua для методов: a:area(), а не a.area().
Механизм метаметода типа C наиболее полезен при использовании в сочетании с библиотеками C, которые написаны в объектно-ориентированном стиле. Создатели возвращают указатель на новый экземпляр, а методы принимают указатель экземпляра в качестве первого аргумента. Иногда вы можете просто указать __index в пространство имен библиотеки и __gc деструктору, и все готово. Но достаточно часто вы хотите добавить удобные обертки, например. для возврата реальных строк Lua или при возврате нескольких значений.
Некоторые библиотеки C только объявляют указатели экземпляра как непрозрачный тип void *. В этом случае вы можете использовать поддельный тип для всех объявлений, например. указатель на именованную (неполную) структуру будет выполнять: typedef struct foo_type * foo_handle. Сторона С не знает, что вы заявляете при использовании FFI LuaJIT, но пока совместимые типы совместимы, все по-прежнему работает.

Перевод идиома C
Вот список общих идиом C и их перевод в FFI LuaJIT:
ИдиомаC кодLua код
Вывод указателя
int *p;
x = *p;
*p = y;
x = p[0]
p[0] = y
Индексирование указателя
int i, *p;
x = p[i];
p[i+1] = y;
x = p[i]
p[i+1] = y
Индексирование массива
int i, a[];
x = a[i];
a[i+1] = y;
x = a[i]
a[i+1] = y
Разыменования структуры/объединения
struct foo s;
x = s.field;
s.field = y;
x = s.field
s.field = y
Разыменования указателя структуры/объединения
struct foo *sp;
x = sp->field;
sp->field = y;
x = s.field
s.field = y
Арифметика указателя
int i, *p;
x = p + i;
y = p - i;
x = p + i
y = p - i
Вычисление разницы указателей
int *p1, *p2;
x = p1 - p2;x = p1 - p2
Указатель элемента массива
int i, a[];
x = &a[i];x = a+i
Смена указателя на адрес
int *p;
x = (intptr_t)p;x = tonumber(
ffi.cast("intptr_t",
p))
Функции с аргументами
void foo(int *inoutlen);
int len = x;
foo(&len);
y = len;
local len =
ffi.new("int[1]", x)
foo(len)
y = len[0]
Изменение аргументов
int printf(char *fmt, ...);
printf("%g", 1.0);
printf("%d", 1);
printf("%g", 1);
printf("%d",
ffi.new("int", 1))

Кэшировать или не кэшировать
Это обычная идиома Lua для кэширования функций библиотеки в локальных переменных или повышениях, например:
Lua:
local byte, char = string.byte, string.char
local function foo(x)
  return char(byte(x)+1)
end
Это заменяет несколько запросов хеш-таблицы с (более быстрым) непосредственным использованием локального или верхнего значения. Это менее важно с LuaJIT, поскольку компилятор JIT очень много оптимизирует поиск хеш-таблиц и даже способен вытащить большинство из них из внутренних циклов. Тем не менее, он не может устранить все из них, и это экономит некоторую типизацию для часто используемых функций. Таким образом, для этого еще есть место, даже с LuaJIT.
Ситуация немного отличается от вызовов функций C через библиотеку FFI. Компилятор JIT имеет специальную логику для устранения всех служебных задач поиска для функций, разрешенных из пространства имен библиотеки C! Таким образом, не полезно и на самом деле контрпродуктивно кэшировать отдельные функции C следующим образом:
Lua:
local funca, funcb = ffi.C.funca, ffi.C.funcb -- Не полезно!
local function foo(x, n)
  for i=1,n do funcb(funca(x, i), 1) end
end
Это превращает их в косвенные вызовы и генерирует больший и медленный машинный код. Вместо этого вы захотите кэшировать пространство имен и полагаться на компилятор JIT для устранения поиска:
Lua:
local C = ffi.C          -- Вместо этого используйте это!
local function foo(x, n)
  for i=1,n do C.funcb(C.funca(x, i), 1) end
end
Это генерирует как более короткий, так и быстрый код. Так что не кешируйте C-функции, а занимайте пространства имен кэшей! Чаще всего пространство имен уже находится в локальной переменной во внешней области видимости, например. из локального lib = ffi.load (...). Обратите внимание, что копирование его в локальную переменную в области функции не нужно.
 

imring

чо тут
Администратор
Lampo Team
FFI: Функции
В этом пункте подробно описаны функции API, предоставляемые библиотекой FFI. Рекомендуется сначала прочитать введение и учебник FFI.

Словарь
  • cdecl — абстрактное объявление типа C (строка Lua).
  • ctype — объект типа C. Это особый вид cdata, возвращаемый ffi.typeof(). Он выступает в качестве конструктора cdata при вызове.
  • cdata — объект данных C. Он содержит значение соответствующего ctype.
  • ct — Спецификация типа C, которая может использоваться для большинства функций API. Или cdecl, ctype или cdata, служащие типом шаблона.
  • cb — обратный объект. Это объект данных C, содержащий специальный указатель функции. Вызов этой функции из кода C выполняет связанную с ней функцию Lua.
  • VLA — массив переменной длины объявлен с помощью ? вместо количества элементов, например. "int[?]". Число элементов (nelem) должно быть указано, когда оно создано.
  • VLS — структурой переменной длины является тип struct C, где последний элемент является VLA. Применяются те же правила для объявления и создания.

Объявление и доступ к внешним символам
Внешние символы должны быть объявлены первыми, и затем их можно получить путем индексации пространства имен библиотеки C, которое автоматически связывает символ с конкретной библиотекой.

Lua:
ffi.cdef(def)
Добавляет несколько объявлений C для типов или внешних символов (именованных переменных или функций). def должен быть строкой Lua. Рекомендуется использовать синтаксический сахар для строковых аргументов следующим образом:
Lua:
ffi.cdef[[
typedef struct foo { int a, b; } foo_t;  // Объявить структуру и typedef.
int dofoo(foo_t *f, int n);  /* Объявить внешнюю функцию C. */
]]
Содержимое строки (часть в зеленом выше) должна быть последовательностью С-деклараций, разделенных точками с запятой. Конечная точка с запятой для одного объявления может быть опущена.
Обратите внимание, что внешние символы только объявлены, но они еще не привязаны к какому-либо конкретному адресу. Связывание осуществляется с помощью пространств имен библиотеки C (см. ниже).
Однако объявления C не передаются через предварительный процессор C. Допускаются допроцессорные маркеры, кроме #pragma pack. Замените #define в существующих файлах заголовков C с enum, static const или typedef и/или передайте файлы через внешний предварительный процессор C (один раз). Будьте внимательны, чтобы не включать ненужные или избыточные объявления из несвязанных файлов заголовков.

Lua:
ffi.C
Это пространство имен библиотеки C по умолчанию — обратите внимание на верхний регистр 'C'. Он привязывается к набору символов или библиотек по умолчанию в целевой системе. Это более или менее то же, что и компилятор C по умолчанию, без указания дополнительных библиотек ссылок.
В системах POSIX это привязывается к символам в пространстве имен по умолчанию или в глобальном пространстве имен. Сюда входят все экспортированные символы из исполняемого файла и любые библиотеки, загружаемые в глобальное пространство имен. Это включает, по крайней мере, libc, libm, libdl (в Linux), libgcc (если скомпилирован с GCC), а также любые экспортированные символы из Lua/C API, предоставленные самой LuaJIT.
В системах Windows это привязывается к символам, экспортированным из *.exe, lua51.dll (то есть к Lua/C API, предоставленному самой LuaJIT), библиотека времени выполнения LuaJIT была связана с (msvcrt*.dll), kernel32.dll, user32.dll и gdi32.dll.
Lua:
clib = ffi.load(name [,global])
Это загружает динамическую библиотеку, заданную по имени, и возвращает новое пространство имен библиотеки C, которое привязывается к его символам. В системах POSIX, если глобальное значение истинно, символы библиотеки также загружаются в глобальное пространство имен.
Если имя - это путь, библиотека загружается с этого пути. В противном случае имя канонизируется системно-зависимым образом и выполняется поиск по пути поиска по умолчанию для динамических библиотек:
В системах POSIX, если имя не содержит точки, добавляется расширение .so. Кроме того, при необходимости префикс lib добавляется. Поэтому ffi.load("z") ищет "libz.so" в пути поиска по умолчанию общей библиотеки.
В системах Windows, если имя не содержит точки, добавляется расширение .dll. Поэтому ffi.load("ws2_32") ищет "ws2_32.dll" в пути поиска по умолчанию DLL.

Создание объектов cdata
Следующие функции API создают объекты cdata (type() возвращает "cdata"). Все созданные объекты cdata собираются с мусором.
Lua:
cdata = ffi.new(ct [,nelem] [,init...])
cdata = ctype([nelem,] [init...])
Создает объект cdata для данного ct. Для типов VLA / VLS требуется аргумент nelem. Второй синтаксис использует ctype как конструктор и в остальном полностью эквивалентен.
Объект cdata инициализируется в соответствии с правилами инициализаторов, используя необязательные аргументы init. Избыточные инициализаторы вызывают ошибку.
Уведомление о производительности: если вы хотите создать много объектов одного типа, проанализируйте cdecl только один раз и получите его ctype с помощью ffi.typeof(). Затем используйте ctype как конструктор повторно.
Обратите внимание, что анонимная декларация структуры неявно создает новый и различаемый тип ctype каждый раз, когда вы используете его для ffi.new(). Вероятно, это не то, что вы хотите, особенно если вы создаете более одного объекта cdata. Различные анонимные структуры не считаются совместимыми с назначением по стандарту C, хотя они могут иметь одинаковые поля! Кроме того, JIT-компилятор рассматривается различными типами, что может вызвать чрезмерное количество следов. Настоятельно рекомендуется объявить именованную структуру или typedef с помощью ffi.cdef() или создать один объект ctype для анонимной структуры с помощью ffi.typeof().
Lua:
ctype = ffi.typeof(ct)
Создает объект ctype для данного ct.
Эта функция особенно полезна для разбора cdecl только один раз, а затем использовать полученный объект ctype в качестве конструктора.
Lua:
cdata = ffi.cast(ct, init)
Создает скалярный объект cdata для данного ct. Объект cdata инициализируется init, используя вариант "cast" для правил преобразования типа C.
Эти функции в основном полезны для переопределения проверок совместимости указателей или для преобразования указателей на адреса или наоборот.
Lua:
ctype = ffi.metatype(ct, metatable)
Создает объект ctype для данного ct и связывает его с метатебельным. Допускаются только типы структуры/объединения, комплексные числа и векторы. Другие типы могут быть завернуты в структуру, если это необходимо.
Связь с метатаблицей является постоянной и не может быть изменена впоследствии. После этого содержимое метатаблицы или содержимое таблицы __index (если оно есть) могут быть изменены. Связана мататаблица автоматически применяется ко всем видам использования этого типа, независимо от того, как создаются объекты или откуда они происходят. Обратите внимание, что предопределенные операции над типами имеют приоритет (например, объявленные имена полей не могут быть переопределены).
Lua:
cdata = ffi.gc(cdata, finalizer)
Связывает финализатор с указателем или агрегированным объектом cdata. Объект cdata возвращается без изменений.
Эта функция позволяет безопасно интегрировать неуправляемые ресурсы в автоматическое управление памятью сборщика мусора LuaJIT. Типичное использование:
Lua:
local p = ffi.gc(ffi.C.malloc(n), ffi.C.free)
...
p = nil -- Последняя ссылка на p ушла.
-- GC в конечном итоге запускает финализатор: ffi.C.free(p)
Финализатор cdata работает как метаданный __gc для объектов userdata: когда последняя ссылка на объект cdata отсутствует, связанный финализатор вызывается с объектом cdata в качестве аргумента. Финализатор может быть функцией Lua или функцией cdata или указателем функции cdata. Существующий финализатор можно удалить, установив нулевой финализатор, например. прямо перед явным удалением ресурса:
Lua:
ffi.C.free(ffi.gc(p, nil)) -- Вручную освобождает память.
Информация о типах C
Следующие функции API возвращают информацию о типах C. Они наиболее полезны для проверки объектов cdata.
Lua:
size = ffi.sizeof(ct [,nelem])
Возвращает размер ct в байтах. Возвращает nil, если размер неизвестен (например, для "void" или типов функций). Требуется nelem для типов VLA/VLS, за исключением объектов cdata.
Lua:
align = ffi.alignof(ct)
Возвращает минимальное требуемое выравнивание для ct в байтах.
Lua:
ofs [,bpos,bsize] = ffi.offsetof(ct, field)
Возвращает смещение (в байтах) fiend относительно начала ct, которое должно быть структурой. Дополнительно возвращает позицию и размер поля (в битах) для полей бит.
Lua:
status = ffi.istype(ct, obj)
Возвращает true, если obj имеет тип C, заданный ct. Возвращает false в противном случае.
Квалификаторы типа C (const и т.д.) игнорируются. Указатели проверяются стандартными правилами совместимости указателей, но без специальной обработки для void*. Если ct указывает struct/union, то также указывается указатель на этот тип. В противном случае типы должны точно соответствовать.
Примечание: эта функция принимает все типы объектов Lua для аргумента obj, но всегда возвращает false для объектов, отличных от cdata.

Полезные функции
Lua:
err = ffi.errno([newerr])
Возвращает номер ошибки, установленный последним вызовом функции C, который указывает на состояние ошибки. Если присутствует дополнительный аргумент newerr, номер ошибки устанавливается в новое значение и возвращается предыдущее значение.
Эта функция предлагает портативный и независимый от ОС способ получения и установки номера ошибки. Обратите внимание, что только некоторые функции C задают номер ошибки. И это очень важно, если функция фактически указала условие ошибки (например, с возвратным значением -1 или NULL). В противном случае оно может содержать или не содержать ранее установленное значение.
Вам рекомендуется называть эту функцию только при необходимости и как можно ближе к возврату связанной функции C. Значение errno сохраняется в зависимости от перехвата, выделения памяти, вызовов JIT-компилятора и другой внутренней активности виртуальной машины. То же самое относится к значению, возвращаемому GetLastError() в Windows, но вам нужно объявить и называть его самостоятельно.
Lua:
str = ffi.string(ptr [,len])
Создает внутреннюю строку Lua из данных, на которые указывает ptr.
Если необязательный аргумент len отсутствует, то ptr преобразуется в "char *", и предполагается, что данные оканчиваются на нуль. Длина строки вычисляется с помощью strlen().
В противном случае ptr преобразуется в "void *", а len - длину данных. Данные могут содержать внедренные нули и не должны быть байт-ориентированными (хотя это может привести к проблемам с энзиманией).
Эта функция в основном полезна для преобразования (временных) указателей "const char *", возвращаемых функциями C, в строки Lua и сохранения их или передачи их другим функциям, ожидающим строку Lua. Строка Lua является (интернированной) копией данных и больше не имеет отношения к исходной области данных. Строки Lua 8 бит чистые и могут использоваться для хранения произвольных, несимвольных данных.
Уведомление о производительности: быстрее передать длину строки, если она известна. Например. когда длина возвращается вызовом C, подобным sprintf().
Lua:
ffi.copy(dst, src, len)
ffi.copy(dst, str)
Копирует данные, на которые указывает src на dst. dst преобразуется в "void *", а src преобразуется в "const void *".
В первом синтаксисе len дает количество байтов для копирования. Предостережение: если src является строкой Lua, то len не должен превышать #src + 1.
Во втором синтаксисе источником копии должна быть строка Lua. Все байты строки плюс нулевой ограничитель копируются в dst (т.е. #src + 1 байт).
Уведомление о производительности: ffi.copy() может использоваться как более быстрая (встроенная) замена для функций библиотеки C memcpy(), strcpy() и strncpy().
Lua:
ffi.fill(dst, len [,c])
Заполняет данные, на которые указывает dst с постоянными байтами len, заданными c. Если c опускается, данные заполняются нулями.
Уведомление о производительности: ffi.fill() может использоваться как более быстрая (встроенная) замена для функции памяти библиотеки C (dst, c, len). Обратите внимание на различный порядок аргументов!

Целевая информация
Lua:
status = ffi.abi(param)
Возвращает true, если param (строка Lua) применяется для целевого ABI (Application Binary Interface). Возвращает false в противном случае. В настоящее время определены следующие параметры:
ПараметрОписание
32bit32-битная структура
64bit64-битная структура
leСтруктура с прямым порядком байтов
beСтруктура с обратным порядком байтов
fpuЦель имеет аппаратный FPU
softfpСоглашения о вызовах softfp
hardfpСоглашения о вызовах hardfp
eabiEABI вариант стандарта ABI
winWindows вариант стандарта ABI
Lua:
ffi.os
Содержит имя целевой ОС. То же содержание, что и jit.os.
Lua:
ffi.arch
Содержит имя целевой архитектуры. То же содержание, что и jit.arch.

Методы для обратных вызовов
Типы C для обратных вызовов имеют несколько дополнительных методов:
Lua:
cb:free()
Освобождает ресурсы, связанные с обратным вызовом. Связанная функция Lua не используется и может быть собрана сборщиком мусора. Указатель на функцию обратного вызова больше не действителен и больше не должен вызываться (он может быть повторно использован последующим созданным обратным вызовом).
Lua:
cb:set(func)
Связывает новую функцию Lua с обратным вызовом. Тип C обратного вызова и указатель функции обратного вызова не изменяются.
Этот метод полезен для динамического переключения получателя обратных вызовов без создания нового обратного вызова каждый раз и его повторной регистрации (например, с помощью библиотеки GUI).

Расширенные стандартные библиотечные функции
Следующие стандартные библиотечные функции были расширены для работы с объектами cdata:
Lua:
n = tonumber(cdata)
Преобразует объект cdata с номером в double и возвращает его как число Lua. Это особенно полезно для 64-битных целочисленных значений в штучной упаковке. Предостережение: это преобразование может привести к потере точности.
Lua:
s = tostring(cdata)
Возвращает строковое представление значения 64-битных целых чисел ("nnnLL" или "nnnULL") или комплексных чисел ("re±imi"). В противном случае возвращает строковое представление типа C объекта ctype ("ctype <type>") или объекта cdata ("cdata <type>: address"), если вы не переопределите его метаметодом __tostring (см. ffi.metatype()).
Lua:
iter, obj, start = pairs(cdata)
iter, obj, start = ipairs(cdata)
Вызывает метаметод __pairs или __ipairs соответствующего ctype.

Расширения для Lua Parser
Парсер исходного кода Lua обрабатывает числовые литералы с суффиксами LL или ULL как 64-разрядные целые числа со знаком или без знака. Регистр не имеет значения, но заглавные буквы рекомендуется для удобства чтения. Он обрабатывает как десятичные (42LL), так и шестнадцатеричные (0x2aLL) литералы.
Мнимая часть комплексных чисел может быть задана суффиксом числовых литералов с i или I, например, 12.5i. Предостережение: вам нужно использовать 1i, чтобы получить мнимую часть со значением 1, так как сам по-прежнему ссылается на переменную с именем i.
 

imring

чо тут
Администратор
Lampo Team
FFI: Семантика
На этой странице описана подробная семантика, лежащая в основе библиотеки FFI и ее взаимодействия с кодом Lua и C.
Учитывая, что библиотека FFI предназначена для взаимодействия с кодом C и что объявления могут быть написаны в простом синтаксисе C, она, по возможности, следует семантике языка C. Для более плавного взаимодействия с семантикой языка Lua требуются небольшие уступки.
Пожалуйста, не ошеломляйтесь содержанием этой страницы - это ссылка, и вам, возможно, придется проконсультироваться с ней, если вы сомневаетесь. Просматривать эту страницу не повредит, но большая часть семантики "просто работает" так, как вы ожидаете, что они будут работать. Написание приложений с использованием FFI LuaJIT должно быть простым для разработчиков с фоном C или C++.

Поддержка языка C
Библиотека FFI имеет встроенный синтаксический анализатор C с минимальным объемом памяти. Он используется библиотечными функциями ffi.* для объявления типов C или внешних символов.
Его единственная цель - анализ объявлений C, например, найденных в заголовочных файлах C. Хотя он вычисляет константные выражения, он не является компилятором Си. Тело определений встроенной функции C просто игнорируется.
Кроме того, это не проверяющий C-парсер. Он ожидает и принимает правильно сформированные объявления C, но может игнорировать некорректные объявления или показывать довольно общие сообщения об ошибках. Если вы сомневаетесь, пожалуйста, проверьте ввод с вашим любимым компилятором Си.
Анализатор C соответствует стандарту языка C99 и следующие расширения:
  • '\e' экранирует символьные и строковые литералы.
  • Логический тип C99/C++, объявленный с ключевыми словами bool или _Bool.
  • Комплексные числа, объявленные с ключевыми словами complex или _Complex.
  • Два типа комплексных чисел: complex (aka complex double) и complex float.
  • Векторные типы, объявленные с помощью режима GCC или атрибута vector_size.
  • Безымянные ('прозрачные') поля struct/union внутри struct/union.
  • Неполные объявления enum, обрабатываются как неполные объявления структуры.
  • Безымянные поля перечисления внутри struct/union. Это похоже на C++ enum в области видимости, за исключением того, что объявленные константы также видны в глобальном пространстве имен.
  • Статические объявления констант (static const) в рамках struct/union (из C++).
  • Массивы нулевой длины ([0]), пустые struct/union, массивы переменной длины (VLA, [?]) и структуры переменной длины (VLS, с конечным VLA).
  • C++ ссылочные типы (int& x).
  • Альтернативные ключевые слова GCC с '__', например, __const__.
  • GCC __attribute__ со следующими атрибутами: aligned, packed, mode, vector_size, cdecl, fastcall, stdcall, thiscall.
  • Ключевое слово GCC __extension__ и оператор GCC __alignof__.
  • GCC __asm __ ("symname") перенаправление имени символа для объявлений функций.
  • Ключевые слова MSVC для типов фиксированной длины: __int8, __int16, __int32 и __int64.
  • MSVC __cdecl, __fastcall, __stdcall, __thiscall, __ptr32, __ptr64, __declspec(align(n)) и #pragma pack.
  • Все остальные атрибуты GCC / MSVC игнорируются.
Следующие типы C предопределены синтаксическим анализатором C (подобно typedef, за исключением того, что повторные объявления будут игнорироваться):
  • Обработка Vararg: va_list, __builtin_va_list, __gnuc_va_list.
  • Из <stddef.h>: ptrdiff_t, size_t, wchar_t.
  • Из <stdint.h>: int8_t, int16_t, int32_t, int64_t, uint8_t, uint16_t, uint32_t, uint64_t, intptr_t, uintptr_t.
Рекомендуется использовать эти типы в предпочтении перед специфичными для компилятора расширениями или стандартными типами, зависящими от цели. Например. char отличается подписью и долго различается по размеру, в зависимости от целевой архитектуры и платформы ABI.
Следующие функции C не поддерживаются:
  • Объявление всегда должно иметь спецификатор типа; по умолчанию это не тип int.
  • Объявления старых функций в старом стиле (K&R) недопустимы. Все функции C должны иметь правильное объявление прототипа. Функция, объявленная без параметров (int foo();), рассматривается как функция, принимающая нулевые аргументы, как в C++.
  • Длинный двойной тип (long double) C анализируется правильно, но нет поддержки для связанных преобразований, обращений или арифметических операций.
  • Широкие символьные строки и символьные литералы не поддерживаются.
Правила преобразования типов C
Преобразования из типов C в объекты Lua

Эти правила преобразования применяются для доступа для чтения к типам C: указатели индексирования, массивы или типы struct/union; чтение внешних переменных или постоянных значений; получение возвращаемых значений из вызовов C:
ВходПреобразованиеВыход
int8_t, int16_tsign-ext int32_t → doubleчисло
uint8_t, uint16_tzero-ext int32_t → doubleчисло
int32_t, uint32_t→ doubleчисло
int64_t, uint64_tПоле значения64-битная int cdata
double, float→ doubleчисло
bool0 → false, иначе trueboolean
enumПоле значенияenum cdata
Комплексное числоПоле значенияcomplex cdata
ВекторПоле значенияvector cdata
УказательПоле значенияpointer cdata
МассивПоле индексаreference cdata
struct/unionПоле индексаreference cdata
Битовые поля обрабатываются как их основной тип.
Ссылочные типы разыменовываются до того, как может произойти преобразование - преобразование применяется к типу C, указанному ссылкой.

Преобразование объектов Lua в типы C
Эти правила преобразования применяются для доступа при записи к типам C: указатели индексирования, массивы или типы struct/union; инициализация объектов cdata; приведение к типам C; запись во внешние переменные; передача аргументов в вызовы C:
ВходПреобразованиеВыход
Числоdouble
booleanfalse → 0, true → 1bool
nilNULL →(void *)
lightuserdatalightuserdata address →(void *)
userdatauserdata payload →(void *)
io.* fileПолучение FILE * handle →(void *)
СтрокаСопоставление с константой enumenum
СтрокаКопирование строковых данных + нулевой байтint8_t[], uint8_t[]
Строкаstring data →const char[]
ФункцияСоздание callback →Тип функции C
ТаблицаИнициализатор таблицыМассив
ТаблицаИнициализатор таблицыstruct/union
cdatacdata payload →Тип C
Если тип результата этого преобразования не соответствует типу C получателя, применяются правила преобразования между типами C.
Типы ссылок являются неизменными после инициализации ("нет повторного размещения ссылок"). В целях инициализации или при передаче значений в ссылочные параметры они обрабатываются как указатели. Обратите внимание, что в отличие от C++, нет способа реализовать автоматическую генерацию ссылок на переменные в семантике языка Lua. Если вы хотите вызвать функцию со ссылочным параметром, вам нужно явно передать одноэлементный массив.

Преобразования между типами C
Эти правила преобразования более-менее совпадают со стандартными правилами преобразования языка C. Некоторые правила применяются только к приведениям или требуют совместимости указателя или типа:
ВходПреобразованиеВыход
Целое число со знакомnarrow or sign-extendЦелое число
Целое число без знакаnarrow or sign-extendЦелое число
Целое числоrounddouble, float
double, floattrunc int32_t →narrow(u)int8_t,
(u)int16_t​
double, floattrunc(u)int32_t,
(u)int64_t​
double, floatroundfloat, double
Числоn == 0 → 0, иначе 1bool
boolfalse → 0, true → 1Число
Комплексное числоПреобразование реальную частьЧисло
ЧислоПреобразование вещественную часть, imag = 0Комплексное число
Комплексное числоПреобразование реальную и воображаемую частьКомплексное число
ЧислоКонвертирование скаляра и копированиеВектор
ВекторКопирование (того же размера)Вектор
struct/unionВзятие базового адреса (compat)Указатель
МассивВзятие базового адреса (compat)Указатель
ФункцияВзятие адреса функцииУказатель функции
ЧислоКонвертирование через uintptr_t (cast)Указатель
УказательКонвертирование адреса (compat/cast)Указатель
УказательКонвертирование адреса (cast)Целое число
МассивКонвертирование базового адреса (cast)Целое число
МассивКопирование (compat)Массив
struct/unionКопирование (идентичный тип)struct/union
Битовые поля или перечислимые типы обрабатываются как их базовый тип.
Преобразования, не перечисленные выше, вызовут ошибку. Например. невозможно преобразовать указатель в комплексное число или наоборот.

Преобразования для аргументов функции vararg C
Следующие правила преобразования по умолчанию применяются при передаче объектов Lua в переменную часть аргумента функций vararg C:
ВходПреобразованиеВыход
Числоdouble
booleanfalse → 0, true → 1bool
nilNULL →(void *)
userdatauserdata payload →(void *)
lightuserdatalightuserdata address →(void *)
Строкаstring data →const char *
float cdatadouble
Массив cdataВзятие базового адресаУказатель элемента
struct/union cdataВзятие базового адресаУказатель struct/union
Функция cdataВзятие адреса функцииУказатель функции
Любой другой cdataНет преобразованияТип C
Чтобы передать объект Lua, отличный от объекта cdata, в качестве определенного типа, необходимо переопределить правила преобразования: создайте временный объект cdata с помощью конструктора или приведения и инициализируйте его значением для передачи:
Предполагая, что x является числом Lua, вот как передать его как целое число в функцию vararg:
Lua:
ffi.cdef[[
int printf(const char *fmt, ...);
]]
ffi.C.printf("integer value: %d\n", ffi.new("int", x))
Если вы этого не сделаете, то по умолчанию применяется правило числа Lua → преобразование в double. Функция C vararg, ожидающая целое число, увидит искаженное или неинициализированное значение.

Инициализаторы
Создание объекта cdata с помощью ffi.new() или эквивалентного синтаксиса конструктора также всегда инициализирует его содержимое. Применяются разные правила, в зависимости от количества необязательных инициализаторов и задействованных типов C:
  • Если инициализаторы не заданы, объект заполняется нулевыми байтами.
  • Скалярные типы (числа и указатели) принимают один инициализатор. Объект Lua преобразуется в скалярный тип C.
  • Valarrays (комплексные числа и векторы) обрабатываются как скаляры, когда дается один инициализатор. В противном случае они рассматриваются как обычные массивы.
  • Агрегированные типы (массивы и структуры) принимают либо один инициализатор cdata того же типа (конструктор копирования), либо инициализатор одной таблицы, либо плоский список инициализаторов.
  • Элементы массива инициализируются, начиная с нулевого индекса. Если для массива задан один инициализатор, он повторяется для всех остальных элементов. Этого не происходит, если заданы два или более инициализатора: все оставшиеся неинициализированные элементы заполнены нулевыми байтами.
  • Массивы байтов также могут быть инициализированы строкой Lua. Это копирует всю строку плюс завершающий нулевой байт. Копирование останавливается рано, только если массив имеет известный фиксированный размер.
  • Поля структуры инициализируются в порядке их объявления. Неинициализированные поля заполнены нулевыми байтами.
  • Только первое поле union может быть инициализировано плоским инициализатором.
  • Элементы или поля, которые сами являются агрегатами, инициализируются одним инициализатором, но это может быть инициализатор таблицы или совместимый агрегат.
  • Избыточные инициализаторы вызывают ошибку.

Инициализаторы таблиц
Следующие правила применяются, если таблица Lua используется для инициализации массива или struct/union:
  • Если индекс таблицы [0] не равен nil, то предполагается, что таблица начинается с нуля. В противном случае предполагается, что он один.
  • Элементы массива, начиная с нулевого индекса, инициализируются последовательно с последовательными элементами таблицы, начиная с индекса [0] или [1]. Этот процесс останавливается на первом элементе таблицы nil.
  • Если был инициализирован ровно один элемент массива, он повторяется для всех остальных элементов. В противном случае все оставшиеся неинициализированные элементы заполняются нулевыми байтами.
  • Вышеприведенная логика применима только к массивам с фиксированным размером. VLA инициализируется только с элементами, указанными в таблице. В зависимости от варианта использования может потребоваться явно добавить терминатор NULL или 0 в VLA.
  • stuct/union может быть инициализировано в порядке объявления его полей. Каждое поле инициализируется последовательными элементами таблицы, начиная с индекса [0] или [1]. Этот процесс останавливается на первом элементе таблицы nil.
  • В противном случае, если нет ни index [0], ни [1], struct/union инициализируется путем поиска каждого имени поля (как строкового ключа) в таблице. Каждое ненулевое значение используется для инициализации соответствующего поля.
  • Неинициализированные поля структуры заполнены нулевыми байтами, за исключением завершающего VLA VLS.
  • Инициализация union прекращается после инициализации одного поля. Если поле не было инициализировано, объединение заполняется нулевыми байтами.
  • Элементы или поля, которые сами являются агрегатами, инициализируются одним инициализатором, но это может быть инициализатор вложенной таблицы (или совместимый агрегат).
  • Избыточные инициализаторы для массива вызывают ошибку. Избыточные инициализаторы для struct/union игнорируются. Несвязанные записи таблицы также игнорируются.
Пример:
Lua:
local ffi = require("ffi")

ffi.cdef[[
struct foo { int a, b; };
union bar { int i; double d; };
struct nested { int x; struct foo y; };
]]

ffi.new("int[3]", {})            --> 0, 0, 0
ffi.new("int[3]", {1})           --> 1, 1, 1
ffi.new("int[3]", {1,2})         --> 1, 2, 0
ffi.new("int[3]", {1,2,3})       --> 1, 2, 3
ffi.new("int[3]", {[0]=1})       --> 1, 1, 1
ffi.new("int[3]", {[0]=1,2})     --> 1, 2, 0
ffi.new("int[3]", {[0]=1,2,3})   --> 1, 2, 3
ffi.new("int[3]", {[0]=1,2,3,4}) --> ошибка: слишком много инициализаторов

ffi.new("struct foo", {})            --> a = 0, b = 0
ffi.new("struct foo", {1})           --> a = 1, b = 0
ffi.new("struct foo", {1,2})         --> a = 1, b = 2
ffi.new("struct foo", {[0]=1,2})     --> a = 1, b = 2
ffi.new("struct foo", {b=2})         --> a = 0, b = 2
ffi.new("struct foo", {a=1,b=2,c=3}) --> a = 1, b = 2  'c' игнорируется

ffi.new("union bar", {})        --> i = 0, d = 0.0
ffi.new("union bar", {1})       --> i = 1, d = ?
ffi.new("union bar", {[0]=1,2}) --> i = 1, d = ?    '2' игнорируется
ffi.new("union bar", {d=2})     --> i = ?, d = 2.0

ffi.new("struct nested", {1,{2,3}})     --> x = 1, y.a = 2, y.b = 3
ffi.new("struct nested", {x=1,y={2,3}}) --> x = 1, y.a = 2, y.b = 3
Операции над объектами cdata
Все стандартные операторы Lua могут применяться к объектам cdata или к комбинации объекта cdata и другого объекта Lua. В следующем списке показаны предопределенные операции.
Ссылочные типы разыменовываются перед выполнением каждой из перечисленных ниже операций - операция применяется к типу C, указанному ссылкой.
Предопределенные операции всегда пробуются в первую очередь, прежде чем переходить к метаметоду или индексной таблице (если есть) для соответствующего ctype (за исключением __new). Возникает ошибка, если поиск метаметода или поиск в таблице индексов завершился неудачно.

Индексирование объекта cdata
  • Индексирование указателя/массива: указатель/массив cdata может быть проиндексирован по номеру cdata или номеру Lua. Адрес элемента вычисляется как базовый адрес плюс числовое значение, умноженное на размер элемента в байтах. Доступ для чтения загружает значение элемента и преобразует его в объект Lua. Доступ для записи преобразует объект Lua в тип элемента и сохраняет преобразованное значение в элементе. Ошибка возникает, если размер элемента не определен или предпринимается попытка доступа к постоянному элементу.
  • Разыменование поля struct/union: struct/union cdata или указатель на struct/union могут быть разыменованы с помощью строкового ключа с указанием имени поля. Адрес поля вычисляется как базовый адрес плюс относительное смещение поля. Доступ для чтения загружает значение поля и преобразует его в объект Lua. Доступ для записи преобразует объект Lua в тип поля и сохраняет преобразованное значение в поле. Ошибка возникает при попытке доступа к записи в постоянную структуру / объединение или в постоянное поле. Перечисляемые константы или статические константы рассматриваются как постоянное поле.
  • Индексирование комплексного числа: комплексное число может быть проиндексировано либо по номеру cdata, либо по номеру Lua со значениями 0 или 1, либо по строкам "re" или "im". Доступ для чтения загружает действительную часть ([0], .re) или мнимую часть ([1], .im) части комплексного числа и преобразует ее в число Lua. Подразделы комплексного числа являются неизменными - присвоение индекса комплексного числа вызывает ошибку. Доступ к внешним индексам возвращает неопределенные результаты, но гарантированно не вызовет нарушений доступа к памяти.
  • Индексирование вектора: вектор обрабатывается как массив для целей индексации, за исключением того, что элементы вектора являются неизменяемыми - назначение индекса вектора вызывает ошибку.
Объект ctype также может быть проиндексирован строковым ключом. Единственная предопределенная операция - это чтение констант области действия типов struct / union. Все остальные доступы относятся к соответствующим метаметодам или индексным таблицам (если есть).
Примечание: поскольку (преднамеренно) нет оператора адресации, объект cdata, содержащий тип значения, является практически неизменным после инициализации. JIT-компилятор извлекает выгоду из этого факта при применении определенных оптимизаций.
Как следствие, элементы комплексных чисел и векторов являются неизменными. Но элементы совокупности, содержащие эти типы, конечно, могут быть изменены. То есть Вы не можете назначить foo.c.im, но вы можете назначить (вновь созданный) комплексный номер для foo.c.
JIT-компилятор реализует строгие правила псевдонимов: доступ к различным типам не псевдоним, за исключением различий в подписи (это относится даже к указателям на символы, в отличие от C99). Тип наказания через союзы явно обнаружен и разрешен.

Вызов объекта cdata
  • Конструктор: объект ctype может быть вызван и использован как конструктор. Это эквивалентно ffi.new(ct, ...), если не определен метаметод __new. Метаметод __new вызывается с объектом ctype плюс любые другие аргументы, передаваемые в конструктор. Обратите внимание, что вы должны использовать ffi.new внутри него, так как вызов ct (...) вызовет бесконечную рекурсию.
  • Вызов функции C: можно вызвать функцию cdata или указатель на функцию cdata. Переданные аргументы преобразуются в типы C параметров, указанных в объявлении функции. Аргументы, передаваемые переменной-аргументу функции vararg C, используют специальные правила преобразования. Эта функция C вызывается, и возвращаемое значение (если оно есть) преобразуется в объект Lua.
    В системах Windows/x86 функции __stdcall обнаруживаются автоматически, а функция, объявленная как __cdecl (по умолчанию), автоматически фиксируется после первого вызова.

Арифметика на объектах cdata
  • Арифметика указателя: указатель/массив cdata и номер cdata или номер Lua могут быть добавлены или вычтены. Число должно быть справа для вычитания. Результатом является указатель того же типа с адресом плюс или минус числовое значение, умноженное на размер элемента в байтах. Ошибка возникает, если размер элемента не определен.
  • Разница в указателях: два совместимых указателя / массива cdata могут быть вычтены. В результате получается разница между их адресами, разделенная на размер элемента в байтах. Ошибка возникает, если размер элемента не определен или равен нулю.
  • 64-разрядная целочисленная арифметика: стандартные арифметические операторы (+ - * / % ^ и унарный минус) могут быть применены к двум номерам cdata или к числу cdata и числу Lua. Если один из них является uint64_t, другая сторона преобразуется в uint64_t, и выполняется арифметическая операция без знака. В противном случае обе стороны преобразуются в int64_t, и выполняется арифметическая операция со знаком. В результате получается 64-битный объект cdata в штучной упаковке.
    Если один из операндов является перечислением, а другой - строкой, строка преобразуется в значение совпадающей константы перечисления перед приведенным выше преобразованием.
    Эти правила гарантируют, что 64-битные целые числа являются "липкими". Любое выражение, включающее хотя бы один 64-битный целочисленный операнд, приводит к другому. Неопределенные случаи для операторов деления, по модулю и степени возвращают 2LL ^ 63 или 2ULL ^ 63
    Вам придется явно преобразовать 64-битное целое число в число Lua (например, для регулярных вычислений с плавающей запятой) с помощью tonumber(). Но учтите, что это может привести к потере точности.

Сравнение объектов cdata
  • Сравнение указателей: можно сравнивать два совместимых указателя/массива cdata. Результат такой же, как и сравнение без знака их адресов. nil обрабатывается как указатель NULL, который совместим с любым другим типом указателя.
  • 64-разрядное целочисленное сравнение: два числа cdata или число cdata и число Lua можно сравнивать друг с другом. Если один из них является uint64_t, другая сторона преобразуется в uint64_t и выполняется сравнение без знака. В противном случае обе стороны преобразуются в int64_t, и выполняется сравнение со знаком.
    Если один из операндов является перечислением, а другой - строкой, строка преобразуется в значение совпадающей константы перечисления перед приведенным выше преобразованием.
  • Сравнения на равенство/неравенство никогда не вызывают ошибку. Даже несовместимые указатели можно сравнить на равенство по адресу. Любое другое несовместимое сравнение (также с не-cdata объектами) рассматривает обе стороны как неравные.

объекты cdata как ключи таблиц
Таблицы Lua могут быть проиндексированы объектами cdata, но это не обеспечивает никакой полезной семантики - объекты cdata не подходят в качестве ключей таблиц!
Объект cdata обрабатывается как любой другой объект, собираемый мусором, и хэшируется и сравнивается по его адресу для индексации таблицы. Поскольку для типов значений cdata нет интернирования, одно и то же значение может быть упаковано в разные объекты cdata с разными адресами. Таким образом, t[1LL+1LL] и t[2LL] обычно не указывают на один и тот же хэш-слот и, конечно, не указывают на тот же хэш-слот, что и t[2].
Это серьезно увеличило бы сложность реализации и замедлило бы общий случай, если бы добавили дополнительную обработку для хеширования по значениям и сравнения с таблицами Lua. Учитывая повсеместное использование их внутри ВМ, это неприемлемо.
Существует три жизнеспособных варианта, если вам действительно нужно использовать объекты cdata в качестве ключей:
  • Если вы можете обойтись с точностью чисел Lua (52 бита), то используйте tonumber() для номера cdata или объедините несколько полей агрегации cdata в число Lua. Затем используйте полученный номер Lua в качестве ключа при индексации таблиц.
    Одно очевидное преимущество: t[tonumber(2LL)] указывает на тот же слот, что и t[2].
  • В противном случае используйте либо tostring() для 64-битных целых или комплексных чисел, либо объедините несколько полей агрегата cdata в строку Lua (например, с помощью ffi.string()). Затем используйте полученную строку Lua в качестве ключа при индексации таблиц.
  • Создайте свою собственную специализированную реализацию хеш-таблицы, используя типы C, предоставляемые библиотекой FFI, так же, как вы это делаете в C-коде В конечном итоге это может дать гораздо лучшую производительность, чем другие альтернативы, или то, что может предоставить общая хэш-таблица по значению.

Параметризованные типы
Чтобы упростить некоторые абстракции, две функции ffi.typeof и ffi.cdef поддерживают параметризованные типы в объявлениях Си. Примечание: ни одна из других функций API, использующих cdecl, не позволяет этого.
В любом месте, где вы можете написать имя определения типа, идентификатор или номер в объявлении, вы можете вместо этого написать $ (знак доллара). Эти заполнители заменяются в порядке появления аргументами, следующими за строкой cdecl:
Lua:
-- Объявление структуры с параметризованным типом поля и именем:
ffi.cdef([[
typedef struct { $ $; } foo_t;
]], type1, name1)

-- Анонимная структура с динамическими именами:
local bar_t = ffi.typeof("struct { int $, $; }", name1, name2)
-- Тип производного указателя:
local bar_ptr_t = ffi.typeof("$ *", bar_t)

-- Параметризованные размеры работают даже там, где VLA не работает:
local matrix_t = ffi.typeof("uint8_t[$][$]", width, height)
Предостережение: это не простая подстановка текста! Переданный объект ctype или cdata обрабатывается как базовый тип, переданная строка считается идентификатором, а число - числом. Вы не должны смешивать это: например, Передача "int" в виде строки не работает вместо типа, вам нужно использовать ffi.typeof("int").
Основное использование параметризованных типов - это библиотеки, реализующие абстрактные типы данных (пример), аналогичные тем, которые могут быть достигнуты с помощью метапрограммирования шаблонов C++. Другим вариантом использования являются производные типы анонимных структур, что позволяет избежать загрязнения глобального пространства имен struct.
Обратите внимание, что параметризованные типы являются хорошим инструментом и незаменимы для определенных случаев использования. Но вы захотите использовать их экономно в обычном коде, например когда все типы на самом деле исправлены.

Сборка мусора объектов cdata
Все явно (ffi.new(), ffi.cast() и т.д.) или неявно (средства доступа) созданные объекты cdata подвергаются сборке мусора. Вы должны убедиться, что действительные ссылки на объекты cdata находятся где-то в стеке Lua, в повышенном значении или в таблице Lua, пока они еще используются. Как только последняя ссылка на объект cdata исчезнет, сборщик мусора автоматически освободит используемую им память (в конце следующего цикла GC).
Обратите внимание, что сами указатели являются объектами cdata, однако за ними не следует сборщик мусора. Так, например если вы назначаете указателю массив cdata, вы должны поддерживать объект cdata, содержащий массив, пока указатель все еще используется:
Lua:
ffi.cdef[[
typedef struct { int *a; } foo_t;
]]

local s = ffi.new("foo_t", ffi.new("int[10]")) -- НЕПРАВИЛЬНО!

local a = ffi.new("int[10]") -- ОК
local s = ffi.new("foo_t", a)
-- Теперь сделайте что-нибудь с 's', но сохраняйте 'a' живым, пока не закончите.
Аналогичные правила применяются к строкам Lua, которые неявно преобразуются в "const char *": на сам строковый объект необходимо ссылаться где-либо, иначе он будет в конечном итоге собран мусором. Затем указатель будет указывать на устаревшие данные, которые, возможно, уже были перезаписаны. Обратите внимание, что строковые литералы автоматически поддерживаются, пока функция, содержащая их (фактически ее прототип), не является сборщиком мусора.
Объекты, которые передаются в качестве аргумента внешней функции C, остаются живыми до тех пор, пока не будет возвращен вызов. Поэтому обычно безопасно создавать временные объекты cdata в списках аргументов. Это распространенная идиома для передачи определенных типов C в функции vararg.
Области памяти, возвращаемые функциями C (например, из malloc()), должны, конечно, управляться вручную (или использовать ffi.gc()). Указатели на объекты cdata неотличимы от указателей, возвращаемых функциями C (что является одной из причин, по которой GC не может следовать за ними).
 
Сверху