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
46 changes: 29 additions & 17 deletions src/Mono.Android/Java.Interop/TypeManager.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
Expand Down Expand Up @@ -407,8 +408,7 @@ static Type monovm_typemap_java_to_managed (string java_type_name)

static readonly Type[] XAConstructorSignature = new Type [] { typeof (IntPtr), typeof (JniHandleOwnership) };
static readonly Type[] JIConstructorSignature = new Type [] { typeof (JniObjectReference).MakeByRefType (), typeof (JniObjectReferenceOptions) };
static readonly Dictionary<Type, ActivationConstructor> ActivationConstructorCache = new Dictionary<Type, ActivationConstructor> ();
static readonly Lock ActivationConstructorCacheLock = new Lock ();
static readonly ConcurrentDictionary<Type, ActivationConstructor> ActivationConstructorCache = new ConcurrentDictionary<Type, ActivationConstructor> (1, 3);

enum ActivationConstructorKind
{
Expand All @@ -419,6 +419,23 @@ enum ActivationConstructorKind

readonly record struct ActivationConstructor (ConstructorInfo? Constructor, ActivationConstructorKind Kind);

/// <summary>
/// Preserves constructor annotations through the stateful <c>GetOrAdd</c> factory,
/// whose <c>Func</c> key parameter cannot carry <see cref="DynamicallyAccessedMembersAttribute"/>.
/// </summary>
readonly struct AnnotatedType
{
public AnnotatedType (
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
Type type)
{
Type = type;
}

[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
public Type Type { get; }
}

internal static object CreateProxy (
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
Type type,
Expand Down Expand Up @@ -452,23 +469,18 @@ static ActivationConstructor GetActivationConstructor (
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
Type type)
{
lock (ActivationConstructorCacheLock) {
if (ActivationConstructorCache.TryGetValue (type, out var activation))
return activation;

const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
var constructor = type.GetConstructor (flags, null, XAConstructorSignature, null);
if (constructor != null) {
activation = new ActivationConstructor (constructor, ActivationConstructorKind.XA);
} else {
constructor = type.GetConstructor (flags, null, JIConstructorSignature, null);
activation = new ActivationConstructor (
return ActivationConstructorCache.GetOrAdd (type,
static (_, state) => {
const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
var constructor = state.Type.GetConstructor (flags, null, XAConstructorSignature, null);
if (constructor != null)
return new ActivationConstructor (constructor, ActivationConstructorKind.XA);

constructor = state.Type.GetConstructor (flags, null, JIConstructorSignature, null);
return new ActivationConstructor (
constructor,
constructor == null ? ActivationConstructorKind.Missing : ActivationConstructorKind.JI);
}
ActivationConstructorCache.Add (type, activation);
return activation;
}
}, new AnnotatedType (type));
}

static IJavaPeerable GetUninitializedObject (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using Android.Runtime;
using BenchmarkDotNet.Attributes;

namespace Xamarin.Android.Benchmarks;

public class ActivationConstructorCacheBenchmarks
{
delegate object CreateProxyDelegate (Type type, IntPtr handle, JniHandleOwnership transfer);

readonly CreateProxyDelegate createProxy;
IntPtr globalReference;

public ActivationConstructorCacheBenchmarks ()
{
const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Static;
var method = typeof (Java.Interop.TypeManager).GetMethod (
"CreateProxy",
flags,
null,
[typeof (Type), typeof (IntPtr), typeof (JniHandleOwnership)],
null);
if (method == null)
throw new InvalidOperationException ("Could not find TypeManager.CreateProxy.");

createProxy = method.CreateDelegate<CreateProxyDelegate> ();
}

[GlobalSetup]
public void Setup ()
{
IntPtr reference = JNIEnv.NewString ("benchmark");
try {
globalReference = JNIEnv.NewGlobalRef (reference);
} finally {
JNIEnv.DeleteLocalRef (reference);
}
_ = CreateProxy ();
}

[GlobalCleanup]
public void Cleanup ()
{
JNIEnv.DeleteGlobalRef (globalReference);
}

[Benchmark]
public int CreateProxy ()
{
IntPtr reference = JNIEnv.NewLocalRef (globalReference);
Java.Lang.String? proxy = null;
try {
proxy = (Java.Lang.String) createProxy (typeof (Java.Lang.String), reference, JniHandleOwnership.TransferLocalRef);
reference = IntPtr.Zero;
return RuntimeHelpers.GetHashCode (proxy);
} finally {
if (reference != IntPtr.Zero)
JNIEnv.DeleteLocalRef (reference);
proxy?.Dispose ();
}
}
}
Loading