Skip to content

Commit 909239d

Browse files
authored
[generator] Add support for peerConstructorPartialMethod (#1087)
Context: #1085 Context: dotnet/runtime#82121 Some Java objects are *big*, e.g. [`Bitmap`][0] instances, but as far as MonoVM is concerned, the `Bitmap` instances are *tiny*: a few pointers, and that's it. MonoVM doesn't know that it could be referencing several MB of data in the Java VM. MonoVM is gaining support for [`GC.AddMemoryPressure()`][1] and [`GC.RemoveMemoryPressure()`][2], which potentially allows for the parent of all cross-VM GC integrations: using the `GC` methods to inform MonoVM of how much non-managed memory has been allocated. This could allow MonoVM to collect more frequently when total process memory is low. How should we call `GC.AddMemoryPressure()` and `GC.RemoveMemoryPressure()`? `GC.RemoveMemoryPressure()` is straightforward: a class can override `Java.Lang.Object.Dispose(bool)`. `GC.AddMemoryPressure()` is the problem: where and how can it be called from a class binding? This is trickier, as there was no way to have custom code called by the bound type. Instead, various forms of "hacky workarounds" are often employed, e.g. copying `generator`-emitted content into a `partial` class, then using `metadata` to *prevent* `generator` from binding those members. It's all around fugly. Fortunately C# has a solution: [`partial` methods][3]! Add support for a `peerConstructorPartialMethod` metadata entry, applicable to `<class/>` elements, which contains the name of a `partial` method to both declare and invoke from the "peer constructor": <attr path="//class[@name='Bitmap']" name="peerConstructorPartialMethod" >_OnBitmapCreated</attr> This will alter our existing "peer constructor" generation code, a'la: partial class Bitmap : Java.Lang.Object { internal Bitmap (IntPtr h, JniHandleOwnership t) : base (h, t) { } } to instead become: partial class Bitmap : Java.Lang.Object { partial void _OnBitmapCreated (); internal Bitmap (IntPtr h, JniHandleOwnership t) : base (h, t) { _OnBitmapCreated (); } } This allows a hand-written `partial class Bitmap` to do: // Hand-written code partial class Bitmap { int _memoryPressure; partial void _OnBitmapCreated () { _memoryPressure = ByteCount; GC.AddMemoryPressure (_memoryPressure); } protected override void Dispose (bool disposing) { if (_memoryPressure != 0) { GC.RemoveMemoryPressure (_memoryPressure); _memoryPressure = 0; } } } TODO: "extend" this for `<method/>`s as well? [0]: https://developer.android.com/reference/android/graphics/Bitmap [1]: https://learn.microsoft.com/dotnet/api/system.gc.addmemorypressure?view=net-7.0 [2]: https://learn.microsoft.com/dotnet/api/system.gc.removememorypressure?view=net-7.0 [3]: https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/partial-method
1 parent 8f3fe62 commit 909239d

File tree

7 files changed

+67
-3
lines changed

7 files changed

+67
-3
lines changed

src/Xamarin.SourceWriter/Models/MethodWriter.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public class MethodWriter : ISourceWriter, ITakeParameters
1919
public List<string> Body { get; set; } = new List<string> ();
2020
public bool IsSealed { get; set; }
2121
public bool IsStatic { get; set; }
22+
public bool IsPartial { get; set; }
2223
public bool IsPrivate { get => visibility == Visibility.Private; set => visibility = value ? Visibility.Private : Visibility.Default; }
2324
public bool IsProtected { get => visibility == Visibility.Protected; set => visibility = value ? Visibility.Protected : Visibility.Default; }
2425
public bool IsOverride { get; set; }
@@ -103,6 +104,9 @@ public virtual void WriteSignature (CodeWriter writer)
103104
if (IsUnsafe)
104105
writer.Write ("unsafe ");
105106

107+
if (IsPartial)
108+
writer.Write ("partial ");
109+
106110
WriteReturnType (writer);
107111

108112
if (ExplicitInterfaceImplementation.HasValue ())

tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,34 @@ public void CompatVirtualMethod_Class ()
376376
Assert.True (writer.ToString ().NormalizeLineEndings ().Contains ("catch (Java.Lang.NoSuchMethodError) { throw new Java.Lang.AbstractMethodError (__id); }".NormalizeLineEndings ()), $"was: `{writer}`");
377377
}
378378

379+
[Test]
380+
public void PeerConstructorPartialMethod_Class ()
381+
{
382+
var xml = @"<api>
383+
<package name='java.lang' jni-name='java/lang'>
384+
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
385+
</package>
386+
<package name='com.xamarin.android' jni-name='com/xamarin/android'>
387+
<class abstract='false' deprecated='not deprecated'
388+
extends='java.lang.Object' extends-generic-aware='java.lang.Object' jni-extends='Ljava/lang/Object;'
389+
peerConstructorPartialMethod='_OnMyClassCreated'
390+
jni-signature='Lcom/xamarin/android/MyClass;'
391+
name='MyClass'
392+
final='false' static='false' visibility='public'>
393+
</class>
394+
</package>
395+
</api>";
396+
397+
var gens = ParseApiDefinition (xml);
398+
var klass = gens.Single (g => g.Name == "MyClass");
399+
400+
generator.Context.ContextTypes.Push (klass);
401+
generator.WriteType (klass, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
402+
generator.Context.ContextTypes.Pop ();
403+
404+
Assert.True (writer.ToString ().NormalizeLineEndings ().Contains ("partial void _OnMyClassCreated ();".NormalizeLineEndings ()), $"was: `{writer}`");
405+
Assert.True (writer.ToString ().NormalizeLineEndings ().Contains ("{ _OnMyClassCreated (); }".NormalizeLineEndings ()), $"was: `{writer}`");
406+
}
379407
[Test]
380408
public void WriteDuplicateInterfaceEventArgs ()
381409
{

tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ public static ClassGen CreateClass (XElement pkg, XElement elem, CodeGenerationO
110110
FromXml = true,
111111
IsAbstract = elem.XGetAttribute ("abstract") == "true",
112112
IsFinal = elem.XGetAttribute ("final") == "true",
113+
PeerConstructorPartialMethod = elem.XGetAttribute ("peerConstructorPartialMethod"),
113114
// Only use an explicitly set XML attribute
114115
Unnest = elem.XGetAttribute ("unnest") == "true" ? true :
115116
elem.XGetAttribute ("unnest") == "false" ? false :

tools/generator/Java.Interop.Tools.Generator.ObjectModel/ClassGen.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,8 @@ public bool IsExplicitlyImplementedMethod (string sig)
302302

303303
public bool NeedsNew { get; set; }
304304

305+
public string PeerConstructorPartialMethod { get; set; }
306+
305307
protected override bool OnValidate (CodeGenerationOptions opt, GenericParameterDefinitionList type_params, CodeGeneratorContext context)
306308
{
307309
if (validated)

tools/generator/SourceWriters/BoundClass.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,12 @@ void AddBindingInfrastructure (ClassGen klass, CodeGenerationOptions opt)
126126
void AddConstructors (ClassGen klass, CodeGenerationOptions opt, CodeGeneratorContext context)
127127
{
128128
// Add required constructor for all JLO inheriting classes
129-
if (klass.FullName != "Java.Lang.Object" && klass.InheritsObject)
130-
Constructors.Add (new JavaLangObjectConstructor (klass, opt));
129+
if (klass.FullName != "Java.Lang.Object" && klass.InheritsObject) {
130+
if (!string.IsNullOrWhiteSpace (klass.PeerConstructorPartialMethod)) {
131+
Methods.Add (new ConstructorPartialMethod (klass.PeerConstructorPartialMethod));
132+
}
133+
Constructors.Add (new JavaLangObjectConstructor (klass, opt, klass.PeerConstructorPartialMethod));
134+
}
131135

132136
foreach (var ctor in klass.Ctors) {
133137
// Don't bind final or protected constructors
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using MonoDroid.Generation;
7+
using Xamarin.Android.Binder;
8+
using Xamarin.SourceWriter;
9+
10+
namespace generator.SourceWriters
11+
{
12+
public class ConstructorPartialMethod : MethodWriter
13+
{
14+
public ConstructorPartialMethod (string partialMethodName)
15+
{
16+
Name = partialMethodName;
17+
IsPartial = true;
18+
IsDeclaration = true;
19+
ReturnType = new TypeReferenceWriter ("void");
20+
}
21+
}
22+
}

tools/generator/SourceWriters/JavaLangObjectConstructor.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace generator.SourceWriters
1111
{
1212
public class JavaLangObjectConstructor : ConstructorWriter
1313
{
14-
public JavaLangObjectConstructor (ClassGen klass, CodeGenerationOptions opt)
14+
public JavaLangObjectConstructor (ClassGen klass, CodeGenerationOptions opt, string callPartialMethod)
1515
{
1616
Name = klass.Name;
1717

@@ -31,6 +31,9 @@ public JavaLangObjectConstructor (ClassGen klass, CodeGenerationOptions opt)
3131

3232
BaseCall = "base (javaReference, transfer)";
3333
}
34+
if (!string.IsNullOrWhiteSpace (callPartialMethod)) {
35+
Body.Add ($"{callPartialMethod} ();");
36+
}
3437
}
3538
}
3639
}

0 commit comments

Comments
 (0)