Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Java.Base-Tests] Test Java-to-Managed invocations for Java.Base (#975)
Commit bc5bcf4 (Desktop `Java.Base` binding) had a TODO: > ~~ TODO: Marshal Methods ~~ > > Marshal methods are currently skipped. Java-to-managed invocations > are not currently supported. No marshal methods means no facility for Java-to-managed invocations. Add a new `tests/Java.Base-Tests` unit test assembly, and test Java-to-managed invocations. This requires touching and updating ~everything. 😅 Update `Java.Interop.dll` to contain a new `Java.Interop.JniMethodSignatureAttribute` custom attribute. Update `generator` to begin emitting `[JniMethodSignature]` on bound methods. This is necessary so that `jcw-gen` knows the JNI method signature of Java methods to emit. As part of this, *remove* the `JniTypeSignatureAttr` type added in bc5bcf4; trying to follow the `JniTypeSignatureAttr` pattern for `JniMethodSignatureAttr` would make for more intrusive code changes. Instead, "re-use" the existing `RegisterAttr` type, adding a `RegisterAttr.MemberType` property so that we can select between Android `[Register]` output vs. "Desktop" `[JniTypeSignature]` and `[JniMethodSignature]` output. This in turn requires updating many `generator-Tests` artifacts. Update `Java.Interop.Tools.JavaCallableWrappers` to look for `Java.Interop.JniTypeSignatureAttribute` and `Java.Interop.JniMethodSignatureAttribute`. This allows `jcw-gen Java.Base-Tests.dll` to generate Java Callable Wrappers for `Java.BaseTests.MyRunnable`. Update `Java.Interop.Export.dll` so that `MarshalMemberBuilder` doesn't use `Expression.GetActionType()` or `Expression.GetFuncType()`, as .NET doesn't support the use of generic types in [`Marshal.GetFunctionPointerForDelegate()`][0]. Instead, we need to use `System.Reflection.Emit` to define our own custom delegate types. ~~ Comparison with Android ~~ Android bindings (both Classic Xamarin.Android and .NET SDK for Android) use `generator`-emitted "connector methods" which are specified in the `[Register]` attribute (see also 99897b2): namespace Java.Lang { [Register ("java/lang/Object" DoNotGenerateAcw=true)] partial class Object { [Register ("equals", "(Ljava/lang/Object;)Z", "GetEquals_Ljava_lang_Object_Handler")] public virtual unsafe bool Equals (Java.Lang.Object? obj) => … static Delegate GetEquals_Ljava_lang_Object_Handler() => JNINativeWrapper.CreateDelegate((_JniMarshal_PPL_Z) n_Equals_Ljava_lang_Object_); static bool n_Equals_Ljava_lang_Object_ (IntPtr jnienv, IntPtr native__this, IntPtr native_obj) => … } } `jcw-gen` emits the "connector method" into the resulting Java Callable Wrappers for appropriate subclasses: String __md_methods = "n_equals:(Ljava/lang/Object;)Z:GetEquals_Ljava_lang_Object_Handler\n"; At runtime, `AndroidTypeManager.RegisterNativeMembers()` will lookup `Object.GetEquals_Ljava_lang_Object_Handler()` and invoke it. The returned `Delegate` instance is provided to `JNIEnv::RegisterNatives()`. The problem with this approach is that it's inflexible: there is no way to participate in the "connector method" infrastructure to alter marshaling behavior. If you need custom behavior, e.g. `Android.Graphics.Color` customizations, you need to update `generator` and rebuild the binding assemblies. (This has been "fine" for the past 10+ years, so this hasn't been a deal breaker.) For `Java.Base`, @jonpryor wants to support the custom marshaling infrastructure introduced in 77a6bf8. This would allow types to participate in JNI marshal method ("connector method") generation *at runtime*, allowing specialization based on the current set of types and assemblies. This means we *don't* need to specify a "connector method" in `[JniMethodSignatureAttribute]`, nor generate them. namespace Java.Lang { [JniTypeSignatureAttribute("java/lang/Object", GenerateJavaPeer=false)] partial class Object { [JniMethodSignatureAttribute("equals", "(Ljava/lang/Object;)Z")] public virtual unsafe bool Equals (Java.Lang.Object? obj) => … // No GetEquals_Ljava_lang_Object_Handler() // No n_Equals_Ljava_lang_Object_() } } This should result in smaller binding assemblies. The downside is that runtime costs *increase*, significantly. Instead of looking up and invoking a "connector method", the method must be *generated* via System.Linq.Expressions expression trees, then compiled into IL, then JIT'd, all before it can be used. We have no timing data to indicate how "bad" this overhead will be. As always, the hope is that `tools/jnimarshalmethod-gen` (176240d) can be used to get the "best of both worlds": the flexibility of custom marshalers, without the runtime overhead. But… * #14 * #616 At this point in time, `jnimarshalmethod-gen` *cannot* work under .NET 6. This will need to be addressed in order for custom marshalers to be a useful solution. `Java.Base` will use "connector method"-less bindings to act as a "forcing function" in getting `jnimarshalmethod-gen` working in .NET. [0]: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.getfunctionpointerfordelegate?view=net-6.0
- Loading branch information