Skip to content

Commit 6bc87e8

Browse files
authored
[jcw-gen] Use + for nested types, not / (#1304)
Context: dotnet/android#9747 Context: https://discord.com/channels/732297728826277939/732297837953679412/1336353039031734352 Context: https://discord.com/channels/732297728826277939/732297837953679412/1336358257769316372 Context: #1302 Context: dotnet/android#9750 The `[Register]` attribute provides "connector method" names, and for interface methods this will also include the name of the type which declares the method, which itself may be in a nested type: namespace Android.App { public partial class Application { public partial interface IActivityLifecycleCallbacks : IJavaObject, IJavaPeerable { [Register ( name: "onActivityCreated", signature: "(Landroid/app/Activity;Landroid/os/Bundle;)V", connector: "GetOnActivityCreated_Landroid_app_Activity_Landroid_os_Bundle_Handler:Android.App.Application/IActivityLifecycleCallbacksInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null")] void OnActivityCreated (Android.App.Activity activity, Android.OS.Bundle? savedInstanceState); // … } } } The `connector` value is copied as-is into Java Callable Wrappers, as part of the `__md_methods` value and `Runtime.register()` call. Given the C# type: partial class MauiApplication : Application { partial class ActivityLifecycleCallbacks : Java.Lang.Object, Application.IActivityLifecycleCallbacks { public void OnActivityCreated (Activity activity, Bundle? savedInstanceState) => … } } then `Java.Interop.Tools.JavaCallableWrappers` will produce: // Java Callable Wrapper /* partial */ class MauiApplication_ActivityLifecycleCallbacks { public static final String __md_methods; static { __md_methods = // … "n_onActivityCreated:(Landroid/app/Activity;Landroid/os/Bundle;)V:GetOnActivityCreated_Landroid_app_Activity_Landroid_os_Bundle_Handler:Android.App.Application/IActivityLifecycleCallbacksInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\n" + // … ""; mono.android.Runtime.register ("Microsoft.Maui.MauiApplication+ActivityLifecycleCallbacks, Microsoft.Maui", MauiApplication_ActivityLifecycleCallbacks.class, __md_methods); } } The `signature` and `connector` values from the `[Register(…)]` on the method declaration are copied as-is into `__md_methods`. As the `connector` value contains a `/`, `__md_methods` does as well. This has worked fine for nearly 15+ years…on Mono/MonoVM. This *fails* on NativeAOT and CoreCLR, as: Type.GetType ("Android.App.Application/IActivityLifecycleCallbacksInvoker, Mono.Android, …", throwOnError:true) fails with: TypeLoadException: Could not resolve type 'Android.App.Application/IActivityLifecycleCallbacksInvoker' in assembly 'Mono.Android, …'. The reason for the failure is that when using Reflection APIs such as `Type.GetType()`, the [`Type.AssemblyQualifiedName` grammar][0] must be followed, and that grammar uses `+` to "Precede a nested class", *not* `/`. (`/` isn't even in the Reflection grammar!) (Aside: where does `/` come from then? It's the *IL* separator for nested types!) For eventual CoreCLR and NativeAOT support, then, we need to replace `/` with `+` *somewhere* before it hits `Type.GetType()`. There are (at least?) three places to do so: 1. Within `JniRuntime.JniTypeManager.RegisterNativeMembers()` or equivalent override. 2. Within `generator`, updating the contents of `[Register]`. 3. Within `Java.Interop.Tools.JavaCallableWrappers`. (1) is rejected out of hand as it would be additional work done on- device at runtime. Why do that if we don't need to? (2) was attempted in #1302 & dotnet/android#9750. It turned into a bit of a boondoggle, because there are lots of linker steps which interpret the `connector` value on `[Register]`, and it "just worked" that the `connector` value contained IL names, as the linker steps deal in IL! Trying to update `connector` to instead contain Reflection syntax required finding all the places in the linker that used `connector` values, which was tedious & brittle. "Just" (2) is also inadequate, as it would require new binding assemblies to take advantage of, so (2) *also* needed (3). Which brings us to (3), the current approach: *don't* alter the semantics of the `connect` value within `[Register]`, and instead require that `Java.Interop.Tools.JavaCallableWrappers` replace all instances of `/` with `+` within `__md_methods`. This is needed *anyway*, for compatibility with existing bindings, and also avoids lots of pain that (2) encountered. With this approach, `generator` output is unchanged, and `jcw-gen` output for `MauiApplication.ActivityLifecycleCallbacks` becomes: // Java Callable Wrapper /* partial */ class MauiApplication_ActivityLifecycleCallbacks { public static final String __md_methods; static { __md_methods = // … "n_onActivityCreated:(Landroid/app/Activity;Landroid/os/Bundle;)V:GetOnActivityCreated_Landroid_app_Activity_Landroid_os_Bundle_Handler:Android.App.Application+IActivityLifecycleCallbacksInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\n" + // … ""; mono.android.Runtime.register ("Microsoft.Maui.MauiApplication+ActivityLifecycleCallbacks, Microsoft.Maui", MauiApplication_ActivityLifecycleCallbacks.class, __md_methods); } } [0]: https://learn.microsoft.com/en-us/dotnet/api/system.type.assemblyqualifiedname?view=net-9.0#remarks
1 parent dd3c1d0 commit 6bc87e8

File tree

7 files changed

+96
-6
lines changed

7 files changed

+96
-6
lines changed

src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers.Adapters/CecilImporter.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ static CallableWrapperMethod CreateMethod (MethodDefinition methodDefinition, Ca
269269
static CallableWrapperMethod CreateMethod (string name, CallableWrapperType declaringType, string? signature, string? connector, string? managedParameters, string? outerType, string? superCall)
270270
{
271271
signature = signature ?? throw new ArgumentNullException ("`connector` cannot be null.", nameof (connector));
272-
var method_name = "n_" + name + ":" + signature + ":" + connector;
272+
var method_name = "n_" + name + ":" + signature + ":" + connector?.Replace ('/', '+');
273273

274274
var method = new CallableWrapperMethod (declaringType, name, method_name, signature);
275275

src/Java.Interop.Tools.TypeNameMappings/Java.Interop.Tools.TypeNameMappings/JavaNativeTypeManager.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ static string ToCliTypePart (string part)
150150
for (int i = 0; i < parts.Length; ++i) {
151151
parts [i] = ToPascalCase (parts [i], 1);
152152
}
153-
return string.Join ("/", parts);
153+
return string.Join ("+", parts);
154154
}
155155

156156
static string ToPascalCase (string value, int minLength)

tests/Java.Interop.Export-Tests/Java.Interop.Export-Tests.csproj

+4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
<ProjectReference Include="..\..\src\Java.Interop.Export\Java.Interop.Export.csproj" />
2727
<ProjectReference Include="..\..\src\Java.Runtime.Environment\Java.Runtime.Environment.csproj" />
2828
<ProjectReference Include="..\TestJVM\TestJVM.csproj" />
29+
<ProjectReference
30+
Include="..\..\tools\jcw-gen\jcw-gen.csproj"
31+
ReferenceOutputAssembly="false"
32+
/>
2933
</ItemGroup>
3034

3135
<ItemGroup>

tests/Java.Interop.Tools.JavaCallableWrappers-Tests/Java.Interop.Tools.JavaCallableWrappers/JavaCallableWrapperGeneratorTests.cs

+49
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,55 @@ public void monodroidClearReferences ()
104104
Assert.AreEqual (expected, actual);
105105
}
106106

107+
[Test]
108+
public void GenerateTypeMentioningNestedInvoker ()
109+
{
110+
var actual = Generate (typeof (ApplicationName.ActivityLifecycleCallbacks));
111+
var expected = """
112+
package application;
113+
114+
115+
public class Name_ActivityLifecycleCallbacks
116+
extends java.lang.Object
117+
implements
118+
mono.android.IGCUserPeer,
119+
java.lang.Object
120+
{
121+
/** @hide */
122+
public static final String __md_methods;
123+
static {
124+
__md_methods =
125+
"n_onActivityCreated:(Landroid/app/Activity;Landroid/os/Bundle;)V:GetOnActivityCreated_Landroid_app_Activity_Landroid_os_Bundle_Handler:Android.App.Application+IActivityLifecycleCallbacksInvoker, Mono.Android\n" +
126+
"";
127+
mono.android.Runtime.register ("Xamarin.Android.ToolsTests.ApplicationName+ActivityLifecycleCallbacks, Java.Interop.Tools.JavaCallableWrappers-Tests", Name_ActivityLifecycleCallbacks.class, __md_methods);
128+
}
129+
130+
public void onActivityCreated (android.app.Activity p0, android.os.Bundle p1)
131+
{
132+
n_onActivityCreated (p0, p1);
133+
}
134+
135+
private native void n_onActivityCreated (android.app.Activity p0, android.os.Bundle p1);
136+
137+
private java.util.ArrayList refList;
138+
public void monodroidAddReference (java.lang.Object obj)
139+
{
140+
if (refList == null)
141+
refList = new java.util.ArrayList ();
142+
refList.add (obj);
143+
}
144+
145+
public void monodroidClearReferences ()
146+
{
147+
if (refList != null)
148+
refList.clear ();
149+
}
150+
}
151+
152+
""";
153+
Assert.AreEqual (expected, actual);
154+
}
155+
107156
static string Generate (Type type, string applicationJavaClass = null, string monoRuntimeInit = null, JavaPeerStyle style = JavaPeerStyle.XAJavaInterop1)
108157
{
109158
var reader_options = new CallableWrapperReaderOptions {

tests/Java.Interop.Tools.JavaCallableWrappers-Tests/Java.Interop.Tools.JavaCallableWrappers/SupportDeclarations.cs

+35
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,26 @@ class Application : Java.Lang.Object
2222
protected virtual void OnCreate ()
2323
{
2424
}
25+
26+
[Register ("android/app/Application$ActivityLifecycleCallbacks", DoNotGenerateAcw = true)]
27+
public partial interface IActivityLifecycleCallbacks {
28+
[Register (
29+
name: "onActivityCreated",
30+
signature: "(Landroid/app/Activity;Landroid/os/Bundle;)V",
31+
connector: "GetOnActivityCreated_Landroid_app_Activity_Landroid_os_Bundle_Handler:Android.App.Application/IActivityLifecycleCallbacksInvoker, Mono.Android")]
32+
void OnActivityCreated (Android.App.Activity activity, global::Android.OS.Bundle savedInstanceState);
33+
}
34+
35+
internal class IActivityLifecycleCallbacksInvoker : Java.Lang.Object, IActivityLifecycleCallbacks {
36+
static Delegate GetOnActivityCreated_Landroid_app_Activity_Landroid_os_Bundle_Handler ()
37+
{
38+
return null;
39+
}
40+
41+
public void OnActivityCreated (Android.App.Activity activity, global::Android.OS.Bundle savedInstanceState)
42+
{
43+
}
44+
}
2545
}
2646

2747
[Register ("android/app/Activity", DoNotGenerateAcw = true)]
@@ -43,6 +63,14 @@ public virtual void OnCreate (Java.Lang.Object arguments)
4363
}
4464
}
4565

66+
namespace Android.OS {
67+
68+
[Register ("android/os/Bundle", DoNotGenerateAcw = true)]
69+
class Bundle : Java.Lang.Object
70+
{
71+
}
72+
}
73+
4674
namespace Android.Runtime {
4775

4876
interface IJavaObject
@@ -72,6 +100,7 @@ static class SupportDeclarations
72100
typeof (AbstractClass),
73101
typeof (ActivityName),
74102
typeof (ApplicationName),
103+
typeof (ApplicationName.ActivityLifecycleCallbacks),
75104
typeof (DefaultName),
76105
typeof (DefaultName.A),
77106
typeof (DefaultName.A.B),
@@ -139,6 +168,12 @@ class ActivityName : Java.Lang.Object
139168
[Application (Name = "application.Name")]
140169
class ApplicationName : Application
141170
{
171+
public class ActivityLifecycleCallbacks : Java.Lang.Object, Application.IActivityLifecycleCallbacks
172+
{
173+
public void OnActivityCreated (Activity activity, global::Android.OS.Bundle savedInstanceState)
174+
{
175+
}
176+
}
142177
}
143178

144179
class IndirectApplication : ApplicationName

tests/Java.Interop.Tools.JavaCallableWrappers-Tests/Java.Interop.Tools.JavaCallableWrappers/TypeNameMapGeneratorTests.cs

+5-3
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public void WriteJavaToManaged ()
5050
v.WriteJavaToManaged (o);
5151
var a = ToArray (o);
5252
Save (a, "__j2m");
53-
var length = 190;
53+
var length = 193;
5454
var offset = 76;
5555
var e =
5656
"version=1\u0000" +
@@ -59,6 +59,7 @@ public void WriteJavaToManaged ()
5959
"value-offset=" + offset + "\u0000" +
6060
GetJ2MEntryLine (typeof (ActivityName), "activity/Name", offset, length) +
6161
GetJ2MEntryLine (typeof (ApplicationName), "application/Name", offset, length) +
62+
GetJ2MEntryLine (typeof (ApplicationName.ActivityLifecycleCallbacks), "application/Name_ActivityLifecycleCallbacks", offset, length) +
6263
GetJ2MEntryLine (typeof (DefaultName), "crc64197ae30a36756915/DefaultName", offset, length) +
6364
GetJ2MEntryLine (typeof (DefaultName.A), "crc64197ae30a36756915/DefaultName_A", offset, length) +
6465
GetJ2MEntryLine (typeof (DefaultName.A.B), "crc64197ae30a36756915/DefaultName_A_B", offset, length) +
@@ -128,8 +129,8 @@ public void WriteManagedToJava ()
128129
v.WriteManagedToJava (o);
129130
var a = ToArray (o);
130131
Save (a, "__m2j");
131-
var length = 190;
132-
var offset = 114;
132+
var length = 193;
133+
var offset = 117;
133134
var e =
134135
"version=1\u0000" +
135136
$"entry-count={types.Count}\u0000" +
@@ -138,6 +139,7 @@ public void WriteManagedToJava ()
138139
GetM2JEntryLine (typeof (AbstractClass), "my/AbstractClass", offset, length) +
139140
GetM2JEntryLine (typeof (AbstractClassInvoker), "my/AbstractClass", offset, length) +
140141
GetM2JEntryLine (typeof (ActivityName), "activity/Name", offset, length) +
142+
GetM2JEntryLine (typeof (ApplicationName.ActivityLifecycleCallbacks), "application/Name_ActivityLifecycleCallbacks", offset, length) +
141143
GetM2JEntryLine (typeof (ApplicationName), "application/Name", offset, length) +
142144
GetM2JEntryLine (typeof (DefaultName.A.B), "crc64197ae30a36756915/DefaultName_A_B", offset, length) +
143145
GetM2JEntryLine (typeof (DefaultName.A), "crc64197ae30a36756915/DefaultName_A", offset, length) +

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ public static void GenerateTypeRegistrations (CodeGenerationOptions opt, Generat
181181
int ls = reg.Key.LastIndexOf ('/');
182182
string package = ls >= 0 ? reg.Key.Substring (0, ls) : "";
183183

184-
if (JavaNativeTypeManager.ToCliType (reg.Key) == reg.Value)
184+
if (JavaNativeTypeManager.ToCliType (reg.Key) == reg.Value.Replace ('/', '+'))
185185
continue;
186186
if (!mapping.TryGetValue (package, out var v))
187187
mapping.Add (package, v = new List<KeyValuePair<string, string>> ());

0 commit comments

Comments
 (0)