WIP libffi bindings for Godot 4.1+.
Available at the Asset Library.
⚠️ WarningMisuse may easily crash the Godot editor and built games/apps, use with caution!
- Supports Windows, Linux, macOS, iOS and Android platforms, including running in the Godot editor
- Custom ".ffilibrary" file format, which are INI files with a
[libraries]section that defines paths to native libraries separately per platform/architecture, using the exact same format as ".gdextension" files. They are imported as CFFILibrary resources.- TODO: support the
[dependencies]section
- TODO: support the
- Editor export plugin that bundles the correct native library in builds, based on the configurations from ".ffilibrary" files
- Supports built-in types like
intandfloat, pointer types likeconst char *and struct types- TODO: arrays, union, enums, function pointers
- Scoped type definitions, so that different types can be defined with the same name in different libraries.
- Instantiate FFI types using
CFFIType.alloc. The returnedCFFIOwnedValueis RefCounted and releases the memory automatically whenever it gets destroyed. - Get/set struct fields by name from
CFFIPointers - Construct
Strings andPackedByteArrays fromCFFIPointers with a single method call - Use Dictionaries as literal struct values, for example when calling a function
StreamPeerCFFIPointer, a stream peer used to handle binary data streams from native pointers
- Enable the
cffieditor plugin inProject -> Project Settings... -> Pluginstab. - Build your native DLLs and add them to your project
- Create a ".ffilibrary" file and define the paths of the native libraries per platform/architecture inside the
[libraries]section, exactly like you would do for .gdextension files. This file will be imported as a CFFILibrary Resource. - Call
CFFILibrary.open()to open aCFFILibraryHandle - Get native functions using
CFFILibraryHandle.get_functionand invoke them withinvokeorinvokev. - Get native global variables using
CFFILibraryHandle.get_global. - Mess with native data using
CFFIPointers, hopefully not crashing your app - Enjoy 🍾
// native_plugin.c
typedef struct ExampleStruct {
int a;
int b;
} ExampleStruct;
const char *get_message() {
return "Hello world!";
}
int get_a(ExampleStruct s) {
return s.a;
}
int my_strlen(const char *s) {
int len = 0;
while (*s) {
len++;
s++;
}
return len;
}
void call_function_pointer(void (*fptr)(int), int arg1) {
if (fptr) {
fptr(arg1);
}
}
int global_int_variable;# native_plugin.ffilibrary
[libraries]
windows.x86_64 = "res://native_plugin_windows_x86_64.dll"
windows.x86 = "res://native_plugin_windows_x86.dll"
linux.x86_64 = "res://native_plugin_linux_x86_64.so"
linux.x86 = "res://native_plugin_linux_x86.so"
macos = "res://native_plugin_macos.dylib"# script.gd
extends Node
# Using ".ffilibrary" files is the preferred way of bundling FFI libraries in projects.
# You can also use `CFFI.open` to load libraries by name or path directly instead.
static var native_plugin_dll = load("res://native_plugin.ffilibrary").open()
# Define the same structs as the native library
static var ExampleStruct = native_plugin_dll.define_struct("ExampleStruct", {
"a": "int",
"b": "int",
})
# Get functions by name, making sure to use the correct return and argument types
static var get_message = native_plugin_dll.get_function("get_message", "const char *", [])
static var get_a = native_plugin_dll.get_function("get_a", "int", ["ExampleStruct"])
static var my_strlen = native_plugin_dll.get_function("my_strlen", "int", ["const char *"])
static var call_function_pointer = native_plugin_dll.get_function("call_function_pointer", "void", ["void *", "int"])
# Get pointer to global variables by name
static var global_int_variable = native_plugin_dll.get_global("global_int_variable", "int")
func _ready():
# Get strings from raw pointers
var message_ptr: CFFIPointer = get_message.invoke()
var message = message_ptr.get_string_from_utf8()
print(message) # "Hello world!"
# Allocate and access struct values
var example_struct: CFFIOwnedValue = ExampleStruct.alloc()
example_struct.a = 42
var a = get_a.invoke(example_struct)
assert(a == example_struct.a)
assert(a == 42)
# Strings are passed as null-terminated buffers to pointer arguments
var message_length = my_strlen.invoke("Hello World!")
assert(message_length == 12)
# Create native function from Callables
# Make sure to use the correct return and argument types
var callable_function_pointer = CFFI.create_function(func(val): print(val), "void", ["int"])
call_function_pointer.invoke(callable_function_pointer, 5) # prints 5
# Global variables
assert(global_int_variable.get_value() == 0)There is also an example native_plugin.ffilibrary in the "test/native_plugin" folder showing how to setup a native library resource. The script Test.gd shows how to use the FFI to call the native library functions. Refer to plugin.c for the native function implementations in this sample.
- Accept a pointer when expecting a value, just dereference it automagically
- Accept a value when expecting pointer? Could be useful in function calls, but not when setting struct fields
- Support defining and using unions
- Support FFI array types, both fixed and variable length
- Support variadic native function calls (the
variadicflag is there, but theffi_cifis not being prep'd correctly) - Support
[dependencies]section in ".ffilibrary" files - Define enum values? Enum constants can easily be defined as constants in GDScript directly, so this isn't a requirement. Would be nice for self documenting function arguments/return types, though.
CFFI GDExtension uses the following open-source libraries: