Skip to content

Commit

Permalink
[Rgen] Add the needed code to make the objc_msgSend signatures. (#22029)
Browse files Browse the repository at this point in the history
Add the code that will calculate the signature needed in the
objc_msgSend which will be the method that calls the native objc
message.

## Extra context

When we create a binding for a native type we need to be able to send
'messages' to the objc objects. From a C# developer perspective a
message can be considered a method. So for example, the following objc
code:
```objc
[object foo:bar];
```
Can be translated to: 
```c
objc_msgSend(object, sel_getUid("foo:"), bar);
```
We can say this process is similar to reflection and the[ Invoke
method](https://learn.microsoft.com/en-us/dotnet/api/system.reflection.methodbase.invoke?view=net-9.0).
Our bindings provide methods around
[objc_msgsend](https://developer.apple.com/documentation/objectivec/1456712-objc_msgsend)
which we use to send the messaged with the arguments needed by the
message. For example, a property with a NSString backing fields defined
as:
```csharp
[Export ("text")]
public string? Text { get; set; }
```
Results in the following generated code:
```csharp
[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
public virtual string? Text {
	[Export ("text")]
	get {
		global::UIKit.UIApplication.EnsureUIThread ();
		if (IsDirectBinding) {
			return CFString.FromHandle (global::ObjCRuntime.Messaging.NativeHandle_objc_msgSend (this.Handle, Selector.GetHandle ("text")), false)!;
		} else {
			return CFString.FromHandle (global::ObjCRuntime.Messaging.NativeHandle_objc_msgSendSuper (this.SuperHandle, Selector.GetHandle ("text")), false)!;
		}
	}
	[Export ("setText:")]
	set {
		global::UIKit.UIApplication.EnsureUIThread ();
		var nsvalue = CFString.CreateNative (value);
		if (IsDirectBinding) {
			global::ObjCRuntime.Messaging.void_objc_msgSend_NativeHandle (this.Handle, Selector.GetHandle ("setText:"), nsvalue);
		} else {
			global::ObjCRuntime.Messaging.void_objc_msgSendSuper_NativeHandle (this.SuperHandle, Selector.GetHandle ("setText:"), nsvalue);
		}
		CFString.ReleaseNative (nsvalue);
	}
}
```
Our objc_msgSend method signatures are calculated based on the method
parameters and live in the runtime. These objc_msgSend calls are
generated by
https://github.com/xamarin/xamarin-macios/blob/main/runtime/bindings-generator.cs#L20
For example a objc_msgSend signature for a method that takes a string
and returns a string would be:
```c
IntPtr_objc_msgSend_IntPtr
```
But this is a simplifications because there is a diff between sending a
message to a super class (prefix super) or if it deals with a structure
(stret).

More info can be found in the Apple
[documentation](https://developer.apple.com/documentation/objectivec/1456712-objc_msgsend).

---------

Co-authored-by: GitHub Actions Autoformatter <[email protected]>
Co-authored-by: Copilot <[email protected]>
  • Loading branch information
3 people authored Jan 23, 2025
1 parent e62a38f commit d52838b
Show file tree
Hide file tree
Showing 18 changed files with 937 additions and 41 deletions.
37 changes: 37 additions & 0 deletions src/rgen/Microsoft.Macios.Generator/Attributes/MarshalDirective.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;

namespace Microsoft.Macios.Generator.Attributes;

record CustomMarshalDirective (string? NativePrefix, string? NativeSuffix, string? Library);

static class ExportDataExtensions {

public static bool ShouldMarshalNativeExceptions<T> (this ExportData<T> self) where T : Enum
=> self switch {
ExportData<ObjCBindings.Property> property => property.Flags.HasFlag (ObjCBindings.Property
.CustomMarshalDirective),
ExportData<ObjCBindings.Method> method => method.Flags.HasFlag (
ObjCBindings.Property.CustomMarshalDirective),
_ => false,
};

public static CustomMarshalDirective? ToCustomMarshalDirective<T> (this ExportData<T> self) where T : Enum
{
var present = self switch {
ExportData<ObjCBindings.Property> property => property.Flags.HasFlag (ObjCBindings.Property
.CustomMarshalDirective),
ExportData<ObjCBindings.Method> method => method.Flags.HasFlag (
ObjCBindings.Property.CustomMarshalDirective),
_ => false,
};
if (present) {
return new (self.NativePrefix, self.NativeSuffix, self.Library);
}

return null;
}

}
12 changes: 11 additions & 1 deletion src/rgen/Microsoft.Macios.Generator/DataModel/Property.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,18 @@ namespace Microsoft.Macios.Generator.DataModel;

public string BackingField { get; private init; }

readonly TypeInfo returnType;

/// <summary>
/// Representation of the property type.
/// </summary>
public TypeInfo ReturnType { get; } = default;
public TypeInfo ReturnType {
get => returnType;
private init {
returnType = value;
ValueParameter = new Parameter (0, returnType, "value");
}
}

/// <summary>
/// Returns if the property type is bittable.
Expand Down Expand Up @@ -62,6 +70,8 @@ namespace Microsoft.Macios.Generator.DataModel;
/// </summary>
public ImmutableArray<Accessor> Accessors { get; } = [];

public Parameter ValueParameter { get; private init; }

public Accessor? GetAccessor (AccessorKind accessorKind)
{
// careful, do not use FirstOrDefault from LINQ because we are using structs!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ symbol is IArrayTypeSymbol arrayTypeSymbol
IsSmartEnum = symbol.IsSmartEnum ();
IsArray = symbol is IArrayTypeSymbol;
IsReferenceType = symbol.IsReferenceType;
IsStruct = symbol.TypeKind == TypeKind.Struct;
IsInterface = symbol.TypeKind == TypeKind.Interface;
IsNativeIntegerType = symbol.IsNativeIntegerType;
IsNativeEnum = symbol.HasAttribute (AttributesNames.NativeEnumAttribute);
Expand Down
85 changes: 78 additions & 7 deletions src/rgen/Microsoft.Macios.Generator/DataModel/TypeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System.Diagnostics.CodeAnalysis;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.Macios.Generator.Attributes;
using Microsoft.Macios.Generator.Extensions;

namespace Microsoft.Macios.Generator.DataModel;

Expand All @@ -13,10 +15,24 @@ namespace Microsoft.Macios.Generator.DataModel;
/// </summary>
readonly partial struct TypeInfo : IEquatable<TypeInfo> {

public static TypeInfo Void = new ("void", SpecialType.System_Void) { Parents = ["System.ValueType", "object"], };

readonly string fullyQualifiedName = string.Empty;
/// <summary>
/// Type of the parameter.
/// The fully qualified name of the type.
/// </summary>
public string Name { get; }
public string FullyQualifiedName {
get => fullyQualifiedName;
init {
fullyQualifiedName = value;
var index = fullyQualifiedName.LastIndexOf ('.');
Name = index != -1
? fullyQualifiedName.Substring (index + 1)
: fullyQualifiedName;
}
}

public string Name { get; private init; } = string.Empty;

/// <summary>
/// The metadata name of the type. This is normally the same as name except
Expand Down Expand Up @@ -65,6 +81,11 @@ namespace Microsoft.Macios.Generator.DataModel;
/// </summary>
public bool IsReferenceType { get; }

/// <summary>
/// Returns if the type is a struct.
/// </summary>
public bool IsStruct { get; }

/// <summary>
/// Returns if the return type is void.
/// </summary>
Expand Down Expand Up @@ -116,7 +137,7 @@ public ImmutableArray<string> Interfaces {

internal TypeInfo (string name, SpecialType specialType)
{
Name = name;
FullyQualifiedName = name;
SpecialType = specialType;
}

Expand All @@ -126,19 +147,21 @@ internal TypeInfo (string name,
bool isBlittable = false,
bool isSmartEnum = false,
bool isArray = false,
bool isReferenceType = false) : this (name, specialType)
bool isReferenceType = false,
bool isStruct = false) : this (name, specialType)
{
IsNullable = isNullable;
IsBlittable = isBlittable;
IsSmartEnum = isSmartEnum;
IsArray = isArray;
IsReferenceType = isReferenceType;
IsStruct = isStruct;
}

/// <inheritdoc/>
public bool Equals (TypeInfo other)
{
if (Name != other.Name)
if (FullyQualifiedName != other.FullyQualifiedName)
return false;
if (SpecialType != other.SpecialType)
return false;
Expand All @@ -154,6 +177,8 @@ public bool Equals (TypeInfo other)
return false;
if (IsReferenceType != other.IsReferenceType)
return false;
if (IsStruct != other.IsStruct)
return false;
if (IsVoid != other.IsVoid)
return false;
if (EnumUnderlyingType != other.EnumUnderlyingType)
Expand Down Expand Up @@ -184,7 +209,7 @@ public override bool Equals (object? obj)
/// <inheritdoc/>
public override int GetHashCode ()
{
return HashCode.Combine (Name, IsNullable, IsBlittable, IsSmartEnum, IsArray, IsReferenceType, IsVoid);
return HashCode.Combine (FullyQualifiedName, IsNullable, IsBlittable, IsSmartEnum, IsArray, IsReferenceType, IsVoid);
}

public static bool operator == (TypeInfo left, TypeInfo right)
Expand All @@ -197,18 +222,64 @@ public override int GetHashCode ()
return !left.Equals (right);
}

const string NativeHandle = "NativeHandle";
const string IntPtr = "IntPtr";
const string UIntPtr = "UIntPtr";

public string? ToMarshallType (ReferenceKind referenceKind)
{
#pragma warning disable format
var type = this switch {
// special cases based on name
{ Name: "nfloat" or "NFloat" } => "nfloat",
{ Name: "nint" or "nuint" } => MetadataName,
// special string case
{ SpecialType: SpecialType.System_String } => NativeHandle, // use a NSString when we get a string

// NSObject should use the native handle
{ IsNSObject: true } => NativeHandle,
{ IsINativeObject: true } => NativeHandle,

// structs will use their name
{ IsStruct: true, SpecialType: SpecialType.System_Double } => "Double",
{ IsStruct: true } => Name,

// enums:
// IsSmartEnum: We are using a nsstring, so it should be a native handle.
// IsNativeEnum: Depends on the enum backing field kind.
// GeneralEnum: Depends on the EnumUnderlyingType

{ IsSmartEnum: true } => NativeHandle,
{ IsNativeEnum: true, EnumUnderlyingType: SpecialType.System_Int64 } => IntPtr,
{ IsNativeEnum: true, EnumUnderlyingType: SpecialType.System_UInt64 } => UIntPtr,
{ IsEnum: true, EnumUnderlyingType: not null } => EnumUnderlyingType.GetKeyword (),

// special type that is a keyword (none would be a ref type)
{ SpecialType: SpecialType.System_Void } => SpecialType.GetKeyword (),

// This should not happen in bindings because all of the types should either be native objects
// nsobjects, or structs
{ IsReferenceType: false } => Name,

_ => null,
};
#pragma warning restore format
return type;
}

/// <inheritdoc/>
public override string ToString ()
{
var sb = new StringBuilder ("{");
sb.Append ($"Name: '{Name}', ");
sb.Append ($"Name: '{FullyQualifiedName}', ");
sb.Append ($"MetadataName: '{MetadataName}', ");
sb.Append ($"SpecialType: '{SpecialType}', ");
sb.Append ($"IsNullable: {IsNullable}, ");
sb.Append ($"IsBlittable: {IsBlittable}, ");
sb.Append ($"IsSmartEnum: {IsSmartEnum}, ");
sb.Append ($"IsArray: {IsArray}, ");
sb.Append ($"IsReferenceType: {IsReferenceType}, ");
sb.Append ($"IsStruct: {IsStruct}, ");
sb.Append ($"IsVoid : {IsVoid}, ");
sb.Append ($"IsNSObject : {IsNSObject}, ");
sb.Append ($"IsNativeObject: {IsINativeObject}, ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class TypeInfoComparer : IComparer<TypeInfo> {
/// <inheritdoc/>
public int Compare (TypeInfo x, TypeInfo y)
{
var returnTypeComparison = String.Compare (x.Name, y.Name, StringComparison.Ordinal);
var returnTypeComparison = String.Compare (x.FullyQualifiedName, y.FullyQualifiedName, StringComparison.Ordinal);
if (returnTypeComparison != 0)
return returnTypeComparison;
var isNullableComparison = x.IsNullable.CompareTo (y.IsNullable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ public static (Func<string, string, CompilationUnitSyntax> Getter,
if (!property.IsField)
throw new NotSupportedException ("Cannot retrieve getter for non field property.");

var fieldType = property.ReturnType.Name;
var fieldType = property.ReturnType.FullyQualifiedName;
var underlyingEnumType = property.ReturnType.EnumUnderlyingType.GetKeyword ();

Func<string, string, CompilationUnitSyntax> WrapGenericCall (string genericType,
Expand All @@ -621,28 +621,28 @@ Func<string, string, string, CompilationUnitSyntax> WithCast (Func<string, strin
#pragma warning disable format
if (property.ReturnType.IsNSObject) {
return property.ReturnType switch {
{ Name: "Foundation.NSString" } => (Getter: GetStringConstant, Setter: SetString),
{ Name: "Foundation.NSArray" } => (
Getter: WrapGenericCall (property.ReturnType.Name, GetNSObjectField),
{ FullyQualifiedName: "Foundation.NSString" } => (Getter: GetStringConstant, Setter: SetString),
{ FullyQualifiedName: "Foundation.NSArray" } => (
Getter: WrapGenericCall (property.ReturnType.FullyQualifiedName, GetNSObjectField),
Setter: SetArray),
_ => (
Getter: WrapGenericCall (property.ReturnType.Name, GetNSObjectField),
Getter: WrapGenericCall (property.ReturnType.FullyQualifiedName, GetNSObjectField),
Setter: SetObject)
};
}

// use the return type and the special type of the property to decide what getter we are going to us
return property.ReturnType switch {
// special types
{ Name: "CoreGraphics.CGSize" } => (Getter: GetCGSize, Setter: SetCGSize),
{ Name: "CoreMedia.CMTag" } => (
{ FullyQualifiedName: "CoreGraphics.CGSize" } => (Getter: GetCGSize, Setter: SetCGSize),
{ FullyQualifiedName: "CoreMedia.CMTag" } => (
Getter: WrapGenericCall ("CoreMedia.CMTag", GetStruct),
Setter: WrapThrow ()),
{ Name: "nfloat" } => (Getter: GetNFloat, Setter: SetNFloat),
{ FullyQualifiedName: "nfloat" } => (Getter: GetNFloat, Setter: SetNFloat),

// Blittable types
{ Name: "CoreMedia.CMTime" or "AVFoundation.AVCaptureWhiteBalanceGains" }
=> (Getter: WrapGenericCall (property.ReturnType.Name, GetBlittableField),
{ FullyQualifiedName: "CoreMedia.CMTime" or "AVFoundation.AVCaptureWhiteBalanceGains" }
=> (Getter: WrapGenericCall (property.ReturnType.FullyQualifiedName, GetBlittableField),
Setter: WrapThrow ()),

// enum types, decide based on its enum backing field, smart enums have to be done in the binding
Expand Down
Loading

0 comments on commit d52838b

Please sign in to comment.