Skip to content

[RGen] Add support for the use of Callbacks/Trampolines in properties. #23114

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 21, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ internal static (ExpressionSyntax Send, ExpressionSyntax SendSuper) GetGetterInv
{ BindAs.Type.FullyQualifiedName: "Foundation.NSString", ReturnType.IsSmartEnum: true} =>
SmartEnumGetValue (property.ReturnType, [Argument (objMsgSend)]),

// block callback
// global::ObjCRuntime.Trampolines.{TrampolineNativeClass}.Create (ret)!;
{ ReturnType.IsDelegate: true }
=> TrampolineNativeInvocationClassCreate (property.ReturnType, [Argument (objMsgSend)]),

// string[]? => CFArray.StringArrayFromHandle (global::ObjCRuntime.Messaging.NativeHandle_objc_msgSend (class_ptr, Selector.GetHandle ("selector")), false);
{ ReturnType.IsArray: true, ReturnType.Name: "string", ReturnType.IsNullable: true } =>
StringArrayFromHandle ([Argument (objMsgSend), BoolArgument (false)]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1470,4 +1470,36 @@ internal static StatementSyntax CallNativeInvokerDelegate (in DelegateInfo deleg
return LocalDeclarationStatement (declaration);
}

/// <summary>
/// Generates an expression to create an instance of a native invocation class for a given trampoline type.
/// This is used to create a native block from a C# delegate. The generated expression calls the static `Create`
/// method on the appropriate `NativeInvocationClass` within the `ObjCRuntime.Trampolines` namespace.
/// </summary>
/// <param name="trampolineType">The <see cref="TypeInfo"/> of the delegate for which to create the native invocation class.</param>
/// <param name="arguments">The arguments to pass to the `Create` method.</param>
/// <returns>An <see cref="ExpressionSyntax"/> representing the call to create the native invocation class instance.</returns>
internal static ExpressionSyntax TrampolineNativeInvocationClassCreate (in TypeInfo trampolineType, ImmutableArray<ArgumentSyntax> arguments)
{
var argumentList = ArgumentList (
SeparatedList<ArgumentSyntax> (arguments.ToSyntaxNodeOrTokenArray ()));
// get the name of the native class to be used to call the create method
var className =
Nomenclator.GetTrampolineClassName (trampolineType, Nomenclator.TrampolineClassType.NativeInvocationClass);

// generate the needed invocation expression for the Create method with the passed arguments
var invocation = InvocationExpression (
MemberAccessExpression (
SyntaxKind.SimpleMemberAccessExpression,
MemberAccessExpression (
SyntaxKind.SimpleMemberAccessExpression,
Trampolines,
IdentifierName (className)),
IdentifierName ("Create").WithTrailingTrivia (Space))).
WithArgumentList (argumentList);

// null ignore
return PostfixUnaryExpression (
SyntaxKind.SuppressNullableWarningExpression,
invocation);
}
}
8 changes: 8 additions & 0 deletions src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@ void EmitProperties (in BindingContext context, TabbedWriter<StringWriter> class
using (var propertyBlock = classBlock.CreateBlock (property.ToDeclaration ().ToString (), block: true)) {
// be very verbose with the availability, makes the life easier to the dotnet analyzer
propertyBlock.AppendMemberAvailability (getter.Value.SymbolAvailability);
// if we deal with a delegate, include the attr:
// [return: DelegateProxy (typeof ({staticBridge}))]
if (property.ReturnType.IsDelegate)
propertyBlock.AppendDelegateProxyReturn (property.ReturnType);
using (var getterBlock = propertyBlock.CreateBlock ("get", block: true)) {
if (uiThreadCheck is not null) {
getterBlock.WriteLine (uiThreadCheck.ToString ());
Expand Down Expand Up @@ -200,6 +204,10 @@ void EmitProperties (in BindingContext context, TabbedWriter<StringWriter> class

propertyBlock.WriteLine (); // add space between getter and setter since we have the attrs
propertyBlock.AppendMemberAvailability (setter.Value.SymbolAvailability);
// if we deal with a delegate, include the attr:
// [param: BlockProxy (typeof ({nativeInvoker}))]
if (property.ReturnType.IsDelegate)
propertyBlock.AppendDelegateParameter (property.ReturnType);
using (var setterBlock = propertyBlock.CreateBlock ("set", block: true)) {
if (uiThreadCheck is not null) {
setterBlock.WriteLine (uiThreadCheck.ToString ());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

using System.ComponentModel;
using System.IO;
using Microsoft.Macios.Generator.DataModel;
using static Microsoft.Macios.Generator.Emitters.BindingSyntaxFactory;

namespace Microsoft.Macios.Generator.IO;

Expand Down Expand Up @@ -81,4 +83,38 @@ public static TabbedWriter<StringWriter> WriteHeader (this TabbedWriter<StringWr
self.WriteLine ();
return self;
}

/// <summary>
/// Appends a `[return: DelegateProxy]` attribute to the current writer.
/// This attribute is used for properties that return a delegate, and it points to the static bridge class
/// generated for the specified delegate type.
/// </summary>
/// <param name="self">A tabbed string writer.</param>
/// <param name="typeInfo">The <see cref="TypeInfo"/> of the delegate.</param>
/// <returns>The current writer.</returns>
public static TabbedWriter<StringWriter> AppendDelegateProxyReturn (this TabbedWriter<StringWriter> self,
in TypeInfo typeInfo)
{
var staticBridge =
Nomenclator.GetTrampolineClassName (typeInfo, Nomenclator.TrampolineClassType.StaticBridgeClass);
self.WriteLine ($"[return: DelegateProxy (typeof ({Trampolines}.{staticBridge}))]");
return self;
}

/// <summary>
/// Appends a `[param: BlockProxy]` attribute to the current writer.
/// This attribute is used for parameters that are delegates (blocks), and it points to the native invocation class
/// generated for the specified delegate type.
/// </summary>
/// <param name="self">A tabbed string writer.</param>
/// <param name="typeInfo">The <see cref="TypeInfo"/> of the delegate.</param>
/// <returns>The current writer.</returns>
public static TabbedWriter<StringWriter> AppendDelegateParameter (this TabbedWriter<StringWriter> self,
in TypeInfo typeInfo)
{
var nativeInvoker =
Nomenclator.GetTrampolineClassName (typeInfo, Nomenclator.TrampolineClassType.NativeInvocationClass);
self.WriteLine ($"[param: BlockProxy (typeof ({Trampolines}.{nativeInvoker}))]");
return self;
}
}
10 changes: 10 additions & 0 deletions src/rgen/Microsoft.Macios.Generator/Nomenclator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,16 @@ public static string GetTrampolineClassName (string trampolineName, TrampolineCl
};
}

/// <summary>
/// Return the name of the trampoline class to be used for the given type info.
/// This is a convenience overload for <see cref="GetTrampolineClassName(string, TrampolineClassType)"/>.
/// </summary>
/// <param name="typeInfo">The type info for which to get the trampoline class name.</param>
/// <param name="trampolineClassType">The type of class to be generated.</param>
/// <returns>The name to be used by the generated class.</returns>
public static string GetTrampolineClassName (in TypeInfo typeInfo, TrampolineClassType trampolineClassType)
=> GetTrampolineClassName (GetTrampolineName (typeInfo), trampolineClassType);

/// <summary>
/// Returns the name of the aux variable that would have needed for the given parameter. Use the
/// variable type to name it.
Expand Down
Loading
Loading