ffi

Production-ready native FFI for EZ — Python ctypes-style with type annotations, named structs, callbacks, and unlimited arity.

0 downloads owners: imabd645

Install

ez install ffi

Dependencies (1.0.0)

PackageRange
No dependencies.

Readme

EZ FFI (ffi v2)

ffi is the official standard library for EZ that provides a robust, object-oriented abstraction over the native C Foreign Function Interface (FFI). It is designed to achieve full parity with Python's ctypes, providing a seamless and highly expressive bridge between EZ scripts and native C binaries.

With ffi, you can interact natively with shared libraries (.dll, .so, .dylib), raw memory pointers, structures, and C callbacks directly from EZ script without writing any C/C++ wrapper code.


Installation

The ffi library comes bundled with the EZ compiler (v6+). You can import it globally using:

use "ffi"

Or with an alias to avoid polluting the global namespace:

use "ffi" as ffi

1. Library & Function Resolution

The entry point for FFI is loading a native dynamic library.

ffi.load(dll_name)

Loads a native dynamic library (e.g., .dll on Windows, .so on Linux, .dylib on macOS) and returns a Library handle.

Parameters:

Returns: A Library instance. Throws an FFIError if the library cannot be found or opened.

Library.func(name, restype = nil, argtypes = nil)

Extracts a native function from the loaded library by its exported symbol name. It returns a Function object which caches its configuration.

Parameters:

Returns: A Function object.

Function API

Once a Function is resolved, you can set its signature and invoke it.

Invocation:
Function objects implement the __call__ method, meaning they can be executed directly as if they were standard EZ tasks.

lib = ffi.load("User32.dll")
msgBox = lib.func("MessageBoxA")

// Configure the signature
msgBox.argtypes = [ffi.c_void_p, ffi.c_char_p, ffi.c_char_p, ffi.c_uint32]
msgBox.restype = ffi.c_int

// Direct invocation
result = msgBox(0, "Hello, FFI!", "Title", 0)

Alternatively, you can use the explicit array-based invocation:

result = msgBox.call([0, "Hello, FFI!", "Title", 0])

2. Type Descriptors

Native libraries require strict ABI compliance, meaning EZ dynamically coerces its high-level types into precise C byte representations. ffi provides dictionaries that define these types.

Type DescriptorC EquivalentSize (Bytes)EZ Auto-Coercion
ffi.c_int8int8_t1Number, Boolean (0/1)
ffi.c_uint8 / c_byteuint8_t1Number
ffi.c_int16int16_t2Number
ffi.c_uint16uint16_t2Number
ffi.c_int32 / c_intint32_t4Number, Boolean (0/1)
ffi.c_uint32 / c_uintuint32_t4Number
ffi.c_int64 / c_longint64_t8Number
ffi.c_uint64 / c_ulonguint64_t8Number
ffi.c_floatfloat4Number
ffi.c_doubledouble8Number
ffi.c_boolbool1Boolean
ffi.c_char_pchar*8String (auto null-terminated)
ffi.c_void_p / c_voidvoid*8Integer (Address), Pointer

Note: When passing an EZ String to a c_char_p argument, ffi automatically allocates a temporary UTF-8 null-terminated C string in memory, passes its pointer to C, and cleans it up after the function returns.


3. C Structures (ffi.Structure)

To interface with C functions that accept or return struct pointers, ffi allows you to define matching memory layouts in EZ. Structure respects standard C alignment and padding rules automatically.

ffi.Structure(name, fields)

Parameters:

Returns: A Structure factory object.

RECT = ffi.Structure("RECT", [
    ["left",   ffi.c_int32],
    ["top",    ffi.c_int32],
    ["right",  ffi.c_int32],
    ["bottom", ffi.c_int32]
])

Structure API


4. Advanced Memory Management (Pointer)

When interfacing with complex C APIs, you often need to manually manage heap allocations and manipulate memory bytes. The Pointer model provides an RAII (Resource Acquisition Is Initialization) wrapper over raw memory addresses.

Creating Pointers

Reading and Writing

Pointer objects expose strongly-typed methods to read/write memory at specific byte offsets relative to the pointer's base address.

Manual Memory Control


5. String Utilities


6. Native Callbacks (ffi.createCallback)

Many C APIs (like EnumWindows or qsort) require you to pass a "function pointer". ffi allows you to wrap an EZ task inside a native C trampoline that the C library can execute.

ffi.createCallback(ez_task, restype, argtypes)

Generates a native C function pointer that delegates execution back into the EZ interpreter.

Parameters:

Returns: A Pointer object representing the raw C function pointer.

use "ffi"

// 1. Define the EZ logic
task windowEnumCallback(hwnd, lParam) {
    out "Found window handle: " + str(hwnd)
    give 1 // Return 1 to continue enumeration
}

// 2. Wrap it in a C callback
cbPtr = ffi.createCallback(windowEnumCallback, ffi.c_int, [ffi.c_void_p, ffi.c_void_p])

// 3. Pass the callback pointer to the native library
user32 = ffi.load("User32.dll")
enumWindows = user32.func("EnumWindows", ffi.c_int, [ffi.c_void_p, ffi.c_void_p])

enumWindows(cbPtr.addr(), 0)

// 4. Free the callback trampoline when no longer needed
cbPtr.free()

Warning: You must ensure the cbPtr remains alive (not garbage collected) for as long as the C library might call it.


Error Handling

ffi throws typed exceptions for safety boundary violations:

Versions

VersionSizeDownloadsPublished
1.0.0 12.4 KB 0 1 hour ago

Integrity

sha256  cef6303c371b89f91f8857abf8d26e4d57a93afb7f1326865a04827dbfbb21b6