Skip to content

Commit 7d51163

Browse files
radekdoulikjonpryor
authored andcommitted
[Java.Interop] Add JniAddNativeMethodRegistrationAttribute (#206)
Instead of hard-coding the name of a method to register native members in a given type, introduce new attribute to mark appropriate methods within that type. ```csharp // Old-and-busted: static void __RegisterNativeMembers (JniType type, string members) { // ... type.RegisterNativeMethods (...) } // New-Hawtness: [JniAddNativeMethodRegistrationAttribute] static void RegisterNativeMembers (JniNativeMethodRegistrationArguments args) { args.AddRegistrations (...); } // Supporting cast: public sealed class JniAddNativeMethodRegistrationAttribute : Attribute { } public struct JniNativeMethodRegistrationArguments { public JniNativeMethodRegistrationArguments (ICollection<JniNativeMethodRegistration> registrations, string methods); public ICollection<JniNativeMethodRegistration> Registrations {get;} public string Methods {get;} public void AddRegistrations (IEnumerable<JniNativeMethodRegistration> registrations); } ``` Responsibility for invoking `JniType.RegisterNativeMembers()` has been *moved* from `__RegisterNativeMembers` into internal code, and `JniType.RegisterNativeMembers()` has been made `internal` so that it can't be called externally. Rationale: The `__RegisterNativeMembers()` method was "too magical": it's hard to document, but it has a large responsibility in ensuring that the Java `native` methods on a type are properly registered. Introducing a new `[JniAddNativeMethodRegistrationAttribute]` custom attribute simplifies this, as there is now an in-language construct which can contain suitable documentation. Additionally, `JNIEnv::RegisterNativeMethods()` can only be invoked *once* on a given type. (Not *strictly* true -- you *can* call it more than once for a given `jclass`! -- but the second and later invocations will *replace* the contents of the original invocation.) This complicates *documenting* what `[JniAddNativeMethodRegistrationAttribute]` can do. There are only two plausible usage scenarios: 1. A type can contain *only **one*** such method. 2. A type can contain *multiple* such methods. Absent a truly compelling reason to go with (1), the only way to support (2) is to prevent application code from calling `JniType.RegisterNativeMethods()`. This allows `JniRuntime.JniTypeManager.TryRegisterNativeMembers()` to invoke all `[JniAddNativeMethodRegistrationAttribute]` methods and collect the values, then call `JNIEnv::RegisterNativeMethods()` *once*. `TestType.cs` has been updated to demonstrate using multiple `[JniAddNativeMethodRegistrationAttribute]` methods.
1 parent a908424 commit 7d51163

File tree

15 files changed

+120
-40
lines changed

15 files changed

+120
-40
lines changed

gendarme-ignore.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,3 +577,7 @@ T: Java.Interop.JavaObject
577577
R: Gendarme.Rules.Design.Generic.DoNotExposeGenericListsRule
578578
# We don't care here as we don't hold the list and create it on request. So to avoid performace penalty, we keep the list as return type
579579
M: System.Collections.Generic.List`1<Java.Interop.JniSurfacedPeerInfo> Java.Interop.JniRuntime/JniValueManager::GetSurfacedPeers()
580+
581+
R: Gendarme.Rules.Concurrency.DoNotUseLockedRegionOutsideMethodRule
582+
# Looks like Gendarme issue, as there are both Monitor.TryEnter and Monitor.Exit used in this method
583+
M: System.Boolean Java.Interop.JniRuntime/JniTypeManager::TryRegisterNativeMembers(Java.Interop.JniType,System.Type,System.String)

src/Java.Interop.Export/Tests/Java.Interop/ExportTest.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System;
2-
using System.Linq;
32
using System.Linq.Expressions;
43
using System.Reflection;
4+
using System.Collections.Generic;
55

66
using Java.Interop;
77
using Java.Interop.Expressions;
@@ -11,11 +11,10 @@ namespace Java.InteropTests
1111
[JniTypeSignature ("com/xamarin/interop/export/ExportType")]
1212
public class ExportTest : JavaObject
1313
{
14-
static void __RegisterNativeMembers (JniType type, string members)
14+
[JniAddNativeMethodRegistrationAttribute]
15+
static void RegisterNativeMembers (JniNativeMethodRegistrationArguments args)
1516
{
16-
var methods = JniEnvironment.Runtime.MarshalMemberBuilder
17-
.GetExportedMemberRegistrations (typeof (ExportTest));
18-
type.RegisterNativeMethods (methods.ToArray ());
17+
args.AddRegistrations (JniEnvironment.Runtime.MarshalMemberBuilder.GetExportedMemberRegistrations (typeof (ExportTest)));
1918
}
2019

2120
public ExportTest (ref JniObjectReference reference, JniObjectReferenceOptions transfer)

src/Java.Interop/Java.Interop.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@
101101
<DependentUpon>JniPeerMembers.JniFields.tt</DependentUpon>
102102
</Compile>
103103
<Compile Include="Java.Interop\JniPeerMembers.JniStaticFields.cs" />
104+
<Compile Include="Java.Interop\JniAddNativeMethodRegistrationAttribute.cs" />
105+
<Compile Include="Java.Interop\JniNativeMethodRegistrationArguments.cs" />
104106
</ItemGroup>
105107
<ItemGroup Condition=" !$(Configuration.StartsWith('XAIntegration')) ">
106108
<Compile Include="Java.Interop\JavaArray.cs" />

src/Java.Interop/Java.Interop/JavaProxyObject.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ sealed class JavaProxyObject : JavaObject
1111
static readonly JniPeerMembers _members = new JniPeerMembers (JniTypeName, typeof (JavaProxyObject));
1212
static readonly ConditionalWeakTable<object, JavaProxyObject> CachedValues = new ConditionalWeakTable<object, JavaProxyObject> ();
1313

14-
static void __RegisterNativeMembers (JniType type, string members)
14+
[JniAddNativeMethodRegistrationAttribute]
15+
static void RegisterNativeMembers (JniNativeMethodRegistrationArguments args)
1516
{
16-
_members.JniPeerType.RegisterNativeMethods (
17-
new JniNativeMethodRegistration ("equals", "(Ljava/lang/Object;)Z", (Func<IntPtr, IntPtr, IntPtr, bool>) _Equals),
18-
new JniNativeMethodRegistration ("hashCode", "()I", (Func<IntPtr, IntPtr, int>) _GetHashCode),
19-
new JniNativeMethodRegistration ("toString", "()Ljava/lang/String;", (Func<IntPtr, IntPtr, IntPtr>) _ToString));
17+
args.Registrations.Add (new JniNativeMethodRegistration ("equals", "(Ljava/lang/Object;)Z", (Func<IntPtr, IntPtr, IntPtr, bool>)_Equals));
18+
args.Registrations.Add (new JniNativeMethodRegistration ("hashCode", "()I", (Func<IntPtr, IntPtr, int>)_GetHashCode));
19+
args.Registrations.Add (new JniNativeMethodRegistration ("toString", "()Ljava/lang/String;", (Func<IntPtr, IntPtr, IntPtr>)_ToString));
2020
}
2121

2222
public override JniPeerMembers JniPeerMembers {

src/Java.Interop/Java.Interop/JavaProxyThrowable.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@ sealed class JavaProxyThrowable : JavaException
77
{
88
new internal const string JniTypeName = "com/xamarin/java_interop/internal/JavaProxyThrowable";
99

10-
static void __RegisterNativeMembers (JniType type, string members)
11-
{
12-
}
13-
1410
public Exception Exception {get; private set;}
1511

1612
public JavaProxyThrowable (Exception exception)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System;
2+
3+
namespace Java.Interop
4+
{
5+
[AttributeUsage (AttributeTargets.Method)]
6+
public sealed class JniAddNativeMethodRegistrationAttribute : Attribute
7+
{
8+
}
9+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace Java.Interop
5+
{
6+
public struct JniNativeMethodRegistrationArguments
7+
{
8+
public ICollection<JniNativeMethodRegistration> Registrations {
9+
get { return _registrations; }
10+
}
11+
public string Methods { get; }
12+
ICollection<JniNativeMethodRegistration> _registrations;
13+
14+
public JniNativeMethodRegistrationArguments (ICollection<JniNativeMethodRegistration> registrations, string methods)
15+
{
16+
_registrations = registrations ?? throw new ArgumentNullException (nameof (registrations));
17+
Methods = methods ?? throw new ArgumentNullException (nameof (methods));
18+
}
19+
20+
public void AddRegistrations (IEnumerable<JniNativeMethodRegistration> registrations)
21+
{
22+
if (_registrations == null)
23+
throw new InvalidOperationException ($"{nameof(JniNativeMethodRegistrationArguments)} state is invalid. Please use constructor with parameters.");
24+
25+
if (registrations is List<JniNativeMethodRegistration> list) {
26+
list.AddRange (registrations);
27+
} else {
28+
foreach (var registration in registrations)
29+
_registrations.Add (registration);
30+
}
31+
}
32+
}
33+
}

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

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Diagnostics;
44
using System.Linq;
55
using System.Reflection;
6+
using System.Threading;
67

78
namespace Java.Interop {
89

@@ -243,15 +244,41 @@ static Assembly TryLoadAssembly (AssemblyName name)
243244
}
244245
}
245246

247+
static List<JniNativeMethodRegistration> sharedRegistrations = new List<JniNativeMethodRegistration> ();
248+
246249
static bool TryRegisterNativeMembers (JniType nativeClass, Type marshalType, string methods)
247250
{
248-
var registerMethod = marshalType.GetTypeInfo ().GetDeclaredMethod ("__RegisterNativeMembers");
249-
if (registerMethod == null) {
250-
return false;
251+
bool lockTaken = false;
252+
253+
try {
254+
Monitor.TryEnter (sharedRegistrations, ref lockTaken);
255+
List<JniNativeMethodRegistration> registrations;
256+
if (lockTaken) {
257+
sharedRegistrations.Clear ();
258+
registrations = sharedRegistrations;
259+
} else {
260+
registrations = new List<JniNativeMethodRegistration> ();
261+
}
262+
JniNativeMethodRegistrationArguments arguments = new JniNativeMethodRegistrationArguments (registrations, methods);
263+
foreach (var methodInfo in marshalType.GetRuntimeMethods ()) {
264+
if (methodInfo.GetCustomAttribute (typeof (JniAddNativeMethodRegistrationAttribute)) == null) {
265+
continue;
266+
}
267+
268+
if ((methodInfo.Attributes & MethodAttributes.Static) != MethodAttributes.Static) {
269+
throw new InvalidOperationException ($"The method {methodInfo} marked with {nameof (JniAddNativeMethodRegistrationAttribute)} must be static");
270+
}
271+
272+
var register = (Action<JniNativeMethodRegistrationArguments>)methodInfo.CreateDelegate (typeof (Action<JniNativeMethodRegistrationArguments>));
273+
register (arguments);
274+
}
275+
nativeClass.RegisterNativeMethods (registrations.ToArray ());
276+
} finally {
277+
if (lockTaken) {
278+
Monitor.Exit (sharedRegistrations);
279+
}
251280
}
252281

253-
var register = (Action<JniType, string>) registerMethod.CreateDelegate (typeof(Action<JniType, string>));
254-
register (nativeClass, methods);
255282
return true;
256283
}
257284
}

src/Java.Interop/Java.Interop/JniType.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ public bool IsInstanceOfType (JniObjectReference value)
135135
JniNativeMethodRegistration[] methods;
136136
#pragma warning restore 0414
137137

138-
public void RegisterNativeMethods (params JniNativeMethodRegistration[] methods)
138+
internal void RegisterNativeMethods (params JniNativeMethodRegistration[] methods)
139139
{
140140
AssertValid ();
141141

src/Java.Interop/Properties/AssemblyInfo.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,10 @@
2727
"814f144e5d817efc4c6502cc012df310783348304e3ae38573c6d658c234025821fda87a0be8a0" +
2828
"d504df564e2c93b2b878925f42503e9d54dfef9f9586d9e6f38a305769587b1de01f6c0410328b" +
2929
"2c9733db")]
30+
[assembly: InternalsVisibleTo (
31+
"Java.Interop-Tests, PublicKey=" +
32+
"0024000004800000940000000602000000240000525341310004000011000000438ac2a5acfbf1" +
33+
"6cbd2b2b47a62762f273df9cb2795ceccdf77d10bf508e69e7a362ea7a45455bbf3ac955e1f2e2" +
34+
"814f144e5d817efc4c6502cc012df310783348304e3ae38573c6d658c234025821fda87a0be8a0" +
35+
"d504df564e2c93b2b878925f42503e9d54dfef9f9586d9e6f38a305769587b1de01f6c0410328b" +
36+
"2c9733db")]

0 commit comments

Comments
 (0)