Note for OneLuaPro Users on Windows: Unlike on POSIX systems, standard C library functions (libc) are not automatically exported to the global namespace under Windows. Therefore, accessing them via ffi.C (as shown in the following sections of this document) will fail. You must explicitly load the Universal C Runtime (UCRT) using local libc = ffi.load("ucrtbase"), as demonstrated in this interactive example:
> ffi = require("ffi")
> ffi.cdef([[ int puts(const char *s); ]])
> ffi.C.puts("hello")
stdin:1: undefined function 'puts'
stack traceback:
[C]: in metamethod 'index'
stdin:1: in main chunk
[C]: in ?
> libc = ffi.load("ucrtbase")
> libc.puts("hello")
hello
0
>Please keep this in mind when testing the following examples. Simply load libc as shown above and replace all subsequent mentions of ffi.C with libc.
Tip
If you are using an older system like Windows 7 and ucrtbase is not found, try loading the legacy runtime with ffi.load("msvcrt") instead.
local ffi = require("ffi")The module exports functions plus three values:
ffi._VERSIONffi.nullptrffi.C
Use ffi.cdef to provide C declarations.
ffi.cdef([[
struct Point {
int x;
int y;
};
typedef struct Point Point;
typedef int (*callback_t)(int);
int puts(const char *s);
]])- Basic C scalar types supported by this project.
typedefdeclarations.structanduniondeclarations (including nested/anonymous members).- Packed attribute parsing (
__attribute__((packed))). - Function declarations and function pointer types.
- Array declarators, including flexible form
?in type strings used byffi.new.
- Declarations are additive.
- Redefinition of known symbols is rejected.
cdefis for declarations only (no function definitions).
The following built-in basic C types are available by default:
void bool char short int long float double
int8_t int16_t int32_t int64_t uint8_t uint16_t uint32_t uint64_t
ino_t dev_t gid_t mode_t nlink_t uid_t off_t pid_t size_t ssize_t useconds_t suseconds_t blksize_t blkcnt_t time_t
ffi.C is the object returned by the ffi module's default load of the system C library.
Use this object to access declared system C functions.
ffi.cdef([[ int puts(const char *s); ]])
ffi.C.puts("hello")Loads a shared library and returns a library object.
local lib = ffi.load("./libdemo.so")
local lib_global = ffi.load("./libdemo.so", true)path: shared library path.global(optional): if true, loads with global symbol visibility.
Library object behavior:
lib.symbollooks up a declared function.- If declaration is missing: error for missing declaration.
- If declaration exists but symbol is absent in library: undefined function error.
Signature:
ffi.new(ct [, init])ct can be:
- A C type string.
- A ctype object from
ffi.typeof.
local i = ffi.new("int", 123)
local p = ffi.new("struct Point", {1, 2})
local a = ffi.new("int[4]", {1, 2})
local b = ffi.new("int [?]", 8, {1, 2, 3})Initialization semantics:
- Zero-initialized by default.
- Exactly one initializer is accepted (when provided).
- Array and struct initializers accept Lua tables.
Invalid constructions:
voidvalue creation is invalid.- Function value creation is invalid.
Signature:
ffi.cast(ct, value)Typical uses:
local p = ffi.cast("int *", 0x1234)
local n = ffi.cast("int", p)To create a callback function pointer, cast a Lua function:
local cb = ffi.cast("int (*)(int)", function(x)
return x * 2
end)Important behavior:
- Function pointer cast expects a Lua function.
- Callback lifetime is tracked by the resulting cdata object.
Returns canonical ctype object.
local t = ffi.typeof("struct Point")Returns true if obj has exactly the same canonical ctype.
local p = ffi.new("int", 1)
assert(ffi.istype("int", p))Returns byte size of a type or cdata value.
local sz = ffi.sizeof("struct Point")Returns field offset for record types.
local off = ffi.offsetof("struct Point", "y")Returns nil when field is not found.
Returns a pointer cdata to the underlying storage.
local v = ffi.new("int", 7)
local pv = ffi.addressof(v)
pv[0] = 99Converts scalar numeric cdata to Lua number/integer.
local v = ffi.new("int", 42)
assert(ffi.tonumber(v) == 42)Returns nil for non-numeric cdata (for example records).
Converts char pointer/array (or raw memory with explicit length) to Lua string.
local s = ffi.new("char[16]", "hello")
assert(ffi.string(s) == "hello")
assert(ffi.string(s, 3) == "hel")With explicit len, pointer/array/record memory can be read as bytes.
Copies memory to destination cdata.
local dst = ffi.new("char[16]")
ffi.copy(dst, "abc") -- writes NUL terminator too
ffi.copy(dst, "xyz", 2) -- raw copy of 2 bytesmemset style fill.
local buf = ffi.new("uint8_t[4]")
ffi.fill(buf, 4, 0x5a)Signature:
old = ffi.errno([new_errno])- Returns current
errno. - If argument is provided, sets
errnoand returns previous value.
local prev = ffi.errno()
ffi.errno(2)
ffi.errno(prev)Attach or remove Lua finalizer for cdata.
local p = ffi.gc(ffi.C.malloc(128), ffi.C.free)
-- remove finalizer
ffi.gc(p, nil)Returns the same cdata object.
Associates metamethod table with a record type.
ffi.cdef([[
struct Point { int x; int y; };
]])
local Point = ffi.metatype(ffi.typeof("struct Point"), {
__tostring = function(self)
return string.format("(%d,%d)", self.x, self.y)
end,
__index = {
add = function(self, dx, dy)
self.x = self.x + dx
self.y = self.y + dy
end,
},
})
local p = ffi.new(Point, {1, 2})
p:add(10, 20)Supported metatype hooks used by runtime:
__index__tostring
- Record cdata:
obj.field - Pointer/array cdata:
obj[index] - Pointer-to-record supports field access by name.
Assignment to const-qualified targets is rejected.
- Pointer cdata compares pointer values.
- Numeric scalar cdata compares by converted Lua numeric value.
nilcomparison works for pointer-null checks.
Declared C functions become callable cdata values.
ffi.cdef([[ int puts(const char *s); ]])
ffi.C.puts("hi")Vararg declarations are supported.
#cdata is supported for arrays and returns element count.
Version string.
Canonical null pointer cdata.
Example null comparison:
assert(ffi.nullptr == ffi.cast("void *", nil))local ffi = require("ffi")
ffi.cdef([[
struct student {
int age;
char name[0];
};
void *malloc(size_t size);
void free(void *ptr);
int printf(const char *format, ...);
]])
local p = ffi.gc(ffi.C.malloc(ffi.sizeof("struct student") + 32), ffi.C.free)
local st = ffi.new("struct student *", p)
st.age = 18
ffi.copy(st.name, "alice")
ffi.C.printf("name=%s age=%d\n", st.name, st.age)- Declare before use: function calls require matching
cdefprototypes. - Keep callbacks alive: store callback cdata as long as C may call it.
- Be explicit with ownership: use
ffi.gcfor allocated memory. - Use the correct
ffi.stringform:- no length for NUL-terminated char buffers,
- with length for raw bytes.
- Use canonical types when checking with
ffi.istype.