Repository navigation
Add a JniEnvironment.BeginGetValueScope() method. #11941
Description
Activity
Another option for
GetValueScopeis to inhibit exception marshaling: Xamarin.Forms has a request to just have the app exit if an exception is thrown, because it Can't Happen, and if it does there's nothing reasonable to do.Consequently, it would be helpful if exception marshaling could be inhibited in a similar manner.
- added a commit that references this issue
on Sep 26, 2015 - addedenhancementProposed change to current functionality.Proposed change to current functionality.java-interopIssues migrated from dotnet/java-interop / relates to the Java.Interop subtreeIssues migrated from dotnet/java-interop / relates to the Java.Interop subtree
on Apr 16, 2020 WRT inhibiting exception marshaling, the whole concern was around needing to have two JNI invocations "everywhere", e.g. https://github.com/xamarin/java.interop/blob/main/tests/invocation-overhead/jni.cs#L6095-L6099
(in which
JniEnvironment.GetExceptionForLastThrowable()calledJNIEnv::ExceptionOccurred()occurred.)Calling through the delegates is not a "zero-cost" operation, so the idea behind
GetValueBehaviors.DoNotMarshalExceptionswas as an assertion that "this method will not throw", and thus we could avoid theJNIEnv::ExceptionOccurred()invocation.However, with the "new" P/Invoke-based system in 926e4bc (not really "new" anymore),
JNIEnv::ExceptionOccurred()is done as part of the C layer, not C#+delegates, and is faster. (Still not zero-cost, but faster.)I thus don't see much need for
GetValueBehaviors.DoNotMarshalExceptions.GetValueBehaviors.CreateValuesstill seems like a good idea, but needs to be "paired with" a noun to describe existing behavior, e.g..IdentityValues?- added a commit that references this issue
on May 5, 2021 - added a commit that references this issue
on Jun 17, 2021 - removedenhancementProposed change to current functionality.Proposed change to current functionality.
on Apr 29, 2025 A way to "save" dotnet/java-interop#1238 is to take a page out of swift-java: https://youtu.be/QSHO-GUGidA
- Introduce an
Arenatype, and - Add
Arenaas a constructor parameter.
Such as:
// Java.Interop.dll namespace Java.Interop { public partial class JniInstanceArena : Disposable { public static JniInstanecArena Shared {get;} public JniInstanceArena (); public void Dispose (); } public partial class JavaObject { public JavaObject (JniInstanceArena? arena); } } // Java.Base.dll / Mono.Android.dll namespace Java.Lang { partial class Object { public Object (JniInstanceArenea? arena); } }
We would also need to update
generatorto add this parameter overload to all bound types.If you use the existing non-
JniInstanceArenaconstructor, or passnullas thearenaparameter, it's "as if"JniInstanceArena.Sharedwere specified, and existing semantics continue (cross-VM GC bridge, etc.)If you allocate a new
JniInstanceArenavalue and provide it to the constructor:using var arena = new JniInstanceArena(); var o = new Java.Lang.Object(arena);
Then when
arena.Dispose()is invoked, all instances that were constructed with thatarenaare also disposed.This avoids the problems in dotnet/java-interop#1238 because it requires explicit "opt-in" to using the specified
JniInstanceArena. If noJniInstanceArenais specified, as would be the case for #11856 -style caching, then the instance will only be disposed as part ofJniRuntime.Dispose().
Problems: as an "opt-in" solution, it means to fully use it all dependencies need to be built with a
generatorthat provides that newJniInstanceArena? arenaparameter. Non-binding subclasses would also need to add this constructor overload in order to opt-in toJniInstanceArenasemantics.Which re-introduces the original question: is it worth it? If this were "new", sure. In a world with thousands of existing binding assemblies, none of which would be able to use this new feature without changes… is it worth it? ¯\(ツ)/¯
- Introduce an
Alas, after further consideration, the
JniInstanceArenaimprovement suggested above is still insufficient.What do we want? A "reasonable" way to deal with Java-side caching, as with
Typeface.create()and dotnet/java-interop#3, so that we can "limit" GREF usage to a scope. The problem is that Java-side caching results in "inadvertent instance sharing" in C#, e.g. this code is bad:// DO NOT DO THIS using var typeface = Typeface.Create(…);
As mentioned elsewhere, if you want to explicitly
Dispose()of an instance, the only safe and reliable way to do so is if you call the constructor:// This is fine using var v = new Java.Lang.Integer(42);
How can we square this circle? We can extend the
JniInstanceArenaconcept by providing it to any method which returns a reference type:using (var arena = new JniInstanceArena()) { var typeface = Typeface.Create(…, arena); // `typeface` is *not* shared with other arenas, and is present only in `arena` } // `typeface` is `Dispose()`d here
Which means this is non-viable: every method which returns a reference type would need to have this
JniInstanceArena? arena = nullparameter added, which would constitute an ABI break, e.g.namespace Java.Lang; public partial interface IAppendable { Java.Lang.IAppendable Append (char c, JniInstanceArena? arena = null); }
Which means "fix
Typeface.Create()" is not realistically possible; this would need to have been part of the design 15 years ago… :-(- addedjava-interopIssues migrated from dotnet/java-interop / relates to the Java.Interop subtreeIssues migrated from dotnet/java-interop / relates to the Java.Interop subtreeneeds-triageIssues that need to be assigned.Issues that need to be assigned.
on Jul 1, 2026 - addedpossibly-staleIssues that are potentially no longer relevant.Issues that are potentially no longer relevant.
on Jul 1, 2026 dotnet-policy-service commented
on Jul 1, 2026 More actionsWe suspect this issue is stale and no longer relevant. It will be closed if no further activity occurs within 14 more days. Any new comment (by anyone, not necessarily the author) will undo this process.
dotnet-policy-service commented
on Jul 15, 2026 More actionsThis issue will now be closed since it had been marked "possibly-stale" but received no further activity in the past 14 days. It is still possible to reopen or comment on the issue, but please note that the issue will be locked if it remains inactive for another 30 days.
- locked and limited conversation to collaborators
on Aug 15, 2026
Building upon Issue dotnet/java-interop#3, we expect
JavaVM.GetValue()calls to be "buried" within binding code, not directly invoked by developers (most of the time). Thus, how does a developer tell a binding method that a new value should be created instead of retrieving a possibly shared value?Introduce
JniEnvironment.BeginGetValueScope(GetValueScope):Calling
JniEnviornment.BeginGetValueBehaviors()would alter the behavior ofJavaVM.GetValue(): ifGetValueBehaviors.CreateValuesis specified, thenJavaVM.GetValue()will instead behave likeJavaVM.CreateValue(). This allows the end user to maintain some degree of control:The above allows disposing of the temporary with impunity, as
BeginGetValueScope()will ensure thatTypeface.Create()returns unique wrappers instead of possibly shared wrappers.