Skip to content
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#nullable enable

using System;
using System.Runtime.InteropServices;

namespace Java.Interop
{
Expand All @@ -19,19 +20,20 @@ internal bool IsValid {

#if DEBUG
string? name, signature;
IntPtr nameUtf8, signatureUtf8;
#endif // !DEBUG
Comment on lines 21 to 24

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth having the DEBUG values here at all? Or are they useful during development?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, only useful during development


public string Name {
#if DEBUG
get => name ?? throw new NotSupportedException ();
get => name ??= GetUtf8String (nameUtf8);
#else // !DEBUG
get => throw new NotSupportedException ();
#endif // !DEBUG
}

public string Signature {
#if DEBUG
get => signature ?? throw new NotSupportedException ();
get => signature ??= GetUtf8String (signatureUtf8);
#else // !DEBUG
get => throw new NotSupportedException ();
#endif // !DEBUG
Expand All @@ -55,11 +57,54 @@ public JniMethodInfo (string name, string signature, IntPtr methodID, bool isSta
#endif // DEBUG
}

internal JniMethodInfo (IntPtr nameUtf8, string signature, IntPtr methodID, bool isStatic)
{
ID = methodID;
IsStatic = isStatic;

#if DEBUG
this.nameUtf8 = nameUtf8;
this.signature = signature;
#endif // DEBUG
}

internal JniMethodInfo (IntPtr nameUtf8, IntPtr signatureUtf8, IntPtr methodID, bool isStatic)
{
ID = methodID;
IsStatic = isStatic;

#if DEBUG
this.nameUtf8 = nameUtf8;
Comment thread
simonrozsival marked this conversation as resolved.
this.signatureUtf8 = signatureUtf8;
#endif // DEBUG
}

internal JniMethodInfo (string name, IntPtr signatureUtf8, IntPtr methodID, bool isStatic)
{
ID = methodID;
IsStatic = isStatic;

#if DEBUG
this.name = name;
this.signatureUtf8 = signatureUtf8;
#endif // DEBUG
}

#if DEBUG
static unsafe string GetUtf8String (IntPtr value)
{
if (value == IntPtr.Zero)
throw new NotSupportedException ();

return System.Text.Encoding.UTF8.GetString (MemoryMarshal.CreateReadOnlySpanFromNullTerminated ((byte*)value));
}
#endif // DEBUG

public override string ToString ()
{
#if DEBUG
bool haveName = !string.IsNullOrEmpty (name);
bool haveSig = !string.IsNullOrEmpty (signature);
bool haveName = !string.IsNullOrEmpty (name) || nameUtf8 != IntPtr.Zero;
bool haveSig = !string.IsNullOrEmpty (signature) || signatureUtf8 != IntPtr.Zero;
#else // DEBUG
bool haveName = false;
bool haveSig = false;
Expand All @@ -73,4 +118,3 @@ public override string ToString ()
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -105,23 +105,23 @@ public JniMethodInfo GetMethodInfo (string encodedMember)
JniMethodInfo GetMethodInfo (ReadOnlySpan<char> method, ReadOnlySpan<char> signature)
{
var m = (JniMethodInfo?) null;
var newMethod = JniEnvironment.Runtime.TypeManager.GetReplacementMethodInfo (Members.JniPeerTypeName, method, signature);
var newMethod = Members.GetReplacementMethodInfo (method, signature);
if (newMethod.HasValue) {
var typeName = newMethod.Value.TargetJniType ?? Members.JniPeerTypeName;
var methodName = newMethod.Value.TargetJniMethodName is string name ? name.AsSpan () : method;
var methodSig = newMethod.Value.TargetJniMethodSignature is string sig ? sig.AsSpan () : signature;

using var t = new JniType (typeName);
if (newMethod.Value.TargetJniMethodInstanceToStatic &&
t.TryGetStaticMethod (methodName, methodSig, out m)) {
m.ParameterCount = newMethod.Value.TargetJniMethodParameterCount;
m.StaticRedirect = new JniType (typeName);
var info = newMethod.Value;
using var t = CreateTargetType (info, Members);
if (info.TargetJniMethodInstanceToStatic &&
TryGetStaticMethod (t, info, method, signature, out m)) {
m.ParameterCount = info.TargetJniMethodParameterCount;
m.StaticRedirect = CreateTargetType (info, Members);
return m;
}
if (t.TryGetInstanceMethod (methodName, methodSig, out m)) {
if (TryGetInstanceMethod (t, info, method, signature, out m)) {
return m;
}
Console.Error.WriteLine ($"warning: For declared method `{Members.JniPeerTypeName}.{method}.{signature}`, could not find requested method `{typeName}.{methodName}.{methodSig}`!");
var targetType = GetTargetTypeNameForDiagnostics (info, Members);
var targetName = GetTargetMethodNameForDiagnostics (info, method);
var targetSignature = GetTargetMethodSignatureForDiagnostics (info, signature);
Console.Error.WriteLine ($"warning: For declared method `{Members.JniPeerTypeName}.{method}.{signature}`, could not find requested method `{targetType}.{targetName}.{targetSignature}`!");
}
return JniPeerType.GetInstanceMethod (method, signature);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,11 @@ public JniMethodInfo GetMethodInfo (string encodedMember)
JniMethodInfo GetMethodInfo (ReadOnlySpan<char> method, ReadOnlySpan<char> signature)
{
var m = (JniMethodInfo?) null;
var newMethod = JniEnvironment.Runtime.TypeManager.GetReplacementMethodInfo (Members.JniPeerTypeName, method, signature);
var newMethod = Members.GetReplacementMethodInfo (method, signature);
if (newMethod.HasValue) {
using var t = new JniType (newMethod.Value.TargetJniType ?? Members.JniPeerTypeName);
if (t.TryGetStaticMethod (
newMethod.Value.TargetJniMethodName is string name ? name.AsSpan () : method,
newMethod.Value.TargetJniMethodSignature is string sig ? sig.AsSpan () : signature,
out m)) {
var info = newMethod.Value;
using var t = CreateTargetType (info, Members);
if (TryGetStaticMethod (t, info, method, signature, out m)) {
return m;
}
}
Expand Down
145 changes: 130 additions & 15 deletions external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;

namespace Java.Interop {
Expand All @@ -14,26 +15,39 @@ public partial class JniPeerMembers {
private bool isInterface;

public JniPeerMembers (string jniPeerTypeName, Type managedPeerType, bool isInterface)
: this (jniPeerTypeName = GetReplacementType (jniPeerTypeName), managedPeerType, checkManagedPeerType: true, isInterface: isInterface)
: this (GetReplacementType (jniPeerTypeName), managedPeerType, checkManagedPeerType: true, isInterface: isInterface)
{
}

public JniPeerMembers (string jniPeerTypeName, Type managedPeerType)
: this (jniPeerTypeName = GetReplacementType (jniPeerTypeName), managedPeerType, checkManagedPeerType: true, isInterface: false)
: this (GetReplacementType (jniPeerTypeName), managedPeerType, checkManagedPeerType: true, isInterface: false)
{
}

static string GetReplacementType (string jniPeerTypeName)
readonly struct JniPeerTypeNameInfo
{
var replacement = JniEnvironment.Runtime.TypeManager.GetReplacementType (jniPeerTypeName);
if (replacement != null)
return replacement;
return jniPeerTypeName;
public JniPeerTypeNameInfo (string sourceName, string? targetName, IntPtr targetNameUtf8)
{
SourceName = sourceName;
TargetName = targetName;
TargetNameUtf8 = targetNameUtf8;
}

public string SourceName { get; }
public string? TargetName { get; }
public IntPtr TargetNameUtf8 { get; }
}

static JniPeerTypeNameInfo GetReplacementType (string jniPeerTypeName)
{
var typeManager = JniEnvironment.Runtime.TypeManager;
typeManager.GetReplacementTypeInfo (jniPeerTypeName, out var replacement, out var replacementUtf8);
return new JniPeerTypeNameInfo (jniPeerTypeName, replacement, replacementUtf8);
}

JniPeerMembers (string jniPeerTypeName, Type managedPeerType, bool checkManagedPeerType, bool isInterface = false)
JniPeerMembers (JniPeerTypeNameInfo jniPeerTypeName, Type managedPeerType, bool checkManagedPeerType, bool isInterface = false)
{
if (jniPeerTypeName == null)
if (jniPeerTypeName.SourceName == null)
throw new ArgumentNullException (nameof (jniPeerTypeName));

if (checkManagedPeerType) {
Expand All @@ -44,17 +58,19 @@ static string GetReplacementType (string jniPeerTypeName)

#if DEBUG
var signatureFromType = JniEnvironment.Runtime.TypeManager.GetTypeSignature (managedPeerType);
if (signatureFromType.SimpleReference != jniPeerTypeName) {
if (signatureFromType.SimpleReference != jniPeerTypeName.SourceName) {
Debug.WriteLine ("WARNING-Java.Interop: ManagedPeerType <=> JniTypeName Mismatch! javaVM.GetJniTypeInfoForType(typeof({0})).JniTypeName=\"{1}\" != \"{2}\"",
managedPeerType.FullName,
signatureFromType.SimpleReference,
jniPeerTypeName);
jniPeerTypeName.SourceName);
Debug.WriteLine (new System.Diagnostics.StackTrace (true));
}
#endif // DEBUG
}

JniPeerTypeName = jniPeerTypeName;
sourceJniPeerTypeName = jniPeerTypeName.SourceName;
this.jniPeerTypeName = jniPeerTypeName.TargetName;
jniPeerTypeNameUtf8 = jniPeerTypeName.TargetNameUtf8;
ManagedPeerType = managedPeerType;

this.isInterface = isInterface;
Expand All @@ -67,20 +83,27 @@ static string GetReplacementType (string jniPeerTypeName)

static JniPeerMembers CreatePeerMembers (string jniPeerTypeName, Type managedPeerType)
{
return new JniPeerMembers (jniPeerTypeName, managedPeerType, checkManagedPeerType: false);
return new JniPeerMembers (new JniPeerTypeNameInfo (jniPeerTypeName, null, IntPtr.Zero), managedPeerType, checkManagedPeerType: false);
}

JniType? jniPeerType;
string sourceJniPeerTypeName;
string? jniPeerTypeName;
IntPtr jniPeerTypeNameUtf8;
JniInstanceMethods instanceMethods;
JniInstanceFields instanceFields;
JniStaticMethods staticMethods;
JniStaticFields staticFields;

public Type ManagedPeerType {get; private set;}
public string JniPeerTypeName {get; private set;}
public string JniPeerTypeName => jniPeerTypeNameUtf8 == IntPtr.Zero
? jniPeerTypeName ?? sourceJniPeerTypeName
: jniPeerTypeName ??= GetUtf8String (jniPeerTypeNameUtf8);
public JniType JniPeerType {
get {
var t = JniType.GetCachedJniType (ref jniPeerType, JniPeerTypeName);
var t = jniPeerTypeNameUtf8 == IntPtr.Zero
? JniType.GetCachedJniType (ref jniPeerType, jniPeerTypeName ?? sourceJniPeerTypeName)
: JniType.GetCachedJniType (ref jniPeerType, jniPeerTypeNameUtf8);
t.RegisterWithRuntime ();
return t;
}
Expand Down Expand Up @@ -167,6 +190,98 @@ protected virtual JniPeerMembers GetPeerMembers (IJavaPeerable value)
return isInterface ? this : value.JniPeerMembers;
}

JniRuntime.ReplacementMethodInfo? GetReplacementMethodInfo (ReadOnlySpan<char> method, ReadOnlySpan<char> signature)
{
return jniPeerTypeNameUtf8 == IntPtr.Zero
? JniEnvironment.Runtime.TypeManager.GetReplacementMethodInfo (jniPeerTypeName ?? sourceJniPeerTypeName, method, signature)
: JniEnvironment.Runtime.TypeManager.GetReplacementMethodInfo (jniPeerTypeNameUtf8, method, signature);
}

static JniType CreateTargetType (JniRuntime.ReplacementMethodInfo info, JniPeerMembers fallback)
{
if (info.TargetJniTypeUtf8 != IntPtr.Zero)
return new JniType (info.TargetJniTypeUtf8);
if (info.TargetJniType != null)
return new JniType (info.TargetJniType);
return fallback.jniPeerTypeNameUtf8 != IntPtr.Zero
? new JniType (fallback.jniPeerTypeNameUtf8)
: new JniType (fallback.jniPeerTypeName ?? fallback.sourceJniPeerTypeName);
}

static bool TryGetInstanceMethod (
JniType type,
JniRuntime.ReplacementMethodInfo info,
ReadOnlySpan<char> fallbackName,
ReadOnlySpan<char> fallbackSignature,
[System.Diagnostics.CodeAnalysis.NotNullWhen (true)] out JniMethodInfo? method)
{
if (info.TargetJniMethodNameUtf8 != IntPtr.Zero) {
Comment thread
simonrozsival marked this conversation as resolved.
if (info.TargetJniMethodSignatureUtf8 != IntPtr.Zero)
return type.TryGetInstanceMethod (info.TargetJniMethodNameUtf8, info.TargetJniMethodSignatureUtf8, out method);
var signature = info.TargetJniMethodSignature is string targetSignature ? targetSignature.AsSpan () : fallbackSignature;
return type.TryGetInstanceMethod (info.TargetJniMethodNameUtf8, signature, out method);
}

var name = info.TargetJniMethodName is string targetName ? targetName.AsSpan () : fallbackName;
if (info.TargetJniMethodSignatureUtf8 != IntPtr.Zero)
return type.TryGetInstanceMethod (name, info.TargetJniMethodSignatureUtf8, out method);
var fallback = info.TargetJniMethodSignature is string targetSignatureValue ? targetSignatureValue.AsSpan () : fallbackSignature;
return type.TryGetInstanceMethod (name, fallback, out method);
}

static bool TryGetStaticMethod (
JniType type,
JniRuntime.ReplacementMethodInfo info,
ReadOnlySpan<char> fallbackName,
ReadOnlySpan<char> fallbackSignature,
[System.Diagnostics.CodeAnalysis.NotNullWhen (true)] out JniMethodInfo? method)
{
if (info.TargetJniMethodNameUtf8 != IntPtr.Zero) {
if (info.TargetJniMethodSignatureUtf8 != IntPtr.Zero)
return type.TryGetStaticMethod (info.TargetJniMethodNameUtf8, info.TargetJniMethodSignatureUtf8, out method);
var signature = info.TargetJniMethodSignature is string targetSignature ? targetSignature.AsSpan () : fallbackSignature;
return type.TryGetStaticMethod (info.TargetJniMethodNameUtf8, signature, out method);
}

var name = info.TargetJniMethodName is string targetName ? targetName.AsSpan () : fallbackName;
if (info.TargetJniMethodSignatureUtf8 != IntPtr.Zero)
return type.TryGetStaticMethod (name, info.TargetJniMethodSignatureUtf8, out method);
var fallback = info.TargetJniMethodSignature is string targetSignatureValue ? targetSignatureValue.AsSpan () : fallbackSignature;
return type.TryGetStaticMethod (name, fallback, out method);
}

static unsafe string GetUtf8String (IntPtr value)
{
return System.Text.Encoding.UTF8.GetString (MemoryMarshal.CreateReadOnlySpanFromNullTerminated ((byte*)value));
}

static string GetTargetTypeNameForDiagnostics (JniRuntime.ReplacementMethodInfo info, JniPeerMembers fallback)
{
if (info.TargetJniTypeUtf8 != IntPtr.Zero)
return GetUtf8String (info.TargetJniTypeUtf8);
if (info.TargetJniType != null)
return info.TargetJniType;
return fallback.JniPeerTypeName;
}

static string GetTargetMethodNameForDiagnostics (JniRuntime.ReplacementMethodInfo info, ReadOnlySpan<char> fallback)
{
if (info.TargetJniMethodNameUtf8 != IntPtr.Zero)
return GetUtf8String (info.TargetJniMethodNameUtf8);
if (info.TargetJniMethodName != null)
return info.TargetJniMethodName;
return fallback.ToString ();
}

static string GetTargetMethodSignatureForDiagnostics (JniRuntime.ReplacementMethodInfo info, ReadOnlySpan<char> fallback)
{
if (info.TargetJniMethodSignatureUtf8 != IntPtr.Zero)
return GetUtf8String (info.TargetJniMethodSignatureUtf8);
if (info.TargetJniMethodSignature != null)
return info.TargetJniMethodSignature;
return fallback.ToString ();
}

internal static void AssertSelf (IJavaPeerable self)
{
if (self == null)
Expand Down
Loading
Loading