Foreign Function Interface (FFI)
The ffi module provides a foreign function interface for ucode, allowing direct interaction with C libraries. It combines a C declaration parser with libffi-based function calling to enable seamless interop between ucode and C.
The module can be imported using the wildcard import syntax:
import * as ffi from 'ffi';
Synopsis
import * as ffi from 'ffi';
// 1. Declare C types and functions
ffi.cdef(`
struct point { int x; int y; };
extern char **environ;
`);
// 2. Call C functions via the global C namespace
// Primitive return values are auto-converted to ucode types
let strcmp = ffi.C.wrap('int strcmp(const char *, const char *)');
print(strcmp("hello", "world"), "\n"); // => non-zero (number)
// 3. String return values remain as cdata - use ffi.string() to convert
let getenv = ffi.C.wrap('char *getenv(char *)');
let path_ptr = getenv('PATH'); // Returns char* cdata
let path_str = ffi.string(path_ptr); // Convert to ucode string
// 4. Create C data instances
ffi.cdef('struct point { int x; int y; };');
let p = ffi.ctype('struct point', 10, 20);
print(p.get('x'), p.get('y'), "\n"); // => 10 20
// 5. Access global variables
print(ffi.C.dlsym('environ').get(0), "\n");
// 6. Query type information
print(ffi.sizeof('int'), "\n"); // => 4
print(ffi.alignof('double'), "\n"); // => 8
print(ffi.offsetof('struct point', 'y'), "\n"); // => 4
// 7. Load external libraries
let libz = ffi.dlopen('z');
let zlibVersion = libz.wrap('const char *zlibVersion(void)');
print(zlibVersion().slice(), "\n"); // => "1.2.11" (or similar)
// Use in callbacks (primitives auto-converted)
let qsort = ffi.C.wrap('void qsort(void *, size_t, size_t, int (*)(const void *, const void *))');
let cmp = ffi.C.wrap('int strcmp(const char *, const char *)');
let arr = ffi.ctype('char *[5]', ["zebra", "apple", "banana", "cherry", "date"]);
// cmp() returns ucode number directly (primitives auto-converted)
qsort(arr.ptr(), arr.length(), arr.itemsize(),
(a, b) => cmp(a.deref('const char *'), b.deref('const char *')));
Memory Management for char* Return Values
When a wrapped C function returns char*, the return value is a cdata pointer object, not an auto-converted ucode string. This design prevents memory leaks and gives you explicit control over memory management.
Converting char* to ucode Strings
Use ffi.string() or slice() to convert a char* cdata to a ucode string:
let getenv = ffi.C.wrap('char *getenv(char *)');
let path_ptr = getenv('PATH'); // Returns char* cdata
let path = ffi.string(path_ptr); // Convert to ucode string
// or equivalently:
let path = path_ptr.slice(); // slice() without args = string()
Note: Both ffi.string() and slice() create a copy of the C string. The original C memory remains untouched.
Memory Ownership Patterns
Pattern 1: C Manages Memory (No Free Required)
Functions like getenv(), strerror() return pointers to static/internal memory managed by the C library. Do NOT free these.
let getenv = ffi.C.wrap('char *getenv(char *)');
let path_ptr = getenv('PATH');
let path = ffi.string(path_ptr); // Copies to ucode string
// path_ptr points to C internal memory - DO NOT free
// path is a ucode string - managed by ucode GC
Pattern 2: Caller Must Free (malloc'd Memory)
Functions like strdup(), asprintf(), getline() return malloc'd memory that you must free to avoid leaks.
let strdup = ffi.C.wrap('char *strdup(const char *)');
let free = ffi.C.wrap('void free(void *)');
let ptr = strdup("hello"); // malloc'd by strdup
let str = ffi.string(ptr); // Copies to ucode string
free(ptr); // NOW you can safely free
// str is safe - it's a ucode string copy
// ptr memory is freed - no leak
Key: Keep the cdata pointer until you're done copying, then free it.
Pattern 3: Stack-Allocated Buffers
When C writes into a buffer you provide (e.g., sprintf), the buffer is managed by ucode.
let sprintf = ffi.C.wrap('int sprintf(char *, const char *, ...)');
let buf = ffi.ctype('char[256]'); // ucode-managed array
sprintf(buf, "Hello %s", "World");
let msg = ffi.string(buf); // Copies to ucode string
// buf is managed by ucode GC - no manual free needed
Substring Operations with slice()
For char* pointers, slice() supports substring extraction:
let getenv = ffi.C.wrap('char *getenv(char *)');
let ptr = getenv('PATH');
// From start to end (same as ffi.string())
let full = ptr.slice();
// From start index to end
let rest = ptr.slice(5);
// Specific range
let part = ptr.slice(0, 10);
// Negative indices (from end)
let last = ptr.slice(-5);
Common Functions Reference
| Function | Memory Owner | Pattern |
|---|---|---|
getenv() | C (static) | No free needed |
strerror() | C (static) | No free needed |
strdup() | Caller | Must free() |
asprintf() | Caller | Must free() |
getline() | Caller | Must free() |
sprintf() | Caller (buffer) | Buffer managed by you |
strtok() | C (static) | No free needed |
Best Practices
- Always use
ffi.string()orslice()when you need a ucode string fromchar* - Track ownership: Does C manage the memory or do you?
- Free after copying: Call
free(ptr)only afterffi.string(ptr)orptr.slice() - Never free static memory:
getenv(),strerror()return static pointers
Limitations
- No vararg closures:
wrap()cannot create closures with variable arguments - Fixed ABI: Calling convention determined at closure creation time
- Platform constraints: Some architectures have limited support for certain type combinations
The ffi.C Namespace
ffi.C is a special CLib instance representing the process's global symbol table. It provides access to standard C library functions without explicit dlopen():
// These are equivalent:
let strlen1 = ffi.C.wrap('size_t strlen(const char *)');
ffi.cdef('size_t strlen(const char *);');
let strlen2 = ffi.C.wrap('strlen');
Functions declared via cdef() are automatically registered in ffi.C's symbol table.
Pointer Arithmetic and Memory Access
C data objects (cdata) provide methods for pointer arithmetic and memory access:
Creating Pointers with ptr()
Use ptr() to get a pointer to a cdata value:
let x = ffi.ctype('int', 42);
let px = x.ptr(); // int* pointer to x
// Pass to C functions expecting pointers
ffi.cdef('int atoi(const char *)');
let num = ffi.ctype('char[4]', "123");
let result = atoi(num.ptr()); // => 123
Array Indexing with get() and set()
Access array elements using get(index) and set(index, value):
let arr = ffi.ctype('int[5]', [10, 20, 30, 40, 50]);
// Read elements
let first = arr.get(0); // => 10 (ucode number)
let third = arr.get(2); // => 30 (ucode number)
// Modify elements
arr.set(0, 100);
arr.set(4, 200);
// Negative indices work too
let last = arr.get(-1); // => 200 (ucode number)
Understanding get() vs index()
get() returns converted ucode values, while index() returns raw cdata references. This is the key distinction between the two methods.
get() - Converted Values
The get() method immediately converts C values to ucode types:
let arr = ffi.ctype('int[5]', [10, 20, 30, 40, 50]);
// Returns ucode number directly
let val1 = arr.get(0); // => 10 (number)
let val2 = arr.get(2); // => 30 (number)
// Struct field access - returns converted value
ffi.cdef('struct point { int x; int y; };');
let p = ffi.ctype('struct point', 10, 20);
p.get('x'); // => 10 (number)
p.get('y'); // => 20 (number)
index() - Raw cdata References
The index() method returns a cdata reference for further manipulation:
let arr = ffi.ctype('int[5]', [10, 20, 30, 40, 50]);
// Returns cdata reference (unconverted)
let ref1 = arr.index(0); // => cdata (int)
let ref2 = arr.index(2); // => cdata (int)
// Convert to ucode value explicitly
ref1.get(); // => 10 (number)
// Or modify through the reference
arr.index(0).set(100); // Set arr[0] = 100
Pointer Arithmetic
Both methods work with pointers, but return different types:
let ptr = ffi.ctype('int *', arr.ptr());
// index() returns cdata reference
ptr.index(0); // => cdata at ptr[0]
ptr.index(1); // => cdata at ptr[1]
ptr.index(0).get(); // => 10 (number)
// get() returns converted value
ptr.get(0); // => 10 (number)
ptr.get(1); // => 20 (number)
Path Syntax Support
Both methods support path notation for nested access:
ffi.cdef('struct rect { struct point min; struct point max; };');
let r = ffi.ctype('struct rect', {
min: {x: 0, y: 0},
max: {x: 100, y: 100}
});
// get() returns converted value
r.get('min.x'); // => 0 (number)
// index() returns cdata reference
r.index('min.x'); // => cdata (int)
r.index('min.x').get() // => 0 (number)
Practical Guidance
Use get() when:
- You need the value immediately as a ucode type
- Reading values for computation:
let x = arr.get(i) - Accessing struct fields:
let y = struct.get('field') - Most common use cases
Use index() when:
- You need a reference for further manipulation
- Chaining operations:
arr.index(i).set(val) - Pointer arithmetic with cdata:
ptr.index(n).deref() - Passing references to other C functions
For writing values:
- Use
set()for both arrays and structs:arr.set(i, val),struct.set('f', val)
For getting pointers (not values):
- Use
ptr()on scalars:x.ptr()gives youint* - Arrays are already pointers:
arrcan be passed to C functions
Pointer Arithmetic via get() and index()
Both get(n) and index(n) work for pointer arithmetic on pointer types:
ffi.cdef('char *strdup(const char *)');
let strdup = ffi.C.wrap('char *strdup(const char *)');
let ptr = strdup("hello world");
// get() returns converted value (number for char)
let first_char = ptr.get(0); // 'h' (number 104)
let sixth_char = ptr.get(6); // 'w' (number 119)
// index() returns cdata reference
ptr.index(6); // => cdata (char)
ptr.index(6).get() // => 119 (number)
// Get substring from offset
let substring = ffi.string(ptr.get(6)); // "world"
free(ptr);
Path-Based Access for Nested Structures
Use dot notation and array indexing in paths for complex access:
ffi.cdef(`
struct point { int x; int y; };
struct rect { struct point min; struct point max; };
`);
let r = ffi.ctype('struct rect', {
min: {x: 0, y: 0},
max: {x: 100, y: 100}
});
// Nested field access
r.get('min.x'); // => 0
r.set('max.y', 50);
// Array of structs
ffi.cdef('struct point points[3];');
let arr = ffi.ctype('struct point[3]', [
{x: 1, y: 2},
{x: 3, y: 4},
{x: 5, y: 6}
]);
arr.get('[1].x'); // => 3
arr.set('[2].y', 10);
Dereferencing Pointers with deref()
Use deref(type) to read the value pointed to:
let x = ffi.ctype('int', 42);
let px = x.ptr();
let value = px.deref('int'); // => 42
// With char* pointers
ffi.cdef('char *strdup(const char *)');
let strdup = ffi.C.wrap('char *strdup(const char *)');
let ptr = strdup("hello");
let first_byte = ptr.deref('char'); // => 'h' (as number 104)
free(ptr);
Querying Array Properties
Use length() and itemsize() for array information:
let arr = ffi.ctype('int[10]');
arr.length(); // => 10 (number of elements)
arr.itemsize(); // => 4 (size of each element in bytes)
// Calculate total size
let total = arr.length() * arr.itemsize(); // => 40 bytes
Working with Byte Arrays
For char[] or uint8_t[], use slice() to extract strings:
let buf = ffi.ctype('char[10]', "hello");
// Extract as ucode string
let str = buf.slice(); // => "hello"
let part = buf.slice(0, 3); // => "hel"
// Or use ffi.string()
let str2 = ffi.string(buf); // => "hello"
Complete Example: String Manipulation
ffi.cdef(`
char *strdup(const char *);
void free(void *);
size_t strlen(const char *);
`);
let strdup = ffi.C.wrap('char *strdup(const char *)');
let free = ffi.C.wrap('void free(void *)');
let strlen = ffi.C.wrap('size_t strlen(const char *)');
// Create a duplicatable string
let ptr = strdup("hello world");
// Get length
let len = strlen(ptr).get(); // => 11
// Access individual characters via indexing
let first = ptr.get(0); // 'h'
let sixth = ptr.get(6); // 'w'
// Extract substrings
let hello = ptr.slice(0, 5); // "hello"
let world = ptr.slice(6); // "world"
// Modify in place
ptr.set(5, 0); // Null-terminate at space
let str = ffi.string(ptr); // => "hello"
// Clean up
free(ptr);
- Source
Classes
ffi.CData
Represents a C data object holding a value of a C type.
ffi.CLib
Represents a handle to a loaded shared library.
Methods
alignof(type) → {number}
Get the alignment requirement of a C type in bytes.
The alignof() function returns the minimum alignment requirement in bytes for a C type. This is useful for understanding structure padding and memory layout.
| Name | Type | Description |
|---|---|---|
type | string | The C type declaration. |
Throws an exception if the type is invalid.
The alignment requirement in bytes (typically a power of 2).
// Get alignment of primitive types
print(ffi.alignof('int')); // => 4
print(ffi.alignof('double')); // => 8// Get alignment of struct
ffi.cdef('struct foo { char a; int b; };');
print(ffi.alignof('struct foo')); // => 4 (alignment of int member)- Source
cast(type, value) → {CData}
Cast a value to a different C type.
The cast() function converts a value to a specified C type. It supports casts to numbers, enums, and pointers. The cast is performed without intermediate ucode type conversions.
| Name | Type | Description |
|---|---|---|
type | string | The target C type declaration. |
value | * | The value to cast. Can be a ucode value or cdata. |
Throws an exception if the cast is invalid (e.g., casting to a struct).
A cdata of the target type holding the cast value.
// Cast number to pointer
let ptr = ffi.cast('void *', 0x1000);
print(ptr.get()); // => 4096// Cast between pointer types
ffi.cdef('int x;');
let px = ffi.ctype('int *', ffi.ctype('int', 42).ptr());
let pv = ffi.cast('void *', px);// Cast pointer to integer
let str = ffi.string("hello");
let addr = ffi.cast('uintptr_t', str.ptr());
print(addr.get()); // => address as number// Cast integer to enum
ffi.cdef('enum color { RED, GREEN, BLUE };');
let c = ffi.cast('enum color', 2); // => BLUE- Source
cdef(spec) → {CData}
Declare C types and functions.
The cdef() function parses C declaration strings and registers the types with the FFI system. This is required before using types with ctype(), wrapping functions with wrap(), or resolving symbols with dlsym().
Multiple declarations can be provided in a single call, separated by semicolons. The parser supports most C declaration syntax including:
Basic types (
int,char,float,double, etc.)Type modifiers (
const,volatile,unsigned,signed)Pointers and arrays (
int *,char **,int[10])Structs and unions (
struct foo { ... },union bar { ... })Enums (
enum baz { ... })Function declarations (
int foo(int, char *))Typedefs (
typedef ...)Extern declarations (
extern int var;)// Declare a struct type ffi.cdef('struct point { int x; int y; };'); // Declare a function ffi.cdef('int strcmp(const char *, const char *);'); // Declare multiple items ffi.cdef(` typedef unsigned int uint32_t; struct sockaddr { sa_family_t sa_family; char sa_data[14]; }; extern char **environ; `);
After declaring types, you can create instances with ctype(), wrap functions with wrap(), or access global variables with dlsym().
| Name | Type | Description |
|---|---|---|
spec | string | A C declaration string or multiple declarations separated by semicolons. |
Throws an exception if the declaration syntax is invalid.
A cdata holding the CTypeID handle for the last declared type.
// Declare struct and create instance
ffi.cdef('struct point { int x; int y; };');
let p = ffi.ctype('struct point', 10, 20);
print(p.get('x'), p.get('y'));// Declare function and wrap it
ffi.cdef('size_t strlen(const char *);');
let strlen = ffi.C.wrap('size_t strlen(const char *)');
print(strlen("hello").get());- Source
copy(dest, src, lenopt) → {undefined}
Copy memory between pointers.
The copy() function copies memory from a source pointer to a destination pointer. If the source is a ucode string, it copies the string including its null terminator. Otherwise, an explicit length must be provided.
| Name | Type | Description |
|---|---|---|
dest | CData | Destination pointer. |
src | string | | Source string or pointer. |
len | number | (optional) Number of bytes to copy. Required if src is not a string. |
Returns undefined.
// Copy string (includes null terminator)
let buf = ffi.ctype('char[10]');
ffi.copy(buf, "hello");// Copy memory with explicit length
let src = ffi.ctype('char[5]', [1, 2, 3, 4, 5]);
let dst = ffi.ctype('char[5]');
ffi.copy(dst, src, 5);- Source
ctype(type, …initopt) → {CData}nullable
Create a C data instance.
The ctype() function creates a new C data object (cdata) of the specified type. It can be called with optional initializer values that will be used to initialize the object.
Usage patterns:
Without initializer: Creates an uninitialized cdata of the given type. For pointer types, the pointer is set to NULL.
With initializer: Creates and initializes a cdata. The initializer values depend on the type:
- Scalar types: single value (number, boolean)
- Structs: positional arguments for each field or a ucode object
- Arrays: individual element values or a string for char arrays
// Primitive type
let x = ffi.ctype('int', 42);
print(x.get()); // => 42
// Struct type with positional arguments
ffi.cdef('struct point { int x; int y; };');
let p1 = ffi.ctype('struct point', 10, 20);
print(p1.get('x'), p1.get('y')); // => 10 20
// Struct type with object initializer
let p2 = ffi.ctype('struct point', { x: 30, y: 40 });
print(p2.get('x'), p2.get('y')); // => 30 40
// Nested struct with object initializer
ffi.cdef('struct rect { struct point tl; struct point br; };');
let r = ffi.ctype('struct rect', {
tl: { x: 0, y: 0 },
br: { x: 100, y: 200 }
});
let tl = r.get('tl');
print(tl.get('x'), tl.get('y')); // => 0 0
// Array type
let arr = ffi.ctype('int[3]', 1, 2, 3);
print(arr.get(0), arr.get(1), arr.get(2)); // => 1 2 3
// Char array from string
let buf = ffi.ctype('char[10]', 'hello');
print(buf.deref()); // => "hello"
// Pointer type (uninitialized)
let ptr = ffi.ctype('void *');
| Name | Type | Description |
|---|---|---|
type | string | A C type declaration string. Can be a basic type, struct name, array type, pointer type, etc. The type must have been declared via |
init | * | (optional, repeatable) Optional initializer values. |
Throws an exception if the type declaration is invalid or wrong number of initializers provided.
A cdata of the specified type, or null if the type cannot be parsed or has invalid size. For typeof() without initializer, returns a CTypeID handle cdata.
// Create integer
let x = ffi.ctype('int', 42);
print(x.get());// Create struct
ffi.cdef('struct point { int x; int y; };');
let p = ffi.ctype('struct point', 10, 20);
print(p.get('x'));// Create array
let arr = ffi.ctype('double[5]', 1.1, 2.2, 3.3, 4.4, 5.5);
print(arr.length());- Source
dlopen(name, globalopt, cdefsopt) → {CLib}nullable
Load a shared library.
The dlopen() function loads a shared library into the process address space and returns a CLib object that can be used to access symbols via dlsym() or wrap().
// Load zlib compression library
let libz = ffi.dlopen('z');
// Load OpenSSL crypto library
let libcrypto = ffi.dlopen('crypto');
// Load absolute path
let custom = ffi.dlopen('/usr/local/lib/mylib.so');
// Use wrap() to get function pointers
let zlibVersion = libz.wrap('const char *zlibVersion(void)');
print(zlibVersion().slice(), "\n"); // => "1.2.11"
On Unix-like systems, the .so extension is automatically appended if omitted. On macOS, .dylib is used. On Windows, .dll is used.
When the optional third argument is provided, dlopen() will:
- Parse the C definitions to register types and function prototypes
- Resolve and wrap all declared functions
- Attach the wrapped functions as methods on the library object
| Name | Type | Description |
|---|---|---|
name | string | The library name or path. |
global | boolean | (optional, default: false)If |
cdefs | string | (optional) Optional C declaration string containing types and function prototypes. Function declarations will be automatically wrapped and attached to the library object as methods. |
Throws an exception if the library cannot be loaded or if C definitions cannot be parsed.
A CLib object representing the loaded library, or null on error. When cdefs is provided, the returned CLib will have wrapped functions attached as methods.
// Load zlib and call functions
let libz = ffi.dlopen('z');
let zlibVersion = libz.wrap('const char *zlibVersion(void)');
print(zlibVersion().slice());// Load OpenSSL crypto library
let libcrypto = ffi.dlopen('crypto');
let OpenSSL_version = libcrypto.wrap('const char *OpenSSL_version(int)');
print(OpenSSL_version(0).slice()); // => "OpenSSL 3.0.0..."// Load library with automatic wrapping
let libssl = ffi.dlopen('ssl', false, `
typedef void SSL_METHOD;
const SSL_METHOD *TLS_method(void);
`);
// TLS_method is now directly callable
let method = libssl.TLS_method();// Load zlib with pre-wrapped functions
let libz = ffi.dlopen('z', false, `
const char *zlibVersion(void);
uLong compressBound(uLong sourceLen);
`);
print(libz.zlibVersion().slice());
print(libz.compressBound(1024));- Source
errno(valueopt) → {number}
Get or set the C errno value.
The errno() function retrieves the current value of the C errno variable, or sets it to a new value if an argument is provided.
| Name | Type | Description |
|---|---|---|
value | number | (optional) Optional value to set errno to. |
The current errno value (before any set operation).
// Get current errno
let err = ffi.errno();// Set errno
ffi.errno(0); // Clear errno- Source
fill(dest, len, valueopt) → {undefined}
Fill memory with a byte value.
The fill() function sets len bytes at the destination pointer to the specified fill value. The fill value can be a number, boolean, or string (first character used).
| Name | Type | Description |
|---|---|---|
dest | CData | Destination pointer. |
len | number | Number of bytes to fill. |
value | number | | (optional, default: 0)Fill value. Numbers/booleans use the value directly; strings use the first character's ASCII code. |
Returns undefined.
// Zero-fill a buffer
let buf = ffi.ctype('char[10]');
ffi.fill(buf, 10, 0);
// Fill with specific byte
ffi.fill(buf, 10, 0xFF);
// Fill with character
ffi.fill(buf, 10, 'A'); // Fills with 65 (ASCII for 'A')- Source
import(libname, cdefs) → {object|null}
Import a C library with automatic function wrapping.
This is a convenience function that combines library loading, type declaration, and function wrapping into a single call. It loads the specified library, parses the C definitions, and returns an object with all functions pre-wrapped and ready to call.
| Name | Type | Description |
|---|---|---|
libname | string | The library name or path to load. Can be a bare name (e.g., 'z'), a filename (e.g., 'libcrypto.so.3'), or an absolute path. |
cdefs | string | A C declaration string containing function prototypes to import. Only function declarations are wrapped; types, structs, and other declarations are registered but not added to the result object. |
Throws an exception if:
- The library cannot be loaded
- The C declarations are syntactically invalid
- A declared function cannot be resolved in the library
An object containing wrapped functions keyed by their symbol names. Returns null if the library cannot be loaded or if parsing fails.
// Import sqlite3 with all functions
let sqlite3 = ffi.import('sqlite3', `
const char *sqlite3_libversion(void);
int sqlite3_libversion_number(void);
int sqlite3_open(const char *, void **);
int sqlite3_close(void *);
`);
print("Version: ", sqlite3.sqlite3_libversion(), "\n");// Import zlib functions
let zlib = ffi.import('z', `
const char *zlibVersion(void);
uLong compressBound(uLong sourceLen);
`);
print(zlib.zlibVersion());
print(zlib.compressBound(1024));- Source
offsetof(type, field, bitposopt) → {number}nullable
Get the offset of a struct field in bytes.
The offsetof() function returns the byte offset of a field within a struct. For bitfields, the bit position and bit size are returned in an array passed as the third argument.
| Name | Type | Description |
|---|---|---|
type | string | The struct type declaration. |
field | string | The field name to get the offset of. |
bitpos | array | (optional) Optional array to receive [bit_position, bit_size] for bitfield members. |
Throws an exception if the type is not a struct or the field is invalid.
The byte offset of the field, or null if the field doesn't exist.
// Get field offset
ffi.cdef('struct point { int x; int y; };');
print(ffi.offsetof('struct point', 'x')); // => 0
print(ffi.offsetof('struct point', 'y')); // => 4// Get bitfield info
ffi.cdef('struct flags { unsigned int a:4; unsigned int b:4; };');
let bitpos = [];
let offset = ffi.offsetof('struct flags', 'b', bitpos);
print(offset, bitpos[0], bitpos[1]); // => 0 4 4- Source
sizeof(type, nelemopt) → {number}nullable
Get the size of a C type in bytes.
The sizeof() function returns the size in bytes of a C type or cdata expression. For variable-length arrays, an element count can be provided.
| Name | Type | Description |
|---|---|---|
type | string | | The C type declaration or cdata expression to measure. |
nelem | number | (optional) For variable-length arrays, the number of elements. |
Throws an exception if the type is invalid or nelem is required but missing.
The size in bytes, or null if the size is unknown.
// Get size of primitive types
print(ffi.sizeof('int')); // => 4
print(ffi.sizeof('double')); // => 8// Get size of struct
ffi.cdef('struct point { int x; int y; };');
print(ffi.sizeof('struct point')); // => 8// Get size of VLA with element count
ffi.cdef('int vla[];');
print(ffi.sizeof('int[]', 10)); // => 40 (10 * sizeof(int))- Source
string(arg, lenopt) → {string|CData}
Convert between ucode strings and C char arrays/pointers.
The string() function has two modes:
String to buffer: Given a ucode string, creates a C char[] buffer containing the string plus null terminator. Returns a cdata that can be passed to C functions expecting
char*.Pointer to string: Given a char* cdata pointer, reads the C string and returns a ucode string. An optional length parameter can be provided to limit the maximum bytes read (reads up to
lenbytes or until null terminator, whichever comes first).
| Name | Type | Description |
|---|---|---|
arg | string | | A ucode string to convert to char[], or a char* cdata pointer to read. |
len | number | (optional) Optional maximum length for reading C strings (reads up to |
Throws an exception if the argument type is invalid.
When given a char* pointer: returns a ucode string. When given a ucode string: returns a char[] cdata buffer.
// Convert ucode string to char[] buffer
let buf = ffi.string("hello");
// buf is now char[6] cdata (including null terminator)
// Can be passed to C functions expecting char*// Read C string from char* pointer
ffi.cdef('char *getenv(char *);');
let ptr = ffi.C.wrap('char *getenv(char *)')("PATH");
let path = ffi.string(ptr);
print(path); // => "/usr/bin:..."// Read fixed-length string (no null terminator)
ffi.cdef('char *strncpy(char *, const char *, size_t);');
let src = ffi.string("hello world");
let dst = ffi.ctype('char[5]');
ffi.C.wrap('char *strncpy(char *, const char *, size_t)')(dst, src, 5);
let short_str = ffi.string(dst, 5); // => "hello" (no null terminator)- Source
typeof(type) → {CData}
Get the CTypeID for a C type.
The typeof() function returns a CTypeID handle for the specified type. This is useful for storing type references or passing to other FFI functions.
| Name | Type | Description |
|---|---|---|
type | string | The C type declaration. |
Throws an exception if the type declaration is invalid.
A cdata holding the CTypeID handle (an integer type ID).
// Get type ID for struct
ffi.cdef('struct point { int x; int y; };');
let point_type = ffi.typeof('struct point');// Get type ID for function pointer
ffi.cdef('int callback(int, char *);');
let cb_type = ffi.typeof('int (*)(int, char *)');- Source