NSSharp C# Binding Generator
Generate Xamarin/MAUI-style C# binding API definitions from Objective-C headers. Supports category merging, NS_ASSUME_NONNULL scope-aware nullability, weak→NullAllowed inference, [Field] for extern constants, and configurable vendor macro handling.
Quick Start
# Build and run during development
dotnet build NSSharp.slnx
dotnet run --project src/NSSharp -- MyHeader.h
# Or install as a dotnet tool
dotnet pack src/NSSharp/NSSharp.csproj -c Release
dotnet tool install -g --add-source src/NSSharp/bin/Release ASTools.NSSharp
# Or from NuGet.org
dotnet tool install -g ASTools.NSSharp
# C# bindings to stdout (default format)
nssharp MyHeader.h
# To file
nssharp MyHeader.h -o ApiDefinition.cs
# From xcframework
nssharp --xcframework MyLib.xcframework -o Bindings.cs
# Specific slice
nssharp --xcframework MyLib.xcframework --slice ios-arm64 -o Bindings.cs
# One .cs file per header (requires output directory)
nssharp --xcframework MyLib.xcframework --split-by-header -o GeneratedBindings/
# Add namespace
nssharp --xcframework MyLib.xcframework --namespace MyCompany.Bindings -o Bindings.cs
# With vendor export macros
nssharp --xcframework MyLib.xcframework --extern-macros PSPDF_EXPORT -o Bindings.cs
# Include C functions and extern constants in output
nssharp MyHeader.h --emit-c-bindings -o Bindings.cs
Binding Rules
| ObjC Construct |
C# Output |
@interface Foo : Bar |
[BaseType(typeof(Bar))] interface Foo |
@interface Foo (Cat) |
Merged into parent Foo interface (or [Category] if parent not parsed) |
@protocol P |
interface IP {} stub + [Protocol] interface P |
@protocol PDelegate |
interface IPDelegate {} stub + [Protocol, Model] [BaseType(typeof(NSObject))] interface PDelegate |
Protocol conformance <P> |
: IP interface inheritance |
@required method |
[Abstract] [Export("sel")] |
@optional method |
[Export("sel")] (no [Abstract]) |
@property (copy) |
ArgumentSemantic.Copy |
@property (readonly) |
{ get; } only |
@property (nullable) |
[NullAllowed] |
@property (weak) |
[NullAllowed] (implicit) |
@property (class) |
[Static] |
| Object pointer property |
ArgumentSemantic.Strong (readwrite default), ArgumentSemantic.Retain for explicit retain |
Class method + |
[Static] [Export("sel")] |
-(instancetype)init* |
NativeHandle Constructor(...) |
NS_DESIGNATED_INITIALIZER |
[DesignatedInitializer] on constructors |
| Init unavailable macro |
[DisableDefaultCtor] (e.g., PSPDF_EMPTY_INIT_UNAVAILABLE) |
Static factory +classWithParam: |
From<Param> (class name match) or Create<Name> |
Block-type property void (^name)(...) |
Property with Action type |
Method name with Block |
Renamed to Action (e.g., performBlock: → PerformAction) |
isEqualTo<Class>: |
IsEqualTo (class name suffix stripped) |
NS_ENUM(NSInteger, X) |
[Native] enum X : long |
NS_OPTIONS(NSUInteger, X) |
[Flags] enum X : ulong |
NS_CLOSED_ENUM / NS_ERROR_ENUM |
Same as NS_ENUM |
struct |
[StructLayout(LayoutKind.Sequential)] struct |
extern function |
[DllImport("__Internal")] static extern in CFunctions class (requires --emit-c-bindings) |
extern constant |
[Field("name", "__Internal")] in Constants interface |
extern NSNotificationName |
[Notification] [Field("name")] |
| Completion handler method |
[Async] attribute (class methods only, from completion: suffix) |
Protocol @required @property |
[Abstract] + C# property (with [Bind("isX")] for custom getters) |
Protocol @optional @property |
Decomposed into getter/setter method pairs |
Protocol @optional @property (getter=isX) |
Getter uses custom selector, setter uses setX: |
Variadic ... |
IntPtr varArgs parameter |
NS_ASSUME_NONNULL_BEGIN/END |
Scope-aware [NullAllowed] inference |
Type Mapping
See references/type-mapping.md for the complete ObjC→C# type mapping table (70+ types).
Key Mappings
| ObjC |
C# |
NSString * |
string |
NSInteger |
nint |
NSUInteger |
nuint |
CGFloat |
nfloat |
BOOL |
bool |
id |
NSObject |
SEL |
Selector |
instancetype |
Class name (static methods) or NativeHandle Constructor (init) |
NSArray * |
NSObject [] |
NSArray<Type *> |
Type [] (typed arrays) |
NSDictionary<K, V> |
NSDictionary<MappedK, MappedV> (preserves Foundation types) |
NSSet<T> |
NSSet<MappedT> (preserves Foundation types) |
id<Protocol> |
IProtocol |
UIView<Protocol> |
IProtocol (protocol interface) |
IBAction |
void |
IBInspectable BOOL |
bool (IB annotations stripped) |
Type ** |
out Type |
NSError ** |
out NSError + always [NullAllowed] |
Block (^)(...) |
Action |
*Block typedef |
*Handler (.NET convention) |
Programmatic Usage
using NSSharp.Lexer;
using NSSharp.Parser;
using NSSharp.Binding;
var source = File.ReadAllText("MyHeader.h");
var options = new ObjCLexerOptions
{
ExternMacros = ["PSPDF_EXPORT"],
};
var tokens = new ObjCLexer(source, options).Tokenize();
var header = new ObjCParser(tokens).Parse("MyHeader.h");
var generator = new CSharpBindingGenerator();
string csharpBindings = generator.Generate(header);
// Include C functions and extern constants:
string withCBindings = generator.Generate(header, emitCBindings: true);
// For xcframeworks, merge categories across headers before generating:
var headers = new List<ObjCHeader> { header1, header2 };
CSharpBindingGenerator.MergeCategories(headers);
Source Files
| File |
Purpose |
src/NSSharp/Binding/CSharpBindingGenerator.cs |
Main generator (~1078 lines) |
src/NSSharp/Binding/ObjCTypeMapper.cs |
Type mapping + selector→method name conversion (~630 lines) |
src/NSSharp/Lexer/ObjCLexerOptions.cs |
Lexer config (macro heuristic, extern macros) |
Key Methods in ObjCTypeMapper
MapType(string objcType) → C# type string
MapEnumBackingType(string? objcType) → C# enum backing type
IsNativeEnum(string? objcType) → whether [Native] attribute is needed
SelectorToMethodName(string selector, bool isProtocolMethod) → Smart method name: first part only, strips trailing prepositions, strips sender prefix for protocols (multi-part and embedded single-part), renames Block→Action, handles isEqualTo pattern, normalizes acronyms (URL→Url, PDF→Pdf, UID→Uid, XMP→Xmp, etc.)
PascalCase(string name) → PascalCase conversion with acronym normalization
SetTypedefMap(Dictionary<string, string>) → Sets typedef resolution map
ResolveTypedef(string typeName) → Resolves typedef aliases to base types
Key Methods in CSharpBindingGenerator
Generate(ObjCHeader header) → full C# binding output (merges categories in-place)
MergeCategories(List<ObjCHeader> headers) → static cross-header category merging
IsObjectPointerType(string type) → checks if type is an object pointer
BuildTypedefMap(List<ObjCHeader>) → builds typedef resolution map from parsed headers
Notes
- Generated bindings are a starting point; manual review may be needed
- Namespace behavior: single-header input has no namespace by default; xcframework input defaults namespace to the xcframework name unless
--namespace is specified
- Enum prefix stripping:
MyStatusOK → OK when enum is MyStatus
- Constructor detection: methods with
init prefix returning instancetype (including nonnull instancetype) become NativeHandle Constructor(...)
NS_DESIGNATED_INITIALIZER emits [DesignatedInitializer] attribute
NS_REQUIRES_SUPER is consumed without corrupting selectors
- Block-type properties (
void (^name)(params)) are correctly parsed with the block name extracted
- Properties with
copy/strong/retain/assign/weak get ArgumentSemantic annotations
- Object pointer properties without explicit semantic: readwrite infer
ArgumentSemantic.Strong; readonly get no inference
- Non-primitive value type properties (enums) without explicit semantic: readwrite infer
ArgumentSemantic.Assign
out NSError parameters always get [NullAllowed]
- Category properties are decomposed into getter/setter methods (not C# properties)
[DisableDefaultCtor] only emitted when init is explicitly unavailable via macros (e.g., PSPDF_EMPTY_INIT_UNAVAILABLE), not inferred
- The lexer whitelists macros containing
INIT_UNAVAILABLE or EMPTY_INIT, preserving them for the parser
- Static factory methods returning
instancetype get Create<Name> prefix instead of Get<Name>
Block in method names is renamed to Action at word boundaries (e.g., performBlock: → PerformAction)
isEqualTo<ClassName>: selectors are simplified to IsEqualTo (class name suffix stripped)
- Selector naming special-cases: collapse trailing
ForPageAtIndex / OnPageAtIndex; preserve InContext, WithTransform, and ForEvent
- Weak properties always get
[NullAllowed]
- Inside
NS_ASSUME_NONNULL scope: only explicitly nullable types get [NullAllowed]; outside scope: all object pointers get [NullAllowed]
- ObjC categories are merged into parent class interfaces when the parent is available (including
SWIFT_EXTENSION categories)
- Each
@protocol emits an interface IProtocolName {} stub before the protocol definition
- Protocols ending in
Delegate or DataSource get [Protocol, Model] + [BaseType(typeof(NSObject))]
- Protocol properties are decomposed differently based on
@required/@optional:
@required properties get [Abstract] and stay as C# properties with [Bind("isX")] for custom getters
@optional properties are decomposed into getter/setter method pairs (custom getter selectors used when available)
NSNotificationName typed extern constants get [Notification] attribute
- Methods with completion handler parameters (ending in
completion:, completionHandler:, completionBlock:) get [Async] — only on class methods, not protocol methods
- Acronyms in method names are normalized: URL→Url, PDF→Pdf, HUD→Hud, HTML→Html, JSON→Json, UID→Uid, XMP→Xmp
- Typedef aliases are resolved to their base types for correct C# type mapping
- Both leading and trailing
const qualifiers are stripped during type mapping
- Preprocessor directives (
#if, #endif) inside protocol conformance lists are skipped
- PSPDFKitUI benchmark (current DemoFramework): 88.9% exact export parity vs sharpie (489/550 common exports)
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: nssharp-binding-generator3description: Generate Xamarin.iOS / .NET for iOS C# binding definitions from Objective-C headers using NSSharp. Use when creating API binding definitions, generating [Export], [BaseType], [Protocol] attributed interfaces, mapping ObjC types to C# types, converting ObjC selectors to C# method names, or producing interop code from .h files or xcframeworks. Use when this capability is needed.4---56# NSSharp C# Binding Generator78Generate Xamarin/MAUI-style C# binding API definitions from Objective-C headers. Supports category merging, NS_ASSUME_NONNULL scope-aware nullability, weak→NullAllowed inference, [Field] for extern constants, and configurable vendor macro handling.910## Quick Start1112```bash13# Build and run during development14dotnet build NSSharp.slnx15dotnet run --project src/NSSharp -- MyHeader.h1617# Or install as a dotnet tool18dotnet pack src/NSSharp/NSSharp.csproj -c Release19dotnet tool install -g --add-source src/NSSharp/bin/Release ASTools.NSSharp20# Or from NuGet.org21dotnet tool install -g ASTools.NSSharp2223# C# bindings to stdout (default format)24nssharp MyHeader.h2526# To file27nssharp MyHeader.h -o ApiDefinition.cs2829# From xcframework30nssharp --xcframework MyLib.xcframework -o Bindings.cs3132# Specific slice33nssharp --xcframework MyLib.xcframework --slice ios-arm64 -o Bindings.cs3435# One .cs file per header (requires output directory)36nssharp --xcframework MyLib.xcframework --split-by-header -o GeneratedBindings/3738# Add namespace39nssharp --xcframework MyLib.xcframework --namespace MyCompany.Bindings -o Bindings.cs4041# With vendor export macros42nssharp --xcframework MyLib.xcframework --extern-macros PSPDF_EXPORT -o Bindings.cs4344# Include C functions and extern constants in output45nssharp MyHeader.h --emit-c-bindings -o Bindings.cs46```4748## Binding Rules4950| ObjC Construct | C# Output |51|---|---|52| `@interface Foo : Bar` | `[BaseType(typeof(Bar))] interface Foo` |53| `@interface Foo (Cat)` | Merged into parent `Foo` interface (or `[Category]` if parent not parsed) |54| `@protocol P` | `interface IP {}` stub + `[Protocol] interface P` |55| `@protocol PDelegate` | `interface IPDelegate {}` stub + `[Protocol, Model] [BaseType(typeof(NSObject))] interface PDelegate` |56| Protocol conformance `<P>` | `: IP` interface inheritance |57| `@required` method | `[Abstract] [Export("sel")]` |58| `@optional` method | `[Export("sel")]` (no `[Abstract]`) |59| `@property (copy)` | `ArgumentSemantic.Copy` |60| `@property (readonly)` | `{ get; }` only |61| `@property (nullable)` | `[NullAllowed]` |62| `@property (weak)` | `[NullAllowed]` (implicit) |63| `@property (class)` | `[Static]` |64| Object pointer property | `ArgumentSemantic.Strong` (readwrite default), `ArgumentSemantic.Retain` for explicit retain |65| Class method `+` | `[Static] [Export("sel")]` |66| `-(instancetype)init*` | `NativeHandle Constructor(...)` |67| `NS_DESIGNATED_INITIALIZER` | `[DesignatedInitializer]` on constructors |68| Init unavailable macro | `[DisableDefaultCtor]` (e.g., `PSPDF_EMPTY_INIT_UNAVAILABLE`) |69| Static factory `+classWithParam:` | `From<Param>` (class name match) or `Create<Name>` |70| Block-type property `void (^name)(...)` | Property with `Action` type |71| Method name with `Block` | Renamed to `Action` (e.g., `performBlock:` → `PerformAction`) |72| `isEqualTo<Class>:` | `IsEqualTo` (class name suffix stripped) |73| `NS_ENUM(NSInteger, X)` | `[Native] enum X : long` |74| `NS_OPTIONS(NSUInteger, X)` | `[Flags] enum X : ulong` |75| `NS_CLOSED_ENUM` / `NS_ERROR_ENUM` | Same as `NS_ENUM` |76| `struct` | `[StructLayout(LayoutKind.Sequential)] struct` |77| `extern` function | `[DllImport("__Internal")] static extern` in `CFunctions` class (requires `--emit-c-bindings`) |78| `extern` constant | `[Field("name", "__Internal")]` in `Constants` interface |79| `extern NSNotificationName` | `[Notification] [Field("name")]` |80| Completion handler method | `[Async]` attribute (class methods only, from `completion:` suffix) |81| Protocol `@required @property` | `[Abstract]` + C# property (with `[Bind("isX")]` for custom getters) |82| Protocol `@optional @property` | Decomposed into getter/setter method pairs |83| Protocol `@optional @property (getter=isX)` | Getter uses custom selector, setter uses `setX:` |84| Variadic `...` | `IntPtr varArgs` parameter |85| `NS_ASSUME_NONNULL_BEGIN/END` | Scope-aware `[NullAllowed]` inference |8687## Type Mapping8889See [references/type-mapping.md](references/type-mapping.md) for the complete ObjC→C# type mapping table (70+ types).9091### Key Mappings9293| ObjC | C# |94|---|---|95| `NSString *` | `string` |96| `NSInteger` | `nint` |97| `NSUInteger` | `nuint` |98| `CGFloat` | `nfloat` |99| `BOOL` | `bool` |100| `id` | `NSObject` |101| `SEL` | `Selector` |102| `instancetype` | Class name (static methods) or `NativeHandle Constructor` (init) |103| `NSArray *` | `NSObject []` |104| `NSArray<Type *>` | `Type []` (typed arrays) |105| `NSDictionary<K, V>` | `NSDictionary<MappedK, MappedV>` (preserves Foundation types) |106| `NSSet<T>` | `NSSet<MappedT>` (preserves Foundation types) |107| `id<Protocol>` | `IProtocol` |108| `UIView<Protocol>` | `IProtocol` (protocol interface) |109| `IBAction` | `void` |110| `IBInspectable BOOL` | `bool` (IB annotations stripped) |111| `Type **` | `out Type` |112| `NSError **` | `out NSError` + always `[NullAllowed]` |113| Block `(^)(...)` | `Action` |114| `*Block` typedef | `*Handler` (.NET convention) |115116## Programmatic Usage117118```csharp119using NSSharp.Lexer;120using NSSharp.Parser;121using NSSharp.Binding;122123var source = File.ReadAllText("MyHeader.h");124var options = new ObjCLexerOptions125{126 ExternMacros = ["PSPDF_EXPORT"],127};128var tokens = new ObjCLexer(source, options).Tokenize();129var header = new ObjCParser(tokens).Parse("MyHeader.h");130131var generator = new CSharpBindingGenerator();132string csharpBindings = generator.Generate(header);133134// Include C functions and extern constants:135string withCBindings = generator.Generate(header, emitCBindings: true);136137// For xcframeworks, merge categories across headers before generating:138var headers = new List<ObjCHeader> { header1, header2 };139CSharpBindingGenerator.MergeCategories(headers);140```141142## Source Files143144| File | Purpose |145|---|---|146| `src/NSSharp/Binding/CSharpBindingGenerator.cs` | Main generator (~1078 lines) |147| `src/NSSharp/Binding/ObjCTypeMapper.cs` | Type mapping + selector→method name conversion (~630 lines) |148| `src/NSSharp/Lexer/ObjCLexerOptions.cs` | Lexer config (macro heuristic, extern macros) |149150## Key Methods in ObjCTypeMapper151152- `MapType(string objcType)` → C# type string153- `MapEnumBackingType(string? objcType)` → C# enum backing type154- `IsNativeEnum(string? objcType)` → whether `[Native]` attribute is needed155- `SelectorToMethodName(string selector, bool isProtocolMethod)` → Smart method name: first part only, strips trailing prepositions, strips sender prefix for protocols (multi-part and embedded single-part), renames Block→Action, handles isEqualTo pattern, normalizes acronyms (URL→Url, PDF→Pdf, UID→Uid, XMP→Xmp, etc.)156- `PascalCase(string name)` → PascalCase conversion with acronym normalization157- `SetTypedefMap(Dictionary<string, string>)` → Sets typedef resolution map158- `ResolveTypedef(string typeName)` → Resolves typedef aliases to base types159160## Key Methods in CSharpBindingGenerator161162- `Generate(ObjCHeader header)` → full C# binding output (merges categories in-place)163- `MergeCategories(List<ObjCHeader> headers)` → static cross-header category merging164- `IsObjectPointerType(string type)` → checks if type is an object pointer165- `BuildTypedefMap(List<ObjCHeader>)` → builds typedef resolution map from parsed headers166167## Notes168169- Generated bindings are a starting point; manual review may be needed170- Namespace behavior: single-header input has no namespace by default; xcframework input defaults namespace to the xcframework name unless `--namespace` is specified171- Enum prefix stripping: `MyStatusOK` → `OK` when enum is `MyStatus`172- Constructor detection: methods with `init` prefix returning `instancetype` (including `nonnull instancetype`) become `NativeHandle Constructor(...)`173- `NS_DESIGNATED_INITIALIZER` emits `[DesignatedInitializer]` attribute174- `NS_REQUIRES_SUPER` is consumed without corrupting selectors175- Block-type properties (`void (^name)(params)`) are correctly parsed with the block name extracted176- Properties with `copy`/`strong`/`retain`/`assign`/`weak` get `ArgumentSemantic` annotations177- Object pointer properties without explicit semantic: readwrite infer `ArgumentSemantic.Strong`; readonly get no inference178- Non-primitive value type properties (enums) without explicit semantic: readwrite infer `ArgumentSemantic.Assign`179- `out NSError` parameters always get `[NullAllowed]`180- Category properties are decomposed into getter/setter methods (not C# properties)181- `[DisableDefaultCtor]` only emitted when init is explicitly unavailable via macros (e.g., `PSPDF_EMPTY_INIT_UNAVAILABLE`), not inferred182- The lexer whitelists macros containing `INIT_UNAVAILABLE` or `EMPTY_INIT`, preserving them for the parser183- Static factory methods returning `instancetype` get `Create<Name>` prefix instead of `Get<Name>`184- `Block` in method names is renamed to `Action` at word boundaries (e.g., `performBlock:` → `PerformAction`)185- `isEqualTo<ClassName>:` selectors are simplified to `IsEqualTo` (class name suffix stripped)186- Selector naming special-cases: collapse trailing `ForPageAtIndex` / `OnPageAtIndex`; preserve `InContext`, `WithTransform`, and `ForEvent`187- Weak properties always get `[NullAllowed]`188- Inside `NS_ASSUME_NONNULL` scope: only explicitly nullable types get `[NullAllowed]`; outside scope: all object pointers get `[NullAllowed]`189- ObjC categories are merged into parent class interfaces when the parent is available (including `SWIFT_EXTENSION` categories)190- Each `@protocol` emits an `interface IProtocolName {}` stub before the protocol definition191- Protocols ending in `Delegate` or `DataSource` get `[Protocol, Model]` + `[BaseType(typeof(NSObject))]`192- Protocol properties are decomposed differently based on `@required`/`@optional`:193 - `@required` properties get `[Abstract]` and stay as C# properties with `[Bind("isX")]` for custom getters194 - `@optional` properties are decomposed into getter/setter method pairs (custom getter selectors used when available)195- `NSNotificationName` typed extern constants get `[Notification]` attribute196- Methods with completion handler parameters (ending in `completion:`, `completionHandler:`, `completionBlock:`) get `[Async]` — only on class methods, not protocol methods197- Acronyms in method names are normalized: URL→Url, PDF→Pdf, HUD→Hud, HTML→Html, JSON→Json, UID→Uid, XMP→Xmp198- Typedef aliases are resolved to their base types for correct C# type mapping199- Both leading and trailing `const` qualifiers are stripped during type mapping200- Preprocessor directives (`#if`, `#endif`) inside protocol conformance lists are skipped201- PSPDFKitUI benchmark (current DemoFramework): 88.9% exact export parity vs sharpie (489/550 common exports)202203---204> Converted and distributed by [TomeVault](https://tomevault.io/claim/dalexsoto) — claim your Tome and manage your conversions.205<!-- tomevault:4.0:skill_md:2026-04-13 -->