Repository navigation
[Mono.Android] Obsolete JNI delegate wrapping in .NET 11 - #13128
Conversation
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>
There was a problem hiding this comment.
🟡 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()witherror: 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
|
/review |
|
✅ Android PR Reviewer completed successfully!
|
There was a problem hiding this comment.
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
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.

Pull Request
title and
description
should follow the
commit-messages.mdworkflow documentation, and in particular should include:Mark
Android.Runtime.JNINativeWrapper.CreateDelegate(Delegate)obsoletewith
error: truein .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
mainfor 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 referenceCreateDelegate. 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,
RequiresDynamicCodeannotation, 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 localbaseline built before applying the annotation.
Builds use this environment:
export MSBUILDDISABLENODEREUSE=1 DOTNET_CLI_USE_MSBUILD_SERVER=0 UseSharedCompilation=falsegit 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:
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.0reference contract; its 37.1/37.2 targets run but do not perform that
reference comparison under the existing configuration.
Existing host generator tests:
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:
Passed. For each of APIs 37, 37.1, and 37.2, the proof was run as:
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 unchangedRequiresDynamicCode.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 --checkandgit 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.