-
Notifications
You must be signed in to change notification settings - Fork 524
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Marshal methods part 4 of 5 (runtime) #7285
Changes from 52 commits
35f4e24
e34f88e
eb15bd2
a80e4a1
49751f7
803b3e3
e6101c1
657aff0
e8d3026
4d7ec30
39068d3
82ece43
acd5bc8
9a476d8
8d2503f
f913d24
52c99db
e5358f4
2f15f67
21c066f
3c8901a
7dc40fc
2b2ce55
9c38c68
85f2cfa
024b5f5
4d92570
20a2f00
2d3c043
071e9d3
710242e
af91895
673836c
ca2f92d
10e63e2
238417e
72c4b60
eb94adc
a8471cd
cfaabf2
8ca61bb
a473f27
d99b7e3
cfa1b30
9c9612d
334619b
c9332a8
e518a4e
6ca1245
63bf193
c516792
c8a2d9f
0aa3e95
49895b8
92fa671
6b116bf
e1e59f0
df04d47
d3d9007
b459e4a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<linker> | ||
<assembly fullname="System.Runtime.InteropServices"> | ||
<type fullname="System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute" /> | ||
</assembly> | ||
</linker> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -473,8 +473,28 @@ static bool CallRegisterMethodByIndex (JniNativeMethodRegistrationArguments argu | |
public override void RegisterNativeMembers (JniType nativeClass, Type type, string? methods) => | ||
RegisterNativeMembers (nativeClass, type, methods.AsSpan ()); | ||
|
||
#if ENABLE_MARSHAL_METHODS | ||
// Temporary hack, see comments in RegisterNativeMembers below | ||
static readonly Dictionary<string, string[]> dynamicRegistrationMethods = new Dictionary<string, string[]> (StringComparer.Ordinal) { | ||
{"Android.Views.View+IOnLayoutChangeListenerImplementor", new string[] { "GetOnLayoutChange_Landroid_view_View_IIIIIIIIHandler" }}, | ||
{"Android.Views.View+IOnLayoutChangeListenerInvoker", new string[] { "GetOnLayoutChange_Landroid_view_View_IIIIIIIIHandler" }}, | ||
{"Java.Interop.TypeManager+JavaTypeManager", new string[] { "GetActivateHandler" }}, | ||
}; | ||
#endif | ||
|
||
public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan<char> methods) | ||
{ | ||
#if ENABLE_MARSHAL_METHODS | ||
Logger.Log (LogLevel.Info, "monodroid-mm", $"RegisterNativeMembers ('{nativeClass?.Name}', '{type?.FullName}', '{methods.ToString ()}')"); | ||
Logger.Log (LogLevel.Info, "monodroid-mm", "RegisterNativeMembers called from:"); | ||
var st = new StackTrace (true); | ||
Logger.Log (LogLevel.Info, "monodroid-mm", st.ToString ()); | ||
|
||
if (methods.IsEmpty) { | ||
Logger.Log (LogLevel.Info, "monodroid-mm", "No methods to register, returning"); | ||
return; | ||
} | ||
#endif | ||
try { | ||
if (FastRegisterNativeMembers (nativeClass, type, methods)) | ||
return; | ||
|
@@ -497,6 +517,9 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< | |
MethodInfo []? typeMethods = null; | ||
|
||
ReadOnlySpan<char> methodsSpan = methods; | ||
#if ENABLE_MARSHAL_METHODS | ||
bool needToRegisterNatives = false; | ||
#endif | ||
while (!methodsSpan.IsEmpty) { | ||
int newLineIndex = methodsSpan.IndexOf ('\n'); | ||
|
||
|
@@ -508,7 +531,7 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< | |
out ReadOnlySpan<char> callbackString, | ||
out ReadOnlySpan<char> callbackDeclaringTypeString); | ||
|
||
Delegate callback; | ||
Delegate? callback = null; | ||
if (callbackString.SequenceEqual ("__export__")) { | ||
var mname = name.Slice (2); | ||
MethodInfo? minfo = null; | ||
|
@@ -522,6 +545,9 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< | |
if (minfo == null) | ||
throw new InvalidOperationException (String.Format ("Specified managed method '{0}' was not found. Signature: {1}", mname.ToString (), signature.ToString ())); | ||
callback = CreateDynamicCallback (minfo); | ||
#if ENABLE_MARSHAL_METHODS | ||
needToRegisterNatives = true; | ||
#endif | ||
} else { | ||
Type callbackDeclaringType = type; | ||
if (!callbackDeclaringTypeString.IsEmpty) { | ||
|
@@ -530,20 +556,78 @@ public void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan< | |
while (callbackDeclaringType.ContainsGenericParameters) { | ||
callbackDeclaringType = callbackDeclaringType.BaseType!; | ||
} | ||
GetCallbackHandler connector = (GetCallbackHandler) Delegate.CreateDelegate (typeof (GetCallbackHandler), | ||
callbackDeclaringType, callbackString.ToString ()); | ||
callback = connector (); | ||
#if ENABLE_MARSHAL_METHODS | ||
// TODO: this is temporary hack, it needs a full fledged registration mechanism for methods like these (that is, ones which | ||
// aren't registered with [Register] but are baked into Mono.Android's managed and Java code) | ||
bool createCallback = false; | ||
string declaringTypeName = callbackDeclaringType.FullName; | ||
string callbackName = callbackString.ToString (); | ||
|
||
foreach (var kvp in dynamicRegistrationMethods) { | ||
string dynamicTypeName = kvp.Key; | ||
|
||
foreach (string dynamicCallbackMethodName in kvp.Value) { | ||
if (ShouldRegisterDynamically (declaringTypeName, callbackName, dynamicTypeName, dynamicCallbackMethodName)) { | ||
createCallback = true; | ||
break; | ||
} | ||
} | ||
|
||
if (createCallback) { | ||
break; | ||
} | ||
} | ||
|
||
if (createCallback) { | ||
Logger.Log (LogLevel.Info, "monodroid-mm", $" creating delegate for: '{callbackString.ToString()}' in type {callbackDeclaringType.FullName}"); | ||
#endif | ||
GetCallbackHandler connector = (GetCallbackHandler) Delegate.CreateDelegate (typeof (GetCallbackHandler), | ||
callbackDeclaringType, callbackString.ToString ()); | ||
callback = connector (); | ||
#if ENABLE_MARSHAL_METHODS | ||
} else { | ||
Logger.Log (LogLevel.Warn, "monodroid-mm", $" would try to create delegate for: '{callbackString.ToString()}' in type {callbackDeclaringType.FullName}"); | ||
} | ||
#endif | ||
} | ||
|
||
if (callback != null) { | ||
#if ENABLE_MARSHAL_METHODS | ||
needToRegisterNatives = true; | ||
#endif | ||
natives [nativesIndex++] = new JniNativeMethodRegistration (name.ToString (), signature.ToString (), callback); | ||
} | ||
natives [nativesIndex++] = new JniNativeMethodRegistration (name.ToString (), signature.ToString (), callback); | ||
} | ||
|
||
methodsSpan = newLineIndex != -1 ? methodsSpan.Slice (newLineIndex + 1) : default; | ||
} | ||
|
||
JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, natives, natives.Length); | ||
#if ENABLE_MARSHAL_METHODS | ||
if (needToRegisterNatives) { | ||
// We need to reallocate as JniEnvironment.Types.RegisterNatives uses a `foreach` loop and will NREX otherwise (since we aren't filling all | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should fix Java.Interop so that null Rephrased: for (int i = 0; i < numMethods; ++i) {
var m = methods [i];
…
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be fixed by: dotnet/java-interop#1029 |
||
// the slots in the original array potentially) | ||
var newNatives = new JniNativeMethodRegistration[nativesIndex]; | ||
Array.Copy (natives, newNatives, nativesIndex); | ||
natives = newNatives; | ||
#endif | ||
JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, natives, nativesIndex); | ||
#if ENABLE_MARSHAL_METHODS | ||
} | ||
#endif | ||
} catch (Exception e) { | ||
JniEnvironment.Runtime.RaisePendingException (e); | ||
} | ||
|
||
#if ENABLE_MARSHAL_METHODS | ||
bool ShouldRegisterDynamically (string callbackTypeName, string callbackString, string typeName, string callbackName) | ||
{ | ||
if (String.Compare (typeName, callbackTypeName, StringComparison.Ordinal) != 0) { | ||
return false; | ||
} | ||
|
||
return String.Compare (callbackName, callbackString, StringComparison.Ordinal) == 0; | ||
} | ||
#endif | ||
} | ||
|
||
static int CountMethods (ReadOnlySpan<char> methodsSpan) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This raises a "meta-question": the implication here is that whether we register via
JNIEnv::RegisterNatives()
or via CJava_…
function is done on a per-method basis.Is that actually supported on Android?
Rephrased; given the Java type:
and then at runtime we have a
Java_example_C_m_1viaFunction()
function in a loaded.so
, and we also callJNIEnv:RegisterNatives()
but only form_viaRegister()
…Does that work? The JNI docs are not clear on how these two mechanisms interact.
For that matter, if both native mechanisms are provided, which mechanism actually invoked at runtime? Which has priority?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Answering that would require a deeper dive into Android JNI sources I suppose...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this requires reading Android source. An on-device test would answer this as well. (Though that answer could change over time, and thus wouldn't be a "final" answer, but it could still be informative.)