我想动态地从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, 2, 3);我使用这个引导方法间接地引用了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,它像常规方法一样接受参数,然后调用vararg callXXX方法?
发布于 2021-04-13 09:12:33
在包装文件中,我们找到以下语句
调用站点目标的类型必须与从调用的类型描述符派生并传递给引导方法的类型完全相同。
因此,就invoke而言,仅仅兼容是不够的,但它必须与invokeExact兼容。
在应用.asVarargsCollector(Object[].class)之后,可以对句柄进行invoke,但它并不匹配确切的签名。但是我们可以通过asType来调整它
如果当前方法是一个可变的方法,那么处理参数列表转换可能涉及将多个参数转换和集合到一个数组中,如其他地方所述。
这意味着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, 2, 3);
} catch(Throwable t) {
System.out.println(t);
}
try {
System.out.println("\ninvokeExact(1, 2, 3): ");
h.invokeExact(1, 2, 3);
} catch(Throwable t) {
System.out.println(t);
}
MethodType type = MethodType.methodType(void.class, int.class, int.class, int.class);
try {
System.out.println("\n.asType(type).invokeExact(1, 2, 3): ");
h.asType(type).invokeExact(1, 2, 3);
} catch(Throwable t) {
System.out.println(t);
}invoke(1, 2, 3):
1 2 3
invokeExact(1, 2, 3):
java.lang.invoke.WrongMethodTypeException: expected (Object[])PrintStream but found (int,int,int)void
.asType(type).invokeExact(1, 2, 3):
1 2 3引导方法已经接收到所需的MethodType作为第三个参数,所以它所需要做的就是使用该类型应用.asType(type)。
https://stackoverflow.com/questions/67063493
复制相似问题