-
Notifications
You must be signed in to change notification settings - Fork 569
[TrimmableTypeMap] Runtime-only trimmable typemap support #11090
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
Changes from 19 commits
3a8d509
d4152df
5d16a12
656ddcf
5a96a71
aa33e56
47089ad
c4b567c
9c5672b
4a68393
2897a08
7a7c186
48102b6
530b2b2
0bad300
f626700
79ddbfb
cb32b91
af4a2df
6df784c
b9f3b1b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ namespace Microsoft.Android.Runtime; | |
| class TrimmableTypeMap | ||
| { | ||
| static readonly Lock s_initLock = new (); | ||
| static readonly JavaPeerProxy s_noPeerSentinel = new MissingJavaPeerProxy (); | ||
| static TrimmableTypeMap? s_instance; | ||
|
|
||
| internal static TrimmableTypeMap Instance => | ||
|
|
@@ -28,7 +29,8 @@ class TrimmableTypeMap | |
|
|
||
| readonly IReadOnlyDictionary<string, Type> _typeMap; | ||
| readonly IReadOnlyDictionary<Type, Type> _proxyTypeMap; | ||
| readonly ConcurrentDictionary<Type, JavaPeerProxy?> _proxyCache = new (); | ||
| readonly ConcurrentDictionary<Type, JavaPeerProxy> _proxyCache = new (); | ||
| readonly ConcurrentDictionary<string, JavaPeerProxy> _peerProxyCache = new (StringComparer.Ordinal); | ||
|
|
||
| TrimmableTypeMap () | ||
| { | ||
|
|
@@ -55,9 +57,6 @@ internal static void Initialize () | |
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Registers the <c>mono.android.Runtime.registerNatives</c> JNI native method. | ||
| /// </summary> | ||
| unsafe void RegisterNatives () | ||
| { | ||
| using var runtimeClass = new JniType ("mono/android/Runtime"u8); | ||
|
|
@@ -68,49 +67,116 @@ unsafe void RegisterNatives () | |
| } | ||
| } | ||
|
|
||
| internal bool TryGetType (string jniSimpleReference, [NotNullWhen (true)] out Type? type) | ||
| => _typeMap.TryGetValue (jniSimpleReference, out type); | ||
| internal bool TryGetTargetType (string jniSimpleReference, [NotNullWhen (true)] out Type? type) | ||
| { | ||
| if (!_typeMap.TryGetValue (jniSimpleReference, out var mappedType)) { | ||
| type = null; | ||
| return false; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Finds the proxy for a managed type using the generated proxy type map. | ||
| /// Results are cached per type. | ||
| /// </summary> | ||
| internal JavaPeerProxy? GetProxyForManagedType (Type managedType) | ||
| => _proxyCache.GetOrAdd (managedType, static (type, self) => self.ResolveProxyForManagedType (type), this); | ||
| var proxy = mappedType.GetCustomAttribute<JavaPeerProxy> (inherit: false); | ||
| if (proxy is null) { | ||
| // Alias typemap entries (for example "jni/name[1]") are not implemented yet. | ||
| // Support for them will be added in a follow-up for https://github.com/dotnet/android/issues/10788. | ||
| throw new NotImplementedException ( | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤖 ❌ API design — Rule: API design
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks — this is intentional for now. We are still in the middle of the trimmable typemap rollout, and alias handling is explicitly being deferred to the follow-up in #11103. I would prefer to keep that work out of this PR rather than expand the scope further. |
||
| $"Trimmable typemap alias handling is not implemented yet for '{jniSimpleReference}'."); | ||
| } | ||
|
|
||
| type = proxy.TargetType; | ||
| return true; | ||
| } | ||
|
|
||
| JavaPeerProxy? ResolveProxyForManagedType (Type managedType) | ||
| JavaPeerProxy? GetProxyForManagedType (Type managedType) | ||
| { | ||
| if (!_proxyTypeMap.TryGetValue (managedType, out var proxyType)) { | ||
| return null; | ||
| } | ||
| var proxy = _proxyCache.GetOrAdd (managedType, static (type, self) => { | ||
| if (!self._proxyTypeMap.TryGetValue (type, out var proxyType)) { | ||
| return s_noPeerSentinel; | ||
| } | ||
|
|
||
| return proxyType.GetCustomAttribute<JavaPeerProxy> (inherit: false); | ||
| return proxyType.GetCustomAttribute<JavaPeerProxy> (inherit: false) ?? s_noPeerSentinel; | ||
| }, this); | ||
| return ReferenceEquals (proxy, s_noPeerSentinel) ? null : proxy; | ||
| } | ||
|
|
||
| internal bool TryGetJniNameForManagedType (Type managedType, [NotNullWhen (true)] out string? jniName) | ||
| JavaPeerProxy? GetProxyForJavaType (string className) | ||
| { | ||
| var proxy = GetProxyForManagedType (managedType); | ||
| if (proxy is not null) { | ||
| jniName = proxy.JniName; | ||
| return true; | ||
| } | ||
| var proxy = _peerProxyCache.GetOrAdd (className, static (name, self) => { | ||
| if (!self.TryGetTargetType (name, out var managedType)) { | ||
| return s_noPeerSentinel; | ||
| } | ||
|
|
||
| jniName = null; | ||
| return false; | ||
| return self.GetProxyForManagedType (managedType) ?? s_noPeerSentinel; | ||
| }, this); | ||
| return ReferenceEquals (proxy, s_noPeerSentinel) ? null : proxy; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Creates a peer instance using the proxy's CreateInstance method. | ||
| /// Given a managed type, resolves the JNI name, finds the proxy, and calls CreateInstance. | ||
| /// </summary> | ||
| internal bool TryCreatePeer (Type type, IntPtr handle, JniHandleOwnership transfer) | ||
| internal bool TryGetJniNameForManagedType (Type managedType, [NotNullWhen (true)] out string? jniName) | ||
| { | ||
| var proxy = GetProxyForManagedType (type); | ||
| if (proxy is null) { | ||
| return false; | ||
| jniName = GetProxyForManagedType (managedType)?.JniName; | ||
| return jniName is not null; | ||
| } | ||
|
simonrozsival marked this conversation as resolved.
|
||
|
|
||
| internal JavaPeerProxy? GetProxyForJavaObject (IntPtr handle, Type? targetType = null) | ||
| { | ||
| if (handle == IntPtr.Zero) { | ||
| return null; | ||
| } | ||
|
|
||
| return proxy.CreateInstance (handle, transfer) != null; | ||
| return TryGetProxyFromHierarchy (this, handle, targetType) ?? | ||
| TryGetProxyFromTargetType (this, handle, targetType); | ||
|
|
||
| static JavaPeerProxy? TryGetProxyFromHierarchy (TrimmableTypeMap self, IntPtr handle, Type? targetType) | ||
| { | ||
| var selfRef = new JniObjectReference (handle); | ||
| var jniClass = JniEnvironment.Types.GetObjectClass (selfRef); | ||
|
|
||
| try { | ||
| while (jniClass.IsValid) { | ||
| var className = JniEnvironment.Types.GetJniTypeNameFromClass (jniClass); | ||
| if (className != null) { | ||
| var proxy = self.GetProxyForJavaType (className); | ||
| if (proxy != null && (targetType is null || targetType.IsAssignableFrom (proxy.TargetType))) { | ||
| return proxy; | ||
| } | ||
| } | ||
|
|
||
| var super = JniEnvironment.Types.GetSuperclass (jniClass); | ||
| JniObjectReference.Dispose (ref jniClass); | ||
| jniClass = super; | ||
| } | ||
| } finally { | ||
| JniObjectReference.Dispose (ref jniClass); | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| static JavaPeerProxy? TryGetProxyFromTargetType (TrimmableTypeMap self, IntPtr handle, Type? targetType) | ||
| { | ||
| if (targetType is null) { | ||
| return null; | ||
| } | ||
|
|
||
| var proxy = self.GetProxyForManagedType (targetType); | ||
| // Verify the Java object is actually assignable to the target Java type | ||
| // before returning the fallback proxy. Without this, we'd create invalid peers | ||
| // (e.g., IAppendableInvoker wrapping a java.lang.Integer). | ||
| if (proxy is null || !self.TryGetJniNameForManagedType (targetType, out var targetJniName)) { | ||
| return null; | ||
| } | ||
|
|
||
| var selfRef = new JniObjectReference (handle); | ||
| var objClass = default (JniObjectReference); | ||
| var targetClass = default (JniObjectReference); | ||
| try { | ||
| objClass = JniEnvironment.Types.GetObjectClass (selfRef); | ||
| targetClass = JniEnvironment.Types.FindClass (targetJniName); | ||
| return JniEnvironment.Types.IsAssignableFrom (objClass, targetClass) ? proxy : null; | ||
| } finally { | ||
| JniObjectReference.Dispose (ref objClass); | ||
| JniObjectReference.Dispose (ref targetClass); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; | ||
|
|
@@ -162,4 +228,13 @@ static void OnRegisterNatives (IntPtr jnienv, IntPtr klass, IntPtr nativeClassHa | |
| } | ||
| } | ||
|
|
||
| sealed class MissingJavaPeerProxy : JavaPeerProxy | ||
| { | ||
| public MissingJavaPeerProxy () : base ("<missing>", typeof (Java.Lang.Object), null) | ||
| { | ||
| } | ||
|
|
||
| public override IJavaPeerable? CreateInstance (IntPtr handle, JniHandleOwnership transfer) => null; | ||
| } | ||
|
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.