有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

jvm从InvokedDynamic调用Java varargs方法

我想从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));
    }
}

然后使用InvokedDynamic完成调用,如下所示:

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,它像常规方法一样接受参数,然后调用varargcallXXX方法


共 (1) 个答案

  1. # 1 楼答案

    the package documentation中,我们找到了语句

    The type of the call site's target must be exactly equal to the type derived from the invocation's type descriptor and passed to the bootstrap method.

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

    应用.asVarargsCollector(Object[].class)后,可以invoke句柄,但它与确切的签名不匹配。但我们可以通过^{}对其进行调整:

    If the current method is a variable arity method handle argument list conversion may involve the conversion and collection of several arguments into an array, as described elsewhere.

    这意味着asVarargsCollectorasType的组合应该有效。但是我们也可以考虑在同一方法文档中提到的^ {CD1>}和^ {CD2>}之间的一般关系:

    This method provides the crucial behavioral difference between invokeExact and plain, inexact invoke. The two methods perform the same steps when the caller's type descriptor exactly matches the callee's, but when the types differ, plain invoke also calls asType (or some internal equivalent) in order to match up the caller's and callee's types.

    换句话说,如果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
    

    bootstrap方法已经接收到必需的MethodType作为第三个参数,因此它需要做的就是使用该类型应用.asType(type)