如何解决从invokedynamic
我想从 Java 动态调用本机方法。 因为方法签名在编译时是未知的,所以我为大多数具有相同签名的原始返回类型创建了通用本机方法:
class NativeHook {
public static native int callInt(String funcName,Object... funcArgs);
public static native void callVoid(String funcName,Object... funcArgs);
public static native Object callObject(String funcName,Object... funcArgs);
private static MethodHandle getNativeMethod(String callName,Class<?> returnType) {
return MethodHandles.lookup().findStatic(NativeHook.class,callName,MethodType.methodType(returnType,String.class,Object[].class));
}
}
我希望创建一个 MethodHandle,然后调用匹配的 callXXX
方法并传入装箱的 funcArgs
,就好像它们是单独提供的一样。这些 callXXX
方法可以这样访问:
MethodHandle callInt = getNativeMethod("callInt",int.class);
MethodHandle boundCallInt = callInt.bindTo("my_c_function_name").asVarargsCollector(Object[].class);
// returns NativeHook.callInt("my_c_function_name",1,2,3)
boundCallInt.invokeWithArguments(1,3);
我使用这个 bootstrap 方法间接引用了 invokeDynamic 中的这个 callXXX
方法,其工作方式与上面相同:
public static CallSite bootstrap(MethodHandles.Lookup caller,String name,MethodType type) {
if (type.returnType() == int.class) {
MethodHandle callInt = getNativeMethod("callInt",int.class);
return new ConstantCallSite(callInt.bindTo(name).asVarargsCollector(Object[].class));
}
}
然后使用invokedynamic完成调用,如下所示:
mv.visitIntInsn(BIPUSH,1);
mv.visitIntInsn(BIPUSH,2);
mv.visitIntInsn(BIPUSH,3);
mv.visitInvokeDynamicInsn("my_c_function_name","(III)I",NativeHook.bootstrapHandle);
然而,这并没有按预期工作并引发异常:
Caused by: java.lang.invoke.WrongMethodTypeException: MethodHandle(Object[])int should be of type (int,int,int)int
at java.lang.invoke.CallSite.wrongtargettype(CallSite.java:194)
at java.lang.invoke.CallSite.makeSite(CallSite.java:335)
... 16 more
如何构造一个合适的 MethodHandle,它像常规方法一样接受参数,然后调用可变参数 callXXX
方法?
解决方法
在 the package documentation 中,我们找到了语句
调用站点的目标类型必须与从调用的类型描述符派生并传递给引导方法的类型完全相同。
所以只兼容invoke
是不够的,还必须兼容invokeExact
。
应用 .asVarargsCollector(Object[].class)
后,可以 invoke
句柄,但它不匹配确切的签名。但我们可以通过 asType
:
如果当前方法是变量arity方法句柄参数列表转换,可能会涉及到几个参数的转换和集合到一个数组中,如described elsewhere。
这意味着 asVarargsCollector
和 asType
的组合应该有效。但我们也可以考虑同一个方法文档中提到的 invoke
和 invokeExact
之间的一般关系:
此方法提供了 invokeExact
和简单、不精确的 invoke
之间关键的行为差异。当调用者的类型描述符与被调用者的类型描述符完全匹配时,这两种方法执行相同的步骤,但是当类型不同时,普通的 invoke
也会调用 asType
(或某些内部等效项)以匹配调用者的和被调用者的类型。
换句话说,如果 invoke
成功运行,那么 asType
转换也必须能够满足 invokeExact
的要求。
我们可以证明:
MethodHandles.Lookup l = MethodHandles.lookup();
MethodHandle h = l.bind(System.out,"printf",MethodType.methodType(PrintStream.class,String.class,Object[].class));
h = h.bindTo("%s %s %s%n").asVarargsCollector(Object[].class);
try {
System.out.println("invoke(1,2,3): ");
h.invoke(1,3);
} catch(Throwable t) {
System.out.println(t);
}
try {
System.out.println("\ninvokeExact(1,3): ");
h.invokeExact(1,3);
} catch(Throwable t) {
System.out.println(t);
}
MethodType type = MethodType.methodType(void.class,int.class,int.class);
try {
System.out.println("\n.asType(type).invokeExact(1,3): ");
h.asType(type).invokeExact(1,3);
} catch(Throwable t) {
System.out.println(t);
}
invoke(1,3):
1 2 3
invokeExact(1,3):
java.lang.invoke.WrongMethodTypeException: expected (Object[])PrintStream but found (int,int,int)void
.asType(type).invokeExact(1,3):
1 2 3
bootstrap 方法确实接收到所需的 MethodType
作为第三个参数,所以它需要做的就是使用该类型应用 .asType(type)
。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。