This repository was archived by the owner on Aug 27, 2026. It is now read-only.
Add ReadOnlySpan<byte> overloads for JNI member lookups - #1393
Merged
Conversation
simonrozsival
force-pushed
the
dev/simonrozsival/utf8-overloads
branch
4 times, most recently
from
March 19, 2026 14:39
211d339 to
0f746ea
Compare
Add UTF-8 span overloads for JNI class, method, and field lookups that accept ReadOnlySpan<byte> parameters. Callers can pass u8 string literals (e.g., "java/lang/Object"u8) to avoid string-to-UTF-8 marshalling overhead entirely. New overloads (function pointers backend only): - JniEnvironment.Types.FindClass/TryFindClass(ReadOnlySpan<byte>) - JniEnvironment.InstanceMethods.GetMethodID(…, ReadOnlySpan<byte>, ReadOnlySpan<byte>) - JniEnvironment.StaticMethods.GetStaticMethodID(…, ReadOnlySpan<byte>, ReadOnlySpan<byte>) - JniEnvironment.InstanceFields.GetFieldID(…, ReadOnlySpan<byte>, ReadOnlySpan<byte>) - JniEnvironment.StaticFields.GetStaticFieldID(…, ReadOnlySpan<byte>, ReadOnlySpan<byte>) - JniType constructor, Get*/GetCached* for methods, fields, constructors Contributes to #950 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival
force-pushed
the
dev/simonrozsival/utf8-overloads
branch
from
March 19, 2026 14:49
0f746ea to
f72e652
Compare
simonrozsival
force-pushed
the
dev/simonrozsival/utf8-overloads
branch
from
March 19, 2026 15:01
f72e652 to
6bf1fce
Compare
Tests cover: - Equivalence: string vs u8 overloads return same JNI IDs - End-to-end: constructor + method invocation via u8 path - Cached lookups: GetCached* returns same instance - Error handling: disposed type, class not found, invalid signature Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival
force-pushed
the
dev/simonrozsival/utf8-overloads
branch
from
March 19, 2026 15:09
6bf1fce to
8521bb7
Compare
The Class.forName fallback requires converting the UTF-8 span back to a managed string and then to a Java string, which negates the zero-alloc benefit of the UTF-8 path. Callers who need the classloader fallback can use the existing string-based FindClass overload. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jonathanpeppers
approved these changes
Mar 20, 2026
simonrozsival
added a commit
to dotnet/android
that referenced
this pull request
Mar 23, 2026
Avoids string-to-UTF8 conversion at runtime. The ReadOnlySpan<byte> overload was added in dotnet/java-interop#1393. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival
added a commit
to dotnet/android
that referenced
this pull request
Mar 24, 2026
Avoids string-to-UTF8 conversion at runtime. The ReadOnlySpan<byte> overload was added in dotnet/java-interop#1393. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival
added a commit
to dotnet/android
that referenced
this pull request
Mar 25, 2026
Avoids string-to-UTF8 conversion at runtime. The ReadOnlySpan<byte> overload was added in dotnet/java-interop#1393. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival
added a commit
to dotnet/android
that referenced
this pull request
Mar 26, 2026
Avoids string-to-UTF8 conversion at runtime. The ReadOnlySpan<byte> overload was added in dotnet/java-interop#1393. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival
added a commit
to dotnet/android
that referenced
this pull request
Mar 27, 2026
…11002) * Use JniNativeMethod for zero-allocation RegisterNatives bootstrap Replace Marshal.GetDelegateForFunctionPointer wrapper with JniNativeMethod stackalloc + ReadOnlySpan<JniNativeMethod> for the mono.android.Runtime.registerNatives bootstrap registration. No delegate allocation, no GC handle — same pattern as the generated proxy RegisterNatives methods. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Use JniType UTF-8 constructor ("mono/android/Runtime"u8) Avoids string-to-UTF8 conversion at runtime. The ReadOnlySpan<byte> overload was added in dotnet/java-interop#1393. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove RegisterMethod - already removed in a separate PR Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Simplify RegisterNatives: collection expression, less nesting Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add
ReadOnlySpan<byte>overloads for JNI class, method, and field lookups. Callers can pass UTF-8 string literals (e.g.,"java/lang/Object"u8) to skipMarshal.StringToCoTaskMemUTF8/ZeroFreeCoTaskMemUTF8entirely, eliminating the per-call UTF-16→UTF-8 transcoding and native heap alloc/free.Contributes to dotnet/android#11885
New API surface
All overloads are additive (existing
stringAPIs unchanged) and only compiled underFEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS(the .NET Android runtime path).JniEnvironment(low-level):Types.FindClass(ReadOnlySpan<byte>)/TryFindClass(ReadOnlySpan<byte>, out JniObjectReference)InstanceMethods.GetMethodID(JniObjectReference, ReadOnlySpan<byte>, ReadOnlySpan<byte>)StaticMethods.GetStaticMethodID(JniObjectReference, ReadOnlySpan<byte>, ReadOnlySpan<byte>)InstanceFields.GetFieldID(JniObjectReference, ReadOnlySpan<byte>, ReadOnlySpan<byte>)StaticFields.GetStaticFieldID(JniObjectReference, ReadOnlySpan<byte>, ReadOnlySpan<byte>)JniType(high-level):JniType(ReadOnlySpan<byte>)GetConstructor,GetInstanceMethod,GetStaticMethod,GetInstanceField,GetStaticFieldGetCached*variants for all of the aboveGetCachedJniType(ref JniType?, ReadOnlySpan<byte>)Performance
The span overloads use
fixedpinning and pass the pointer directly to the JNI function table — zero marshalling. Benchmarked on macOS ARM64 (Release, 1M iterations per operation, NoOpJniObjectReferenceManager):The improvement comes entirely from eliminating the
Marshal.StringToCoTaskMemUTF8+ZeroFreeCoTaskMemUTF8calls on the native heap — both paths produce identical managed objects (JniMethodInfo/JniFieldInfo).Test coverage
13 new tests covering:
GetCached*returns same instance on repeated calls