- name
- macios-binding-creator
- description
- Create C# bindings for Apple frameworks in dotnet/macios. USE FOR: binding new APIs, implementing .todo file entries, creating Xcode SDK bindings, binding AVFoundation/UIKit/AppKit or any Apple framework, "bind this framework", "implement these APIs". DO NOT USE FOR: Xcode beta version bumps (use macios-xcode-beta-update skill), CI failure investigation (use macios-ci-failure-inspector skill).
# macios Binding Creator
Create C# bindings for Apple platform APIs in the dotnet/macios repository. This skill encodes the end-to-end workflow: from reading `.todo` files through implementation, building, and validating with xtro, cecil, and introspection tests on all platforms.
## When to Use This Skill
Use this skill when:
- Asked to bind a new Apple framework or add missing API bindings
- Implementing entries from `.todo` files in `tests/xtro-sharpie/api-annotations-dotnet/`
- Creating bindings for a new Xcode SDK release
- Adding new types, properties, methods, or enum values to existing framework bindings
- Asked to "bind", "implement", or "add bindings for" any Apple framework
## Prerequisites
- Repository checked out and configured (`./configure` already run)
- Xcode installed at the expected `XCODE_DEVELOPER_ROOT` path
- A successful `make world` or `make all && make install` already completed
## Process
### Step 1: Understand What to Bind
Check the `.todo` files to see what APIs are missing:
```bash
ls tests/xtro-sharpie/api-annotations-dotnet/*-{FrameworkName}.todo
cat tests/xtro-sharpie/api-annotations-dotnet/iOS-{FrameworkName}.todo
```
Each `.todo` file lists missing APIs per platform (iOS, tvOS, macOS, MacCatalyst). The format is:
```
!missing-selector! ClassName::methodName: not bound
!missing-type! ClassName not bound
!missing-field! ClassName FieldName not bound
!missing-enum-value! EnumName::ValueName not bound
```
> ❌ **NEVER** bind APIs that aren't in the `.todo` files unless explicitly asked. The `.todo` files are the source of truth for what's missing.
### Step 2: Generate Reference Bindings
Run the xtro generator to produce reference C# bindings from the SDK headers:
```bash
make -C tests/xtro-sharpie gen-all
```
This creates generated `.cs` files you can search to find the correct C# signatures, attributes, and patterns for the APIs you need to bind. Use these as reference — don't copy them verbatim.
### Step 3: Research the Native API
Before implementing, understand the native API:
- Search the generated reference bindings for the correct Objective-C selectors
- Read Apple header files when available (under `$XCODE_DEVELOPER_ROOT`)
- Check existing bindings in `src/frameworkname.cs` for patterns used in the same framework
### Step 4: Implement Bindings
#### Determine the Correct Availability Version
Before writing any bindings, determine the correct availability version for each API. The version represents **when Apple introduced the API**, not the current SDK version.
**Primary source of truth: the generated reference bindings** from Step 2. After running `make -C tests/xtro-sharpie gen-all`, search the generated `.cs` files for the API you're binding — they include `[Introduced]` attributes extracted from Apple's SDK headers with the correct per-platform introduction versions. Always use these versions.
```bash
# Find the generated reference binding for a specific API.
# gen-all (Step 2) writes these to api/<Platform>/ApiDefinition.cs (gitignored),
# so run Step 2 first. Widen to api/*/ if a symbol isn't in ApiDefinition.cs.
grep -rn "SomeClassName\|SomeMethodName" tests/xtro-sharpie/api/*/ApiDefinition.cs
```
If the generated reference bindings don't include version information, fall back to these sources:
1. **Apple SDK headers** — search under `$XCODE_DEVELOPER_ROOT` for `API_AVAILABLE` macros
2. **Current SDK version from `SdkVersions.cs`** — use this only for **brand-new APIs** introduced in the current Xcode release:
```bash
grep -E 'public const string (iOS|TVOS|OSX|MacCatalyst) ' tools/common/SdkVersions.cs
```
> ❌ **NEVER assume the current SDK version is the introduction version for all APIs.** The SDK version (e.g., `26.5`) is only correct for APIs that are genuinely new in this Xcode release. When adding an existing framework to a new platform (e.g., MediaSetup to MacCatalyst), or adding enum members that were introduced in an earlier release, the introduction version will be different — check the generated reference bindings or Apple headers.
If the user specifies a version, use that instead. **Ask the user if you're unsure which version to use.**
#### File Locations
Bindings go in these locations:
- **`src/frameworkname.cs`** — API definitions (interfaces with `[Export]` attributes)
- **`src/FrameworkName/`** — Manual code (partial classes, enums, P/Invokes, extensions)
- **`src/frameworks.sources`** — Maps frameworks to source files (update if adding new files)
> ⚠️ **Binding an entirely new framework** (no `src/<fw>.cs` yet) needs extra build/test wiring — `frameworks.sources`, `tools/common/Frameworks.cs`, `ProjectTest.cs` link lists, the xtro ignore lists, plus two generated-file build gotchas. See [references/binding-patterns.md](references/binding-patterns.md) § "Registering a Brand-New Framework".
Key binding patterns:
```csharp
// New property on existing class
[Export ("allowsCaptureOfClearKeyVideo")]
bool AllowsCaptureOfClearKeyVideo { get; set; }
// New method on existing class
[Export ("setCaptionPreviewProfileId:")]
void SetCaptionPreviewProfileId ([NullAllowed] string profileId);
// New notification field
[Field ("AVPlayerInterstitialEventMonitorScheduleRequestedNotification")]
[Notification]
NSString ScheduleRequestedNotification { get; }
```
> ❌ **NEVER** forget platform availability attributes. Every new API must have `[iOS]`, `[Mac]`, `[TV]`, `[MacCatalyst]`, and/or `[No*]` attributes matching the `.todo` file platforms where the API appears. This includes **all** binding types:
> - API definition interfaces and members in `src/frameworkname.cs` — use `[iOS (X, Y)]`, `[Mac (X, Y)]`, etc.
> - P/Invoke wrappers and manual properties in `src/FrameworkName/*.cs` — use `[SupportedOSPlatform ("iosX.Y")]`, `[SupportedOSPlatform ("macos")]`, etc.
> - Fields, constants, and enum values
> - **Individual enum members** added to an existing enum — match the native header **per member**: add `[iOS (X, Y)]`/`[No*]`/etc. **only** for what the header annotates on that member (its own `API_AVAILABLE` version newer than the enum, or `API_UNAVAILABLE`/absence on a platform). A member the header doesn't annotate inherits the enum's availability — add **no** attribute, matching its siblings. On a **multi-platform** enum, if you do add a per-member introduced version, add it for **every** applicable platform (bgen otherwise back-fills the enum's older version). **Error enums are an exception — see the next rule.** See [references/binding-patterns.md](references/binding-patterns.md) § "Adding New Members to Existing Enums".
> ❌ **NEVER put availability *or* unavailability attributes on members of an *error enum*** — not even brand-new ones. An error enum is any enum whose name ends in `Error` or `ErrorCode`, **or** that carries `[ErrorDomain]`. The cecil test `EnumTest.NoAvailabilityOnError` (issue #9724) fails — with **no** known-failures allowlist — if **any** field carries an availability/unavailability attribute (`[iOS]`/`[Mac]`/`[TV]`/`[No*]`/`[Introduced]`/`[Unavailable]`/`[Supported/UnsupportedOSPlatform]`); only `[Obsolete]`/`[Obsoleted…]` deprecation attributes are exempt. Bind new error-code members bare — just the value (plus `[Field]` if smart) — matching their siblings (e.g. `VNErrorCode.ResourceUnavailable`, `UNErrorCode.AttachmentUnsupportedType`).
> ❌ **NEVER** use `string.Empty` — use `""`. Never use `Array.Empty<T>()` — use `[]`.
> ❌ **NEVER** add placeholder XML documentation text like `"To be added."` anywhere — not in `<remarks>`, `<summary>`, `<returns>`, `[Async (XmlDocs = ...)]`, or any other XML doc element. Either write meaningful documentation or omit the element entirely.
> ❌ **NEVER** forget `[NullAllowed]` on `out NSError error` parameters. Every method that takes `NSError**` (bound as `out NSError error`) must use `[NullAllowed] out NSError error`. This applies to all error-returning methods — the error output is null on success.
> ❌ **NEVER** forget `#nullable enable` at the top of every new C# file you create.
> ⚠️ **PREFER a named delegate type** (`delegate void SomeFrameworkSomeCallback (…)`) over `Action<T>`/`Func<T>` for completion-handler / callback parameters **when the meaning of a parameter isn't obvious from its type** — only a named delegate can carry parameter names and XML docs. (`Action<T>`/`Func<T>` now support nullable type arguments, so `[NullAllowed]`/nullability is no longer a reason to avoid them.) See [references/binding-patterns.md](references/binding-patterns.md) § "Blocks and Completion Handlers".
> ❌ **NEVER** use non-blittable types (`bool`, `char`) as backing fields in structs. Use `byte` (for `bool`) and `ushort`/`short` (for `char`) with property accessors. See [references/binding-patterns.md](references/binding-patterns.md) for the correct pattern.
> ❌ **NEVER** use `XAMCORE_5_0` for new code. `XAMCORE_5_0` is only for fixing breaking API changes on existing types that shipped in prior releases. However, when xtro reports a mismatch on an **existing** type (e.g., wrong enum backing type, missing `[Native]`), and fixing it directly would be a breaking change, you **must** use `#if XAMCORE_5_0` guards to preserve binary compatibility while queuing the fix for the future. Add a `.ignore` entry for the xtro mismatch. See [references/binding-patterns.md](references/binding-patterns.md) § "XAMCORE_5_0 Pattern for Existing Types".
> ❌ **NEVER** use `#pragma warning disable 0169` for struct fields. Instead, wrap public methods and properties inside `#if !COREBUILD` (but NOT fields — bgen needs to know the struct size).
> ⚠️ **Protocol methods returning opaque types**: If a protocol method returns an opaque C type (e.g., `PMPrintSession`) that has a managed `NativeObject` wrapper in `src/FrameworkName/`, do NOT use `IntPtr`. Register the type as a bgen marshal type so bgen can generate proper `Runtime.GetINativeObject<T>()` marshaling. See [references/binding-patterns.md](references/binding-patterns.md) § "NativeObject Return Types in Protocol Methods".
> ⚠️ Place a space before parentheses and brackets: `Foo ()`, `Bar (1, 2)`, `myarray [0]`.
> ⚠️ Method names should follow .NET naming conventions — use verb-based names, not direct Objective-C selector translations (e.g., `BuildMenu` not `MenuWithContents`).
> ❌ **NEVER** change the casing of the Objective-C class name **prefix** in C# type names. `ARSession` stays `ARSession` (not `ArSession`), `AVPlayer` stays `AVPlayer` (not `AvPlayer`). But an acronym *inside* the name (after the prefix) DOES follow .NET rules — `NSURLSession` → `NSUrlSession`, `NSURLSessionHandler` → `NSUrlSessionHandler` (the `NS` prefix is kept, but `URL` becomes `Url`). When creating new manual types, match the framework's established prefix (e.g., all ARKit types use `AR*`, all CoreGraphics types use `CG*`). The .NET acronym rules (SIMD → Simd, URL → Url) apply within property/method names **and** to acronyms inside type names, NOT to the leading class prefix. (A few frameworks instead preserve an inner acronym across their whole family — e.g. CoreGraphics `CGPDF*` — so match the existing sibling types when a framework is consistent.)
> ⚠️ For in depth binding patterns and conventions See [references/binding-patterns.md](references/binding-patterns.md)
> ⚠️ **Struct array parameters**: When an API takes a C struct pointer + count (e.g., `MyStruct*` + `NSUInteger`), bind the raw pointer as `[Internal]` with `IntPtr`, then create a manual public wrapper using the **factory pattern** with `fixed`. See [references/binding-patterns.md](references/binding-patterns.md) § "Struct Array Parameter Binding".
### Step 4b: Platform Exclusion Patterns for Manual Types
When a manually coded type (struct, extension, etc.) is not available on a specific platform (e.g., tvOS), you must handle compilation on that platform:
1. In the manual code file (`src/FrameworkName/MyStruct.cs`), wrap the struct body with `#if !__TVOS__`
2. Add `[UnsupportedOSPlatform ("tvos")]` on the struct
3. In the API definition file (`src/frameworkname.cs`), add a type alias at the top so compilation succeeds:
```csharp
#if __TVOS__
using MyStruct = Foundation.NSObject;
#endif
```
The `[NoTV]` attribute on the API definition interface ensures the type won't appear in the final tvOS assembly, while the alias prevents compilation errors from method signatures that reference the struct.
> ❌ **NEVER** use platform-specific source file lists (e.g., appending a per-framework list to `MACOS_DOTNET_SOURCES`) for platform-conditional code. Instead, use preprocessor directives (`#if __MACOS__`, `#if !__TVOS__`, `#if __IOS__`) within shared source files. Platform-specific source file lists are for the build system, not for conditional compilation of individual types or members.
Available preprocessor symbols for platform checks:
- `__MACOS__` (preferred) / `MONOMAC` — macOS
- `__IOS__` — iOS
- `__TVOS__` (preferred) / `TVOS` — tvOS
- `__MACCATALYST__` — Mac Catalyst
> ⚠️ Mac Catalyst defines **both** `__MACCATALYST__` **and** `__IOS__` (`msbuild/Xamarin.Shared/Xamarin.Shared.props`). In `#if` chains, test `__MACCATALYST__` **before** `__IOS__`, or the Catalyst case falls into the iOS branch.
> ⚠️ **Foundation/TextKit types shared by AppKit and UIKit** (e.g. `NSTextList`, `NSParagraphStyle`) are bound once in `src/xkit.cs`, not duplicated in `appkit.cs`/`uikit.cs`. If a type in `appkit.cs` (or `uikit.cs`) becomes exposed to the other side, consolidate it there (share identical enums, split only divergent ones, handle platform-only members with `[No*]` attributes — reserving `#if` for divergences attributes can't express — keep back-dated availability). See [references/binding-patterns.md](references/binding-patterns.md) → "Shared AppKit/UIKit Types".
### Step 5: Build
Rebuild **and install** so the test suites — which read the installed NuGet packs, not `src/build/` — pick up your changes:
```bash
make all && make install
```
> ❌ **NEVER** use `make -C src build`. There is no `build` *target* in `src/Makefile`, so the command is unreliable: on a fresh checkout it fails with `make: *** No rule to make target 'build'`, and once the `src/build/` output directory exists the word `build` matches that directory and the command silently no-ops (`Nothing to be done for 'build'`). Either way it compiles nothing and you validate against **stale** (or missing) assemblies. Use `make all && make install` (or `make world` for a full rebuild).
Fix any compilation errors before proceeding. Builds can take up to 60 minutes — do not timeout early.
### Step 5b: Write Monotouch Tests for Manual Bindings
For any manually bound APIs (P/Invokes, manual properties on partial classes, struct accessors), add tests in `tests/monotouch-test/{FrameworkName}/`.
> ⚠️ **Only run monotouch-tests (Step 6d) if you added or modified test files in this step.** If no manual bindings were added (i.e., all APIs were bound via `[Export]` in the API definition file), skip both this step and Step 6d.
```csharp
using CoreText; // framework being tested
using NUnit.Framework;
namespace MonoTouchFixtures.CoreText { // MonoTouchFixtures.{FrameworkName}
[TestFixture]
[Preserve (AllMembers = true)]
public class FontTest {
[Test]
public void UIFontType_SystemFont ()
{
TestRuntime.AssertXcodeVersion (26, 4); // match the availability version
using (var font = new CTFont ("Helvetica", 12)) {
var fontType = font.UIFontType;
Assert.AreEqual (CTUIFontType.System, fontType);
}
}
}
}
```
Key patterns:
- **Namespace**: `MonoTouchFixtures.{FrameworkName}` (e.g., `MonoTouchFixtures.CoreText`)
- **Version guards**: Use `TestRuntime.AssertXcodeVersion (major, minor)` matching the API's availability version. This skips the test on older runtimes instead of failing.
GitHub에서 보기