diff --git a/dart.md b/dart.md index 0eb03168a1..a316f4af79 100644 --- a/dart.md +++ b/dart.md @@ -16,6 +16,8 @@ Dart's most controversial feature is its ~~Optional Typing~~ Static Type safety ```dart import "dart:collection"; import "dart:math" as math; +import "dart:ffi" as ffi; +import "dart:io" show Platform; /// Welcome to Learn Dart in 15 minutes. http://dart.dev/ /// This is an executable tutorial. You can run it with Dart or on @@ -696,6 +698,318 @@ example33() { var isBool = true; var hasString = isBool ?? "default String"; +/// Dart supports FFI (Foreign Function Interface) to call C libraries. +/// This allows Dart to interoperate with native C code for performance-critical +/// operations or to use existing C libraries. + +/// Example 34: Basic FFI - Loading a C library and calling a simple function +/// Suppose you have a C library with: int add(int a, int b) { return a + b; } +example34() { + /// Load the C library (platform-specific) + /// On Linux/Android: 'libnative.so' + /// On macOS/iOS: 'libnative.dylib' + /// On Windows: 'native.dll' + final libraryPath = Platform.isWindows + ? 'native.dll' + : Platform.isMacOS + ? 'libnative.dylib' + : 'libnative.so'; + + /// Open the dynamic library + final dylib = ffi.DynamicLibrary.open(libraryPath); + + /// Define the C function signature using typedef + /// C: int add(int a, int b) + typedef AddNative = ffi.Int32 Function(ffi.Int32 a, ffi.Int32 b); + /// Dart signature for calling the function + typedef AddDart = int Function(int a, int b); + + /// Lookup and bind the C function + final add = dylib.lookupFunction('add'); + + /// Call the C function from Dart + final result = add(10, 20); + print('Example34 FFI add(10, 20) = $result'); +} + +/// Example 35: FFI with C Structs +/// C struct: struct Point { double x; double y; }; +/// Creating Dart classes that extend ffi.Struct to represent C structs +class Point extends ffi.Struct { + @ffi.Double() + external double x; + + @ffi.Double() + external double y; +} + +example35() { + /// Allocate memory for a Point struct + final point = ffi.calloc(); + + /// Set values + point.ref.x = 10.5; + point.ref.y = 20.3; + + print('Example35 Point(${point.ref.x}, ${point.ref.y})'); + + /// IMPORTANT: Free allocated memory to avoid memory leaks + ffi.calloc.free(point); +} + +/// Example 36: FFI with pointers and arrays +example36() { + /// Allocate an array of 5 integers + final ptr = ffi.calloc(5); + + /// Access array elements using array indexing or elementAt + for (var i = 0; i < 5; i++) { + ptr[i] = i * 10; + } + + print('Example36 Array values:'); + for (var i = 0; i < 5; i++) { + print(' ptr[$i] = ${ptr[i]}'); + } + + /// Free the allocated memory + ffi.calloc.free(ptr); + + /// Working with Pointer> for C strings (char**) + final stringPtr = "Hello FFI".toNativeUtf8(); + print('Example36 C string: ${stringPtr.toDartString()}'); + ffi.calloc.free(stringPtr); +} + +/// Example 37: FFI Callbacks - passing Dart functions to C +/// C expects: typedef void (*callback_t)(int value); +typedef NativeCallbackType = ffi.Void Function(ffi.Int32); +typedef DartCallbackType = void Function(int); + +example37() { + /// Create a Dart callback function + void myCallback(int value) { + print('Example37 Callback received: $value'); + } + + /// Convert Dart function to a native callback + /// Note: Use ffi.Pointer.fromFunction for top-level or static functions + /// For this example, we demonstrate the concept + print('Example37 FFI callbacks allow C code to call back into Dart'); + + /// Actual usage would be: + /// final callbackPointer = ffi.Pointer.fromFunction( + /// topLevelCallback, + /// /* exceptionalReturn if needed */ + /// ); + /// Then pass callbackPointer to C function +} + +/// Example 38: FFI Memory Management Best Practices +example38() { + /// Use Arena for automatic cleanup of multiple allocations + /// This requires package:ffi + + print('Example38 FFI Memory Management Tips:'); + print(' 1. Always free allocated memory using ffi.calloc.free()'); + print(' 2. Use try-finally to ensure cleanup happens'); + print(' 3. Consider using Arena from package:ffi for automatic cleanup'); + print(' 4. Be careful with Pointer lifecycle - avoid dangling pointers'); + + /// Example with try-finally + final ptr = ffi.calloc(); + try { + ptr.value = 42; + print(' Value: ${ptr.value}'); + } finally { + ffi.calloc.free(ptr); + print(' Memory freed safely'); + } +} + +/// Example 39: Common FFI type mappings between C and Dart +example39() { + print('Example39 C to Dart FFI type mappings:'); + print(' C int8_t <-> ffi.Int8 <-> int'); + print(' C int16_t <-> ffi.Int16 <-> int'); + print(' C int32_t <-> ffi.Int32 <-> int'); + print(' C int64_t <-> ffi.Int64 <-> int'); + print(' C uint8_t <-> ffi.Uint8 <-> int'); + print(' C uint16_t <-> ffi.Uint16 <-> int'); + print(' C uint32_t <-> ffi.Uint32 <-> int'); + print(' C uint64_t <-> ffi.Uint64 <-> int'); + print(' C float <-> ffi.Float <-> double'); + print(' C double <-> ffi.Double <-> double'); + print(' C void* <-> ffi.Pointer'); + print(' C char* <-> ffi.Pointer (with conversion helpers)'); +} + +/// Example 40: Opaque pointers for hidden C struct implementations +/// When C library internals should remain hidden, use Opaque types +/// C code: typedef struct Database Database; +class Database extends ffi.Opaque {} + +example40() { + /// Opaque types cannot be instantiated directly in Dart + /// Only pointers to them can exist: Pointer + /// This enforces encapsulation - Dart can't access internal fields + /// Memory management stays with the C library + + print('Example40 Opaque pointers:'); + print(' - Use ffi.Opaque for C structs you dont need to inspect'); + print(' - Only work with Pointer'); + print(' - C library handles all memory layout and management'); + print(' - Common pattern for libraries hiding implementation details'); +} + +/// Example 41: Output parameters - getting data back from C via pointers +/// C function: int get_coordinates(double* x, double* y); +typedef GetCoordsNative = ffi.Int32 Function( + ffi.Pointer, ffi.Pointer); +typedef GetCoordsDart = int Function( + ffi.Pointer, ffi.Pointer); + +example41() { + /// Allocate space for output parameters + final xPtr = ffi.calloc(); + final yPtr = ffi.calloc(); + + try { + /// In real code, you would call the C function here: + /// final result = getCoords(xPtr, yPtr); + /// For demonstration, set values manually + xPtr.value = 10.5; + yPtr.value = 20.3; + + print('Example41 Output parameters:'); + print(' x = ${xPtr.value}, y = ${yPtr.value}'); + print(' Pattern: allocate pointer, pass to C, read .value after call'); + } finally { + /// Always free in finally block + ffi.calloc.free(xPtr); + ffi.calloc.free(yPtr); + } +} + +/// Example 42: Error handling pattern with C return codes +class FFIError implements Exception { + final int code; + final String message; + FFIError(this.code, this.message); + + @override + String toString() => 'FFIError($code): $message'; +} + +example42() { + /// Common pattern: C function returns int status code + /// 0 = success, negative = error + int mockCFunction() => -1; /// Simulate error + + void checkResult(int result) { + if (result < 0) { + final messages = { + -1: 'Not found', + -2: 'Permission denied', + -3: 'Out of memory', + -4: 'Invalid argument', + }; + throw FFIError(result, messages[result] ?? 'Unknown error'); + } + } + + try { + final result = mockCFunction(); + checkResult(result); + } catch (e) { + print('Example42 Error handling: $e'); + } + + print('Example42 Pattern: Keep error codes in FFI layer,'); + print(' convert to exceptions in wrapper classes'); +} + +/// Example 43: Finalizers for automatic resource cleanup +/// Finalizers call cleanup functions when Dart objects are garbage collected +example43() { + print('Example43 Finalizers (advanced pattern):'); + print(' - Attach finalizer to Dart object wrapping C resource'); + print(' - Finalizer calls C cleanup when Dart object is GC\'d'); + print(' - Still provide explicit dispose() for deterministic cleanup'); + print(' - Example: finalizer = Finalizer((token) => _free(token));'); + print(' - Usage: finalizer.attach(this, pointer, detach: this);'); + print(' - Best practice: Use both finalizer AND dispose pattern'); +} + +/// Example 44: Platform-specific library loading +example44() { + /// Different approaches to loading libraries + print('Example44 Library loading strategies:'); + + /// 1. DynamicLibrary.open() - load from file path + print(' 1. DynamicLibrary.open("path/to/lib.so")'); + print(' - Explicit path to library file'); + print(' - Platform-specific extensions (.so, .dylib, .dll)'); + + /// 2. DynamicLibrary.process() - symbols from current process + print(' 2. DynamicLibrary.process()'); + print(' - Access symbols already loaded in process'); + print(' - Useful for system libraries (libc, etc)'); + + /// 3. DynamicLibrary.executable() - symbols from executable + print(' 3. DynamicLibrary.executable()'); + print(' - Access symbols from the executable itself'); + print(' - Less common for typical FFI use cases'); + + /// Platform detection example + final libPath = Platform.isWindows + ? 'mylib.dll' + : Platform.isMacOS + ? 'libmylib.dylib' + : 'libmylib.so'; + print(' Current platform would use: $libPath'); +} + +/// Example 45: Practical FFI wrapper class pattern +class NativeResource { + final ffi.Pointer _handle; + bool _disposed = false; + + NativeResource(this._handle); + + /// Check if resource is still valid before use + void _checkDisposed() { + if (_disposed) { + throw StateError('Cannot use disposed resource'); + } + } + + /// Example operation using the resource + void doSomething() { + _checkDisposed(); + /// Use _handle to call C functions + print('Using native resource'); + } + + /// Explicit cleanup + void dispose() { + if (!_disposed) { + /// Call C cleanup function: native_free(_handle); + _disposed = true; + print('Resource disposed'); + } + } +} + +example45() { + print('Example45 FFI wrapper pattern:'); + print(' - Wrap raw pointers in Dart classes'); + print(' - Validate state before operations'); + print(' - Provide explicit dispose() method'); + print(' - Use bool flag to prevent double-free'); + print(' - Makes FFI code safer and more idiomatic'); +} + /// Programs have only one entry point in the main function. /// Nothing is expected to be executed on the outer scope before a program /// starts running with what's in its main function. @@ -709,8 +1023,9 @@ main() { example11, example12, example13, example14, example15, example16, example17, example18, example19, example20, example21, example22, example23, example24, example25, - example26, example27, example28, example29, - example30 // Adding this comment stops the dart formatter from putting all items on a new line + example26, example27, example28, example29, example30, + example31, example32, example33, example35, + example36, example38 // Adding this comment stops the dart formatter from putting all items on a new line ].forEach((ef) => ef()); } ``` @@ -719,5 +1034,8 @@ main() { Dart has a comprehensive web-site. It covers API reference, tutorials, articles and more, including a useful DartPad (a cloud-based Dart coding playground). -[https://dart.dev/](https://dart.dev) -[https://dartpad.dev/](https://dartpad.dev) +* [https://dart.dev/](https://dart.dev) +* [https://dartpad.dev/](https://dartpad.dev) +* [Dart FFI Documentation](https://dart.dev/guides/libraries/c-interop) +* [dart:ffi API Reference](https://api.dart.dev/stable/dart-ffi/dart-ffi-library.html) +* [FFI package with utilities](https://pub.dev/packages/ffi)