Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Java.Interop] Replace JniMarshalInfo with JniValueMarshaler.
Context: #2 Context: #8 From Issue #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 #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 #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.
- Loading branch information