Skip to content

[Mono.Android] call new Java "GC Bridge" APIs #10125

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 29 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
0ae638d
[Mono.Android] call new Java "GC Bridge" APIs
jonathanpeppers May 8, 2025
f4a2148
What if everything was managed-only?
jonathanpeppers May 8, 2025
aa0a4ae
Now uses: https://github.com/dotnet/java-interop/pull/1334
jonathanpeppers May 12, 2025
0bc80b2
Revert "What if everything was managed-only?"
jonathanpeppers May 19, 2025
8f9c0fc
Setup `g_bpFinishCallback`
jonathanpeppers May 19, 2025
fda9d43
Update src/native/clr/host/internal-pinvokes.cc
jonathanpeppers May 20, 2025
eba552f
Update src/native/clr/host/internal-pinvokes.cc
jonathanpeppers May 20, 2025
4313f89
dotnet/runtime builds from BrzVlad:runtime:feature-clr-gcbridge
jonathanpeppers May 20, 2025
f4f8dcf
Add native portion of the GC interface
grendello May 21, 2025
4d8bd88
Don't need this anymore
grendello May 21, 2025
fccda54
Enable git lfs on AzDO
jonathanpeppers May 21, 2025
5b2e13e
Enable `$(system.debug)`
jonathanpeppers May 21, 2025
461d127
Revert "Enable `$(system.debug)`"
jonathanpeppers May 21, 2025
6cac797
Revert "Enable git lfs on AzDO"
jonathanpeppers May 21, 2025
0654d76
Update azure-pipelines.yaml
jonathanpeppers May 21, 2025
a0f2561
Working on yaml/git lfs
jonathanpeppers May 21, 2025
0221203
git lfs working directory
jonathanpeppers May 21, 2025
c990184
workingDirectory: ${{ parameters.xaSourcePath }}
jonathanpeppers May 21, 2025
f42f328
`git lfs install`
jonathanpeppers May 21, 2025
0ebf234
[native\mono] use `JniObjectReferenceControlBlock` structure
jonathanpeppers May 21, 2025
ba60ff7
Update to APIs from BrzVlad's branch
jonathanpeppers May 21, 2025
02c2562
Some updates to the recent native code changes
grendello May 21, 2025
664be1a
android-x64 runtime pack
jonathanpeppers May 21, 2025
96e1b9c
Remove comment that no longer applies
grendello May 22, 2025
75cfd4d
git lfs for test lanes
jonathanpeppers May 22, 2025
d51469c
Copy `packages` folder
jonathanpeppers May 22, 2025
1c25938
On-device tests use `custom-runtime.targets`
jonathanpeppers May 22, 2025
5023378
Update pinvoke-tables.include
jonathanpeppers May 22, 2025
1fba234
Let's see...
grendello May 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ Makefile eol=lf
*.wixproj eol=crlf
*.wxs eol=crlf
*.rtf eol=crlf

packages/* filter=lfs diff=lfs merge=lfs -text
25 changes: 19 additions & 6 deletions Documentation/workflow/DevelopmentTips.md
Original file line number Diff line number Diff line change
Expand Up @@ -450,13 +450,26 @@ A second (better) way is to add this MSBuild target to your Android
`.csproj` file:

```xml
<Target Name="UpdateMonoRuntimePacks" BeforeTargets="ProcessFrameworkReferences">
<Target Name="UpdateRuntimeVersion" BeforeTargets="ProcessFrameworkReferences">
<PropertyGroup>
<!-- This could be a version I built myself -->
<_Version>10.0.0-dev</_Version>
</PropertyGroup>
<ItemGroup>
<KnownRuntimePack
Update="Microsoft.NETCore.App"
Condition=" '%(KnownRuntimePack.TargetFramework)' == 'net6.0' "
LatestRuntimeFrameworkVersion="6.0.0-preview.7.21364.3"
/>
<!-- For runtime packs only -->
<KnownRuntimePack
Update="Microsoft.NETCore.App"
Condition=" '%(KnownRuntimePack.TargetFramework)' == 'net10.0' "
LatestRuntimeFrameworkVersion="$(_Version)"
/>
<!-- For new .NET APIs -->
<KnownFrameworkReference
Update="Microsoft.NETCore.App"
Condition=" '%(KnownFrameworkReference.TargetingPackName)' == 'Microsoft.NETCore.App.Ref' and '%(KnownFrameworkReference.TargetFramework)' == 'net10.0' "
DefaultRuntimeFrameworkVersion="$(_Version)"
LatestRuntimeFrameworkVersion="$(_Version)"
TargetingPackVersion="$(_Version)"
/>
</ItemGroup>
</Target>
```
Expand Down
1 change: 1 addition & 0 deletions NuGet.config
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<!-- End: Package sources from dotnet-android -->
<!--End: Package sources managed by Dependency Flow automation. Do not edit the sources above.-->
<!-- ensure only the sources defined below are used -->
<add key="local-packages" value="packages" />
<add key="dotnet-public" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json" protocolVersion="3" />
<add key="dotnet-eng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" protocolVersion="3" />
<!-- This is for packages needed by debugger-libs -->
Expand Down
6 changes: 6 additions & 0 deletions build-tools/automation/yaml-templates/build-linux.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ stages:
path: ${{ parameters.checkoutPath }}
persistCredentials: ${{ parameters.checkoutPersistCredentials }}

- script: |
git lfs install
git lfs pull
displayName: git lfs setup
workingDirectory: ${{ parameters.xaSourcePath }}

# Always checkout a second resource to ensure we are using multi-repo checkout behavior
# https://learn.microsoft.com/en-us/azure/devops/pipelines/repos/multi-repo-checkout?view=azure-devops#checkout-path
- checkout: maui
Expand Down
6 changes: 6 additions & 0 deletions build-tools/automation/yaml-templates/build-macos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ stages:
path: ${{ parameters.checkoutPath }}
persistCredentials: ${{ parameters.checkoutPersistCredentials }}

- script: |
git lfs install
git lfs pull
displayName: git lfs setup
workingDirectory: ${{ parameters.xaSourcePath }}

- template: /build-tools/automation/yaml-templates/commercial-build.yaml
parameters:
xaSourcePath: ${{ parameters.xaSourcePath }}
Expand Down
6 changes: 6 additions & 0 deletions build-tools/automation/yaml-templates/build-windows.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ stages:
path: ${{ parameters.checkoutPath }}
persistCredentials: ${{ parameters.checkoutPersistCredentials }}

- script: |
git lfs install
git lfs pull
displayName: git lfs setup
workingDirectory: ${{ parameters.xaSourcePath }}

- template: /build-tools/automation/yaml-templates/kill-processes.yaml

- template: /build-tools/automation/yaml-templates/clean.yaml
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ steps:
clean: true
submodules: recursive

- script: |
git lfs install
git lfs pull
displayName: git lfs setup
workingDirectory: ${{ parameters.xaSourcePath }}

- template: /build-tools/automation/yaml-templates/setup-jdk-variables.yaml
parameters:
jdkMajorVersion: ${{ parameters.jdkMajorVersion }}
Expand Down
22 changes: 22 additions & 0 deletions build-tools/scripts/custom-runtime.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project>
<!-- Use version in local packages folder -->
<Target Name="UpdateRuntimeVersion" BeforeTargets="ProcessFrameworkReferences">
<PropertyGroup>
<_Version>10.0.0-dev</_Version>
</PropertyGroup>
<ItemGroup>
<KnownFrameworkReference
Update="Microsoft.NETCore.App"
Condition=" '%(KnownFrameworkReference.TargetingPackName)' == 'Microsoft.NETCore.App.Ref' and '%(KnownFrameworkReference.TargetFramework)' == 'net10.0' "
DefaultRuntimeFrameworkVersion="$(_Version)"
LatestRuntimeFrameworkVersion="$(_Version)"
TargetingPackVersion="$(_Version)"
/>
<KnownRuntimePack
Update="Microsoft.NETCore.App"
Condition=" '%(KnownRuntimePack.TargetFramework)' == 'net10.0' "
LatestRuntimeFrameworkVersion="$(_Version)"
/>
</ItemGroup>
</Target>
</Project>
3 changes: 3 additions & 0 deletions packages/Microsoft.NETCore.App.Ref.10.0.0-dev.nupkg
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
1 change: 1 addition & 0 deletions src/Mono.Android.Runtime/Mono.Android.Runtime.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,6 @@
<ProjectReference Include="..\..\external\Java.Interop\src\Java.Interop\Java.Interop.csproj" />
</ItemGroup>

<Import Project="..\..\build-tools\scripts\custom-runtime.targets" />
<Import Project="Mono.Android.Runtime.targets" />
</Project>
14 changes: 13 additions & 1 deletion src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Java;
using System.Text;

namespace Android.Runtime
Expand All @@ -18,7 +19,7 @@ enum TraceKind : uint
All = Java | Managed | Native | Signals,
}

internal static class RuntimeNativeMethods
internal unsafe static class RuntimeNativeMethods
{
[DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)]
internal extern static void monodroid_log (LogLevel level, LogCategories category, string message);
Expand Down Expand Up @@ -92,6 +93,17 @@ internal static class RuntimeNativeMethods
[DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)]
internal static extern bool clr_typemap_java_to_managed (string java_type_name, out IntPtr managed_assembly_name, out uint managed_type_token_id);

/// <summary>
/// TODO: implement this in the native side.
/// Initializes the "GC Bridge" implementation for the CoreCLR runtime.
/// </summary>
/// <param name="bridge_processing_finished_callback">A function pointer to a C# callback that will be invoked when bridge processing has completed.</param>
/// <returns>A function pointer that should be passed to JavaMarshal.Initialize() on startup.</returns>
[DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)]
internal static extern delegate* unmanaged<MarkCrossReferences*, void> clr_initialize_gc_bridge (
delegate* unmanaged<MarkCrossReferences*, void> bridge_processing_finished_callback
);

[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern void monodroid_unhandled_exception (Exception javaException);

Expand Down
73 changes: 54 additions & 19 deletions src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Java;
using System.Threading;
using Android.Runtime;
using Java.Interop;
Expand All @@ -20,10 +21,12 @@ class ManagedValueManager : JniRuntime.JniValueManager
{
const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;

Dictionary<int, List<IJavaPeerable>>? RegisteredInstances = new Dictionary<int, List<IJavaPeerable>>();
Dictionary<int, List<GCHandle>>? RegisteredInstances = new ();

internal ManagedValueManager ()
internal unsafe ManagedValueManager ()
{
var mark_cross_references_ftn = RuntimeNativeMethods.clr_initialize_gc_bridge (&BridgeProcessingFinished);
JavaMarshal.Initialize (mark_cross_references_ftn);
}

public override void WaitForGCBridgeProcessing ()
Expand All @@ -35,7 +38,7 @@ public override void CollectPeers ()
if (RegisteredInstances == null)
throw new ObjectDisposedException (nameof (ManagedValueManager));

var peers = new List<IJavaPeerable> ();
var peers = new List<GCHandle> ();

lock (RegisteredInstances) {
foreach (var ps in RegisteredInstances.Values) {
Expand All @@ -48,7 +51,8 @@ public override void CollectPeers ()
List<Exception>? exceptions = null;
foreach (var peer in peers) {
try {
peer.Dispose ();
if (peer.Target is IDisposable disposable)
disposable.Dispose ();
}
catch (Exception e) {
exceptions = exceptions ?? new List<Exception> ();
Expand All @@ -74,33 +78,35 @@ public override void AddPeer (IJavaPeerable value)
}
int key = value.JniIdentityHashCode;
lock (RegisteredInstances) {
List<IJavaPeerable>? peers;
List<GCHandle>? peers;
if (!RegisteredInstances.TryGetValue (key, out peers)) {
peers = new List<IJavaPeerable> () {
value,
peers = new List<GCHandle> () {
CreateReferenceTrackingHandle (value)
};
RegisteredInstances.Add (key, peers);
return;
}

for (int i = peers.Count - 1; i >= 0; i--) {
var p = peers [i];
if (!JniEnvironment.Types.IsSameObject (p.PeerReference, value.PeerReference))
if (p.Target is not IJavaPeerable peer)
continue;
if (!JniEnvironment.Types.IsSameObject (peer.PeerReference, value.PeerReference))
continue;
if (Replaceable (p)) {
peers [i] = value;
peers [i] = CreateReferenceTrackingHandle (value);
} else {
WarnNotReplacing (key, value, p);
WarnNotReplacing (key, value, peer);
}
return;
}
peers.Add (value);
peers.Add (CreateReferenceTrackingHandle (value));
}
}

static bool Replaceable (IJavaPeerable peer)
static bool Replaceable (GCHandle handle)
{
if (peer == null)
if (handle.Target is not IJavaPeerable peer)
return true;
return peer.JniManagedPeerState.HasFlag (JniManagedPeerStates.Replaceable);
}
Expand Down Expand Up @@ -132,14 +138,14 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal
int key = GetJniIdentityHashCode (reference);

lock (RegisteredInstances) {
List<IJavaPeerable>? peers;
List<GCHandle>? peers;
if (!RegisteredInstances.TryGetValue (key, out peers))
return null;

for (int i = peers.Count - 1; i >= 0; i--) {
var p = peers [i];
if (JniEnvironment.Types.IsSameObject (reference, p.PeerReference))
return p;
if (p.Target is IJavaPeerable peer && JniEnvironment.Types.IsSameObject (reference, peer.PeerReference))
return peer;
}
if (peers.Count == 0)
RegisteredInstances.Remove (key);
Expand All @@ -157,14 +163,15 @@ public override void RemovePeer (IJavaPeerable value)

int key = value.JniIdentityHashCode;
lock (RegisteredInstances) {
List<IJavaPeerable>? peers;
List<GCHandle>? peers;
if (!RegisteredInstances.TryGetValue (key, out peers))
return;

for (int i = peers.Count - 1; i >= 0; i--) {
var p = peers [i];
if (object.ReferenceEquals (value, p)) {
if (object.ReferenceEquals (value, p.Target)) {
peers.RemoveAt (i);
FreeHandle (p);
}
}
if (peers.Count == 0)
Expand Down Expand Up @@ -251,13 +258,41 @@ public override List<JniSurfacedPeerInfo> GetSurfacedPeers ()
var peers = new List<JniSurfacedPeerInfo> (RegisteredInstances.Count);
foreach (var e in RegisteredInstances) {
foreach (var p in e.Value) {
peers.Add (new JniSurfacedPeerInfo (e.Key, new WeakReference<IJavaPeerable> (p)));
if (p.Target is not IJavaPeerable peer)
continue;
peers.Add (new JniSurfacedPeerInfo (e.Key, new WeakReference<IJavaPeerable> (peer)));
}
}
return peers;
}
}

static GCHandle CreateReferenceTrackingHandle (IJavaPeerable value) =>
JavaMarshal.CreateReferenceTrackingHandle (value, value.JniObjectReferenceControlBlock);

static unsafe void FreeHandle (GCHandle handle)
{
IntPtr context = JavaMarshal.GetContext (handle);
NativeMemory.Free ((void*) context);
}

[UnmanagedCallersOnly]
internal static unsafe void BridgeProcessingFinished (MarkCrossReferences* mcr)
{
List<GCHandle> handlesToFree = [];
for (int i = 0; i < mcr->ComponentsLen; i++)
{
for (int j = 0; j < mcr->Components [i].Count; j++)
{
IntPtr *pContext = (IntPtr*) mcr->Components [i].Context [j];
handlesToFree.Add (GCHandle.FromIntPtr (*pContext));
NativeMemory.Free (pContext);
}
}

JavaMarshal.FinishCrossReferenceProcessing (mcr, CollectionsMarshal.AsSpan (handlesToFree));
}

const BindingFlags ActivationConstructorBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

static readonly Type[] XAConstructorSignature = new Type [] { typeof (IntPtr), typeof (JniHandleOwnership) };
Expand Down
1 change: 1 addition & 0 deletions src/Mono.Android/Mono.Android.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
<Import Project="..\Xamarin.Android.NamingCustomAttributes\Xamarin.Android.NamingCustomAttributes.projitems" Label="Shared" Condition="Exists('..\Xamarin.Android.NamingCustomAttributes\Xamarin.Android.NamingCustomAttributes.projitems')" />
<Import Project="Mono.Android.targets" />
<Import Project="..\..\build-tools\scripts\JavaCallableWrappers.targets" />
<Import Project="..\..\build-tools\scripts\custom-runtime.targets" />
<Import Project="$(IntermediateOutputPath)mcw\Mono.Android.projitems" Condition="Exists('$(IntermediateOutputPath)mcw\Mono.Android.projitems')" />

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,17 @@ public void CopyNuGetConfig (string relativeDirectory)
doc.Save (projNugetConfig);
}
}

// Copy packages folder
var repoPackages = Path.Combine (XABuildPaths.TopDirectory, "packages");
var projPackages = Path.Combine (Root, relativeDirectory, "packages");
if (Directory.Exists (repoPackages)) {
Directory.CreateDirectory (projPackages);
foreach (var package in Directory.GetFiles (repoPackages, "*.nupkg")) {
var destination = Path.Combine (projPackages, Path.GetFileName (package));
File.Copy (package, destination, overwrite: true);
}
}
}

/// <summary>
Expand Down
1 change: 1 addition & 0 deletions src/native/clr/host/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ set(XAMARIN_NET_ANDROID_STATIC_LIB "${XAMARIN_NET_ANDROID_LIB}-static")

set(XAMARIN_MONODROID_SOURCES
assembly-store.cc
gc-bridge.cc
host.cc
host-jni.cc
host-util.cc
Expand Down
Loading
Loading