微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

从invokedynamic

如何解决从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

这意味着 asVarargsCollectorasType 的组合应该有效。但我们也可以考虑同一个方法文档中提到的 invokeinvokeExact 之间的一般关系:

此方法提供了 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 举报,一经查实,本站将立刻删除。