Created
May 19, 2024 21:24
-
-
Save matanlurey/dbd6aa04fa17cccdc8680e3ac10d6b63 to your computer and use it in GitHub Desktop.
A fully working example of using `dart:ffi` to write to `stdout`
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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