Skip to content
This repository was archived by the owner on Jul 6, 2026. It is now read-only.

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

7 files changed

Lines changed: 67 additions & 3 deletions

File tree

‎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
publicList<string>Body{get;set;}=newList<string>();
2020
publicboolIsSealed{get;set;}
2121
publicboolIsStatic{get;set;}
22+
publicboolIsPartial{get;set;}
2223
publicboolIsPrivate{get=>visibility==Visibility.Private;set=>visibility=value?Visibility.Private:Visibility.Default;}
2324
publicboolIsProtected{get=>visibility==Visibility.Protected;set=>visibility=value?Visibility.Protected:Visibility.Default;}
2425
publicboolIsOverride{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+
publicvoidPeerConstructorPartialMethod_Class()
381+
{
382+
varxml=@"<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+
vargens=ParseApiDefinition(xml);
398+
varklass=gens.Single(g =>g.Name=="MyClass");
399+
400+
generator.Context.ContextTypes.Push(klass);
401+
generator.WriteType(klass,string.Empty,newGenerationInfo("","","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
publicvoidWriteDuplicateInterfaceEventArgs()
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
publicboolNeedsNew{get;set;}
304304

305+
publicstringPeerConstructorPartialMethod{get;set;}
306+
305307
protectedoverrideboolOnValidate(CodeGenerationOptionsopt,GenericParameterDefinitionListtype_params,CodeGeneratorContextcontext)
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
voidAddConstructors(ClassGenklass,CodeGenerationOptionsopt,CodeGeneratorContextcontext)
127127
{
128128
// Add required constructor for all JLO inheriting classes
129-
if(klass.FullName!="Java.Lang.Object"&&klass.InheritsObject)
130-
Constructors.Add(newJavaLangObjectConstructor(klass,opt));
129+
if(klass.FullName!="Java.Lang.Object"&&klass.InheritsObject){
130+
if(!string.IsNullOrWhiteSpace(klass.PeerConstructorPartialMethod)){
131+
Methods.Add(newConstructorPartialMethod(klass.PeerConstructorPartialMethod));
132+
}
133+
Constructors.Add(newJavaLangObjectConstructor(klass,opt,klass.PeerConstructorPartialMethod));
134+
}
131135

132136
foreach(varctorinklass.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+
usingSystem;
2+
usingSystem.Collections.Generic;
3+
usingSystem.Linq;
4+
usingSystem.Text;
5+
usingSystem.Threading.Tasks;
6+
usingMonoDroid.Generation;
7+
usingXamarin.Android.Binder;
8+
usingXamarin.SourceWriter;
9+
10+
namespacegenerator.SourceWriters
11+
{
12+
publicclassConstructorPartialMethod:MethodWriter
13+
{
14+
publicConstructorPartialMethod(stringpartialMethodName)
15+
{
16+
Name=partialMethodName;
17+
IsPartial=true;
18+
IsDeclaration=true;
19+
ReturnType=newTypeReferenceWriter("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
publicclassJavaLangObjectConstructor:ConstructorWriter
1313
{
14-
publicJavaLangObjectConstructor(ClassGenklass,CodeGenerationOptionsopt)
14+
publicJavaLangObjectConstructor(ClassGenklass,CodeGenerationOptionsopt,stringcallPartialMethod)
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)