Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
304 changes: 2 additions & 302 deletions pkgs/ffigen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Rust. For more details, see https://dart.dev/guides/libraries/c-interop.
FFIgen also has experimental support for calling ObjC and Swift code;
for details see https://dart.dev/guides/libraries/objective-c-interop.

More FFIgen documentation can be found [here](doc/README.md).

## Getting Started

This guide demonstrates how to call a custom C API from a standalone Dart
Expand Down Expand Up @@ -955,305 +957,3 @@ include-transitive-objc-categories: false
</tr>
</tbody>
</table>

## FAQ

### Can FFIgen be used for removing underscores or renaming declarations?

FFIgen supports **regexp-based renaming**. The regexp must be a full match.
For renaming you can use regexp groups (`$1` means group 1).

To renaming `clang_dispose_string` to `string_dispose` we can match it using
`clang_(.*)_(.*)` and rename with `$2_$1`.

Here's an example of how to remove prefix underscores from any struct and its
members.

```yaml
structs:
...
rename:
'_(.*)': '$1' # Removes prefix underscores from all structures.
member-rename:
'.*': # Matches any struct.
'_(.*)': '$1' # Removes prefix underscores from members.
```
### How to generate declarations only from particular headers?

The default behavior is to include everything directly/transitively under
each of the `entry-points` specified.

If you only want to have declarations directly particular header you can do so
using `include-directives`. You can use **glob matching** to match header paths.

```yaml
headers:
entry-points:
- 'path/to/my_header.h'
include-directives:
- '**my_header.h' # This glob pattern matches the header path.
```
### Can FFIgen filter declarations by name?

FFIgen supports including/excluding declarations using full regexp matching.

Here's an example to filter functions using names:

```yaml
functions:
include:
- 'clang.*' # Include all functions starting with clang.
exclude:
- '.*dispose': # Exclude all functions ending with dispose.
```

This will include `clang_help`. But will exclude `clang_dispose`.

Note: exclude overrides include.

### How does FFIgen handle C Strings?

FFIgen treats `char*` just as any other pointer (`Pointer<Int8>`).
To convert these to/from `String`, you can use [package:ffi](https://pub.dev/packages/ffi).
Use `ptr.cast<Utf8>().toDartString()` to convert `char*` to dart `string` and
`"str".toNativeUtf8()` to convert `string` to `char*`.

### How are unnamed enums handled?

Unnamed enums are handled separately, under the key `unnamed-enums`, and are
generated as top level constants.

Here's an example that shows how to include/exclude/rename unnamed enums:

```yaml
unnamed-enums:
include:
- 'CX_.*'
exclude:
- '.*Flag'
rename:
'CXType_(.*)': '$1'
```

### How can I handle unexpected enum values?

Native enums are, by default, generated into Dart enums with `int get value` and
`fromValue(int)`. This works well in the case that your enums values are known
in advance and not going to change, and in return, you get the full benefits of
Dart enums like exhaustiveness checking.

However, if a native library adds another possible enum value after you generate
your bindings, and this new value is passed to your Dart code, this will result
in an `ArgumentError` at runtime. To fix this, you can regenerate the bindings
on the new header file, but if you wish to avoid this issue entirely, you can
tell FFIgen to generate plain Dart integers for your enum instead. To do this,
simply list your enum's name in the `as-int` section of your FFIgen config:

```yaml
enums:
as-int:
include:
- MyIntegerEnum
- '*IntegerEnum'
exclude:
- FakeIntegerEnum
```

Functions that accept or return these enums will now accept or return integers
instead, and it will be up to your code to map integer values to behavior and
handle invalid values. But your code will be future-proof against new additions
to the enums.

### Why are some struct/union declarations generated even after excluded them in config?

This happens when an excluded struct/union is a dependency to some included
declaration. (A dependency means a struct is being passed/returned by a function
or is member of another struct in some way.)

Note: If you supply `structs.dependency-only` as `opaque` FFIgen will generate
these struct dependencies as `Opaque` if they were only passed by reference
(pointer).

```yaml
structs:
dependency-only: opaque
unions:
dependency-only: opaque
```

### How to expose the native pointers?

By default, the native pointers are private, but you can use the
`symbol-address` subkey for functions/globals and make them public by matching
with its name. The pointers are then accessible via `nativeLibrary.addresses`.

Example:

```yaml
functions:
symbol-address:
include:
- 'myFunc' # Match function name.
- '.*' # Do this to expose all function pointers.
exclude: # If you only use exclude, then everything not excluded is generated.
- 'dispose'
```

### How to get typedefs to Native and Dart type of a function?

By default, these types are inline. But you can use the `expose-typedef` subkey
for functions to generate them. This will expose the Native and Dart type.
E.g. for a function named `hello` the generated typedefs are named as
`NativeHello` and `DartHello`.

Example:

```yaml
functions:
expose-typedefs:
include:
- 'myFunc' # Match function name.
- '.*' # Do this to expose types for all functions.
exclude: # If you only use exclude, then everything not excluded is generated.
- 'dispose'
```

### How are Structs/Unions/Enums that are referred to via typedefs handled?

Named declarations use their own names even when inside another typedef.
However, unnamed declarations inside typedefs take the name of the _first_
typedef that refers to them.

### Why are some typedefs not generated?

The following typedefs are not generated:
- They are not referred to anywhere in the included declarations.
- They refer to a struct/union having the same name as itself.
- They refer to a boolean, enum, inline array, Handle or any unsupported type.

### How are macros handled?

FFIgen uses `clang`'s own compiler frontend to parse and traverse the `C`
header files. FFIgen expands the macros using `clang`'s macro expansion and
then traverses the expanded code. To do this, FFIgen generates temporary files
in a system tmp directory.

A custom temporary directory can be specified by setting the `TEST_TMPDIR`
environment variable.

### What are these logs generated by FFIgen and how to fix them?

FFIgen can sometimes generate a lot of logs, especially when it's parsing a lot
of code.
- `SEVERE` logs are something you *definitely need to address*. They can be
caused due to syntax errors, or more generally missing header files
(which need to be specified using `compiler-opts` in config).
- `WARNING` logs are something *you can ignore*, but should probably look into.
These are mostly indications of declarations FFIgen couldn't generate due
to limitations of `dart:ffi`, private declarations (which can be resolved
by renaming them via FFIgen's config) or other minor issues in the config
file itself.
- Everything else can be safely ignored. Its purpose is to simply let you know
what FFIgen is doing.
- The verbosity of the logs can be changed by adding a flag with
the log level, e.g. `dart run ffigen --verbose <level>`.
Level options are `[all, fine, info (default), warning, severe]`.
The `all` and `fine` will print a ton of logs are meant for debugging
purposes only.

### How can type definitions be shared?

FFIgen can share type definitions using symbol files.
- A package can generate a symbol file using the `output.symbol-file` config.
- And another package can then import this, using `import.symbol-files` config.
- Doing so will reuse all the types such as Struct/Unions, and will automatically
exclude generating other types (E.g. functions, enums, macros).

Checkout `examples/shared_bindings` for details.

For manually reusing definitions from another package, the `library-imports`
and `type-map` config can be used.

### How does ObjC method filtering work?

Methods and properties on ObjC interfaces and protocols can be filtered using
the `member-filter` option under `objc-interfaces` and `objc-protocols`. For
simplicity we'll focus on interface methods, but the same rules apply to
properties and protocols. There are two parts to the filtering process: matching
the interface, and then filtering the method.

The syntax of `member-filter` is a YAML map from a pattern to some
`include`/`exclude` rules, and `include` and `exclude` are each a list of
patterns.

```yaml
objc-interfaces:
member-filter:
MyInterface: # Matches an interface.
include:
- "someMethod:withArg:" # Matches a method.
exclude:
- someOtherMethod # Matches a method.
```

The interface matching logic is the same as the matching logic for the
`member-rename` option:

- The pattern is compared against the original name of the interface (before any
renaming is applied).
- The pattern may be a string or a regexp, but in either case they must match
the entire interface name.
- If the pattern contains only alphanumeric characters, or `_`, it is treated as
a string rather than a regex.
- String patterns take precedence over regexps. That is, if an interface matches
both a regexp pattern, and a string pattern, it uses the string pattern's
`include`/`exclude` rules.

The method filtering logic uses the same `include`/`exclude` rules as the rest
of the config:

- `include` and `exclude` are a list of patterns.
- The patterns are compared against the original name of the method, before
renaming.
- The patterns can be strings or regexps, but must match the entire method name.
- The method name is in ObjC selector syntax, which means that the method name
and all the external parameter names are concatenated together with `:`
characters. This is the same name you'll see in ObjC's API documentation.
- **NOTE:** Since the pattern must match the entire method name, and most ObjC
method names end with a `:`, it's a good idea to surround the pattern with
quotes, `"`. Otherwise, YAML will think you're defining a map key.
- If no `include` or `exclude` rules are defined, all methods are included,
regardless of the top level `exclude-all-by-default` rule.
- If only `include` rules are `defined`, all non-matching methods are excluded.
- If only `exclude` rules are `defined`, all non-matching methods are included.
- If both `include` and `exclude` rules are defined, the `exclude` rules take
precedence. That is, if a method name matches both an `include` rule and an
`exclude` rule, the method is excluded. All non-matching methods are also
excluded.

The property filtering rules live in the same `objc-interfaces.member-filter`
option as the methods. There is no distinction between methods and properties in
the filters. The protocol filtering rules live in
`objc-protocols.member-filter`.

### How do I generate bindings for Apple APIs?

It can be tricky to locate header files containing Apple's ObjC frameworks, and
the paths can vary between computers depending on which version of Xcode you are
using and where it is installed. So FFIgen provides the following variable
substitutions that can be used in the `headers.entry-points` list:

- `$XCODE`: Replaced with the result of `xcode-select -p`, which is the
directory where Xcode's APIs are installed.
- `$IOS_SDK`: Replaced with `xcrun --show-sdk-path --sdk iphoneos`, which is the
directory within `$XCODE` where the iOS SDK is installed.
- `$MACOS_SDK`: Replaced with `xcrun --show-sdk-path --sdk macosx`, which is the
directory within `$XCODE` where the macOS SDK is installed.

For example:

```Yaml
headers:
entry-points:
- '$MACOS_SDK/System/Library/Frameworks/Foundation.framework/Headers/NSDate.h'
```
15 changes: 15 additions & 0 deletions pkgs/ffigen/doc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# FFIgen Documentation

## General documentation

- [FAQ](faq.md)
- [Common errors](errors.md) in FFIgen and how to deal with them.

## Objective-C specific documentation

- [Objective-C memory management considerations](objc_gc.md)
- [Dealing with OS differences](objc_os_differences.md)
- [Runtime type checks in Objective-C](objc_runtime_types.md)
- [Objective-C threading considerations](objc_threading.md)
- [Objective-C method filtering](objc_method_filtering.md)
- [Generating bindings for Apple APIs](apple_apis.md)
21 changes: 21 additions & 0 deletions pkgs/ffigen/doc/apple_apis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
## Generating bindings for Apple APIs

It can be tricky to locate header files containing Apple's ObjC frameworks, and
the paths can vary between computers depending on which version of Xcode you are
using and where it is installed. So FFIgen provides the following variable
substitutions that can be used in the `headers.entry-points` list:

- `$XCODE`: Replaced with the result of `xcode-select -p`, which is the
directory where Xcode's APIs are installed.
- `$IOS_SDK`: Replaced with `xcrun --show-sdk-path --sdk iphoneos`, which is the
directory within `$XCODE` where the iOS SDK is installed.
- `$MACOS_SDK`: Replaced with `xcrun --show-sdk-path --sdk macosx`, which is the
directory within `$XCODE` where the macOS SDK is installed.

For example:

```Yaml
headers:
entry-points:
- '$MACOS_SDK/System/Library/Frameworks/Foundation.framework/Headers/NSDate.h'
```
Loading
Loading