Skip to content

Commit b4d44e4

Browse files
committed
[Java.Interop] JNIEnv::FindClass() fallback is ClassLoader.loadClass()
Context: xamarin/monodroid@ea292a5 Android is..."special", in that not all threads get the same ClassLoader behavior. Specifically, *managed* threads -- System.Threading.Thread instances -- get a different ClassLoader than the main/UI thread on Android. (Untested, but the ClassLoader *may* behave sanely if you use a java.lang.Thread instance instead. But who wants to use java.lang.Thread instances...?) System-provided types can be found, but *Application*-provided types *can't* be found through this "different" ClassLoader; consequently, JNIEnv::FindClass() throws an exception similar to [0, 1]. The Xamarin.Android solution -- ~copied here -- is to grab the java.lang.ClassLoader instance that the main/UI thread has, and use *that* ClassLoader instance to perform a type lookup when JNIEnv::FindClass() fails. If ClassLoader.loadClass() then fails, throw the exception that JNIEnv::FindClass() threw. There's one problem with ClassLoader.loadClass(): it wants a *Java*-convention name, *not* a JNI convention. Meaning `.`, not `/`! // BAD classLoader.loadClass ("java/lang/Object"); // throws // GOOD classLoader.loadClass ("java.lang.Object"); // throws Consequently, we need to "fixup" the classname value to use `.` instead of `/` before passing to ClassLoader.loadClass(). This is done in the new JniRuntimeInfo.ToJavaName() method, which -- for *overall* memory reduction and GC purposes -- uses a per-thread char[] cache that is reused between calls. This avoids the need to construct temporary StringBuilder and string instances, and should serve to reduce overall GC pressure. (This currently uses a 1KB cache per thread -- 512 chars -- so hopefully it won't be *too* problematic...) Unfortunately, there's no way to test this crazy behavior on the desktop JVM, at least not that I've yet imagined. Consequently, the only test for this behavior is in monodroid/tests/runtime. [0]: Java.Lang.NoClassDefFoundError: md5ec473bf3e64a9dd1c666612e93870662/MyCb ---> Java.Lang.ClassNotFoundException: Didn't find class "md5ec473bf3e64a9dd1c666612e93870662.MyCb" on path: DexPathList[[directory "."],nativeLibraryDirectories=[/system/lib]] [1]: Note that only `.` and `/system/lib` are present. Notably absent are the application directories, e.g. DexPathList[ [zip file "/data/app/Xamarin.Android.RuntimeTests-2.apk", zip file "/data/app/Xamarin.Android.RuntimeTests-2.apk"], nativeLibraryDirectories=[/data/app-lib/Xamarin.Android.RuntimeTests-2, /data/app-lib/Xamarin.Android.RuntimeTests-2, /system/lib]]
1 parent e3481bc commit b4d44e4

File tree

7 files changed

+810
-682
lines changed

7 files changed

+810
-682
lines changed

src/Java.Interop/Java.Interop/JniEnvironment.Types.cs

+66
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,72 @@ static Types ()
2828
}
2929
}
3030

31+
public static unsafe JniObjectReference FindClass (string classname)
32+
{
33+
if (classname == null)
34+
throw new ArgumentNullException ("classname");
35+
if (classname.Length == 0)
36+
throw new ArgumentException ("'classname' cannot be a zero-length string.", nameof (classname));
37+
38+
var info = JniEnvironment.CurrentInfo;
39+
#if FEATURE_JNIENVIRONMENT_JI_PINVOKES
40+
IntPtr thrown;
41+
var c = JavaInterop_FindClass (info.EnvironmentPointer, out thrown, classname);
42+
if (thrown == IntPtr.Zero) {
43+
var r = new JniObjectReference (c, JniObjectReferenceType.Local);
44+
JniEnvironment.LogCreateLocalRef (r);
45+
return r;
46+
}
47+
JniEnvironment.Exceptions.JavaInterop_ExceptionClear (info.EnvironmentPointer);
48+
var e = new JniObjectReference (thrown, JniObjectReferenceType.Local);
49+
LogCreateLocalRef (e);
50+
51+
var java = info.ToJavaName (classname);
52+
var __args = stackalloc JniArgumentValue [1];
53+
__args [0] = new JniArgumentValue (java);
54+
55+
IntPtr ignoreThrown;
56+
c = JniEnvironment.InstanceMethods.JavaInterop_CallObjectMethodA (info.EnvironmentPointer, out ignoreThrown, info.Runtime.ClassLoader.Handle, info.Runtime.ClassLoader_LoadClass.ID, __args);
57+
JniObjectReference.Dispose (ref java);
58+
if (ignoreThrown == IntPtr.Zero) {
59+
JniObjectReference.Dispose (ref e);
60+
var r = new JniObjectReference (c, JniObjectReferenceType.Local);
61+
JniEnvironment.LogCreateLocalRef (r);
62+
return r;
63+
}
64+
JniEnvironment.Exceptions.JavaInterop_ExceptionClear (info.EnvironmentPointer);
65+
JniEnvironment.References.JavaInterop_DeleteLocalRef (info.EnvironmentPointer, ignoreThrown);
66+
throw info.Runtime.GetExceptionForThrowable (ref e, JniObjectReferenceOptions.DisposeSourceReference);
67+
#endif // !FEATURE_JNIENVIRONMENT_JI_PINVOKES
68+
#if FEATURE_JNIOBJECTREFERENCE_SAFEHANDLES
69+
var c = info.Invoker.FindClass (info.EnvironmentPointer, classname);
70+
var thrown = info.Invoker.ExceptionOccurred (info.EnvironmentPointer);
71+
if (thrown.IsInvalid) {
72+
JniEnvironment.LogCreateLocalRef (c);
73+
return new JniObjectReference (c, JniObjectReferenceType.Local);
74+
}
75+
info.Invoker.ExceptionClear (info.EnvironmentPointer);
76+
LogCreateLocalRef (thrown);
77+
78+
var java = info.ToJavaName (classname);
79+
var __args = stackalloc JniArgumentValue [1];
80+
__args [0] = new JniArgumentValue (java);
81+
82+
c = info.Invoker.CallObjectMethodA (info.EnvironmentPointer, info.Runtime.ClassLoader.SafeHandle, info.Runtime.ClassLoader_LoadClass.ID, __args);
83+
JniObjectReference.Dispose (ref java);
84+
var ignoreThrown = info.Invoker.ExceptionOccurred (info.EnvironmentPointer);
85+
if (ignoreThrown.IsInvalid) {
86+
thrown.Dispose ();
87+
JniEnvironment.LogCreateLocalRef (c);
88+
return new JniObjectReference (c, JniObjectReferenceType.Local);
89+
}
90+
info.Invoker.ExceptionClear (info.EnvironmentPointer);
91+
LogCreateLocalRef (ignoreThrown);
92+
ignoreThrown.Dispose ();
93+
var e = new JniObjectReference (thrown, JniObjectReferenceType.Local);
94+
throw info.Runtime.GetExceptionForThrowable (ref e, JniObjectReferenceOptions.DisposeSourceReference);
95+
#endif // !FEATURE_JNIOBJECTREFERENCE_SAFEHANDLES
96+
}
3197

3298
public static JniType GetTypeFromInstance (JniObjectReference reference)
3399
{

src/Java.Interop/Java.Interop/JniEnvironment.cs

+30
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,10 @@ internal static int GetJavaVM (IntPtr jnienv, out IntPtr vm)
168168

169169
class JniEnvironmentInfo {
170170

171+
const int NameBufferLength = 512;
172+
171173
IntPtr environmentPointer;
174+
char[] nameBuffer;
172175

173176
public JniRuntime Runtime {get; private set;}
174177
public int LocalReferenceCount {get; internal set;}
@@ -212,6 +215,33 @@ internal JniEnvironmentInfo (IntPtr environmentPointer, JniRuntime runtime)
212215
Runtime = runtime;
213216
}
214217

218+
internal unsafe JniObjectReference ToJavaName (string jniTypeName)
219+
{
220+
int index = jniTypeName.IndexOf ('/');
221+
222+
if (index == -1)
223+
return JniEnvironment.Strings.NewString (jniTypeName);
224+
225+
int length = jniTypeName.Length;
226+
if (length > NameBufferLength)
227+
return JniEnvironment.Strings.NewString (jniTypeName.Replace ('/', '.'));
228+
229+
if (nameBuffer == null)
230+
nameBuffer = new char [NameBufferLength];
231+
232+
fixed (char* src = jniTypeName, dst = nameBuffer) {
233+
char* src_ptr = src;
234+
char* dst_ptr = dst;
235+
char* end_ptr = src + length;
236+
while (src_ptr < end_ptr) {
237+
*dst_ptr = (*src_ptr == '/') ? '.' : *src_ptr;
238+
src_ptr++;
239+
dst_ptr++;
240+
}
241+
return JniEnvironment.Strings.NewString ((IntPtr) dst, length);
242+
}
243+
}
244+
215245
#if FEATURE_JNIENVIRONMENT_SAFEHANDLES
216246
internal List<List<JniLocalReference>> LocalReferences = new List<List<JniLocalReference>> () {
217247
new List<JniLocalReference> (),

src/Java.Interop/Java.Interop/JniRuntime.cs

+31
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ public class CreationOptions {
5353
public IntPtr InvocationPointer {get; set;}
5454
public IntPtr EnvironmentPointer {get; set;}
5555

56+
public JniObjectReference ClassLoader {get; set;}
57+
public IntPtr ClassLoader_LoadClass_id {get; set;}
58+
5659
public JniObjectReferenceManager ObjectReferenceManager {get; set;}
5760
public JniTypeManager TypeManager {get; set;}
5861

@@ -119,6 +122,9 @@ public static void SetCurrent (JniRuntime newCurrent)
119122
JavaVMInterface Invoker;
120123
bool DestroyRuntimeOnDispose;
121124

125+
internal JniObjectReference ClassLoader;
126+
internal JniInstanceMethodInfo ClassLoader_LoadClass;
127+
122128
public IntPtr InvocationPointer {get; private set;}
123129

124130
internal bool TrackIDs {get; private set;}
@@ -155,6 +161,29 @@ protected JniRuntime (CreationOptions options)
155161
#if !XA_INTEGRATION
156162
ManagedPeer.Init ();
157163
#endif // !XA_INTEGRATION
164+
165+
ClassLoader = options.ClassLoader;
166+
if (options.ClassLoader_LoadClass_id != IntPtr.Zero) {
167+
ClassLoader_LoadClass = new JniInstanceMethodInfo (options.ClassLoader_LoadClass_id);
168+
}
169+
170+
if (ClassLoader.IsValid) {
171+
ClassLoader = ClassLoader.NewGlobalRef ();
172+
}
173+
174+
if (!ClassLoader.IsValid || ClassLoader_LoadClass == null) {
175+
using (var t = new JniType ("java/lang/ClassLoader")) {
176+
if (!ClassLoader.IsValid) {
177+
var m = t.GetStaticMethod ("getSystemClassLoader", "()Ljava/lang/ClassLoader;");
178+
var loader = m.InvokeObjectMethod (t.PeerReference);
179+
ClassLoader = loader.NewGlobalRef ();
180+
JniObjectReference.Dispose (ref loader);
181+
}
182+
if (ClassLoader_LoadClass == null) {
183+
ClassLoader_LoadClass = t.GetInstanceMethod ("loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
184+
}
185+
}
186+
}
158187
}
159188

160189
T SetRuntime<T> (T value)
@@ -200,6 +229,8 @@ protected virtual void Dispose (bool disposing)
200229
if (current == this)
201230
current = null;
202231

232+
JniObjectReference.Dispose (ref ClassLoader);
233+
203234
#if !XA_INTEGRATION
204235
foreach (var o in RegisteredInstances.Values) {
205236
var t = (IDisposable) o.Target;

0 commit comments

Comments
 (0)