Skip to content

[Mono.Android] Obsolete JNI delegate wrapping in .NET 11 - #13128

Merged
simonrozsival merged 1 commit into
release/11.0.1xxfrom
simonrozsival-jni-wrapper-release-obsoletion
Oct 19, 2026
Merged

simonrozsival merged 1 commit into
release/11.0.1xxfrom
simonrozsival-jni-wrapper-release-obsoletion

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

Pull Request
title and
description
should follow the
commit-messages.md workflow documentation, and in particular should include:

  • Useful description of why the change is necessary.
  • Links to issues fixed
  • Unit tests

Mark Android.Runtime.JNINativeWrapper.CreateDelegate(Delegate) obsolete
with error: true in .NET 11, ahead of retiring runtime JNI delegate wrapping in .NET 12.
This is an independent PR targeting release/11.0.1xx, not a stack or
a backport of the .NET 12 implementation change. No linked issue.

Related PR: #13130 retains the same public class and method on main for legacy binary references, but replaces runtime wrapping with an obsolete, throwing stub in .NET 12. Trimmable typemaps read connector metadata and invoke marshal methods directly, without executing the legacy connectors that reference CreateDelegate. This .NET 11 PR leaves the existing implementation intact; neither PR removes the public class/method, and they are not stacked.

The change deliberately makes direct source calls fail with CS0619.
Existing compiled bindings retain the public class, method signature,
RequiresDynamicCode annotation, and the entire wrapper implementation,
including built-in delegates, Reflection.Emit fallback, exception handling,
and GC bridge processing.

The migration message directs binding authors to rebuild with the current
.NET for Android binding generator. On this release branch its generated
callbacks already use JniMarshal.SafeInvokeAction/SafeInvokeFunc.
A delegate constructor alone is not a replacement for those JNI transition
and exception-handling semantics. Add a release note explaining this.

No generator, T4, dispatch, runtime, public API baseline, or compatibility
suppression changes are needed.

Validation

Validated against release base
259e958240e3811a311f540b119b7ee5ff466fa2, with an unchanged local
baseline built before applying the annotation.

Builds use this environment:

export MSBUILDDISABLENODEREUSE=1 DOTNET_CLI_USE_MSBUILD_SERVER=0 UseSharedCompilation=false
  • git submodule update --init --recursive — passed.
  • make prepare CONFIGURATION=Release MSBUILD_ARGS='-m:1 --disable-build-servers -nr:false -p:UseSharedCompilation=false -p:AndroidSupportedTargetJitAbis=arm64-v8a -p:AndroidSupportedTargetAotAbis=arm64' — passed.
  • make all CONFIGURATION=Release MSBUILD_ARGS='-m:1 --disable-build-servers -nr:false -p:UseSharedCompilation=false -p:AndroidSupportedTargetJitAbis=arm64-v8a -p:AndroidSupportedTargetAotAbis=arm64 -p:NinjaPath=/Users/simon/.copilot/session-state/14fe2f79-c75a-46b9-8d88-1d3bff3ab69d/files/ninja-j1' — passed for the unchanged baseline and annotated SDK after building all configured API variants. The session-only Ninja wrapper executes the prepared Ninja with -j1. The initial baseline workload setup failed because API 37.1 had not yet been built; binlog replay identified the missing reference assembly. Building the configured 37.1/37.2 variants and rerunning the exact command resolved it without product changes.

The following exact commands passed for the annotated implementation
(the same 37.1/37.2 commands were also run on the unannotated baseline).
All use the release branch's configured stable API matrix and retain the
API gates:

./dotnet-local.sh build src/Mono.Android/Mono.Android.csproj -c Release -m:1 --disable-build-servers -nr:false -p:UseSharedCompilation=false -p:AndroidApiLevel=37 -p:AndroidPlatformId=37.0 -p:AndroidFrameworkVersion=v17.0 -p:IsUnstableVersion=false -p:DisableApiCompatibilityCheck=false -bl:bin/BuildRelease/obsolete-api-37.binlog
./dotnet-local.sh build src/Mono.Android/Mono.Android.csproj -c Release -m:1 --disable-build-servers -nr:false -p:UseSharedCompilation=false -p:AndroidApiLevel=37.1 -p:AndroidPlatformId=37.1 -p:AndroidFrameworkVersion=v17.1 -p:IsUnstableVersion=false -p:DisableApiCompatibilityCheck=false -bl:bin/BuildRelease/obsolete-api-37.1.binlog
./dotnet-local.sh build src/Mono.Android/Mono.Android.csproj -c Release -m:1 --disable-build-servers -nr:false -p:UseSharedCompilation=false -p:AndroidApiLevel=37.2 -p:AndroidPlatformId=37.2 -p:AndroidFrameworkVersion=v17.2 -p:IsUnstableVersion=false -p:DisableApiCompatibilityCheck=false -bl:bin/BuildRelease/obsolete-api-37.2.binlog

Results: API 37 passed with 28 existing warnings and zero errors;
37.1 and 37.2 passed with 30 existing warnings and zero errors each.
The actual compiler invocation includes PublicApiAnalyzers 5.6.0,
the shipped/unshipped API files, and RS0016/RS0017 as errors.
The existing ApiCompat gate compares API 37 against the net11.0
reference contract; its 37.1/37.2 targets run but do not perform that
reference comparison under the existing configuration.

Existing host generator tests:

DOTNET_ROLL_FORWARD=Major ./dotnet-local.sh test external/Java.Interop/tests/generator-Tests/generator-Tests.csproj -c Release -m:1 --disable-build-servers -nr:false -p:UseSharedCompilation=false '-p:GradleArgs=--stacktrace --no-daemon --max-workers=1 -Pkotlin.compiler.execution.strategy=in-process' --filter 'FullyQualifiedName~generatortests.NormalMethods|FullyQualifiedName~generatortests.NormalProperties|FullyQualifiedName~generatortests.Interfaces|Name~CallbackVariableNamesShouldntCollide' --logger 'trx;LogFileName=jni-obsolete-generator.trx' --results-directory bin/TestRelease/TestResults -- NUnit.NumberOfTestWorkers=1

Passed: 4/4, including generated method, property, and interface
callback compilation. No new product test infrastructure.

An additional session-local Roslyn/Cecil proof was built with:

./dotnet-local.sh build /Users/simon/.copilot/session-state/14fe2f79-c75a-46b9-8d88-1d3bff3ab69d/files/release-proof/release-proof.csproj -c Release -m:1 --disable-build-servers -nr:false -p:UseSharedCompilation=false -v:minimal

Passed. For each of APIs 37, 37.1, and 37.2, the proof was run as:

bin/Release/dotnet/dotnet /Users/simon/.copilot/session-state/14fe2f79-c75a-46b9-8d88-1d3bff3ab69d/files/release-proof/bin/Release/net11.0/release-proof.dll <unannotated-baseline-Mono.Android.dll> <annotated-implementation-Mono.Android.dll> <annotated-reference-Mono.Android.dll>

Passed for all three APIs, both compiler reference assemblies and
installed local workload reference packs: the baseline direct consumer
compiles, while each annotated implementation/reference consumer fails
with exactly CS0619 and the migration message. Assembly metadata confirms
the public class and Delegate CreateDelegate(Delegate) signature,
Obsolete(message, true), and unchanged RequiresDynamicCode.
All 46 wrapper/counter/nested method bodies, including locals and
exception regions, are unchanged against the corresponding baseline
after resolving IL metadata tokens.

git diff --check and git diff --cached --check — passed.
Device tests and the broader host MSBuild suites were not run:
this changes source diagnostics and metadata only, not runtime behavior.

Stage the planned .NET 12 retirement of Android.Runtime.JNINativeWrapper.CreateDelegate() on release/11.0.1xx.  Mark only the public method obsolete with error: true so direct source calls fail with CS0619 while existing compiled bindings retain the unchanged binary API and wrapper implementation.

Direct binding authors to regenerate supported JNI callbacks, which already use JniMarshal.SafeInvokeAction/SafeInvokeFunc on this branch.  Document the intentional source break and the distinction from a plain delegate constructor.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 13, 2026 18:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Existing interop documentation still contains examples that now fail with CS0619.

1 open finding
What changed in this PR

Marks legacy JNI delegate wrapping as a compile-time error in .NET 11 while preserving binary compatibility.

Changes:

  • Obsoletes JNINativeWrapper.CreateDelegate() with error: true.
  • Adds migration guidance and release notes.
File Description
JNINativeWrapper.cs Adds the obsolete diagnostic.
jni-wrapper-release-obsoletion.md Documents compatibility and migration.

🧠 Review effort: Balanced

Comment thread src/Mono.Android/Android.Runtime/JNINativeWrapper.cs
@jonathanpeppers

Copy link
Copy Markdown
Member

/review

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

✅ Android PR Reviewer completed successfully!

Generated by Android PR Reviewer for #13128

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Needs Changes — 0 errors, 1 warning, 0 suggestions.

The source-breaking obsoletion is narrowly scoped, preserves the existing implementation for binary compatibility, and the full CI run is green. The remaining issue is that both user-facing messages claim the method itself will be removed in .NET 12, contradicting the documented plan to retain it as an obsolete throwing stub; please phrase this as retirement of runtime JNI delegate wrapping instead.

Generated by Android PR Reviewer for #13128 · copilot · gpt56 · 53 AIC · ⌖ 8.57 AIC · ⊞ 26.9K
Comment /review to run again

Comment thread src/Mono.Android/Android.Runtime/JNINativeWrapper.cs
@simonrozsival simonrozsival changed the title [Mono.Android] Obsolete JNI delegate wrapping in .NET 11 [Unsafe][Mono.Android] Obsolete JNI delegate wrapping in .NET 11 Oct 15, 2026
@simonrozsival simonrozsival changed the title [Unsafe][Mono.Android] Obsolete JNI delegate wrapping in .NET 11 [Mono.Android] Obsolete JNI delegate wrapping in .NET 11 Oct 15, 2026
@simonrozsival simonrozsival added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label Oct 16, 2026
@simonrozsival
simonrozsival merged commit 2f235e3 into release/11.0.1xx Oct 19, 2026
45 checks passed
@simonrozsival
simonrozsival deleted the simonrozsival-jni-wrapper-release-obsoletion branch October 19, 2026 07:37
simonrozsival added a commit that referenced this pull request Oct 21, 2026
Retire runtime JNI delegate wrapping in .NET for Android 12, **without removing the shipped public API**. Legacy generated connector methods can reference `Android.Runtime.JNINativeWrapper.CreateDelegate(Delegate)` even though trimmable typemaps read connector metadata and invoke the marshal method directly instead of executing the connector.

Retain the public class and exact `Delegate CreateDelegate(Delegate)` signature as `[Obsolete(..., error: true)]`, and always throw `NotSupportedException` if it is called. Existing binary references therefore resolve; executing the unsupported API is a deliberate behavior break, and new direct source calls fail with CS0619. Remove `RequiresDynamicCode`, because the stub no longer generates code.

**Related PR:** [#13128](#13128) targets `release/11.0.1xx` and marks the method obsolete while retaining its existing .NET 11 implementation. This independent `main` PR replaces wrapping with the throwing stub in .NET 12. Neither PR removes the public class/method; they are not stacked.

Remove all 40 built-in wrappers, private dispatch/exception helpers, Reflection.Emit fallback, dynamic-method counter, and canonical wrapper T4 input/output. Preserve shared `_JniMarshal_*` delegates, supported generated marshal callbacks, connector metadata, constructor activation, exception handling, GC bridge processing, and reference ownership. Generator changes are comments only. No framework/package/runtime pin upgrades. No linked issue.

The earlier revision removed the class and method. This revision restores them and removes all six PublicAPI removal records and the missing-type ApiCompat allowance. The only new compatibility baseline entry accepts removal of the class's compiler-generated `ExtensionAttribute`, which came from the deleted internal extension wrappers; no public API removal is suppressed.

Restore and update `Documentation/project-docs/exception-handling.md` rather than deleting it. The guide now explains current CoreCLR/NativeAOT callback boundaries, pending Java exception translation, synchronous round trips, notification-hook differences, and debugger integration, with implementation and regression-test links. Drop the standalone JNI delegate-wrapper release note in response to review feedback.

Two new runtime regressions use an actual legacy-shaped connector whose body references `JNINativeWrapper.CreateDelegate`: invoking the connector directly throws `NotSupportedException`, while a Java `hashCode()` call returns 42 through the marshal callback without invoking that connector. Both run under CoreCLR and NativeAOT.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants