Skip to content

Conversation

@spacebear21
Copy link
Collaborator

This builds on top of @kumulynja 's PR #99, but I needed to add a couple of fixes on top of it for usage in payjoin-ffi. Will close this PR once #99 gets merged.

kumulynja and others added 16 commits November 25, 2025 18:22
…compatibility

Updates the generated Dart code to use simple library names instead of
absolute paths for Android and Linux platforms. This makes the
generated code compatible with Dart's Native Assets system.

Changes:
- Android: "${Directory.current.path}/lib{libname}.so" → "lib{libname}.so"
- Linux: "${Directory.current.path}/lib{libname}.so" → "lib{libname}.so"
Introduces a `library_loading_strategy` configuration option that allows
users to choose between traditional directory-based library loading and
Native Assets mode.

Configuration options:
- `directory_path` (default): Uses `${Directory.current.path}` for
  Android/Linux, maintaining backward compatibility
- `native_assets`: Uses `DynamicLibrary.process()` for all platforms,
  enabling Dart Native Assets integration

Usage in uniffi.toml:
```toml
[bindings.dart]
library_loading_strategy = "native_assets"
The generate_dart_bindings function was ignoring the config_file_override
parameter when library_mode was true, always using the udl_file path
instead. This prevented custom configurations in uniffi.toml from being
read when generating bindings in library mode.

Fixed by using config_file_override when provided, falling back to
udl_file only when no override is specified:
  let config_path = config_file_override.unwrap_or(udl_file)

This ensures the library_loading_strategy and other custom configurations
are properly applied in library mode.
This is a major refactor that modernizes the FFI bindings to use Dart 3.10+'s
Native Assets system instead of manual DynamicLibrary.open() calls.

Key changes:
- Replace DynamicLibrary.open() with @Native annotations using asset IDs
- Generate Native Assets build hooks (hook/build.dart) using hooks package
- Implement asset ID system: package:{dart_package_name}/uniffi:{cdylib_name}
- Update test infrastructure to support Native Assets in test environments
- Add code_assets and hooks as dependencies in generated pubspec.yaml
- Remove runtime library path resolution (handled by Native Assets)

Technical details:
- Asset IDs use format: "package:{package}/uniffi:{cdylib}"
- Build hooks register simple names; Dart auto-prefixes with package path
- Generated Dart code constructs full asset ID for @Native annotations
- Preserved library_file parameter (needed for proc macro metadata extraction)

Benefits:
- No manual library loading required
- Cross-platform library resolution handled automatically
- Better integration with Dart's build system
- Simpler generated code with declarative @Native functions

All 19 tests passing with new Native Assets implementation.
- Add free + clone entries to callback vtables (0.30.0 layout)
- Implement proper handle-cloning via UniffiHandleMap for callbacks
- Wire uniffiFree/uniffiClone into vtable init
- Add trait handle LSB check and reject foreign trait implementations
…nals

When a primitive type (like u16) was only used within optional fields (u16?)
and never as a bare type, uniffi-dart would generate FfiConverterOptionalUInt16
that referenced FfiConverterUInt16, but would never generate the base converter
itself, causing compilation errors.

The issue was in the compound type (Optional/Sequence) rendering logic, which
only rendered inner helpers for Sequence types. Now it also renders helpers
for primitive types (Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64,
Float32, Float64) when they haven't been registered yet.

This ensures that when Optional<UInt16> is encountered, both FfiConverterUInt16
and FfiConverterOptionalUInt16 are generated.
The previous implementation of rustCall would call the FFI function and
immediately deserialize the result before checking if an error occurred.
When a Rust function throws an error, the FFI returns garbage data in the
result buffer, causing RangeError or other deserialization failures when
Dart tries to lift the invalid data.

This fix introduces rustCallWithLifter which:
1. Calls the FFI function to get the raw result
2. Checks the error status
3. Only deserializes (lifts) the result if no error occurred

Changes:
- Add rustCallWithLifter() function in types.rs that separates FFI call
  from result lifting
- Update function generation in functions.rs to use rustCallWithLifter
- Update object method generation in objects.rs to use rustCallWithLifter
- Keep original rustCall() for void-returning functions

This ensures error handling happens before any deserialization, preventing
crashes when functions throw expected errors.
Per the uniffi changelog: "Foreign handles must always have the lowest
bit set".
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants