From 9acc5407a66df164f91b681f774ad5853baa71c1 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 23 Mar 2026 19:35:46 +0100 Subject: [PATCH] [sharpie] Add --custom-delegates option to generate named delegates instead of Func<>/Action<> When binding ObjC blocks, sharpie normally generates Func<...> and Action<...> types for anonymous block parameters and properties. The new --custom-delegates option generates custom named delegate declarations instead. For method parameters, the delegate name is derived from the method and parameter names (e.g. SetActionHandler). For properties, it's derived from the property name (e.g. ActionHandler). Implementation uses a new CustomDelegateMassager that runs as a post-processing pass on the generated C# AST, consistent with the existing massager architecture. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../sharpie/Sharpie.Bind.Tests/OnDiskTests.cs | 3 + tests/sharpie/Tests/Nullability.h | 21 +++ tests/sharpie/Tests/Nullability.iphoneos.cs | 58 +++++++ tests/sharpie/Tests/Nullability.macosx.cs | 58 +++++++ .../Tests/Types/Blocks.custom_delegates.cs | 129 ++++++++++++++++ tests/sharpie/Tests/Types/Blocks.h | 3 + tests/xtro-sharpie/Makefile | 1 + tools/sharpie/Sharpie.Bind/BindingMassager.cs | 3 + .../Massagers/CustomDelegateMassager.cs | 146 ++++++++++++++++++ .../sharpie/Sharpie.Bind/ObjectiveCBinder.cs | 5 + tools/sharpie/Sharpie.Bind/Tools.cs | 2 + 11 files changed, 429 insertions(+) create mode 100644 tests/sharpie/Tests/Types/Blocks.custom_delegates.cs create mode 100644 tools/sharpie/Sharpie.Bind/Massagers/CustomDelegateMassager.cs diff --git a/tests/sharpie/Sharpie.Bind.Tests/OnDiskTests.cs b/tests/sharpie/Sharpie.Bind.Tests/OnDiskTests.cs index be0db1ff8e84..b631ea0d3e04 100644 --- a/tests/sharpie/Sharpie.Bind.Tests/OnDiskTests.cs +++ b/tests/sharpie/Sharpie.Bind.Tests/OnDiskTests.cs @@ -137,6 +137,9 @@ void ParseBindTestImpl (string path, string variant, string bindArguments) binder.Massagers.Add ((massagerName.TrimStart ('+', '-'), enable)); i++; break; + case "-custom-delegates": + binder.UseCustomDelegates = true; + break; default: clangArguments.Add (bindArgs [i]); break; diff --git a/tests/sharpie/Tests/Nullability.h b/tests/sharpie/Tests/Nullability.h index 87c2af23c6f7..5728ff72c2f4 100644 --- a/tests/sharpie/Tests/Nullability.h +++ b/tests/sharpie/Tests/Nullability.h @@ -13,6 +13,27 @@ void Func (const char * __nullable str); +// Functions with nullable return types +extern NSObject * _Nullable NullableReturnObject (void); +extern NSString * _Nullable NullableReturnString (int code); + +// Functions with nullable parameters +extern void FuncWithNullableObject (NSObject * _Nullable obj); +extern void FuncWithNullableString (NSString * _Nullable str); +extern void FuncWithMixedNullability (NSObject * _Nonnull required, NSObject * _Nullable optional); + +// Functions with both nullable params and return +extern NSObject * _Nullable FuncNullableInAndOut (NSString * _Nullable input); + +// Functions with nullable block parameters +extern void FuncWithNullableBlock (void (^ _Nullable block)(void)); +extern void FuncWithNullableBlockParam (void (^ _Nullable block)(NSObject * _Nullable obj)); +extern void FuncWithNonnullBlock (void (^ _Nonnull block)(void)); + +// Functions returning nullable block +typedef void (^SimpleBlock)(void); +extern SimpleBlock _Nullable FuncReturningNullableBlock (void); + @interface Foo @property (nullable) SEL selector; diff --git a/tests/sharpie/Tests/Nullability.iphoneos.cs b/tests/sharpie/Tests/Nullability.iphoneos.cs index 81dc7a469a4c..8d7463f2df86 100644 --- a/tests/sharpie/Tests/Nullability.iphoneos.cs +++ b/tests/sharpie/Tests/Nullability.iphoneos.cs @@ -1,3 +1,4 @@ +using System; using System.Runtime.InteropServices; using Foundation; using ObjCRuntime; @@ -7,8 +8,65 @@ static class CFunctions { [DllImport ("__Internal")] [Verify (PlatformInvoke)] static extern unsafe void Func ([NullAllowed] sbyte* str); + + // extern NSObject * _Nullable NullableReturnObject (); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + [return: NullAllowed] + static extern NSObject NullableReturnObject (); + + // extern NSString * _Nullable NullableReturnString (int code); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + [return: NullAllowed] + static extern NSString NullableReturnString (int code); + + // extern void FuncWithNullableObject (NSObject * _Nullable obj); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithNullableObject ([NullAllowed] NSObject obj); + + // extern void FuncWithNullableString (NSString * _Nullable str); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithNullableString ([NullAllowed] NSString str); + + // extern void FuncWithMixedNullability (NSObject * _Nonnull required, NSObject * _Nullable optional); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithMixedNullability (NSObject required, [NullAllowed] NSObject optional); + + // extern NSObject * _Nullable FuncNullableInAndOut (NSString * _Nullable input); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + [return: NullAllowed] + static extern NSObject FuncNullableInAndOut ([NullAllowed] NSString input); + + // extern void FuncWithNullableBlock (void (^ _Nullable)(void) block); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithNullableBlock ([NullAllowed] Action block); + + // extern void FuncWithNullableBlockParam (void (^ _Nullable)(NSObject * _Nullable) block); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithNullableBlockParam ([NullAllowed] Action block); + + // extern void FuncWithNonnullBlock (void (^ _Nonnull)(void) block); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithNonnullBlock (Action block); + + // extern SimpleBlock _Nullable FuncReturningNullableBlock (); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + [return: NullAllowed] + static extern SimpleBlock FuncReturningNullableBlock (); } +// typedef void (^SimpleBlock)(); +delegate void SimpleBlock (); + // @interface Foo interface Foo { // @property SEL _Nullable selector; diff --git a/tests/sharpie/Tests/Nullability.macosx.cs b/tests/sharpie/Tests/Nullability.macosx.cs index 81dc7a469a4c..8d7463f2df86 100644 --- a/tests/sharpie/Tests/Nullability.macosx.cs +++ b/tests/sharpie/Tests/Nullability.macosx.cs @@ -1,3 +1,4 @@ +using System; using System.Runtime.InteropServices; using Foundation; using ObjCRuntime; @@ -7,8 +8,65 @@ static class CFunctions { [DllImport ("__Internal")] [Verify (PlatformInvoke)] static extern unsafe void Func ([NullAllowed] sbyte* str); + + // extern NSObject * _Nullable NullableReturnObject (); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + [return: NullAllowed] + static extern NSObject NullableReturnObject (); + + // extern NSString * _Nullable NullableReturnString (int code); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + [return: NullAllowed] + static extern NSString NullableReturnString (int code); + + // extern void FuncWithNullableObject (NSObject * _Nullable obj); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithNullableObject ([NullAllowed] NSObject obj); + + // extern void FuncWithNullableString (NSString * _Nullable str); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithNullableString ([NullAllowed] NSString str); + + // extern void FuncWithMixedNullability (NSObject * _Nonnull required, NSObject * _Nullable optional); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithMixedNullability (NSObject required, [NullAllowed] NSObject optional); + + // extern NSObject * _Nullable FuncNullableInAndOut (NSString * _Nullable input); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + [return: NullAllowed] + static extern NSObject FuncNullableInAndOut ([NullAllowed] NSString input); + + // extern void FuncWithNullableBlock (void (^ _Nullable)(void) block); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithNullableBlock ([NullAllowed] Action block); + + // extern void FuncWithNullableBlockParam (void (^ _Nullable)(NSObject * _Nullable) block); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithNullableBlockParam ([NullAllowed] Action block); + + // extern void FuncWithNonnullBlock (void (^ _Nonnull)(void) block); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + static extern void FuncWithNonnullBlock (Action block); + + // extern SimpleBlock _Nullable FuncReturningNullableBlock (); + [DllImport ("__Internal")] + [Verify (PlatformInvoke)] + [return: NullAllowed] + static extern SimpleBlock FuncReturningNullableBlock (); } +// typedef void (^SimpleBlock)(); +delegate void SimpleBlock (); + // @interface Foo interface Foo { // @property SEL _Nullable selector; diff --git a/tests/sharpie/Tests/Types/Blocks.custom_delegates.cs b/tests/sharpie/Tests/Types/Blocks.custom_delegates.cs new file mode 100644 index 000000000000..4e021e30f0ce --- /dev/null +++ b/tests/sharpie/Tests/Types/Blocks.custom_delegates.cs @@ -0,0 +1,129 @@ +using System; +using Foundation; +using ObjCRuntime; + +// typedef void (^Action)(); +delegate void Action (); + +// typedef int (^Anon_Func_Long_Int)(long long); +delegate int Anon_Func_Long_Int (long arg0); + +// typedef int (^Named_Func_Long_Int)(long long); +delegate int Named_Func_Long_Int (long arg0); + +// typedef void (^Variadic)(int, ...); +delegate void Variadic (int arg0, IntPtr varArgs); + +// @interface TypedefBlockTests +interface TypedefBlockTests { + // -(Action)get_Action; + [Export ("get_Action")] + [Verify (MethodToProperty)] + Action Get_Action { get; } + + // -(Anon_Func_Long_Int)get_Anon_Func_Long_Int; + [Export ("get_Anon_Func_Long_Int")] + [Verify (MethodToProperty)] + Anon_Func_Long_Int Get_Anon_Func_Long_Int { get; } + + // -(Named_Func_Long_Int)get_Named_Func_Long_Int; + [Export ("get_Named_Func_Long_Int")] + [Verify (MethodToProperty)] + Named_Func_Long_Int Get_Named_Func_Long_Int { get; } +} + +delegate void ActionHandler (); + +delegate void Action_intHandler (int arg0); + +delegate void Action_actionHandler (Action arg0); + +delegate void Action_action_intHandler (Action arg0); + +delegate void Action_action_actionHandler (Action arg0); + +delegate int Func_int_intHandler (int arg0); + +delegate nint Func_short_nintHandler (short arg0); + +// @interface PropertyBlockTests +interface PropertyBlockTests { + // @property (readonly, copy) void (^action)(); + [Export ("action", ArgumentSemantic.Copy)] + ActionHandler Action { get; } + + // @property (readonly, copy) void (^action_int)(int); + [Export ("action_int", ArgumentSemantic.Copy)] + Action_intHandler Action_int { get; } + + // @property (readonly, copy) void (^action_action)(void (^)()); + [Export ("action_action", ArgumentSemantic.Copy)] + Action_actionHandler Action_action { get; } + + // @property (readonly, copy) void (^action_action_int)(void (^)(int)); + [Export ("action_action_int", ArgumentSemantic.Copy)] + Action_action_intHandler Action_action_int { get; } + + // @property (readonly, copy) void (^action_action_action)(void (^)(void (^)())); + [Export ("action_action_action", ArgumentSemantic.Copy)] + Action_action_actionHandler Action_action_action { get; } + + // @property (readonly, copy) int (^func_int_int)(int); + [Export ("func_int_int", ArgumentSemantic.Copy)] + Func_int_intHandler Func_int_int { get; } + + // @property (readonly, copy) long (^func_short_nint)(short); + [Export ("func_short_nint", ArgumentSemantic.Copy)] + Func_short_nintHandler Func_short_nint { get; } +} + +delegate void Set_ActionHandler (); + +delegate void Set_Action_intHandler (int arg0); + +delegate void Set_Action_short_int_longHandler (short arg0, int arg1, long arg2); + +delegate int Set_Func_intHandler (); + +delegate int Set_Func_int_intHandler (int arg0); + +delegate bool Set_Func_short_int_long_boolHandler (short arg0, int arg1, long arg2); + +delegate bool Set_Func_Func_short_Action_long_short_boolHandler (Func arg0, short arg1); + +delegate bool Set_Func_Func_short_Action_Action_int_nint_long_short_boolHandler (Func>, long> arg0, short arg1); + +// @interface AnonymousBlockTests +interface AnonymousBlockTests { + // -(void)set_Action:(void (^)())handler; + [Export ("set_Action:")] + void Set_Action (Set_ActionHandler handler); + + // -(void)set_Action_int:(void (^)(int))handler; + [Export ("set_Action_int:")] + void Set_Action_int (Set_Action_intHandler handler); + + // -(void)set_Action_short_int_long:(void (^)(short, int, long long))handler; + [Export ("set_Action_short_int_long:")] + void Set_Action_short_int_long (Set_Action_short_int_longHandler handler); + + // -(void)set_Func_int:(int (^)())handler; + [Export ("set_Func_int:")] + void Set_Func_int (Set_Func_intHandler handler); + + // -(void)set_Func_int_int:(int (^)(int))handler; + [Export ("set_Func_int_int:")] + void Set_Func_int_int (Set_Func_int_intHandler handler); + + // -(void)set_Func_short_int_long_bool:(_Bool (^)(short, int, long long))handler; + [Export ("set_Func_short_int_long_bool:")] + void Set_Func_short_int_long_bool (Set_Func_short_int_long_boolHandler handler); + + // -(void)set_Func_Func_short_Action_long_short_bool:(_Bool (^)(long long (^)(short, void (^)()), short))handler; + [Export ("set_Func_Func_short_Action_long_short_bool:")] + void Set_Func_Func_short_Action_long_short_bool (Set_Func_Func_short_Action_long_short_boolHandler handler); + + // -(void)set_Func_Func_short_Action_Action_int_nint_long_short_bool:(_Bool (^)(long long (^)(short, void (^)(void (^)(int, long))), short))handler; + [Export ("set_Func_Func_short_Action_Action_int_nint_long_short_bool:")] + void Set_Func_Func_short_Action_Action_int_nint_long_short_bool (Set_Func_Func_short_Action_Action_int_nint_long_short_boolHandler handler); +} diff --git a/tests/sharpie/Tests/Types/Blocks.h b/tests/sharpie/Tests/Types/Blocks.h index f75e4297fd88..d0fe7f691376 100644 --- a/tests/sharpie/Tests/Types/Blocks.h +++ b/tests/sharpie/Tests/Types/Blocks.h @@ -1,6 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +// RUN: -x objective-c +// RUN custom_delegates: -x objective-c -custom-delegates + typedef void (^Action)(); typedef int (^Anon_Func_Long_Int)(long long); typedef int (^Named_Func_Long_Int)(long long longArg); diff --git a/tests/xtro-sharpie/Makefile b/tests/xtro-sharpie/Makefile index eb3f4d554531..26d0e6debd16 100644 --- a/tests/xtro-sharpie/Makefile +++ b/tests/xtro-sharpie/Makefile @@ -126,6 +126,7 @@ IGNORED_MACCATALYST_FRAMEWORKS = \ COMMON_SHARPIE_ARGUMENTS = \ -a arm64 \ -modules true \ + --custom-delegates \ --clang-resource-dir $(TOP)/tools/sharpie/clang \ IOS_SHARPIE_ARGUMENTS = \ diff --git a/tools/sharpie/Sharpie.Bind/BindingMassager.cs b/tools/sharpie/Sharpie.Bind/BindingMassager.cs index ef107c47f462..a4e820574a08 100644 --- a/tools/sharpie/Sharpie.Bind/BindingMassager.cs +++ b/tools/sharpie/Sharpie.Bind/BindingMassager.cs @@ -18,6 +18,7 @@ public BindingMassager (ObjectiveCBinder binder) } static readonly List defaultMassagers = new List { + typeof (CustomDelegateMassager), typeof (DefaultConstructorMassager), typeof (DelegateMassager), typeof (MethodToPropertyMassager), @@ -80,6 +81,8 @@ static MassagerBase CreateMassager (ObjectiveCBinder binder, string name) name = name.Substring (ns.Length + 1); switch (name) { + case nameof (CustomDelegateMassager): + return new CustomDelegateMassager (binder); case nameof (DefaultConstructorMassager): return new DefaultConstructorMassager (binder); case nameof (DelegateMassager): diff --git a/tools/sharpie/Sharpie.Bind/Massagers/CustomDelegateMassager.cs b/tools/sharpie/Sharpie.Bind/Massagers/CustomDelegateMassager.cs new file mode 100644 index 000000000000..3685d9b32fcb --- /dev/null +++ b/tools/sharpie/Sharpie.Bind/Massagers/CustomDelegateMassager.cs @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using ICSharpCode.NRefactory.CSharp; + +using Sharpie.Bind.Types; + +namespace Sharpie.Bind.Massagers; + +/// +/// Converts anonymous Action<...>/Func<...> delegate types in method +/// parameters and property return types into custom named delegate declarations. +/// +[RegisterBefore (typeof (GenerateUsingStatementsMassager))] +public sealed class CustomDelegateMassager : Massager { + readonly HashSet usedNames = new HashSet (); + readonly List<(AstNode InsertBefore, DelegateDeclaration Delegate)> pendingDelegates = new (); + + public CustomDelegateMassager (ObjectiveCBinder binder) + : base (binder) + { + } + + public override bool Initialize () + { + return Binder.UseCustomDelegates; + } + + public override void VisitMethodDeclaration (MethodDeclaration methodDeclaration) + { + if (HasVisited (methodDeclaration)) + return; + + MarkVisited (methodDeclaration); + + var parent = methodDeclaration.Parent as TypeDeclaration; + if (parent is null) + return; + + foreach (var param in methodDeclaration.Parameters.ToList ()) { + if (param.Type is not DelegateType delegateType) + continue; + + var delegateName = GenerateUniqueName (methodDeclaration.Name, param.Name.UCFirst ()); + var del = CreateDelegateDeclaration (delegateName, delegateType); + pendingDelegates.Add ((parent, del)); + + var newType = new SimpleType (delegateName); + delegateType.CopyAnnotationsTo (newType); + param.Type = newType; + } + + if (methodDeclaration.ReturnType is DelegateType returnDelegateType) { + var delegateName = GenerateUniqueName (methodDeclaration.Name, "Return"); + var del = CreateDelegateDeclaration (delegateName, returnDelegateType); + pendingDelegates.Add ((parent, del)); + + var newType = new SimpleType (delegateName); + returnDelegateType.CopyAnnotationsTo (newType); + methodDeclaration.ReturnType = newType; + } + } + + public override void VisitPropertyDeclaration (PropertyDeclaration propertyDeclaration) + { + if (HasVisited (propertyDeclaration)) + return; + + MarkVisited (propertyDeclaration); + + if (propertyDeclaration.ReturnType is not DelegateType delegateType) + return; + + var parent = propertyDeclaration.Parent as TypeDeclaration; + if (parent is null) + return; + + var delegateName = GenerateUniqueName (propertyDeclaration.Name, "Handler"); + var del = CreateDelegateDeclaration (delegateName, delegateType); + pendingDelegates.Add ((parent, del)); + + var newType = new SimpleType (delegateName); + delegateType.CopyAnnotationsTo (newType); + propertyDeclaration.ReturnType = newType; + } + + public override void VisitSyntaxTree (SyntaxTree syntaxTree) + { + base.VisitSyntaxTree (syntaxTree); + FlushPendingDelegates (); + } + + public override void VisitNamespaceDeclaration (NamespaceDeclaration namespaceDeclaration) + { + base.VisitNamespaceDeclaration (namespaceDeclaration); + FlushPendingDelegates (); + } + + void FlushPendingDelegates () + { + foreach (var (insertBefore, del) in pendingDelegates) { + var container = insertBefore.Parent; + if (container is SyntaxTree syntaxTree) + syntaxTree.Members.InsertBefore (insertBefore, del); + else if (container is NamespaceDeclaration ns) + ns.Members.InsertBefore (insertBefore, del); + } + pendingDelegates.Clear (); + } + + static DelegateDeclaration CreateDelegateDeclaration (string name, DelegateType delegateType) + { + var del = new DelegateDeclaration { + Name = name + }; + + int i = 0; + foreach (var typeArg in delegateType.TypeArguments) { + var clone = typeArg.Clone (); + del.Parameters.Add (new ParameterDeclaration (clone, String.Format ("arg{0}", i++))); + } + + if (delegateType is FuncType) { + // For Func, the last type argument is the return type + var returnParam = del.Parameters.LastOrNullObject (); + var returnType = returnParam.Type; + returnType.Remove (); + returnParam.Remove (); + del.ReturnType = returnType; + } else { + del.ReturnType = new PrimitiveType ("void"); + } + + return del; + } + + string GenerateUniqueName (string memberName, string? suffix) + { + var baseName = memberName + suffix; + var name = baseName; + var counter = 2; + while (!usedNames.Add (name)) + name = baseName + counter++; + return name; + } +} diff --git a/tools/sharpie/Sharpie.Bind/ObjectiveCBinder.cs b/tools/sharpie/Sharpie.Bind/ObjectiveCBinder.cs index 01f5ef80dacd..306d065170d0 100644 --- a/tools/sharpie/Sharpie.Bind/ObjectiveCBinder.cs +++ b/tools/sharpie/Sharpie.Bind/ObjectiveCBinder.cs @@ -35,6 +35,7 @@ public string Sdk { public bool? EnableModules { get; set; } public bool SplitDocuments { get; set; } = true; public bool DeepSplit { get; set; } + public bool UseCustomDelegates { get; set; } public string ApiDefinitionName { get; set; } = "ApiDefinition.cs"; public string StructsAndEnumsName { get; set; } = "StructsAndEnums.cs"; @@ -254,6 +255,10 @@ protected virtual bool AddArguments (List args) args.Add ("--deepsplit"); } + if (UseCustomDelegates) { + args.Add ("--custom-delegates"); + } + if (EnableModules.HasValue) { if (EnableModules.Value) args.Add ("--modules=true"); diff --git a/tools/sharpie/Sharpie.Bind/Tools.cs b/tools/sharpie/Sharpie.Bind/Tools.cs index fbc0bc23b027..bd8dc79dc146 100644 --- a/tools/sharpie/Sharpie.Bind/Tools.cs +++ b/tools/sharpie/Sharpie.Bind/Tools.cs @@ -45,6 +45,7 @@ public static int Bind (string [] arguments) { "m|massage=", "Register (+ prefix) or exclude (- prefix) a massager by name.", v => binder.AddMassager (v) }, { "nosplit", "Do not split the generated binding into multiple files.", v => binder.SplitDocuments = false }, { "deepsplit", "Split the generated binding into one file per source header.", v => binder.DeepSplit = true }, + { "custom-delegates", "Generate custom named delegates instead of Func<>/Action<>.", v => binder.UseCustomDelegates = true }, }; os.EndOfParsingArguments.Clear (); @@ -114,6 +115,7 @@ public static bool Visit (string [] arguments, Func createV { "clang-resource-dir=", "Specify the Clang resource directory.", v => binder.ClangResourceDirectory = v }, { "platform-assembly=", "Specify the platform assembly to use for binding.", v => binder.PlatformAssembly = v }, { "nosplit", "Do not split the generated binding into multiple files.", v => binder.SplitDocuments = false }, + { "custom-delegates", "Generate custom named delegates instead of Func<>/Action<>.", v => binder.UseCustomDelegates = true }, }; os.EndOfParsingArguments.Clear ();