Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion external/Java.Interop
2 changes: 1 addition & 1 deletion external/debugger-libs
Original file line number Diff line number Diff line change
@@ -1,53 +1,126 @@
using System;
using System.Collections.Generic;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;

/// <summary>
/// JNI primitive type kinds used for mapping JNI signatures → CLR types.
/// </summary>
enum JniParamKind
{
Void, // V
Boolean, // Z → sbyte
Byte, // B → sbyte
Char, // C → char
Short, // S → short
Int, // I → int
Long, // J → long
Float, // F → float
Double, // D → double
Object, // L...; or [ → IntPtr
}

/// <summary>
/// Helpers for parsing JNI method signatures.
/// </summary>
static class JniSignatureHelper
{
/// <summary>
/// Parses the parameter types from a JNI method signature like "(Landroid/os/Bundle;)V".
/// </summary>
public static List<JniParamKind> ParseParameterTypes (string jniSignature)
{
var result = new List<JniParamKind> ();
int i = 1; // skip opening '('
while (i < jniSignature.Length && jniSignature [i] != ')') {
result.Add (ParseSingleType (jniSignature, ref i));
}
return result;
}

/// <summary>

/// Parses the raw JNI type descriptor strings from a JNI method signature.

/// </summary>
public static List<string> ParseParameterTypeStrings (string jniSignature)
{
var result = new List<string> ();
int i = 1; // skip opening '('
while (i < jniSignature.Length && jniSignature [i] != ')') {
int start = i;
SkipSingleType (jniSignature, ref i);
ParseSingleType (jniSignature, ref i);
result.Add (jniSignature.Substring (start, i - start));
}
return result;
}

/// <summary>

/// Extracts the return type descriptor from a JNI method signature.

/// </summary>
public static string ParseReturnTypeString (string jniSignature)
{
int i = jniSignature.IndexOf (')') + 1;
return jniSignature.Substring (i);
}

static void SkipSingleType (string sig, ref int i)
/// <summary>

/// Parses the return type from a JNI method signature.

/// </summary>
public static JniParamKind ParseReturnType (string jniSignature)
{
int i = jniSignature.IndexOf (')') + 1;
return ParseSingleType (jniSignature, ref i);
}

static JniParamKind ParseSingleType (string sig, ref int i)
{
switch (sig [i]) {
case 'V': case 'Z': case 'B': case 'C': case 'S':
case 'I': case 'J': case 'F': case 'D':
i++;
break;
case 'V': i++; return JniParamKind.Void;
case 'Z': i++; return JniParamKind.Boolean;
case 'B': i++; return JniParamKind.Byte;
case 'C': i++; return JniParamKind.Char;
case 'S': i++; return JniParamKind.Short;
case 'I': i++; return JniParamKind.Int;
case 'J': i++; return JniParamKind.Long;
case 'F': i++; return JniParamKind.Float;
case 'D': i++; return JniParamKind.Double;
case 'L':
i = sig.IndexOf (';', i) + 1;
break;
return JniParamKind.Object;
case '[':
i++;
SkipSingleType (sig, ref i);
break;
ParseSingleType (sig, ref i); // skip element type
return JniParamKind.Object;
default:
throw new ArgumentException ($"Unknown JNI type character '{sig [i]}' in '{sig}' at index {i}");
}
}

/// <summary>

/// Encodes the CLR type for a JNI parameter kind into a signature type encoder.

/// </summary>
public static void EncodeClrType (SignatureTypeEncoder encoder, JniParamKind kind)
{
switch (kind) {
case JniParamKind.Boolean: encoder.Boolean (); break;
case JniParamKind.Byte: encoder.SByte (); break;
case JniParamKind.Char: encoder.Char (); break;
case JniParamKind.Short: encoder.Int16 (); break;
case JniParamKind.Int: encoder.Int32 (); break;
case JniParamKind.Long: encoder.Int64 (); break;
case JniParamKind.Float: encoder.Single (); break;
case JniParamKind.Double: encoder.Double (); break;
case JniParamKind.Object: encoder.IntPtr (); break;
default: throw new ArgumentException ($"Cannot encode JNI param kind {kind} as CLR type");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,34 @@ sealed class JavaPeerProxyData
/// </summary>
public bool IsGenericDefinition { get; init; }

}
/// <summary>

/// Whether this proxy needs ACW support (RegisterNatives + UCO wrappers + IAndroidCallableWrapper).

/// </summary>
public bool IsAcw { get; init; }

/// <summary>

/// UCO method wrappers for marshal methods (non-constructor).

/// </summary>
public List<UcoMethodData> UcoMethods { get; } = new ();

/// <summary>

/// UCO constructor wrappers.

/// </summary>
public List<UcoConstructorData> UcoConstructors { get; } = new ();

/// <summary>

/// RegisterNatives registrations (method name, JNI signature, wrapper name).

/// </summary>
public List<NativeRegistrationData> NativeRegistrations { get; } = new ();
}

/// <summary>
/// A cross-assembly type reference (assembly name + full managed type name).
Expand All @@ -157,6 +183,92 @@ sealed record TypeRefData
public required string AssemblyName { get; init; }
}

/// <summary>
/// An [UnmanagedCallersOnly] static wrapper for a marshal method.
/// Body: load all args → call n_* callback → ret.
/// </summary>
sealed record UcoMethodData
{
/// <summary>
/// Name of the generated wrapper method, e.g., "n_onCreate_uco_0".
/// </summary>
public required string WrapperName { get; init; }

/// <summary>

/// Name of the n_* callback to call, e.g., "n_OnCreate".

/// </summary>
public required string CallbackMethodName { get; init; }

/// <summary>

/// Type containing the callback method.

/// </summary>
public required TypeRefData CallbackType { get; init; }

/// <summary>

/// JNI method signature, e.g., "(Landroid/os/Bundle;)V". Used to determine CLR parameter types.

/// </summary>
public required string JniSignature { get; init; }
}

/// <summary>
/// An [UnmanagedCallersOnly] static wrapper for a constructor callback.
/// Signature must match the full JNI native method signature (jnienv + self + ctor params)
/// so the ABI is correct when JNI dispatches the call.
/// Body: TrimmableNativeRegistration.ActivateInstance(self, typeof(TargetType)).
/// </summary>
sealed record UcoConstructorData
{
/// <summary>
/// Name of the generated wrapper, e.g., "nctor_0_uco".
/// </summary>
public required string WrapperName { get; init; }

/// <summary>

/// Target type to pass to ActivateInstance.

/// </summary>
public required TypeRefData TargetType { get; init; }

/// <summary>

/// JNI constructor signature, e.g., "(Landroid/content/Context;)V". Used for RegisterNatives registration.

/// </summary>
public required string JniSignature { get; init; }
}

/// <summary>
/// One JNI native method registration in RegisterNatives.
/// </summary>
sealed record NativeRegistrationData
{
/// <summary>
/// JNI method name to register, e.g., "n_onCreate" or "nctor_0".
/// </summary>
public required string JniMethodName { get; init; }

/// <summary>

/// JNI method signature, e.g., "(Landroid/os/Bundle;)V".

/// </summary>
public required string JniSignature { get; init; }

/// <summary>

/// Name of the UCO wrapper method whose function pointer to register.

/// </summary>
public required string WrapperMethodName { get; init; }
}

/// <summary>
/// Describes how the proxy's CreateInstance should construct the managed peer.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ public static TypeMapAssemblyData Build (IReadOnlyList<JavaPeerInfo> peers, stri
var referencedAssemblies = new SortedSet<string> (StringComparer.Ordinal);
foreach (var proxy in model.ProxyTypes) {
AddIfCrossAssembly (referencedAssemblies, proxy.TargetType?.AssemblyName, assemblyName);
foreach (var uco in proxy.UcoMethods) {
AddIfCrossAssembly (referencedAssemblies, uco.CallbackType.AssemblyName, assemblyName);
}
if (proxy.ActivationCtor != null && !proxy.ActivationCtor.IsOnLeafType) {
AddIfCrossAssembly (referencedAssemblies, proxy.ActivationCtor.DeclaringType.AssemblyName, assemblyName);
}
Expand All @@ -103,10 +106,11 @@ static void EmitPeers (TypeMapAssemblyData model, string jniName,
string entryJniName = i == 0 ? jniName : $"{jniName}[{i}]";

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

JavaPeerProxyData? proxy = null;
if (hasProxy) {
proxy = BuildProxyType (peer);
proxy = BuildProxyType (peer, isAcw);
model.ProxyTypes.Add (proxy);
}

Expand Down Expand Up @@ -178,7 +182,7 @@ static void AddIfCrossAssembly (SortedSet<string> set, string? asmName, string o
}
}

static JavaPeerProxyData BuildProxyType (JavaPeerInfo peer)
static JavaPeerProxyData BuildProxyType (JavaPeerInfo peer, bool isAcw)
{
// Use managed type name for proxy naming to guarantee uniqueness across aliases
// (two types with the same JNI name will have different managed names).
Expand All @@ -190,6 +194,7 @@ static JavaPeerProxyData BuildProxyType (JavaPeerInfo peer)
ManagedTypeName = peer.ManagedTypeName,
AssemblyName = peer.AssemblyName,
},
IsAcw = isAcw,
IsGenericDefinition = peer.IsGenericDefinition,
};

Expand All @@ -212,9 +217,80 @@ static JavaPeerProxyData BuildProxyType (JavaPeerInfo peer)
};
}

if (isAcw) {
BuildUcoMethods (peer, proxy);
BuildUcoConstructors (peer, proxy);
BuildNativeRegistrations (proxy);
}

return proxy;
}

static void BuildUcoMethods (JavaPeerInfo peer, JavaPeerProxyData proxy)
{
int ucoIndex = 0;
for (int i = 0; i < peer.MarshalMethods.Count; i++) {
var mm = peer.MarshalMethods [i];
if (mm.IsConstructor) {
continue;
}

proxy.UcoMethods.Add (new UcoMethodData {
WrapperName = $"n_{mm.JniName}_uco_{ucoIndex}",
CallbackMethodName = mm.NativeCallbackName,
CallbackType = new TypeRefData {
ManagedTypeName = !string.IsNullOrEmpty (mm.DeclaringTypeName) ? mm.DeclaringTypeName : peer.ManagedTypeName,
AssemblyName = !string.IsNullOrEmpty (mm.DeclaringAssemblyName) ? mm.DeclaringAssemblyName : peer.AssemblyName,
},
JniSignature = mm.JniSignature,
});
ucoIndex++;
}
}

static void BuildUcoConstructors (JavaPeerInfo peer, JavaPeerProxyData proxy)
{
if (peer.ActivationCtor == null || peer.JavaConstructors.Count == 0) {
return;
}

foreach (var ctor in peer.JavaConstructors) {
proxy.UcoConstructors.Add (new UcoConstructorData {
WrapperName = $"nctor_{ctor.ConstructorIndex}_uco",
JniSignature = ctor.JniSignature,
TargetType = new TypeRefData {
ManagedTypeName = peer.ManagedTypeName,
AssemblyName = peer.AssemblyName,
},
});
}
}

static void BuildNativeRegistrations (JavaPeerProxyData proxy)
{
foreach (var uco in proxy.UcoMethods) {
proxy.NativeRegistrations.Add (new NativeRegistrationData {
JniMethodName = uco.CallbackMethodName,
JniSignature = uco.JniSignature,
WrapperMethodName = uco.WrapperName,
});
}

foreach (var uco in proxy.UcoConstructors) {
string jniName = uco.WrapperName;
int ucoSuffix = jniName.LastIndexOf ("_uco", StringComparison.Ordinal);
if (ucoSuffix >= 0) {
jniName = jniName.Substring (0, ucoSuffix);
}

proxy.NativeRegistrations.Add (new NativeRegistrationData {
JniMethodName = jniName,
JniSignature = uco.JniSignature,
WrapperMethodName = uco.WrapperName,
});
}
}

static TypeMapAttributeData BuildEntry (JavaPeerInfo peer, JavaPeerProxyData? proxy,
string outputAssemblyName, string jniName)
{
Expand Down
Loading