Skip to content

Commit 77a6bf8

Browse files
committed
[Java.Interop] Replace JniMarshalInfo with JniValueMarshaler.
Context: dotnet#2 Context: dotnet#8 From Issue dotnet#8: > I don't like JniMarshalInfo; it's too convoluted, makes the > simple cases hard. What do we mean by "hard"? We mean that it's use is error prone, because what you need to do depends on the values of the JniMarshalInfo members, which is best characterized by what JniArgumentMarshalInfo had to do to create a JNI Local refererence: JniObjectReference lref; var marshaler = JniRuntime.CurrentRuntime.ValueManager.GetJniMarshalInfoForType (type); if (info.CreateMarshalCollection != null) { var obj = info.CreateMarshalCollection (value); lref = obj.PeerReference; } else if (info.CreateLocalRef != null) { lref = info.CreateLocalRef (value); } else throw new NotSupportedException ("Don't know how to get a JNI Local Reference!"); // can now use `lref`... Need to pass as a method argument? Similar-yet-different. Then there's the cleanup code! The eventual intention is for tools/jnimarshalmethod-gen to post-process assemblies and generate Java > Managed marshal methods for all resolvable methods, and do this "dynamically" based on the actual types involved. This will allow "fixing" binding assemblies when involved types change, because binding assemblies won't be as "static" as they are in Xamarin.Android. (Which is why Issue dotnet#8 mentions using Expressions for the marshaling.) However, before we can get there we first need a rational marshaling abstrasction, something that is "pluggable" and readily extensible, so taht we can e.g. have a custom attribute to control which marshaler to use on types, parameters, and method return types. JniMarshalInfo wasn't that abstraction, and couldn't *be* that abstraction. To replace JniMarshalInfo, introduce JniValueMarshaler and JniValueMarshler<T>: public abstract partial class JniValueMarshaler { public abstract object CreateValue (ref JniObjectReference reference, JniObjectReferenceOptions options, Type targetType = null); public virtual JniValueMarshalerState CreateArgumentState (object value, ParameterAttributes synchronize = 0); public abstract JniValueMarshalerState CreateObjectReferenceArgumentState (object value, ParameterAttributes synchronize = 0); public abstract void DestroyArgumentState (object value, ref JniValueMarshalerState state, ParameterAttributes synchronize = 0); } public abstract partial class JniValueMarshaler<T> : JniValueMarshaler { public abstract T CreateGenericValue (ref JniObjectReference reference, JniObjectReferenceOptions options, Type targetType = null); public virtual JniValueMarshalerState CreateGenericArgumentState (T value, ParameterAttributes synchronize = 0); public abstract JniValueMarshalerState CreateGenericObjectReferenceArgumentState (T value, ParameterAttributes synchronize = 0); public abstract void DestroyGenericArgumentState (T value, ref JniValueMarshalerState state, ParameterAttributes synchronize = 0); public override object CreateValue (ref JniObjectReference reference, JniObjectReferenceOptions options, Type targetType = null); public override JniValueMarshalerState CreateArgumentState (object value, ParameterAttributes synchronize = 0); public override JniValueMarshalerState CreateObjectReferenceArgumentState (object value, ParameterAttributes synchronize = 0); public override void DestroyArgumentState (object value, ref JniValueMarshalerState state, ParameterAttributes synchronize = 0); } The intention is that a custom marshaler can be introduced by inheriting from JniValueMarshaler<T> and overriding three methods. This also provides a reasonable abstraction, e.g. to create a JNI Local Reference: var state = marshaler.CreateGenericObjectReferenceArgumentState (value); // state.ReferenceValue contains a JNI Object Reference // use, then cleanup: marshaler.DestroyGenericArgumentState (value, ref state); The methods are as follows: * CreateValue, CreateGenericValue: Java > Managed marshaler. Given a JniObjectReference, marshal the value to a type compatible with specified targetType, which should be either null or a class assignment compatible to T. The other methods use a JniValueMarshalerState type: public partial struct JniValueMarshalerState { public JniArgumentValue JniArgumentValue {get;} public JniObjectReference ReferenceValue {get;} public IJavaPeerable PeerableValue {get;} } The remaining methods of import are: * CreateArgumentState(), CreateGenericArgumentState(): Managed > Java marshaler. Used when the intention is to use JniValueMarshalerState.JniArgumentValue, which in turn is for use with JniEnvironment.*Methods.Call*Method(). * CreateObjectReferenceArgumentState(), CreateGenericObjectReferenceArgumentState(): Managed > Java marshaler. Used when the intention is to use JniValueMarshalerState.ReferenceValue. * DestroyArgumentState(), DestroyGenericArgumentState(): Generic resource cleanup mechanism for use with Create*ArgumentState(). The primary difference between Create*ArgumentState() and Create*ObjectReferenceArgumentState() is with builtin value types, e.g. `int`. Create*ArgumentState() will *only* provide a JniArgumentValue, while JniValueMarshalerState.ReferenceValue will be null. Create*ObjectReferenceArgumentState(), meanwhile, will "box" the value type, providing a valid JniValueMarshalerState.ReferenceValue. For all other types, these methods are identical. JniValueMarshaler and JniValueMarshalerState together combine to replace JniMarshalInfo and JniArgumentMarshalInfo, the latter of which (1) wasn't public, and (2) was "duplicated" between the generic marshalers and Java.Interop.Dynamic. JniValueMarshaler thus allows cleaning up this duplication. Along with the new JniValueMarshaler abstraction, we provide implementations for all builtin types, Nullable<T> for builtin types (which always box), arrays of the builtin types, JavaObjectArray<T>, IJavaPeerable, and `object`. The `object` marshaler supersedes the previous JniMarshal.GetValue<T>() semantics, using JavaProxyObject as needed. One thing to note is the ParameterAttributes parameters. This value is intended to correspond with System.Reflection.ParameterInfo.Attributes. jnimarshalmethod-gen can thus use the actual MethodInfo + ParameterInfo to encode parameter marshaling direction. For most types this won't be useful; this is largely intended for *arrays*, e.g. we could have an `[In] int[]` parameter which thus avoids the "copy out" behavior Xamarin.Android currently performs, thus avoiding an extra JNI transition. Or vice-versa, have an `[Out] int[]` parameter, which avoids a JNI transition to copy the managed `int[]` to the Java `int[]`, replacing it with the copy-out transition. Partially addressing Issue dotnet#2, this also introduces JniRuntime.JniValueManager.GetValue() and GetValue<T>() methods. GetObject() is not yet replaced, but will be in a future commit. Actual implementation will be done alongside introduction of CreateValue<T>() and cleaning up JniValueMarshaler.Create*Value() methods to always create values when able, simplifying the logic if JniValueMarshaler implementations.
1 parent 15fbb57 commit 77a6bf8

23 files changed

+10905
-6938
lines changed

src/Java.Interop.Dynamic/Java.Interop.Dynamic/DynamicJavaClass.cs

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -144,41 +144,5 @@ static JavaModifiers ()
144144
}
145145
}
146146
}
147-
148-
struct JniArgumentMarshalInfo {
149-
JniArgumentValue jvalue;
150-
JniObjectReference lref;
151-
IJavaPeerable obj;
152-
Action<IJavaPeerable, object> cleanup;
153-
154-
internal JniArgumentMarshalInfo (object value, Type valueType)
155-
{
156-
this = new JniArgumentMarshalInfo ();
157-
var jvm = JniEnvironment.Runtime;
158-
var info = jvm.ValueManager.GetJniMarshalInfoForType (valueType);
159-
if (info.CreateJniArgumentValue != null)
160-
jvalue = info.CreateJniArgumentValue (value);
161-
else if (info.CreateMarshalCollection != null) {
162-
obj = info.CreateMarshalCollection (value);
163-
jvalue = new JniArgumentValue (obj);
164-
} else if (info.CreateLocalRef != null) {
165-
lref = info.CreateLocalRef (value);
166-
jvalue = new JniArgumentValue (lref);
167-
} else
168-
throw new NotSupportedException ("Don't know how to get a JniArgumentValue for: " + valueType.FullName);
169-
cleanup = info.CleanupMarshalCollection;
170-
}
171-
172-
public JniArgumentValue JniArgumentValue {
173-
get {return jvalue;}
174-
}
175-
176-
public void Cleanup (object value)
177-
{
178-
if (cleanup != null && obj != null)
179-
cleanup (obj, value);
180-
JniObjectReference.Dispose (ref lref);
181-
}
182-
}
183147
}
184148

src/Java.Interop.Dynamic/Java.Interop.Dynamic/JavaClassInfo.cs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,8 @@ static bool IsStatic (JniObjectReference member)
291291
internal unsafe bool TryInvokeMember (IJavaPeerable self, JavaMethodBase[] overloads, DynamicMetaObject[] args, out object value)
292292
{
293293
value = null;
294-
var margs = (List<JniArgumentMarshalInfo>) null;
294+
var vms = (List<JniValueMarshaler>) null;
295+
var states = (JniValueMarshalerState[]) null;
295296

296297
var jtypes = GetJniTypes (args);
297298
try {
@@ -300,16 +301,21 @@ internal unsafe bool TryInvokeMember (IJavaPeerable self, JavaMethodBase[] overl
300301
if (invoke == null)
301302
return false;
302303

303-
margs = args.Select (arg => new JniArgumentMarshalInfo (arg.Value, arg.LimitType)).ToList ();
304-
var jargs = stackalloc JniArgumentValue [margs.Count];
305-
for (int i = 0; i < margs.Count; ++i)
306-
jargs [i] = margs [i].JniArgumentValue;
304+
var jvm = JniEnvironment.Runtime;
305+
vms = args.Select (arg => jvm.ValueManager.GetValueMarshaler (arg.LimitType)).ToList ();
306+
states = new JniValueMarshalerState [vms.Count];
307+
for (int i = 0; i < vms.Count; ++i) {
308+
states [i] = vms [i].CreateArgumentState (args [i].Value);
309+
}
310+
var jargs = stackalloc JniArgumentValue [vms.Count];
311+
for (int i = 0; i < states.Length; ++i)
312+
jargs [i] = states [i].JniArgumentValue;
307313
value = invoke.Invoke (self, jargs);
308314
return true;
309315
}
310316
finally {
311-
for (int i = 0; margs != null && i < margs.Count; ++i) {
312-
margs [i].Cleanup (args [i].Value);
317+
for (int i = 0; vms != null && i < vms.Count; ++i) {
318+
vms [i].DestroyArgumentState (args [i].Value, ref states [i]);
313319
}
314320
for (int i = 0; i < jtypes.Count; ++i) {
315321
if (jtypes [i] != null)

src/Java.Interop.Dynamic/Java.Interop.Dynamic/JavaFieldInfo.cs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,16 @@ void SetStaticValue (object value)
137137
case 'D': members.StaticFields.SetValue (JniSignature, (double) value); break;
138138
case 'L':
139139
case '[':
140-
var lref = JniMarshal.CreateLocalRef (value);
140+
if (value == null) {
141+
members.StaticFields.SetValue (JniSignature, new JniObjectReference ());
142+
return;
143+
}
144+
var vm = JniEnvironment.Runtime.ValueManager.GetValueMarshaler (value.GetType ());
145+
var s = vm.CreateArgumentState (value);
141146
try {
142-
members.StaticFields.SetValue (JniSignature, lref);
147+
members.StaticFields.SetValue (JniSignature, s.ReferenceValue);
143148
} finally {
144-
JniObjectReference.Dispose (ref lref);
149+
vm.DestroyArgumentState (value, ref s, 0);
145150
}
146151
return;
147152
default:
@@ -163,11 +168,16 @@ void SetInstanceValue (IJavaPeerable self, object value)
163168
case 'D': members.InstanceFields.SetValue (JniSignature, self, (double) value); break;
164169
case 'L':
165170
case '[':
166-
var lref = JniMarshal.CreateLocalRef (value);
171+
if (value == null) {
172+
members.InstanceFields.SetValue (JniSignature, self, new JniObjectReference ());
173+
return;
174+
}
175+
var vm = JniEnvironment.Runtime.ValueManager.GetValueMarshaler (value.GetType ());
176+
var s = vm.CreateArgumentState (value);
167177
try {
168-
members.InstanceFields.SetValue (JniSignature, self, lref);
178+
members.InstanceFields.SetValue (JniSignature, self, s.ReferenceValue);
169179
} finally {
170-
JniObjectReference.Dispose (ref lref);
180+
vm.DestroyArgumentState (value, ref s, 0);
171181
}
172182
return;
173183
default:

src/Java.Interop.Dynamic/Java.Interop.Dynamic/JniMetaObject.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,14 @@ public override DynamicMetaObject BindConvert (ConvertBinder binder)
5252
return new DynamicMetaObject (typeE, BindingRestrictions.GetTypeRestriction (typeE, binder.Type), type);
5353
}
5454

55-
var marshalInfo = vm.ValueManager.GetJniMarshalInfoForType (binder.Type);
56-
if (marshalInfo.GetValueFromJni == null)
55+
object value;
56+
try {
57+
var r = ConversionTarget;
58+
value = vm.ValueManager.GetValue (ref r, JniObjectReferenceOptions.Copy, binder.Type);
59+
} catch {
5760
return binder.FallbackConvert (this);
61+
}
5862

59-
var r = ConversionTarget;
60-
var value = marshalInfo.GetValueFromJni (ref r, JniObjectReferenceOptions.Copy, binder.Type);
6163
var valueE = Expression.Convert (Expression.Constant (value), binder.Type);
6264
return new DynamicMetaObject (valueE, BindingRestrictions.GetTypeRestriction (valueE, binder.Type), value);
6365
}

src/Java.Interop/Java.Interop.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,14 +119,15 @@
119119
<Compile Include="Java.Interop\JniLocalReference.cs" />
120120
<Compile Include="Java.Interop\JniMarshal.cs" />
121121
<Compile Include="Java.Interop\JniMarshalMethod.cs" />
122-
<Compile Include="Java.Interop\JniMarshalInfo.cs" />
123122
<Compile Include="Java.Interop\JniPeerMembers.JniMethods.cs">
124123
<DependentUpon>JniPeerMembers.JniMethods.tt</DependentUpon>
125124
</Compile>
126125
<Compile Include="Java.Interop\JniReferenceSafeHandle.cs" />
127126
<Compile Include="Java.Interop\JniRuntime.JniExportedMemberBuilder.cs" />
128127
<Compile Include="Java.Interop\JniRuntime.JniValueManager.cs" />
128+
<Compile Include="Java.Interop\JniStringValueMarshaler.cs" />
129129
<Compile Include="Java.Interop\JniSystem.cs" />
130+
<Compile Include="Java.Interop\JniValueMarshaler.cs" />
130131
<Compile Include="Java.Interop\JniWeakGlobalReference.cs" />
131132
<Compile Include="Java.Interop\ManagedPeer.cs" />
132133
</ItemGroup>

src/Java.Interop/Java.Interop/JavaArray.cs

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

78
namespace Java.Interop
89
{
@@ -102,7 +103,7 @@ internal IList<T> ToTargetType (Type targetType, bool dispose)
102103
{
103104
if (TargetTypeIsCurrentType (targetType))
104105
return this;
105-
if (targetType == typeof (T[])) {
106+
if (targetType == typeof (T[]) || targetType.GetTypeInfo ().IsAssignableFrom (typeof (IList<T>).GetTypeInfo ())) {
106107
try {
107108
return ToArray ();
108109
} finally {
@@ -125,22 +126,7 @@ internal static Exception CreateMarshalNotSupportedException (Type sourceType, T
125126
sourceType.FullName, targetType.FullName));
126127
}
127128

128-
internal static JniObjectReference CreateLocalRef<TArray> (object value, Func<IList<T>, TArray> creator)
129-
where TArray : JavaArray<T>
130-
{
131-
if (value == null)
132-
return new JniObjectReference ();
133-
var array = value as TArray;
134-
if (array != null)
135-
return array.PeerReference.NewLocalRef ();
136-
var items = value as IList<T>;
137-
if (items == null)
138-
throw CreateMarshalNotSupportedException (value.GetType (), typeof (TArray));
139-
using (array = creator (items))
140-
return array.PeerReference.NewLocalRef ();
141-
}
142-
143-
internal static IList<T> GetValueFromJni<TArray> (ref JniObjectReference reference, JniObjectReferenceOptions transfer, Type targetType, ArrayCreator<TArray> creator)
129+
internal static IList<T> CreateValue<TArray> (ref JniObjectReference reference, JniObjectReferenceOptions transfer, Type targetType, ArrayCreator<TArray> creator)
144130
where TArray : JavaArray<T>
145131
{
146132
var value = JniEnvironment.Runtime.ValueManager.PeekObject (reference);
@@ -153,37 +139,57 @@ internal static IList<T> GetValueFromJni<TArray> (ref JniObjectReference referen
153139
.ToTargetType (targetType, dispose: true);
154140
}
155141

156-
internal static IJavaPeerable CreateMarshalCollection<TArray> (object value, Func<IList<T>, TArray> creator)
142+
internal static JniValueMarshalerState CreateArgumentState<TArray> (IList<T> value, ParameterAttributes synchronize, Func<IList<T>, bool, TArray> creator)
157143
where TArray : JavaArray<T>
158144
{
159145
if (value == null)
160-
return null;
146+
return new JniValueMarshalerState ();
161147
var v = value as TArray;
162-
if (v != null)
163-
return v;
148+
if (v != null) {
149+
return new JniValueMarshalerState (v);
150+
}
164151
var list = value as IList<T>;
165152
if (list == null)
166153
throw CreateMarshalNotSupportedException (value.GetType (), typeof (TArray));
167-
return creator (list);
154+
synchronize = GetCopyDirection (synchronize);
155+
var c = (synchronize & ParameterAttributes.In) == ParameterAttributes.In;
156+
var a = creator (list, c);
157+
return new JniValueMarshalerState (a);
168158
}
169159

170-
internal static void CleanupMarshalCollection<TArray> (IJavaPeerable marshalObject, object value)
160+
internal static void DestroyArgumentState<TArray> (IList<T> value, ref JniValueMarshalerState state, ParameterAttributes synchronize)
171161
where TArray : JavaArray<T>
172162
{
173-
var source = (TArray) marshalObject;
163+
var source = (TArray) state.PeerableValue;
174164
if (source == null)
175165
return;
176166

177-
var arrayDest = value as T[];
178-
var listDest = value as IList<T>;
179-
if (arrayDest != null)
180-
source.CopyTo (arrayDest, 0);
181-
else if (listDest != null)
182-
source.CopyToList (listDest, 0);
167+
Debug.WriteLine ("# jonp: JavaArray<{0}>.DestroyArgumentState<{1}>: synchronize={2}", typeof(T).FullName, typeof(TArray).FullName, synchronize);
168+
synchronize = GetCopyDirection (synchronize);
169+
if ((synchronize & ParameterAttributes.Out) == ParameterAttributes.Out) {
170+
var arrayDest = value as T[];
171+
var listDest = value as IList<T>;
172+
if (arrayDest != null)
173+
source.CopyTo (arrayDest, 0);
174+
else if (listDest != null)
175+
source.CopyToList (listDest, 0);
176+
}
183177

184178
if (source.forMarshalCollection) {
185179
source.Dispose ();
186180
}
181+
182+
state = new JniValueMarshalerState ();
183+
}
184+
185+
internal static ParameterAttributes GetCopyDirection (ParameterAttributes value)
186+
{
187+
// If .In or .Out are specified, use as-is.
188+
// Otherwise, we should copy both directions.
189+
const ParameterAttributes inout = ParameterAttributes.In | ParameterAttributes.Out;
190+
if ((value & inout) != 0)
191+
return (value & inout);
192+
return inout;
187193
}
188194

189195
internal virtual void CopyToList (IList<T> list, int index)

src/Java.Interop/Java.Interop/JavaObjectArray.cs

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Reflection;
34

45
namespace Java.Interop
56
{
@@ -59,14 +60,15 @@ public override T this [int index] {
5960
T GetElementAt (int index)
6061
{
6162
var lref = JniEnvironment.Arrays.GetObjectArrayElement (PeerReference, index);
62-
return JniMarshal.GetValue<T> (ref lref, JniObjectReferenceOptions.CopyAndDispose);
63+
return JniEnvironment.Runtime.ValueManager.GetValue<T> (ref lref, JniObjectReferenceOptions.CopyAndDispose);
6364
}
6465

6566
void SetElementAt (int index, T value)
6667
{
67-
var h = JniMarshal.CreateLocalRef (value);
68-
JniEnvironment.Arrays.SetObjectArrayElement (PeerReference, index, h);
69-
JniObjectReference.Dispose (ref h, JniObjectReferenceOptions.CopyAndDispose);
68+
var vm = JniEnvironment.Runtime.ValueManager.GetValueMarshaler<T> ();
69+
var s = vm.CreateGenericObjectReferenceArgumentState (value);
70+
JniEnvironment.Arrays.SetObjectArrayElement (PeerReference, index, s.ReferenceValue);
71+
vm.DestroyGenericArgumentState (value, ref s, 0);
7072
}
7173

7274
public override IEnumerator<T> GetEnumerator ()
@@ -80,11 +82,12 @@ public override IEnumerator<T> GetEnumerator ()
8082
public override void Clear ()
8183
{
8284
int len = Length;
83-
var v = JniMarshal.CreateLocalRef (default (T));
85+
var vm = JniEnvironment.Runtime.ValueManager.GetValueMarshaler<T> ();
86+
var s = vm.CreateArgumentState (default (T));
8487
for (int i = 0; i < len; i++) {
85-
JniEnvironment.Arrays.SetObjectArrayElement (PeerReference, i, v);
88+
JniEnvironment.Arrays.SetObjectArrayElement (PeerReference, i, s.ReferenceValue);
8689
}
87-
JniObjectReference.Dispose (ref v, JniObjectReferenceOptions.CopyAndDispose);
90+
vm.DestroyGenericArgumentState (default (T), ref s, 0);
8891
}
8992

9093
public override int IndexOf (T item)
@@ -132,28 +135,30 @@ internal override bool TargetTypeIsCurrentType (Type targetType)
132135
targetType == typeof (JavaObjectArray<T>);
133136
}
134137

135-
internal static object GetValue (ref JniObjectReference handle, JniObjectReferenceOptions transfer, Type targetType)
136-
{
137-
return JavaArray<T>.GetValueFromJni (ref handle, transfer, targetType, (ref JniObjectReference h, JniObjectReferenceOptions t) => new JavaObjectArray<T> (ref h, t) {
138-
forMarshalCollection = true,
139-
});
140-
}
138+
internal class ValueMarshaler : JniValueMarshaler<IList<T>> {
141139

142-
internal static JniObjectReference CreateLocalRef (object value)
143-
{
144-
return JavaArray<T>.CreateLocalRef (value, list => new JavaObjectArray<T>(list));
145-
}
140+
public override IList<T> CreateGenericValue (ref JniObjectReference reference, JniObjectReferenceOptions options, Type targetType)
141+
{
142+
return JavaArray<T>.CreateValue (ref reference, options, targetType, (ref JniObjectReference h, JniObjectReferenceOptions t) => new JavaObjectArray<T> (ref h, t) {
143+
forMarshalCollection = true,
144+
});
145+
}
146146

147-
internal static IJavaPeerable CreateMarshalCollection (object value)
148-
{
149-
return JavaArray<T>.CreateMarshalCollection (value, list => new JavaObjectArray<T> (list) {
150-
forMarshalCollection = true,
151-
});
152-
}
147+
public override JniValueMarshalerState CreateGenericObjectReferenceArgumentState (IList<T> value, ParameterAttributes synchronize)
148+
{
149+
return JavaArray<T>.CreateArgumentState (value, synchronize, (list, copy) => {
150+
var a = copy
151+
? new JavaObjectArray<T> (list)
152+
: new JavaObjectArray<T> (list.Count);
153+
a.forMarshalCollection = true;
154+
return a;
155+
});
156+
}
153157

154-
internal static void CleanupMarshalCollection (IJavaPeerable marshalObject, object value)
155-
{
156-
JavaArray<T>.CleanupMarshalCollection<JavaObjectArray<T>> (marshalObject, value);
158+
public override void DestroyGenericArgumentState (IList<T> value, ref JniValueMarshalerState state, ParameterAttributes synchronize)
159+
{
160+
JavaArray<T>.DestroyArgumentState<JavaObjectArray<T>> (value, ref state, synchronize);
161+
}
157162
}
158163
}
159164
}

0 commit comments

Comments
 (0)