Skip to content

Commit d5b2ece

Browse files
radekdoulikjonpryor
authored andcommitted
[Android.Runtime] Use JniTypeManager to register natives (#1550)
The new code uses `JniTypeManager`'s `RegisterNativeMembers()` method to register native marshaling methods. The type manager itself uses the register method generated by the `jnimarshalmethod-gen.exe` tool or method(s) on the given type, which have the `[Java.Interop.JniAddNativeMethodRegistrationAttribute]` custom attribute. When no such `__<$>_jni_marshal_methods.__RegisterNativeMembers()` method exists, we fallback to the original (old) code. Example register class generated by `jnimarshalmethod-gen.exe`: partial class MainActivity { partial class '__<$>_jni_marshal_methods' { [JniAddNativeMethodRegistration] public static void __RegisterNativeMembers (JniNativeMethodRegistrationArguments args) { args.AddRegistrations (new JniNativeMethodRegistration[] { new JniNativeMethodRegistration ( "n_onCreate", "(Landroid/os/Bundle;)V", new Action<IntPtr, IntPtr, IntPtr> (MainActivity.__<$>_jni_marshal_methods.n_onCreate_Landroid_os_Bundle_)) }); } public static void n_onCreate_Landroid_os_Bundle_ (IntPtr __jnienv, IntPtr __this, IntPtr bundle) { var __envp = new JniTransition (__this); JniRuntime __jvm; try { __jvm = JniEnvironment.Runtime; __jvm.ValueManager.WaitForGCBridgeProcessing(); var __this_val = __jvm.ValueManager.GetValue<MainActivity>(__this); var __bundle_val = __jvm.ValueManager.GetValue<Bundle>(bundle); __this_val.OnCreate (__bundle_val); } catch (Exception __e) if (__jvm.ExceptionShouldTransitionToJni(__e)) { __envp.SetPendingException (__e); } finally { __envp.Dispose (); } } } } Note that `jnimarshalmethod-gen.exe` will generate at *packaging-time* what is currently done at *runtime* within `JNINativeWrapper` using `System.Reflection.Emit`. This avoids the need to use `System.Reflection.Emit` during process startup, so long as the type contains `jnimarshalmethod-gen.exe`-generated types. Note that `AndroidValueManager` needs to override `PeekPeer()`, as the `__jvm.ValueManager.GetValue<T>()` invocation uses `AndroidValueManager.PeekPeer()` as part of its operation: android.runtime.JavaProxyThrowable: System.NotImplementedException: The method or operation is not implemented. at Android.Runtime.AndroidValueManager.PeekPeer (Java.Interop.JniObjectReference reference) [0x00001] at Java.Interop.JniRuntime+JniValueManager.PeekValue (Java.Interop.JniObjectReference reference) [0x0002f] at Java.Interop.JniRuntime+JniValueManager.GetValue[T] (Java.Interop.JniObjectReference& reference, Java.Interop.JniObjectReferenceOptions options, System.Type targetType) at Java.Interop.JniRuntime+JniValueManager.GetValue[T] (System.IntPtr handle) [0x0000a] at xatemplateaot.MainActivity+__<$>_jni_marshal_methods.n_onCreate_Landroid_os_Bundle_ (System.IntPtr __jnienv, System.IntPtr __this, System.IntPtr savedInstanceState) [0x0001e] at md58018c7d08c228c8e2a3e03723c59ca27.MainActivity.n_onCreate(Native Method) at md58018c7d08c228c8e2a3e03723c59ca27.MainActivity.onCreate(MainActivity.java:29)
1 parent efe81f5 commit d5b2ece

File tree

2 files changed

+67
-59
lines changed

2 files changed

+67
-59
lines changed

src/Mono.Android/Android.Runtime/AndroidRuntime.cs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
using System.Runtime.InteropServices;
66
using System.Text;
77
using System.Threading;
8+
using System.Reflection;
89

910
using Java.Interop;
11+
using Java.Interop.Tools.TypeNameMappings;
1012

1113
#if JAVA_INTEROP
1214
namespace Android.Runtime {
@@ -225,6 +227,62 @@ protected override IEnumerable<string> GetSimpleReferences (Type type)
225227
return base.GetSimpleReferences (type)
226228
.Concat (Enumerable.Repeat (j, 1));
227229
}
230+
231+
delegate Delegate GetCallbackHandler ();
232+
233+
static MethodInfo dynamic_callback_gen;
234+
235+
static Delegate CreateDynamicCallback (MethodInfo method)
236+
{
237+
if (dynamic_callback_gen == null) {
238+
var assembly = Assembly.Load ("Mono.Android.Export");
239+
if (assembly == null)
240+
throw new InvalidOperationException ("To use methods marked with ExportAttribute, Mono.Android.Export.dll needs to be referenced in the application");
241+
var type = assembly.GetType ("Java.Interop.DynamicCallbackCodeGenerator");
242+
if (type == null)
243+
throw new InvalidOperationException ("The referenced Mono.Android.Export.dll does not match the expected version. The required type was not found.");
244+
dynamic_callback_gen = type.GetMethod ("Create");
245+
if (dynamic_callback_gen == null)
246+
throw new InvalidOperationException ("The referenced Mono.Android.Export.dll does not match the expected version. The required method was not found.");
247+
}
248+
return (Delegate)dynamic_callback_gen.Invoke (null, new object [] { method });
249+
}
250+
251+
public override void RegisterNativeMembers (JniType jniType, Type type, string methods)
252+
{
253+
if (base.TryRegisterNativeMembers (jniType, type, methods))
254+
return;
255+
256+
if (methods == null)
257+
return;
258+
259+
string[] members = methods.Split ('\n');
260+
if (members.Length == 0)
261+
return;
262+
263+
JniNativeMethodRegistration[] natives = new JniNativeMethodRegistration [members.Length-1];
264+
for (int i = 0; i < members.Length; ++i) {
265+
string method = members [i];
266+
if (string.IsNullOrEmpty (method))
267+
continue;
268+
string[] toks = members [i].Split (new[]{':'}, 4);
269+
Delegate callback;
270+
if (toks [2] == "__export__") {
271+
var mname = toks [0].Substring (2);
272+
var minfo = type.GetMethods (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static).Where (m => m.Name == mname && JavaNativeTypeManager.GetJniSignature (m) == toks [1]).FirstOrDefault ();
273+
if (minfo == null)
274+
throw new InvalidOperationException (String.Format ("Specified managed method '{0}' was not found. Signature: {1}", mname, toks [1]));
275+
callback = CreateDynamicCallback (minfo);
276+
} else {
277+
GetCallbackHandler connector = (GetCallbackHandler) Delegate.CreateDelegate (typeof (GetCallbackHandler),
278+
toks.Length == 4 ? Type.GetType (toks [3], true) : type, toks [2]);
279+
callback = connector ();
280+
}
281+
natives [i] = new JniNativeMethodRegistration (toks [0], toks [1], callback);
282+
}
283+
284+
JniEnvironment.Types.RegisterNatives (jniType.PeerReference, natives, natives.Length);
285+
}
228286
}
229287

230288
class AndroidValueManager : JniRuntime.JniValueManager {
@@ -246,7 +304,7 @@ public override void RemovePeer (IJavaPeerable value)
246304

247305
public override IJavaPeerable PeekPeer (JniObjectReference reference)
248306
{
249-
throw new NotImplementedException ();
307+
return (IJavaPeerable) Java.Lang.Object.GetObject (reference.Handle, JniHandleOwnership.DoNotTransfer);
250308
}
251309

252310
public override void CollectPeers ()

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

Lines changed: 8 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,6 @@
1313
using Java.Interop;
1414
using Java.Interop.Tools.TypeNameMappings;
1515

16-
#if JAVA_INTEROP
17-
using JniNativeMethod = Java.Interop.JniNativeMethodRegistration;
18-
#else
19-
using JniNativeMethod = Android.Runtime.JNIEnv.JNINativeMethod;
20-
#endif // JAVA_INTEROP
21-
2216
namespace Android.Runtime {
2317

2418
struct JnienvInitializeArgs {
@@ -61,6 +55,8 @@ public static partial class JNIEnv {
6155

6256
internal static bool IsRunningOnDesktop;
6357

58+
static AndroidRuntime androidRuntime;
59+
6460
#if !JAVA_INTEROP
6561
static JNIInvokeInterface invoke_iface;
6662

@@ -116,26 +112,6 @@ public static void CheckHandle (IntPtr jnienv)
116112
[DllImport ("libc")]
117113
static extern int gettid ();
118114

119-
delegate Delegate GetCallbackHandler ();
120-
121-
static MethodInfo dynamic_callback_gen;
122-
123-
static Delegate CreateDynamicCallback (MethodInfo method)
124-
{
125-
if (dynamic_callback_gen == null) {
126-
var assembly = Assembly.Load ("Mono.Android.Export");
127-
if (assembly == null)
128-
throw new InvalidOperationException ("To use methods marked with ExportAttribute, Mono.Android.Export.dll needs to be referenced in the application");
129-
var type = assembly.GetType ("Java.Interop.DynamicCallbackCodeGenerator");
130-
if (type == null)
131-
throw new InvalidOperationException ("The referenced Mono.Android.Export.dll does not match the expected version. The required type was not found.");
132-
dynamic_callback_gen = type.GetMethod ("Create");
133-
if (dynamic_callback_gen == null)
134-
throw new InvalidOperationException ("The referenced Mono.Android.Export.dll does not match the expected version. The required method was not found.");
135-
}
136-
return (Delegate) dynamic_callback_gen.Invoke (null, new object [] {method});
137-
}
138-
139115
static unsafe void RegisterJniNatives (IntPtr typeName_ptr, int typeName_len, IntPtr jniClass, IntPtr methods_ptr, int methods_len)
140116
{
141117
string typeName = new string ((char*) typeName_ptr, 0, typeName_len);
@@ -156,39 +132,13 @@ static unsafe void RegisterJniNatives (IntPtr typeName_ptr, int typeName_len, In
156132
return;
157133
}
158134

159-
TypeManager.RegisterType (Java.Interop.TypeManager.GetClassName (jniClass), type);
135+
var className = Java.Interop.TypeManager.GetClassName (jniClass);
136+
TypeManager.RegisterType (className, type);
160137

161-
string[] methods = new string ((char*) methods_ptr, 0, methods_len).Split ('\n');
162-
if (methods.Length == 0)
163-
return;
138+
JniType jniType = null;
139+
JniType.GetCachedJniType (ref jniType, className);
164140

165-
JniNativeMethod[] natives = new JniNativeMethod [methods.Length-1];
166-
for (int i = 0; i < methods.Length; ++i) {
167-
string method = methods [i];
168-
if (string.IsNullOrEmpty (method))
169-
continue;
170-
string[] toks = methods [i].Split (new[]{':'}, 4);
171-
Delegate callback;
172-
if (toks [2] == "__export__") {
173-
var mname = toks [0].Substring (2);
174-
var minfo = type.GetMethods (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static).Where (m => m.Name == mname && JavaNativeTypeManager.GetJniSignature (m) == toks [1]).FirstOrDefault ();
175-
if (minfo == null)
176-
throw new InvalidOperationException (String.Format ("Specified managed method '{0}' was not found. Signature: {1}", mname, toks [1]));
177-
callback = CreateDynamicCallback (minfo);
178-
} else {
179-
GetCallbackHandler connector = (GetCallbackHandler) Delegate.CreateDelegate (typeof (GetCallbackHandler),
180-
toks.Length == 4 ? Type.GetType (toks [3], true) : type, toks [2]);
181-
callback = connector ();
182-
}
183-
natives [i] = new JniNativeMethod (toks [0], toks [1], callback);
184-
}
185-
186-
#if JAVA_INTEROP
187-
JniEnvironment.Types.RegisterNatives (new JniObjectReference (jniClass), natives, natives.Length);
188-
#else // !JAVA_INTEROP
189-
if (Env.RegisterNatives (Handle, jniClass, natives, natives.Length) != 0)
190-
AndroidEnvironment.FailFast ("Unable to register JNI native methods for type: " + typeName);
191-
#endif // !JAVA_INTEROP
141+
androidRuntime.TypeManager.RegisterNativeMembers (jniType, type, methods_ptr == IntPtr.Zero ? null : new string ((char*) methods_ptr, 0, methods_len));
192142

193143
if (Logger.LogTiming) {
194144
var __end = DateTime.UtcNow;
@@ -241,7 +191,7 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
241191
IdentityHash = v => v;
242192

243193
#if JAVA_INTEROP
244-
new AndroidRuntime (args->env, args->javaVm, androidSdkVersion > 10, args->grefLoader, args->Loader_loadClass);
194+
androidRuntime = new AndroidRuntime (args->env, args->javaVm, androidSdkVersion > 10, args->grefLoader, args->Loader_loadClass);
245195
#endif // JAVA_INTEROP
246196

247197
if (Logger.LogTiming) {

0 commit comments

Comments
 (0)