Skip to content

Commit 3a8d509

Browse files
[TrimmableTypeMap] Runtime trimmable typemap support
Rebuild the remaining runtime-only portion of PR 11090 on top of main. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent f7080fe commit 3a8d509

File tree

6 files changed

+186
-22
lines changed

6 files changed

+186
-22
lines changed

src/Mono.Android/Android.Runtime/JNIEnv.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,12 @@ static unsafe IntPtr monovm_typemap_managed_to_java (Type type, byte* mvidptr)
442442

443443
internal static unsafe string? TypemapManagedToJava (Type type)
444444
{
445+
if (RuntimeFeature.TrimmableTypeMap) {
446+
// The trimmable typemap doesn't use the native typemap tables.
447+
// Delegate to the managed TrimmableTypeMap instead.
448+
return TrimmableTypeMap.Instance.TryGetJniName (type, out var jniName) ? jniName : null;
449+
}
450+
445451
if (mvid_bytes == null)
446452
mvid_bytes = new byte[16];
447453

src/Mono.Android/Android.Runtime/JNIEnvInit.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ internal static void InitializeJniRuntimeEarly (JnienvInitializeArgs args)
110110
internal static void InitializeJniRuntime (JniRuntime runtime, JnienvInitializeArgs args)
111111
{
112112
androidRuntime = runtime;
113+
JniRuntime.SetCurrent (runtime);
113114
SetSynchronizationContext ();
114115
}
115116

@@ -159,6 +160,7 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
159160
valueManager,
160161
args->jniAddNativeMethodRegistrationAttributePresent != 0
161162
);
163+
JniRuntime.SetCurrent (androidRuntime);
162164

163165
if (RuntimeFeature.TrimmableTypeMap) {
164166
TrimmableTypeMap.Initialize ();

src/Mono.Android/Java.Interop/TypeManager.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,13 @@ static Type monovm_typemap_java_to_managed (string java_type_name)
268268
return type;
269269
}
270270

271-
if (RuntimeFeature.IsMonoRuntime) {
271+
if (RuntimeFeature.TrimmableTypeMap) {
272+
// The trimmable typemap doesn't use the native typemap tables.
273+
// Delegate to the managed TrimmableTypeMap instead.
274+
if (!TrimmableTypeMap.Instance.TryGetType (class_name, out type)) {
275+
return null;
276+
}
277+
} else if (RuntimeFeature.IsMonoRuntime) {
272278
type = monovm_typemap_java_to_managed (class_name);
273279
} else if (RuntimeFeature.IsCoreClrRuntime) {
274280
type = clr_typemap_java_to_managed (class_name);

src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -509,16 +509,11 @@ void ProcessContext (HandleContext* context)
509509
{
510510
if (RuntimeFeature.TrimmableTypeMap) {
511511
var typeMap = TrimmableTypeMap.Instance;
512-
if (typeMap is not null && targetType is not null) {
513-
var proxy = typeMap.GetProxyForManagedType (targetType);
514-
if (proxy is not null) {
515-
var peer = proxy.CreateInstance (reference.Handle, JniHandleOwnership.DoNotTransfer);
516-
if (peer is not null) {
517-
peer.SetJniManagedPeerState (peer.JniManagedPeerState | JniManagedPeerStates.Replaceable);
518-
JniObjectReference.Dispose (ref reference, transfer);
519-
return peer;
520-
}
521-
}
512+
var peer = typeMap.CreatePeer (reference.Handle, JniHandleOwnership.DoNotTransfer, targetType);
513+
if (peer is not null) {
514+
peer.SetJniManagedPeerState (peer.JniManagedPeerState | JniManagedPeerStates.Replaceable);
515+
JniObjectReference.Dispose (ref reference, transfer);
516+
return peer;
522517
}
523518
}
524519

src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs

Lines changed: 164 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Threading;
1010
using Android.Runtime;
1111
using Java.Interop;
12+
using Java.Interop.Tools.TypeNameMappings;
1213

1314
namespace Microsoft.Android.Runtime;
1415

@@ -29,6 +30,8 @@ class TrimmableTypeMap
2930
readonly IReadOnlyDictionary<string, Type> _typeMap;
3031
readonly IReadOnlyDictionary<Type, Type> _proxyTypeMap;
3132
readonly ConcurrentDictionary<Type, JavaPeerProxy?> _proxyCache = new ();
33+
readonly ConcurrentDictionary<Type, string?> _jniNameCache = new ();
34+
readonly ConcurrentDictionary<string, JavaPeerProxy?> _peerProxyCache = new (StringComparer.Ordinal);
3235

3336
TrimmableTypeMap ()
3437
{
@@ -69,7 +72,19 @@ unsafe void RegisterNatives ()
6972
}
7073

7174
internal bool TryGetType (string jniSimpleReference, [NotNullWhen (true)] out Type? type)
72-
=> _typeMap.TryGetValue (jniSimpleReference, out type);
75+
{
76+
if (!_typeMap.TryGetValue (jniSimpleReference, out var mappedType)) {
77+
type = null;
78+
return false;
79+
}
80+
81+
// External typemap entries usually point at the generated proxy for ACW-backed types.
82+
// Surface the real managed peer when possible so activation and virtual dispatch land
83+
// on the user's override instead of the generated proxy type.
84+
var proxy = mappedType.GetCustomAttribute<JavaPeerProxy> (inherit: false);
85+
type = proxy?.TargetType ?? mappedType;
86+
return true;
87+
}
7388

7489
/// <summary>
7590
/// Finds the proxy for a managed type using the generated proxy type map.
@@ -87,30 +102,170 @@ internal bool TryGetType (string jniSimpleReference, [NotNullWhen (true)] out Ty
87102
return proxyType.GetCustomAttribute<JavaPeerProxy> (inherit: false);
88103
}
89104

90-
internal bool TryGetJniNameForManagedType (Type managedType, [NotNullWhen (true)] out string? jniName)
105+
internal bool TryGetJniName (Type type, [NotNullWhen (true)] out string? jniName)
91106
{
92-
var proxy = GetProxyForManagedType (managedType);
107+
if (_jniNameCache.TryGetValue (type, out jniName)) {
108+
return jniName != null;
109+
}
110+
111+
var proxy = GetProxyForManagedType (type);
93112
if (proxy is not null) {
94113
jniName = proxy.JniName;
114+
_jniNameCache [type] = jniName;
115+
return true;
116+
}
117+
118+
if (TryGetJniNameForType (type, out jniName)) {
119+
_jniNameCache [type] = jniName;
95120
return true;
96121
}
97122

123+
if (TryGetCompatJniNameForAndroidComponent (type, out jniName)) {
124+
_jniNameCache [type] = jniName;
125+
return true;
126+
}
127+
128+
// Prefer the JavaNativeTypeManager calculation for user/application types,
129+
// as it matches the ACW generation rules used during the build.
130+
if (typeof (IJavaPeerable).IsAssignableFrom (type)) {
131+
jniName = JavaNativeTypeManager.ToJniName (type);
132+
if (!string.IsNullOrEmpty (jniName) && jniName != "java/lang/Object") {
133+
_jniNameCache [type] = jniName;
134+
return true;
135+
}
136+
}
137+
98138
jniName = null;
139+
_jniNameCache [type] = null;
99140
return false;
100141
}
101142

143+
internal bool TryGetJniNameForManagedType (Type managedType, [NotNullWhen (true)] out string? jniName)
144+
=> TryGetJniName (managedType, out jniName);
145+
146+
internal JavaPeerProxy? GetProxyForPeer (IntPtr handle, Type? targetType = null)
147+
{
148+
if (handle == IntPtr.Zero) {
149+
return null;
150+
}
151+
152+
var selfRef = new JniObjectReference (handle);
153+
var jniClass = JniEnvironment.Types.GetObjectClass (selfRef);
154+
155+
try {
156+
while (jniClass.IsValid) {
157+
var className = JniEnvironment.Types.GetJniTypeNameFromClass (jniClass);
158+
if (className != null) {
159+
if (_peerProxyCache.TryGetValue (className, out var cached)) {
160+
if (cached != null && (targetType is null || targetType.IsAssignableFrom (cached.TargetType))) {
161+
return cached;
162+
}
163+
} else if (_typeMap.TryGetValue (className, out var mappedType)) {
164+
var proxy = mappedType.GetCustomAttribute<JavaPeerProxy> (inherit: false);
165+
_peerProxyCache [className] = proxy;
166+
if (proxy != null && (targetType is null || targetType.IsAssignableFrom (proxy.TargetType))) {
167+
return proxy;
168+
}
169+
}
170+
}
171+
172+
var super = JniEnvironment.Types.GetSuperclass (jniClass);
173+
JniObjectReference.Dispose (ref jniClass);
174+
jniClass = super;
175+
}
176+
} finally {
177+
JniObjectReference.Dispose (ref jniClass);
178+
}
179+
180+
return null;
181+
}
182+
183+
internal IJavaPeerable? CreatePeer (IntPtr handle, JniHandleOwnership transfer, Type? targetType = null)
184+
{
185+
var proxy = GetProxyForPeer (handle, targetType);
186+
if (proxy is null && targetType is not null) {
187+
proxy = GetProxyForManagedType (targetType);
188+
// Verify the Java object is actually assignable to the target Java type
189+
// before creating the peer. Without this, we'd create invalid peers
190+
// (e.g., IAppendableInvoker wrapping a java.lang.Integer).
191+
if (proxy is not null && TryGetJniName (targetType, out var targetJniName)) {
192+
var selfRef = new JniObjectReference (handle);
193+
var objClass = JniEnvironment.Types.GetObjectClass (selfRef);
194+
var targetClass = JniEnvironment.Types.FindClass (targetJniName);
195+
try {
196+
if (!JniEnvironment.Types.IsAssignableFrom (objClass, targetClass)) {
197+
proxy = null;
198+
}
199+
} finally {
200+
JniObjectReference.Dispose (ref objClass);
201+
JniObjectReference.Dispose (ref targetClass);
202+
}
203+
}
204+
}
205+
206+
return proxy?.CreateInstance (handle, transfer);
207+
}
208+
209+
static bool TryGetCompatJniNameForAndroidComponent (Type type, [NotNullWhen (true)] out string? jniName)
210+
{
211+
if (!IsAndroidComponentType (type)) {
212+
jniName = null;
213+
return false;
214+
}
215+
216+
var (typeName, parentJniName, ns) = GetCompatTypeNameParts (type);
217+
jniName = parentJniName is not null
218+
? $"{parentJniName}_{typeName}"
219+
: ns.Length == 0
220+
? typeName
221+
: $"{ns.ToLowerInvariant ().Replace ('.', '/')}/{typeName}";
222+
return true;
223+
}
224+
225+
static bool IsAndroidComponentType (Type type)
226+
{
227+
return type.IsDefined (typeof (global::Android.App.ActivityAttribute), inherit: false) ||
228+
type.IsDefined (typeof (global::Android.App.ApplicationAttribute), inherit: false) ||
229+
type.IsDefined (typeof (global::Android.App.InstrumentationAttribute), inherit: false) ||
230+
type.IsDefined (typeof (global::Android.App.ServiceAttribute), inherit: false) ||
231+
type.IsDefined (typeof (global::Android.Content.BroadcastReceiverAttribute), inherit: false) ||
232+
type.IsDefined (typeof (global::Android.Content.ContentProviderAttribute), inherit: false);
233+
}
234+
235+
static (string TypeName, string? ParentJniName, string Namespace) GetCompatTypeNameParts (Type type)
236+
{
237+
var nameParts = new List<string> { SanitizeTypeName (type.Name) };
238+
var current = type;
239+
string? parentJniName = null;
240+
241+
while (current.DeclaringType is Type parentType) {
242+
if (TryGetJniNameForType (parentType, out var explicitJniName) ||
243+
TryGetCompatJniNameForAndroidComponent (parentType, out explicitJniName)) {
244+
parentJniName = explicitJniName;
245+
break;
246+
}
247+
248+
nameParts.Add (SanitizeTypeName (parentType.Name));
249+
current = parentType;
250+
}
251+
252+
nameParts.Reverse ();
253+
return (string.Join ("_", nameParts), parentJniName, current.Namespace ?? "");
254+
}
255+
256+
static string SanitizeTypeName (string name)
257+
{
258+
var tick = name.IndexOf ('`');
259+
return (tick >= 0 ? name.Substring (0, tick) : name).Replace ('+', '_');
260+
}
261+
102262
/// <summary>
103263
/// Creates a peer instance using the proxy's CreateInstance method.
104264
/// Given a managed type, resolves the JNI name, finds the proxy, and calls CreateInstance.
105265
/// </summary>
106266
internal bool TryCreatePeer (Type type, IntPtr handle, JniHandleOwnership transfer)
107267
{
108-
var proxy = GetProxyForManagedType (type);
109-
if (proxy is null) {
110-
return false;
111-
}
112-
113-
return proxy.CreateInstance (handle, transfer) != null;
268+
return CreatePeer (handle, transfer, type) != null;
114269
}
115270

116271
const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;

src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapTypeManager.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ protected override IEnumerable<Type> GetTypesForSimpleReference (string jniSimpl
2828

2929
protected override IEnumerable<string> GetSimpleReferences (Type type)
3030
{
31-
if (TrimmableTypeMap.Instance.TryGetJniNameForManagedType (type, out var jniName)) {
31+
if (TrimmableTypeMap.Instance.TryGetJniName (type, out var jniName)) {
3232
yield return jniName;
3333
yield break;
3434
}
@@ -40,7 +40,7 @@ protected override IEnumerable<string> GetSimpleReferences (Type type)
4040
// Walk the base type chain for managed-only subclasses (e.g., JavaProxyThrowable
4141
// extends Java.Lang.Error but has no [Register] attribute itself).
4242
for (var baseType = type.BaseType; baseType is not null; baseType = baseType.BaseType) {
43-
if (TrimmableTypeMap.Instance.TryGetJniNameForManagedType (baseType, out var baseJniName)) {
43+
if (TrimmableTypeMap.Instance.TryGetJniName (baseType, out var baseJniName)) {
4444
yield return baseJniName;
4545
yield break;
4646
}

0 commit comments

Comments
 (0)