Skip to content
Open
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 @@ -324,12 +324,14 @@ internal RegisterInfo ParseJniTypeSignatureAttribute (CustomAttribute ca)
}

var isArrayType = TryGetNamedArgument<int> (value, "ArrayRank", out var rank) && rank > 0;
TryGetNamedArgument<string> (value, "InvokerType", out var invokerTypeName);

return new RegisterInfo {
JniName = jniName.Replace ('.', '/'),
DoNotGenerateAcw = doNotGenerateAcw,
IsFromJniTypeSignature = true,
IsArrayType = isArrayType,
InvokerTypeName = invokerTypeName,
};
}

Expand Down Expand Up @@ -669,6 +671,7 @@ sealed record RegisterInfo
public bool DoNotGenerateAcw { get; init; }
public bool IsFromJniTypeSignature { get; init; }
public bool IsArrayType { get; init; }
public string? InvokerTypeName { get; init; }
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ void ScanAssembly (AssemblyIndex index, Dictionary<(string ManagedName, string A
// Keep ActivationCtor scoped to the target/base hierarchy for legacy parity,
// and store the invoker ctor style separately for CreateInstance emission.
if (invokerTypeName is not null) {
invokerActivationCtorStyle = TryResolveActivationCtorOnInvoker (invokerTypeName)?.Style;
invokerActivationCtorStyle = TryResolveActivationCtorOnInvoker (invokerTypeName, index)?.Style;
}

var peer = new JavaPeerInfo {
Expand Down Expand Up @@ -2082,19 +2082,20 @@ string ManagedTypeToJniDescriptor (TypeRefData managedType, ExportParameterKindI

string? TryFindInvokerTypeName (string typeName, TypeDefinitionHandle typeHandle, AssemblyIndex index)
{
if (index.RegisterInfoByType.TryGetValue (typeHandle, out var registerInfo)) {
var explicitInvokerTypeName = registerInfo.InvokerTypeName;
if (explicitInvokerTypeName is { Length: > 0 }) {
return TryGetSameAssemblyTypeName (explicitInvokerTypeName, index.AssemblyName);
}
}

// First, check the [Register] attribute's connector arg (3rd arg).
// In real Mono.Android, interfaces have [Register("jni/name", "", "InvokerTypeName, Assembly")]
// where the connector contains the assembly-qualified invoker type name.
if (index.RegisterInfoByType.TryGetValue (typeHandle, out var registerInfo) && registerInfo.Connector is not null) {
if (registerInfo is not null && registerInfo.Connector is not null) {
var connector = registerInfo.Connector;
// The connector may be "TypeName" or "TypeName, Assembly, Version=..., Culture=..., PublicKeyToken=..."
// We want just the type name (before the first comma, if any)
var commaIndex = connector.IndexOf (',');
if (commaIndex > 0) {
return NormalizeConnectorManagedTypeName (connector.Substring (0, commaIndex));
}
if (connector.Length > 0) {
return NormalizeConnectorManagedTypeName (connector);
return TryGetSameAssemblyTypeName (connector, index.AssemblyName);
}
}

Expand All @@ -2106,27 +2107,36 @@ string ManagedTypeToJniDescriptor (TypeRefData managedType, ExportParameterKindI
return null;
}

static string? TryGetSameAssemblyTypeName (string value, string defaultAssemblyName)
{
var commaIndex = value.IndexOf (',');
if (commaIndex < 0) {
return NormalizeConnectorManagedTypeName (value);
}

var typeName = NormalizeConnectorManagedTypeName (value.Substring (0, commaIndex));
var remainder = value.Substring (commaIndex + 1).Trim ();
var nextCommaIndex = remainder.IndexOf (',');
var assemblyName = nextCommaIndex < 0 ? remainder : remainder.Substring (0, nextCommaIndex).Trim ();
return string.Equals (assemblyName, defaultAssemblyName, StringComparison.Ordinal) ? typeName : null;
}

static string NormalizeConnectorManagedTypeName (string managedTypeName)
{
return managedTypeName.Trim ().Replace ('/', '+');
}

/// <summary>
/// Resolve the activation ctor on a known invoker type (search all loaded assemblies).
/// Resolve the activation ctor on a known invoker type in its target assembly.
/// Used for interface peers, whose own type definition has no constructors.
/// The assemblyCache typically contains 10–30 entries (app + framework assemblies),
/// and each lookup is an O(1) dictionary probe, so the linear scan is cheap.
/// </summary>
ActivationCtorInfo? TryResolveActivationCtorOnInvoker (string invokerTypeName)
ActivationCtorInfo? TryResolveActivationCtorOnInvoker (string invokerTypeName, AssemblyIndex index)
{
foreach (var assembly in assemblyCache.Values) {
if (!assembly.TypesByFullName.TryGetValue (invokerTypeName, out var invokerHandle)) {
continue;
}
var invokerDef = assembly.Reader.GetTypeDefinition (invokerHandle);
return ResolveActivationCtor (invokerTypeName, invokerDef, assembly);
if (!index.TypesByFullName.TryGetValue (invokerTypeName, out var invokerHandle)) {
return null;
}
return null;
var invokerDef = index.Reader.GetTypeDefinition (invokerHandle);
return ResolveActivationCtor (invokerTypeName, invokerDef, index);
}

public void Dispose ()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,11 @@ internal bool TryGetJniNameForManagedType (Type managedType, [NotNullWhen (true)
return null;
}

if (targetType is { IsInterface: true } || targetType is { IsAbstract: true }) {
return TryGetProxyFromTargetType (handle, targetType) ??
TryGetProxyFromHierarchy (handle, targetType);
}

return TryGetProxyFromHierarchy (handle, targetType) ??
TryGetProxyFromTargetType (handle, targetType);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,27 @@ public void Scan_InvokerAndInterface_ShareJavaName ()
Assert.Contains (clickListenerPeers, p => p.DoNotGenerateAcw);
}

[Theory]
[InlineData ("MyApp.IExplicitJavaInteropCollection", "MyApp.ExplicitJavaInteropCollectionProxy")]
[InlineData ("MyApp.IInheritedJavaInteropList", "MyApp.InheritedJavaInteropListProxy")]
[InlineData ("MyApp.AbstractJavaInteropList", "MyApp.AbstractJavaInteropListProxy")]
public void Scan_JniTypeSignatureExplicitInvoker_UsesJavaInteropConstructor (string targetType, string invokerType)
{
var peer = FindFixtureByManagedName (targetType);

Assert.Equal (invokerType, peer.InvokerTypeName);
Assert.Equal (ActivationCtorStyle.JavaInterop, peer.InvokerActivationCtorStyle);
}

[Fact]
public void Scan_JniTypeSignatureCrossAssemblyInvoker_IsIgnored ()
{
var peer = FindFixtureByManagedName ("MyApp.IUnsupportedExternalInvoker");

Assert.Null (peer.InvokerTypeName);
Assert.Null (peer.InvokerActivationCtorStyle);
}

[Fact]
public void Scan_AllTypes_HaveAssemblyName ()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using Java.Interop;

namespace MyApp;

[JniTypeSignature ("java/util/Collection", GenerateJavaPeer = false, InvokerType = typeof (ExplicitJavaInteropCollectionProxy))]
public interface IExplicitJavaInteropCollection
{
}

public sealed class ExplicitJavaInteropCollectionProxy : Java.Lang.Object
{
public ExplicitJavaInteropCollectionProxy (ref JniObjectReference reference, JniObjectReferenceOptions options)
: base ((System.IntPtr) 0, Android.Runtime.JniHandleOwnership.DoNotTransfer)
{
}
}

[JniTypeSignature ("java/util/List", GenerateJavaPeer = false, InvokerType = typeof (InheritedJavaInteropListProxy))]
public interface IInheritedJavaInteropList : IExplicitJavaInteropCollection
{
}

public sealed class InheritedJavaInteropListProxy : Java.Lang.Object
{
public InheritedJavaInteropListProxy (ref JniObjectReference reference, JniObjectReferenceOptions options)
: base ((System.IntPtr) 0, Android.Runtime.JniHandleOwnership.DoNotTransfer)
{
}
}

[JniTypeSignature ("java/util/AbstractList", GenerateJavaPeer = false, InvokerType = typeof (AbstractJavaInteropListProxy))]
public abstract class AbstractJavaInteropList : Java.Lang.Object
{
protected AbstractJavaInteropList (ref JniObjectReference reference, JniObjectReferenceOptions options)
: base ((System.IntPtr) 0, Android.Runtime.JniHandleOwnership.DoNotTransfer)
{
}
}

public sealed class AbstractJavaInteropListProxy : AbstractJavaInteropList
{
public AbstractJavaInteropListProxy (ref JniObjectReference reference, JniObjectReferenceOptions options)
: base (ref reference, options)
{
}
}

[JniTypeSignature ("java/lang/UnsupportedExternalInvoker", GenerateJavaPeer = false, InvokerType = typeof (string))]
public interface IUnsupportedExternalInvoker
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -224,13 +224,14 @@ public sealed class JniConstructorSignatureAttribute : Attribute

namespace Java.Interop
{
[AttributeUsage (AttributeTargets.Class, AllowMultiple = false)]
[AttributeUsage (AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = false)]
public sealed class JniTypeSignatureAttribute : Attribute
{
public string SimpleReference { get; }
public bool GenerateJavaPeer { get; set; } = true;
public bool IsKeyword { get; set; }
public int ArrayRank { get; set; }
public Type? InvokerType { get; set; }

public JniTypeSignatureAttribute (string simpleReference) => SimpleReference = simpleReference;
}
Expand Down
Loading
Loading