Skip to content

Commit d18059a

Browse files
[TrimmableTypeMap] Add UCO wrappers, RegisterNatives, and IAndroidCallableWrapper
Add ACW (Android Callable Wrapper) support to TypeMap proxy types: - UcoMethodData/UcoConstructorData/NativeRegistrationData model types - ModelBuilder: generates UCO method wrappers, constructor wrappers, and native method registrations for ACW types - TypeMapAssemblyEmitter: emits [UnmanagedCallersOnly] UCO methods, UCO constructor wrappers, RegisterNatives, IAndroidCallableWrapper - IsAcw detection for types that need JNI native method registration - Cross-assembly IgnoresAccessChecksTo for UCO callback types - Tests for all UCO/RegisterNatives generation scenarios Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 0ecbfd8 commit d18059a

File tree

5 files changed

+949
-6
lines changed

5 files changed

+949
-6
lines changed

src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,18 @@ sealed class JavaPeerProxyData
8686
/// <summary>True if this is an open generic type definition. CreateInstance throws NotSupportedException.</summary>
8787
public bool IsGenericDefinition { get; init; }
8888

89-
}
89+
/// <summary>Whether this proxy needs ACW support (RegisterNatives + UCO wrappers + IAndroidCallableWrapper).</summary>
90+
public bool IsAcw { get; init; }
91+
92+
/// <summary>UCO method wrappers for marshal methods (non-constructor).</summary>
93+
public List<UcoMethodData> UcoMethods { get; } = new ();
9094

95+
/// <summary>UCO constructor wrappers.</summary>
96+
public List<UcoConstructorData> UcoConstructors { get; } = new ();
97+
98+
/// <summary>RegisterNatives registrations (method name, JNI signature, wrapper name).</summary>
99+
public List<NativeRegistrationData> NativeRegistrations { get; } = new ();
100+
}
91101

92102
/// <summary>
93103
/// A cross-assembly type reference (assembly name + full managed type name).
@@ -101,6 +111,58 @@ sealed record TypeRefData
101111
public required string AssemblyName { get; init; }
102112
}
103113

114+
/// <summary>
115+
/// An [UnmanagedCallersOnly] static wrapper for a marshal method.
116+
/// Body: load all args → call n_* callback → ret.
117+
/// </summary>
118+
sealed record UcoMethodData
119+
{
120+
/// <summary>Name of the generated wrapper method, e.g., "n_onCreate_uco_0".</summary>
121+
public required string WrapperName { get; init; }
122+
123+
/// <summary>Name of the n_* callback to call, e.g., "n_OnCreate".</summary>
124+
public required string CallbackMethodName { get; init; }
125+
126+
/// <summary>Type containing the callback method.</summary>
127+
public required TypeRefData CallbackType { get; init; }
128+
129+
/// <summary>JNI method signature, e.g., "(Landroid/os/Bundle;)V". Used to determine CLR parameter types.</summary>
130+
public required string JniSignature { get; init; }
131+
}
132+
133+
/// <summary>
134+
/// An [UnmanagedCallersOnly] static wrapper for a constructor callback.
135+
/// Signature must match the full JNI native method signature (jnienv + self + ctor params)
136+
/// so the ABI is correct when JNI dispatches the call.
137+
/// Body: TrimmableNativeRegistration.ActivateInstance(self, typeof(TargetType)).
138+
/// </summary>
139+
sealed record UcoConstructorData
140+
{
141+
/// <summary>Name of the generated wrapper, e.g., "nctor_0_uco".</summary>
142+
public required string WrapperName { get; init; }
143+
144+
/// <summary>Target type to pass to ActivateInstance.</summary>
145+
public required TypeRefData TargetType { get; init; }
146+
147+
/// <summary>JNI constructor signature, e.g., "(Landroid/content/Context;)V". Used for RegisterNatives registration.</summary>
148+
public required string JniSignature { get; init; }
149+
}
150+
151+
/// <summary>
152+
/// One JNI native method registration in RegisterNatives.
153+
/// </summary>
154+
sealed record NativeRegistrationData
155+
{
156+
/// <summary>JNI method name to register, e.g., "n_onCreate" or "nctor_0".</summary>
157+
public required string JniMethodName { get; init; }
158+
159+
/// <summary>JNI method signature, e.g., "(Landroid/os/Bundle;)V".</summary>
160+
public required string JniSignature { get; init; }
161+
162+
/// <summary>Name of the UCO wrapper method whose function pointer to register.</summary>
163+
public required string WrapperMethodName { get; init; }
164+
}
165+
104166
/// <summary>
105167
/// Describes how the proxy's CreateInstance should construct the managed peer.
106168
/// </summary>

src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ public static TypeMapAssemblyData Build (IReadOnlyList<JavaPeerInfo> peers, stri
8383
var referencedAssemblies = new SortedSet<string> (StringComparer.Ordinal);
8484
foreach (var proxy in model.ProxyTypes) {
8585
AddIfCrossAssembly (referencedAssemblies, proxy.TargetType?.AssemblyName, assemblyName);
86+
foreach (var uco in proxy.UcoMethods) {
87+
AddIfCrossAssembly (referencedAssemblies, uco.CallbackType.AssemblyName, assemblyName);
88+
}
8689
if (proxy.ActivationCtor != null && !proxy.ActivationCtor.IsOnLeafType) {
8790
AddIfCrossAssembly (referencedAssemblies, proxy.ActivationCtor.DeclaringType.AssemblyName, assemblyName);
8891
}
@@ -103,10 +106,11 @@ static void EmitPeers (TypeMapAssemblyData model, string jniName,
103106
string entryJniName = i == 0 ? jniName : $"{jniName}[{i}]";
104107

105108
bool hasProxy = peer.ActivationCtor != null || peer.InvokerTypeName != null;
109+
bool isAcw = !peer.DoNotGenerateAcw && !peer.IsInterface && peer.MarshalMethods.Count > 0;
106110

107111
JavaPeerProxyData? proxy = null;
108112
if (hasProxy) {
109-
proxy = BuildProxyType (peer);
113+
proxy = BuildProxyType (peer, isAcw);
110114
model.ProxyTypes.Add (proxy);
111115
}
112116

@@ -178,7 +182,7 @@ static void AddIfCrossAssembly (SortedSet<string> set, string? asmName, string o
178182
}
179183
}
180184

181-
static JavaPeerProxyData BuildProxyType (JavaPeerInfo peer)
185+
static JavaPeerProxyData BuildProxyType (JavaPeerInfo peer, bool isAcw)
182186
{
183187
// Use managed type name for proxy naming to guarantee uniqueness across aliases
184188
// (two types with the same JNI name will have different managed names).
@@ -190,6 +194,7 @@ static JavaPeerProxyData BuildProxyType (JavaPeerInfo peer)
190194
ManagedTypeName = peer.ManagedTypeName,
191195
AssemblyName = peer.AssemblyName,
192196
},
197+
IsAcw = isAcw,
193198
IsGenericDefinition = peer.IsGenericDefinition,
194199
};
195200

@@ -212,9 +217,80 @@ static JavaPeerProxyData BuildProxyType (JavaPeerInfo peer)
212217
};
213218
}
214219

220+
if (isAcw) {
221+
BuildUcoMethods (peer, proxy);
222+
BuildUcoConstructors (peer, proxy);
223+
BuildNativeRegistrations (proxy);
224+
}
225+
215226
return proxy;
216227
}
217228

229+
static void BuildUcoMethods (JavaPeerInfo peer, JavaPeerProxyData proxy)
230+
{
231+
int ucoIndex = 0;
232+
for (int i = 0; i < peer.MarshalMethods.Count; i++) {
233+
var mm = peer.MarshalMethods [i];
234+
if (mm.IsConstructor) {
235+
continue;
236+
}
237+
238+
proxy.UcoMethods.Add (new UcoMethodData {
239+
WrapperName = $"n_{mm.JniName}_uco_{ucoIndex}",
240+
CallbackMethodName = mm.NativeCallbackName,
241+
CallbackType = new TypeRefData {
242+
ManagedTypeName = !string.IsNullOrEmpty (mm.DeclaringTypeName) ? mm.DeclaringTypeName : peer.ManagedTypeName,
243+
AssemblyName = !string.IsNullOrEmpty (mm.DeclaringAssemblyName) ? mm.DeclaringAssemblyName : peer.AssemblyName,
244+
},
245+
JniSignature = mm.JniSignature,
246+
});
247+
ucoIndex++;
248+
}
249+
}
250+
251+
static void BuildUcoConstructors (JavaPeerInfo peer, JavaPeerProxyData proxy)
252+
{
253+
if (peer.ActivationCtor == null || peer.JavaConstructors.Count == 0) {
254+
return;
255+
}
256+
257+
foreach (var ctor in peer.JavaConstructors) {
258+
proxy.UcoConstructors.Add (new UcoConstructorData {
259+
WrapperName = $"nctor_{ctor.ConstructorIndex}_uco",
260+
JniSignature = ctor.JniSignature,
261+
TargetType = new TypeRefData {
262+
ManagedTypeName = peer.ManagedTypeName,
263+
AssemblyName = peer.AssemblyName,
264+
},
265+
});
266+
}
267+
}
268+
269+
static void BuildNativeRegistrations (JavaPeerProxyData proxy)
270+
{
271+
foreach (var uco in proxy.UcoMethods) {
272+
proxy.NativeRegistrations.Add (new NativeRegistrationData {
273+
JniMethodName = uco.CallbackMethodName,
274+
JniSignature = uco.JniSignature,
275+
WrapperMethodName = uco.WrapperName,
276+
});
277+
}
278+
279+
foreach (var uco in proxy.UcoConstructors) {
280+
string jniName = uco.WrapperName;
281+
int ucoSuffix = jniName.LastIndexOf ("_uco", StringComparison.Ordinal);
282+
if (ucoSuffix >= 0) {
283+
jniName = jniName.Substring (0, ucoSuffix);
284+
}
285+
286+
proxy.NativeRegistrations.Add (new NativeRegistrationData {
287+
JniMethodName = jniName,
288+
JniSignature = uco.JniSignature,
289+
WrapperMethodName = uco.WrapperName,
290+
});
291+
}
292+
}
293+
218294
static TypeMapAttributeData BuildEntry (JavaPeerInfo peer, JavaPeerProxyData? proxy,
219295
string outputAssemblyName, string jniName)
220296
{

0 commit comments

Comments
 (0)