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).
77
96%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
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.
Use this skill when:
.todo files in tests/xtro-sharpie/api-annotations-dotnet/./configure already run)XCODE_DEVELOPER_ROOT pathmake world or make all && make install already completedCheck the .todo files to see what APIs are missing:
ls tests/xtro-sharpie/api-annotations-dotnet/*-{FrameworkName}.todo
cat tests/xtro-sharpie/api-annotations-dotnet/iOS-{FrameworkName}.todoEach .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
.todofiles unless explicitly asked. The.todofiles are the source of truth for what's missing.
Run the xtro generator to produce reference C# bindings from the SDK headers:
make -C tests/xtro-sharpie gen-allThis 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.
Before implementing, understand the native API:
$XCODE_DEVELOPER_ROOT)src/frameworkname.cs for patterns used in the same frameworkBefore writing any bindings, determine the SDK version you're targeting:
# Check the current SDK versions
grep -E 'public const string (iOS|TVOS|OSX|MacCatalyst) ' tools/common/SdkVersions.cs
# Or from Make.versions
grep '_NUGET_OS_VERSION=' Make.versionsUse the version from SdkVersions.cs (e.g., 26.2) for all availability attributes. If the user specifies a different version (e.g., binding a beta branch at 26.4), use that instead. Ask the user if you're unsure which version to use.
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)Key binding patterns:
// 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.todofile 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
❌ NEVER use
string.Empty— use"". Never useArray.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]onout NSError errorparameters. Every method that takesNSError**(bound asout 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 enableat the top of every new C# file you create.
❌ NEVER use non-blittable types (
bool,char) as backing fields in structs. Usebyte(forbool) andushort/short(forchar) with property accessors. See references/binding-patterns.md for the correct pattern.
❌ NEVER use
XAMCORE_5_0for new code.XAMCORE_5_0is 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_0guards to preserve binary compatibility while queuing the fix for the future. Add a.ignoreentry for the xtro mismatch. See references/binding-patterns.md § "XAMCORE_5_0 Pattern for Existing Types".
❌ NEVER use
#pragma warning disable 0169for struct fields. Instead, wrap public methods and properties inside#if !COREBUILD(but NOT fields — bgen needs to know the struct size).
⚠️ 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.,
BuildMenunotMenuWithContents).
⚠️ For in depth binding patterns and conventions See 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]withIntPtr, then create a manual public wrapper using the factory pattern withfixed. See references/binding-patterns.md § "Struct Array Parameter Binding".
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:
src/FrameworkName/MyStruct.cs), wrap the struct body with #if !TVOS[UnsupportedOSPlatform ("tvos")] on the structsrc/frameworkname.cs), add a type alias at the top so compilation succeeds:#if TVOS
using MyStruct = Foundation.NSObject;
#endifThe [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.
make -C src buildFix any compilation errors before proceeding. Builds can take up to 60 minutes — do not timeout early.
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.
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:
MonoTouchFixtures.{FrameworkName} (e.g., MonoTouchFixtures.CoreText)TestRuntime.AssertXcodeVersion (major, minor) matching the API's availability version. This skips the test on older runtimes instead of failing.using statements for handle-based types⚠️ If adding a new test file, make sure the
.csprojattests/monotouch-test/picks it up (it typically uses wildcard includes, but verify).
See references/binding-patterns.md for more monotouch-test patterns.
⚠️ Stale build artifacts: If you encounter unexpected test failures (SIGABRT, segfaults in unrelated types, false "pre-existing" failures), always run
make worldFIRST before investigating. Never conclude a failure is "pre-existing" without rebuilding — stale_build/artifacts are the #1 cause of spurious introspection crashes after binding changes.
Run all three test suites. Run them sequentially, not in parallel.
make -C tests/xtro-sharpie run-ios
make -C tests/xtro-sharpie run-tvos
make -C tests/xtro-sharpie run-macos
make -C tests/xtro-sharpie run-maccatalystVerify all .todo entries for the bound framework are resolved. If any remain, they need binding or explicit .ignore entries with justification.
⚠️ Delete empty
.todofiles after resolving all entries:git rm tests/xtro-sharpie/api-annotations-dotnet/{platform}-{Framework}.todo. Do not leave empty.todofiles in the repository.
make -C tests/cecil-tests run-testsIMPORTANT: Clean shared obj directories before each platform to avoid NETSDK1005 errors:
# iOS — build, then run via mlaunch directly for reliable output capture
rm -rf tests/common/Touch.Unit/Touch.Client/dotnet/obj tests/common/MonoTouch.Dialog/obj
make -C tests/introspection/dotnet/iOS clean
make -C tests/introspection/dotnet build-ios
# Get the app path and run via mlaunch directly:
APP_PATH=$(make -C tests/introspection/dotnet/iOS print-executable | sed 's|/introspection$||')
SIMCTL_CHILD_NUNIT_AUTOSTART=true \
SIMCTL_CHILD_NUNIT_AUTOEXIT=true \
$DOTNET_DESTDIR/Microsoft.iOS.Sdk/tools/bin/mlaunch \
--launchsim "$APP_PATH" \
--device :v2:runtime=com.apple.CoreSimulator.SimRuntime.iOS-26-4,devicetype=com.apple.CoreSimulator.SimDeviceType.iPhone-16-Pro \
--wait-for-exit:true --
# tvOS — same approach as iOS
rm -rf tests/common/Touch.Unit/Touch.Client/dotnet/obj tests/common/MonoTouch.Dialog/obj
make -C tests/introspection/dotnet/tvOS clean
make -C tests/introspection/dotnet build-tvos
APP_PATH=$(make -C tests/introspection/dotnet/tvOS print-executable | sed 's|/introspection$||')
SIMCTL_CHILD_NUNIT_AUTOSTART=true \
SIMCTL_CHILD_NUNIT_AUTOEXIT=true \
$DOTNET_DESTDIR/Microsoft.tvOS.Sdk/tools/bin/mlaunch \
--launchsim "$APP_PATH" \
--device :v2:runtime=com.apple.CoreSimulator.SimRuntime.tvOS-26-4,devicetype=com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-3rd-generation-4K \
--wait-for-exit:true --
# macOS (use run-bare for direct execution with captured output)
rm -rf tests/common/Touch.Unit/Touch.Client/dotnet/obj tests/common/MonoTouch.Dialog/obj
make -C tests/introspection/dotnet/macOS clean build
make -C tests/introspection/dotnet/macOS run-bare
# MacCatalyst (use run-bare for direct execution with captured output)
rm -rf tests/common/Touch.Unit/Touch.Client/dotnet/obj tests/common/MonoTouch.Dialog/obj
make -C tests/introspection/dotnet/MacCatalyst clean build
make -C tests/introspection/dotnet/MacCatalyst run-bare⚠️ iOS/tvOS output capture:
make run-ios/run-tvosusesdotnet build -t:Runwhich does NOT reliably capture the app's stdout. Thecom.apple.gamedstderr message causes MSBuild to report failure (exit code -1) even when tests pass, and NUnit results are lost. Use mlaunch directly as shown above to capture test output reliably.
⚠️ mlaunch device strings: Use
xcrun simctl list runtimesandxcrun simctl list devicetypesto find the correct runtime and device type identifiers for your Xcode version. The--deviceformat is:v2:runtime=<runtime-id>,devicetype=<devicetype-id>.
⚠️
cleanandrun-baremust be run from the platform subdirectory (e.g.,tests/introspection/dotnet/macOS/), not from the parentdotnet/directory. The parent only hasbuild-%andrun-%pattern rules — there are noclean-%orrun-bare-%targets.
⚠️ macOS/MacCatalyst: Use
run-bare(notrun) —runlaunches the app without waiting or capturing stdout.run-bareruns the executable directly to capture test output.
Look for this pattern in test output to confirm results:
Tests run: X Passed: X Inconclusive: X Failed: X Ignored: XSkip this step if no monotouch-test files were added or modified.
make -C tests/monotouch-test runIf introspection tests fail for newly bound types:
ApiCtorInitTest.cs files if needed[DesignatedInitializer] constructor crashes (segfault) when passed null, the correct fix is to remove [NullAllowed] from that parameter rather than adding introspection test exclusions. The null is genuinely not allowed by the native API.If xtro still shows unresolved entries:
.ignore entries with comments explaining why they can't be bound.todo entries for known limitations.todo files unless explicitly asked.When reporting results, use this structure:
.todo entries intentionally left unbound, with reasonsd259b5f
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.