首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >从invokedynamic调用Java varargs方法

从invokedynamic调用Java varargs方法
EN

Stack Overflow用户
提问于 2021-04-12 18:07:15
回答 1查看 182关注 0票数 1

我想动态地从Java调用一个本机方法。因为方法签名在编译时是未知的,所以我为大多数具有相同签名的原始返回类型创建了通用的本机方法:

代码语言:javascript
复制
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方法:

代码语言:javascript
复制
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方法,它的工作方式与上面的相同:

代码语言:javascript
复制
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完成:

代码语言:javascript
复制
mv.visitIntInsn(BIPUSH, 1);
mv.visitIntInsn(BIPUSH, 2);
mv.visitIntInsn(BIPUSH, 3);
mv.visitInvokeDynamicInsn("my_c_function_name", "(III)I", NativeHook.bootstrapHandle);

但是,这不像预期的那样工作,并引发异常:

代码语言:javascript
复制
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方法?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-04-13 09:12:33

包装文件中,我们找到以下语句

调用站点目标的类型必须与从调用的类型描述符派生并传递给引导方法的类型完全相同。

因此,就invoke而言,仅仅兼容是不够的,但它必须与invokeExact兼容。

在应用.asVarargsCollector(Object[].class)之后,可以对句柄进行invoke,但它并不匹配确切的签名。但是我们可以通过asType来调整它

如果当前方法是一个可变的方法,那么处理参数列表转换可能涉及将多个参数转换和集合到一个数组中,如其他地方所述

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

这种方法提供了invokeExact和普通的、不精确的invoke之间的关键行为区别。当调用方的类型描述符与被调用者的类型完全匹配时,这两个方法执行相同的步骤,但当类型不同时,普通invoke也调用asType (或某些内部等效的),以便匹配调用方和被叫者的类型。

换句话说,如果invoke成功地工作,那么asType转换也必须能够满足invokeExact的要求。

我们可以证明:

代码语言:javascript
复制
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);
}
代码语言:javascript
复制
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)

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67063493

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档