Skip to content

Instantly share code, notes, and snippets.

@matanlurey
Created May 19, 2024 21:24
Show Gist options
  • Save matanlurey/dbd6aa04fa17cccdc8680e3ac10d6b63 to your computer and use it in GitHub Desktop.
Save matanlurey/dbd6aa04fa17cccdc8680e3ac10d6b63 to your computer and use it in GitHub Desktop.
A fully working example of using `dart:ffi` to write to `stdout`
import 'dart:ffi';
import 'dart:io' as io show Platform;
void main() {
// Note these instructions were *only* tested on Linux or MacOS.
if (!io.Platform.isLinux && !io.Platform.isMacOS) {
throw UnsupportedError('This code was only tested on Linux and MacOS.');
}
// First, you need access to a dynamic library. The default C library is
// typically exported as global symbols, so we can access it in-process.
final global = DynamicLibrary.process();
// Next, we need some minimum functionality to interact with the C library,
// namely an allocator, access to the 'write' function, and the file
// descriptor for standard output.
final malloc = global.lookupFunction<
Pointer Function(IntPtr),
Pointer Function(int)
>('malloc');
final free = global.lookupFunction<
Void Function(Pointer),
void Function(Pointer)
>('free');
// Let's create our allocator which uses 'malloc' and 'free'.
final allocate = _Allocator(malloc, free);
const stdout = 1;
final write = global.lookupFunction<
// Signature on the C side: ssize_t write(int fd, const void* buf, size_t count);
Int64 Function(Int32, Pointer<Uint8>, IntPtr),
// Signature on the Dart side has some implicit conversions.
int Function(int, Pointer<Uint8>, int)
>('write');
// Let's take the string we want to write to standard output.
final message = 'Hello, world!\n';
// We'll need to convert the string into a buffer of bytes. There is no way
// to share a Dart string with C code, so we need to allocate a buffer and
// copy the string into it.
final buffer = allocate<Uint8>(message.length + 1);
buffer.asTypedList(message.length).setAll(0, message.codeUnits);
// Finally, we can write the buffer to standard output.
write(stdout, buffer, message.length);
// Don't forget to free the buffer when you're done with it.
allocate.free(buffer);
}
final class _Allocator implements Allocator {
const _Allocator(this._malloc, this._free);
final Pointer Function(int) _malloc;
final void Function(Pointer) _free;
@override
Pointer<T> allocate<T extends NativeType>(int byteCount, {int? alignment}) {
final pointer = _malloc(byteCount);
if (pointer.address == 0) {
throw ArgumentError('Could not allocate $byteCount bytes.');
}
return pointer.cast();
}
@override
void free(Pointer pointer) {
_free(pointer);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment