I want to dynamically call a native method from Java. Because the method signature is unknown at compile time, I've made generic native methods for most primitive return types that have the same signature:
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));
}
}
I'm looking to create a MethodHandle that would then call a matching callXXX method and pass in the boxed funcArgs as if they were provided individually. These callXXX methods can be accessed like this:
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);
I'm using this bootstrap method to indirectly reference this callXXX method in invokedynamic, which works the same way as above:
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));
}
}
The call is then done with invokedynamic like this:
mv.visitIntInsn(BIPUSH, 1);
mv.visitIntInsn(BIPUSH, 2);
mv.visitIntInsn(BIPUSH, 3);
mv.visitInvokeDynamicInsn("my_c_function_name", "(III)I", NativeHook.bootstrapHandle);
However, this does not work as expected and throws an exception:
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
How do I construct a proper MethodHandle that accepts arguments like a regular method but then calls the vararg callXXX method?
In the package documentation we find the statement
So it is not enough to be compatible in terms of
invoke, but it has to be compatible withinvokeExact.After applying
.asVarargsCollector(Object[].class), it is possible toinvokethe handle, but it’s not matching the exact signature. But we can adapt it viaasType:This implies that the combination of
asVarargsCollectorandasTypeshould work. But we can also consider the general relationship betweeninvokeandinvokeExactmentioned in the same method documentation:In other words, if
invokeworks successfully, theasTypeconversion also must be possible to meet the requirements forinvokeExact.Which we can demonstrate:
The bootstrap method does receive the required
MethodTypeas third argument already, so all it needs to do, it to apply.asType(type)using that type.