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 toinvoke
the handle, but it’s not matching the exact signature. But we can adapt it viaasType
:This implies that the combination of
asVarargsCollector
andasType
should work. But we can also consider the general relationship betweeninvoke
andinvokeExact
mentioned in the same method documentation:In other words, if
invoke
works successfully, theasType
conversion also must be possible to meet the requirements forinvokeExact
.Which we can demonstrate:
The bootstrap method does receive the required
MethodType
as third argument already, so all it needs to do, it to apply.asType(type)
using that type.