diff --git a/external/Java.Interop/src/Java.Interop/Java.Interop/JavaProxyObject.cs b/external/Java.Interop/src/Java.Interop/Java.Interop/JavaProxyObject.cs index 909629b188b..b08c8c4b8ef 100644 --- a/external/Java.Interop/src/Java.Interop/Java.Interop/JavaProxyObject.cs +++ b/external/Java.Interop/src/Java.Interop/Java.Interop/JavaProxyObject.cs @@ -14,13 +14,27 @@ sealed class JavaProxyObject : JavaObject, IEquatable static readonly JniPeerMembers _members = new JniPeerMembers (JniTypeName, typeof (JavaProxyObject)); static readonly ConditionalWeakTable CachedValues = new ConditionalWeakTable (); + static bool nativeMethodsRegistered; - [JniAddNativeMethodRegistrationAttribute] - static void RegisterNativeMembers (JniNativeMethodRegistrationArguments args) + static void RegisterNativeMethods () { - args.Registrations.Add (new JniNativeMethodRegistration ("equals", "(Ljava/lang/Object;)Z", new EqualsMarshalMethod (Equals))); - args.Registrations.Add (new JniNativeMethodRegistration ("hashCode", "()I", new GetHashCodeMarshalMethod (GetHashCode))); - args.Registrations.Add (new JniNativeMethodRegistration ("toString", "()Ljava/lang/String;", new ToStringMarshalMethod (ToString))); + unsafe { + using (var proxyType = new JniType ("net/dot/jni/internal/JavaProxyObject"u8)) { + Span methods = stackalloc JniNativeMethod [3]; + fixed (byte* equalsName = "equals"u8, equalsSignature = "(Ljava/lang/Object;)Z"u8) + fixed (byte* hashCodeName = "hashCode"u8, hashCodeSignature = "()I"u8) + fixed (byte* toStringName = "toString"u8, toStringSignature = "()Ljava/lang/String;"u8) { + methods [0] = new JniNativeMethod (equalsName, equalsSignature, + (IntPtr) (delegate* unmanaged) &Equals); + methods [1] = new JniNativeMethod (hashCodeName, hashCodeSignature, + (IntPtr) (delegate* unmanaged) &GetHashCode); + methods [2] = new JniNativeMethod (toStringName, toStringSignature, + (IntPtr) (delegate* unmanaged) &ToString); + JniEnvironment.Types.RegisterNatives (proxyType.PeerReference, methods); + } + } + } + nativeMethodsRegistered = true; } public override JniPeerMembers JniPeerMembers { @@ -66,36 +80,35 @@ public override bool Equals (object? obj) lock (CachedValues) { if (CachedValues.TryGetValue (value, out var proxy)) return proxy; + // Register before JavaObject's constructor allocates the Java peer. + if (!nativeMethodsRegistered) + RegisterNativeMethods (); proxy = new JavaProxyObject (value); CachedValues.Add (value, proxy); return proxy; } } - // TODO: Keep in sync with the code generated by ExportedMemberBuilder - [UnmanagedFunctionPointer (CallingConvention.Winapi)] - delegate bool EqualsMarshalMethod (IntPtr jnienv, IntPtr n_self, IntPtr n_value); - static bool Equals (IntPtr jnienv, IntPtr n_self, IntPtr n_value) + [UnmanagedCallersOnly] + static byte Equals (IntPtr jnienv, IntPtr n_self, IntPtr n_value) { var envp = new JniTransition (jnienv); try { var self = (JavaProxyObject?) JniEnvironment.Runtime.ValueManager.PeekPeer (new JniObjectReference (n_self)); var r_value = new JniObjectReference (n_value); var value = JniEnvironment.Runtime.ValueManager.GetValue (ref r_value, JniObjectReferenceOptions.Copy); - return self?.Equals (value) ?? false; + return self?.Equals (value) == true ? (byte) 1 : (byte) 0; } catch (Exception e) when (JniEnvironment.Runtime.ExceptionShouldTransitionToJni (e)) { envp.SetPendingException (e); - return false; + return 0; } finally { envp.Dispose (); } } - // TODO: Keep in sync with the code generated by ExportedMemberBuilder - [UnmanagedFunctionPointer (CallingConvention.Winapi)] - delegate int GetHashCodeMarshalMethod (IntPtr jnienv, IntPtr n_self); + [UnmanagedCallersOnly] static int GetHashCode (IntPtr jnienv, IntPtr n_self) { var envp = new JniTransition (jnienv); @@ -112,8 +125,7 @@ static int GetHashCode (IntPtr jnienv, IntPtr n_self) } } - [UnmanagedFunctionPointer (CallingConvention.Winapi)] - delegate IntPtr ToStringMarshalMethod (IntPtr jnienv, IntPtr n_self); + [UnmanagedCallersOnly] static IntPtr ToString (IntPtr jnienv, IntPtr n_self) { var envp = new JniTransition (jnienv); diff --git a/external/Java.Interop/src/Java.Interop/java/net/dot/jni/internal/JavaProxyObject.java b/external/Java.Interop/src/Java.Interop/java/net/dot/jni/internal/JavaProxyObject.java index 013cb070876..094baffc411 100644 --- a/external/Java.Interop/src/Java.Interop/java/net/dot/jni/internal/JavaProxyObject.java +++ b/external/Java.Interop/src/Java.Interop/java/net/dot/jni/internal/JavaProxyObject.java @@ -8,12 +8,6 @@ extends java.lang.Object implements GCUserPeerable { - static { - net.dot.jni.ManagedPeer.registerNativeMembers ( - JavaProxyObject.class, - ""); - } - ArrayList managedReferences = new ArrayList(); @Override @@ -35,4 +29,3 @@ public void jiClearManagedReferences () managedReferences.clear (); } } - diff --git a/external/Java.Interop/src/Java.Interop/java/net/dot/jni/internal/JavaProxyThrowable.java b/external/Java.Interop/src/Java.Interop/java/net/dot/jni/internal/JavaProxyThrowable.java index 066025ac58a..f5379edf403 100644 --- a/external/Java.Interop/src/Java.Interop/java/net/dot/jni/internal/JavaProxyThrowable.java +++ b/external/Java.Interop/src/Java.Interop/java/net/dot/jni/internal/JavaProxyThrowable.java @@ -8,12 +8,6 @@ extends java.lang.Error implements GCUserPeerable { - static { - net.dot.jni.ManagedPeer.registerNativeMembers ( - JavaProxyThrowable.class, - ""); - } - ArrayList managedReferences = new ArrayList(); public JavaProxyThrowable () { @@ -33,4 +27,3 @@ public void jiClearManagedReferences () managedReferences.clear (); } } - diff --git a/external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/JniValueMarshalerContractTests.cs b/external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/JniValueMarshalerContractTests.cs index 40a6440889d..28a9f659eab 100644 --- a/external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/JniValueMarshalerContractTests.cs +++ b/external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/JniValueMarshalerContractTests.cs @@ -584,6 +584,41 @@ public class JniValueMarshaler_object_ContractTests : JniValueMarshalerContractT protected override object Value {get {return value;}} protected override bool UsesProxy {get {return true;}} + [Test] + public unsafe void ProxyObjectMethodsDelegateToManagedValue () + { + var value = new ProxyValue (42); + var equalValue = new ProxyValue (42); + var otherValue = new ProxyValue (43); + var valueState = marshaler.CreateGenericObjectReferenceArgumentState (value); + var equalState = marshaler.CreateGenericObjectReferenceArgumentState (equalValue); + var otherState = marshaler.CreateGenericObjectReferenceArgumentState (otherValue); + + try { + using (var proxyType = new JniType ("net/dot/jni/internal/JavaProxyObject")) { + var equals = proxyType.GetInstanceMethod ("equals", "(Ljava/lang/Object;)Z"); + var hashCode = proxyType.GetInstanceMethod ("hashCode", "()I"); + var toString = proxyType.GetInstanceMethod ("toString", "()Ljava/lang/String;"); + var args = stackalloc JniArgumentValue [1]; + + args [0] = new JniArgumentValue (equalState.ReferenceValue); + Assert.IsTrue (JniEnvironment.InstanceMethods.CallBooleanMethod (valueState.ReferenceValue, equals, args)); + + args [0] = new JniArgumentValue (otherState.ReferenceValue); + Assert.IsFalse (JniEnvironment.InstanceMethods.CallBooleanMethod (valueState.ReferenceValue, equals, args)); + + Assert.AreEqual (value.GetHashCode (), JniEnvironment.InstanceMethods.CallIntMethod (valueState.ReferenceValue, hashCode)); + + var proxyString = JniEnvironment.InstanceMethods.CallObjectMethod (valueState.ReferenceValue, toString); + Assert.AreEqual (value.ToString (), JniEnvironment.Strings.ToString (ref proxyString, JniObjectReferenceOptions.CopyAndDispose)); + } + } finally { + marshaler.DestroyGenericArgumentState (otherValue, ref otherState); + marshaler.DestroyGenericArgumentState (equalValue, ref equalState); + marshaler.DestroyGenericArgumentState (value, ref valueState); + } + } + [Test] public void SpecificTypesAreUsed () { @@ -599,6 +634,31 @@ public void SpecificTypesAreUsed () Assert.AreEqual ("net/dot/jni/internal/JavaProxyObject", JniEnvironment.Types.GetJniTypeNameFromInstance (s.ReferenceValue)); marshaler.DestroyGenericArgumentState (value, ref s); } + + sealed class ProxyValue { + + readonly int value; + + public ProxyValue (int value) + { + this.value = value; + } + + public override bool Equals (object obj) + { + return obj is ProxyValue other && value == other.value; + } + + public override int GetHashCode () + { + return value; + } + + public override string ToString () + { + return $"ProxyValue({value})"; + } + } } [TestFixture] diff --git a/src/java-runtime/java-trimmable/net/dot/jni/internal/JavaProxyObject.java b/src/java-runtime/java-trimmable/net/dot/jni/internal/JavaProxyObject.java index f18107b89e5..071ffdad070 100644 --- a/src/java-runtime/java-trimmable/net/dot/jni/internal/JavaProxyObject.java +++ b/src/java-runtime/java-trimmable/net/dot/jni/internal/JavaProxyObject.java @@ -10,11 +10,8 @@ { ArrayList managedReferences = new ArrayList (); - // This trimmable runtime copy cannot use Java.Interop's native object methods: - // those are registered through ManagedPeer.registerNativeMembers, which is not - // supported in the trimmable typemap path. - // Trimmable proxies use Java identity semantics: equals/hashCode/toString - // do not delegate to the wrapped .NET object. + // Trimmable proxies use Java identity semantics instead of Java.Interop's + // native equals/hashCode/toString callbacks. @Override public boolean equals (Object obj) {