Skip to content

Commit db15d6e

Browse files
JNI remapping native code generator
Co-authored-by: Jonathan Peppers <[email protected]>
1 parent e1af958 commit db15d6e

33 files changed

+1037
-229
lines changed

src/Mono.Android/Android.Runtime/AndroidRuntime.cs

Lines changed: 33 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,13 @@ public override void DeleteWeakGlobalReference (ref JniObjectReference value)
243243
}
244244

245245
class AndroidTypeManager : JniRuntime.JniTypeManager {
246+
struct JniRemappingReplacementMethod
247+
{
248+
public string target_type;
249+
public string target_name;
250+
public bool is_static;
251+
};
252+
246253
bool jniAddNativeMethodRegistrationAttributePresent;
247254

248255
public AndroidTypeManager (bool jniAddNativeMethodRegistrationAttributePresent)
@@ -317,123 +324,57 @@ protected override IEnumerable<string> GetSimpleReferences (Type type)
317324
};
318325
}
319326

327+
[DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)]
328+
static extern IntPtr _monodroid_lookup_replacement_type (string jniSimpleReference);
329+
320330
protected override string? GetReplacementTypeCore (string jniSimpleReference)
321331
{
322-
if (JNIEnv.ReplacementTypes == null) {
332+
if (!JNIEnv.jniRemappingInUse) {
323333
return null;
324334
}
325-
if (JNIEnv.ReplacementTypes.TryGetValue (jniSimpleReference, out var v)) {
326-
return v;
335+
336+
IntPtr ret = _monodroid_lookup_replacement_type (jniSimpleReference);
337+
if (ret == IntPtr.Zero) {
338+
return null;
327339
}
328-
return null;
340+
341+
return Marshal.PtrToStringAnsi (ret);
329342
}
330343

344+
[DllImport (AndroidRuntime.InternalDllName, CallingConvention = CallingConvention.Cdecl)]
345+
static extern IntPtr _monodroid_lookup_replacement_method_info (string jniSourceType, string jniMethodName, string jniMethodSignature);
346+
331347
protected override JniRuntime.ReplacementMethodInfo? GetReplacementMethodInfoCore (string jniSourceType, string jniMethodName, string jniMethodSignature)
332348
{
333-
if (JNIEnv.ReplacementMethods == null) {
349+
if (!JNIEnv.jniRemappingInUse) {
334350
return null;
335351
}
336-
#if !STRUCTURED
337-
if (!JNIEnv.ReplacementMethods.TryGetValue (CreateReplacementMethodsKey (jniSourceType, jniMethodName, jniMethodSignature), out var r) &&
338-
!JNIEnv.ReplacementMethods.TryGetValue (CreateReplacementMethodsKey (jniSourceType, jniMethodName, GetMethodSignatureWithoutReturnType ()), out r) &&
339-
!JNIEnv.ReplacementMethods.TryGetValue (CreateReplacementMethodsKey (jniSourceType, jniMethodName, null), out r)) {
340-
return null;
341-
}
342-
ReadOnlySpan<char> replacementInfo = r;
343-
344-
var targetType = GetNextString (ref replacementInfo);
345-
var targetName = GetNextString (ref replacementInfo);
346-
var targetSig = GetNextString (ref replacementInfo);
347-
var paramCountStr = GetNextString (ref replacementInfo);
348-
var isStaticStr = GetNextString (ref replacementInfo);
349352

350-
int? paramCount = null;
351-
if (!paramCountStr.IsEmpty) {
352-
if (!int.TryParse (paramCountStr, 0, System.Globalization.CultureInfo.InvariantCulture, out var count)) {
353-
return null;
354-
}
355-
paramCount = count;
353+
IntPtr retInfo = _monodroid_lookup_replacement_method_info (jniSourceType, jniMethodName, jniMethodSignature);
354+
if (retInfo == IntPtr.Zero) {
355+
return null;
356356
}
357357

358-
bool isStatic = false;
359-
if (isStaticStr.Equals ("true", StringComparison.Ordinal)) {
360-
isStatic = true;
361-
}
358+
var method = new JniRemappingReplacementMethod ();
359+
method = Marshal.PtrToStructure<JniRemappingReplacementMethod>(retInfo);
362360

363-
if (targetSig.IsEmpty && isStatic) {
364-
paramCount = paramCount ?? JniMemberSignature.GetParameterCountFromMethodSignature (jniMethodSignature);
365-
paramCount++;
361+
int? paramCount = null;
362+
if (method.is_static) {
363+
paramCount = JniMemberSignature.GetParameterCountFromMethodSignature (jniMethodSignature) + 1;
366364
jniMethodSignature = $"(L{jniSourceType};" + jniMethodSignature.Substring ("(".Length);
367365
}
368366

369367
return new JniRuntime.ReplacementMethodInfo {
370368
SourceJniType = jniSourceType,
371369
SourceJniMethodName = jniMethodName,
372370
SourceJniMethodSignature = jniMethodSignature,
373-
TargetJniType = targetType.IsEmpty ? jniSourceType : new string (targetType),
374-
TargetJniMethodName = targetName.IsEmpty ? jniMethodName : new string (targetName),
375-
TargetJniMethodSignature = targetSig.IsEmpty ? jniMethodSignature : new string (targetSig),
371+
TargetJniType = method.target_type,
372+
TargetJniMethodName = method.target_name,
373+
TargetJniMethodSignature = jniMethodSignature,
376374
TargetJniMethodParameterCount = paramCount,
377-
TargetJniMethodInstanceToStatic = isStatic,
375+
TargetJniMethodInstanceToStatic = method.is_static,
378376
};
379-
#else
380-
if (!JNIEnv.ReplacementMethods.TryGetValue ((jniSourceType, jniMethodName, jniMethodSignature), out var r) &&
381-
!JNIEnv.ReplacementMethods.TryGetValue ((jniSourceType, jniMethodName, GetMethodSignatureWithoutReturnType ()), out r) &&
382-
!JNIEnv.ReplacementMethods.TryGetValue ((jniSourceType, jniMethodName, null), out r)) {
383-
return null;
384-
}
385-
var targetSig = r.TargetSignature;
386-
var paramCount = r.ParamCount;
387-
if (targetSig == null && r.TurnStatic) {
388-
targetSig = $"(L{jniSourceType};" + jniMethodSignature.Substring ("(".Length);
389-
paramCount = paramCount ?? JniMemberSignature.GetParameterCountFromMethodSignature (jniMethodSignature);
390-
paramCount++;
391-
}
392-
return new JniRuntime.ReplacementMethodInfo {
393-
SourceJniType = jniSourceType,
394-
SourceJniMethodName = jniMethodName,
395-
SourceJniMethodSignature = jniMethodSignature,
396-
TargetJniType = r.TargetType ?? jniSourceType,
397-
TargetJniMethodName = r.TargetName ?? jniMethodName,
398-
TargetJniMethodSignature = targetSig ?? jniMethodSignature,
399-
TargetJniMethodParameterCount = paramCount,
400-
TargetJniMethodInstanceToStatic = r.TurnStatic,
401-
};
402-
#endif // !STRUCTURED
403-
404-
string GetMethodSignatureWithoutReturnType ()
405-
{
406-
int i = jniMethodSignature.IndexOf (')');
407-
return jniMethodSignature.Substring (0, i+1);
408-
}
409-
410-
string GetValue (string? value)
411-
{
412-
return value == null ? "null" : $"\"{value}\"";
413-
}
414-
415-
ReadOnlySpan<char> GetNextString (ref ReadOnlySpan<char> info)
416-
{
417-
int index = info.IndexOf ('\t');
418-
var r = info;
419-
if (index >= 0) {
420-
r = info.Slice (0, index);
421-
info = info.Slice (index+1);
422-
return r;
423-
}
424-
info = default;
425-
return r;
426-
}
427377
}
428-
429-
static string CreateReplacementMethodsKey (string? sourceType, string? methodName, string? methodSignature) =>
430-
new StringBuilder ()
431-
.Append (sourceType)
432-
.Append ('\t')
433-
.Append (methodName)
434-
.Append ('\t')
435-
.Append (methodSignature)
436-
.ToString ();
437378
#endif // NET
438379

439380
delegate Delegate GetCallbackHandler ();

src/Mono.Android/Android.Runtime/JNIEnv.cs

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,6 @@
1515
using Java.Interop.Tools.TypeNameMappings;
1616
using System.Diagnostics.CodeAnalysis;
1717

18-
#if NET
19-
using ReplacementTypesDict = System.Collections.Generic.Dictionary<string, string>;
20-
using ReplacementMethodsDict = System.Collections.Generic.Dictionary<string, string>;
21-
#endif // NET
22-
2318
namespace Android.Runtime {
2419
#pragma warning disable 0649
2520
struct JnienvInitializeArgs {
@@ -40,8 +35,7 @@ struct JnienvInitializeArgs {
4035
public int packageNamingPolicy;
4136
public byte ioExceptionType;
4237
public int jniAddNativeMethodRegistrationAttributePresent;
43-
public IntPtr mappingXml;
44-
public int mappingXmlLen;
38+
public bool jniRemappingInUse;
4539
}
4640
#pragma warning restore 0649
4741

@@ -55,6 +49,7 @@ public static partial class JNIEnv {
5549
static int androidSdkVersion;
5650

5751
static bool AllocObjectSupported;
52+
internal static bool jniRemappingInUse;
5853

5954
static IntPtr grefIGCUserPeer_class;
6055

@@ -68,11 +63,6 @@ public static partial class JNIEnv {
6863
static AndroidRuntime? androidRuntime;
6964
static BoundExceptionType BoundExceptionType;
7065

71-
#if NET
72-
internal static ReplacementTypesDict? ReplacementTypes;
73-
internal static ReplacementMethodsDict? ReplacementMethods;
74-
#endif // NET
75-
7666
[ThreadStatic]
7767
static byte[]? mvid_bytes;
7868

@@ -167,6 +157,7 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
167157

168158
gref_gc_threshold = args->grefGcThreshold;
169159

160+
jniRemappingInUse = args->jniRemappingInUse;
170161
java_vm = args->javaVm;
171162

172163
version = args->version;
@@ -178,13 +169,6 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
178169
gref_class = args->grefClass;
179170
mid_Class_forName = new JniMethodInfo (args->Class_forName, isStatic: true);
180171

181-
#if NET
182-
if (args->mappingXml != IntPtr.Zero) {
183-
var xml = Encoding.UTF8.GetString ((byte*) args->mappingXml, args->mappingXmlLen);
184-
(ReplacementTypes, ReplacementMethods) = MamXmlParser.ParseStrings (xml);
185-
}
186-
#endif // NET
187-
188172
if (args->localRefsAreIndirect == 1)
189173
IdentityHash = v => _monodroid_get_identity_hash_code (Handle, v);
190174
else

src/Mono.Android/Mono.Android.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,6 @@
253253
<Compile Include="Android.Runtime\JObjectRefType.cs" />
254254
<Compile Include="Android.Runtime\JValue.cs" />
255255
<Compile Include="Android.Runtime\Logger.cs" />
256-
<Compile Include="Android.Runtime\MamXmlParser.cs" />
257256
<Compile Include="Android.Runtime\NamespaceMappingAttribute.cs" />
258257
<Compile Include="Android.Runtime\OutputStreamAdapter.cs" />
259258
<Compile Include="Android.Runtime\OutputStreamInvoker.cs" />
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
using System;
2+
using System.IO;
3+
using System.Collections.Generic;
4+
using System.Xml;
5+
6+
using Microsoft.Build.Framework;
7+
using Microsoft.Build.Utilities;
8+
using Microsoft.Android.Build.Tasks;
9+
10+
namespace Xamarin.Android.Tasks
11+
{
12+
public class GenerateJniRemappingNativeCode : AndroidTask
13+
{
14+
internal const string JniRemappingNativeCodeInfoKey = ".:!JniRemappingNativeCodeInfo!:.";
15+
16+
internal sealed class JniRemappingNativeCodeInfo
17+
{
18+
public int ReplacementTypeCount { get; }
19+
public int ReplacementMethodIndexEntryCount { get; }
20+
21+
public JniRemappingNativeCodeInfo (int replacementTypeCount, int replacementMethodIndexEntryCount)
22+
{
23+
ReplacementTypeCount = replacementTypeCount;
24+
ReplacementMethodIndexEntryCount = replacementMethodIndexEntryCount;
25+
}
26+
}
27+
28+
public override string TaskPrefix => "GJRNC";
29+
30+
public ITaskItem RemappingXmlFilePath { get; set; }
31+
32+
[Required]
33+
public string OutputDirectory { get; set; }
34+
35+
[Required]
36+
public string [] SupportedAbis { get; set; }
37+
38+
public bool GenerateEmptyCode { get; set; }
39+
40+
public override bool RunTask ()
41+
{
42+
if (!GenerateEmptyCode) {
43+
if (RemappingXmlFilePath == null) {
44+
throw new InvalidOperationException ("RemappingXmlFilePath parameter is required");
45+
}
46+
47+
Generate ();
48+
} else {
49+
GenerateEmpty ();
50+
}
51+
52+
return !Log.HasLoggedErrors;
53+
}
54+
55+
void GenerateEmpty ()
56+
{
57+
Generate (new JniRemappingAssemblyGenerator (), typeReplacementsCount: 0);
58+
}
59+
60+
void Generate ()
61+
{
62+
var typeReplacements = new List<JniRemappingTypeReplacement> ();
63+
var methodReplacements = new List<JniRemappingMethodReplacement> ();
64+
65+
var readerSettings = new XmlReaderSettings {
66+
XmlResolver = null,
67+
};
68+
69+
using (var reader = XmlReader.Create (File.OpenRead (RemappingXmlFilePath.ItemSpec), readerSettings)) {
70+
if (reader.MoveToContent () != XmlNodeType.Element || reader.LocalName != "replacements") {
71+
Log.LogError ($"Input file `{RemappingXmlFilePath.ItemSpec}` does not start with `<replacements/>`");
72+
} else {
73+
ReadXml (reader, typeReplacements, methodReplacements);
74+
}
75+
}
76+
77+
Generate (new JniRemappingAssemblyGenerator (typeReplacements, methodReplacements), typeReplacements.Count);
78+
}
79+
80+
void Generate (JniRemappingAssemblyGenerator jniRemappingGenerator, int typeReplacementsCount)
81+
{
82+
jniRemappingGenerator.Init ();
83+
84+
foreach (string abi in SupportedAbis) {
85+
string baseAsmFilePath = Path.Combine (OutputDirectory, $"jni_remap.{abi.ToLowerInvariant ()}");
86+
string llFilePath = $"{baseAsmFilePath}.ll";
87+
88+
using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) {
89+
jniRemappingGenerator.Write (GeneratePackageManagerJava.GetAndroidTargetArchForAbi (abi), sw, llFilePath);
90+
sw.Flush ();
91+
Files.CopyIfStreamChanged (sw.BaseStream, llFilePath);
92+
}
93+
}
94+
95+
BuildEngine4.RegisterTaskObjectAssemblyLocal (
96+
JniRemappingNativeCodeInfoKey,
97+
new JniRemappingNativeCodeInfo (typeReplacementsCount, jniRemappingGenerator.ReplacementMethodIndexEntryCount),
98+
RegisteredTaskObjectLifetime.Build
99+
);
100+
}
101+
102+
void ReadXml (XmlReader reader, List<JniRemappingTypeReplacement> typeReplacements, List<JniRemappingMethodReplacement> methodReplacements)
103+
{
104+
bool haveAllAttributes;
105+
106+
while (reader.Read ()) {
107+
if (reader.NodeType != XmlNodeType.Element) {
108+
continue;
109+
}
110+
111+
haveAllAttributes = true;
112+
if (String.Compare ("replace-type", reader.LocalName, StringComparison.Ordinal) == 0) {
113+
haveAllAttributes &= GetRequiredAttribute ("from", out string from);
114+
haveAllAttributes &= GetRequiredAttribute ("to", out string to);
115+
if (!haveAllAttributes) {
116+
continue;
117+
}
118+
119+
typeReplacements.Add (new JniRemappingTypeReplacement (from, to));
120+
} else if (String.Compare ("replace-method", reader.LocalName, StringComparison.Ordinal) == 0) {
121+
haveAllAttributes &= GetRequiredAttribute ("source-type", out string sourceType);
122+
haveAllAttributes &= GetRequiredAttribute ("source-method-name", out string sourceMethodName);
123+
haveAllAttributes &= GetRequiredAttribute ("target-type", out string targetType);
124+
haveAllAttributes &= GetRequiredAttribute ("target-method-name", out string targetMethodName);
125+
haveAllAttributes &= GetRequiredAttribute ("target-method-instance-to-static", out string targetIsStatic);
126+
127+
if (!haveAllAttributes) {
128+
continue;
129+
}
130+
131+
if (!Boolean.TryParse (targetIsStatic, out bool isStatic)) {
132+
Log.LogError ($"Attribute 'target-method-instance-to-static' in element '{reader.LocalName}' value '{targetIsStatic}' cannot be parsed as boolean; {RemappingXmlFilePath.ItemSpec} line {GetCurrentLineNumber ()}");
133+
continue;
134+
}
135+
136+
string sourceMethodSignature = reader.GetAttribute ("source-method-signature");
137+
methodReplacements.Add (
138+
new JniRemappingMethodReplacement (
139+
sourceType, sourceMethodName, sourceMethodSignature,
140+
targetType, targetMethodName, isStatic
141+
)
142+
);
143+
}
144+
}
145+
146+
bool GetRequiredAttribute (string attributeName, out string attributeValue)
147+
{
148+
attributeValue = reader.GetAttribute (attributeName);
149+
if (!String.IsNullOrEmpty (attributeValue)) {
150+
return true;
151+
}
152+
153+
Log.LogError ($"Attribute '{attributeName}' missing from element '{reader.LocalName}'; {RemappingXmlFilePath.ItemSpec} line {GetCurrentLineNumber ()}");
154+
return false;
155+
}
156+
157+
int GetCurrentLineNumber () => ((IXmlLineInfo)reader).LineNumber;
158+
}
159+
}
160+
}

0 commit comments

Comments
 (0)