diff --git a/src/main/java/jnr/ffi/annotations/Variadic.java b/src/main/java/jnr/ffi/annotations/Variadic.java new file mode 100644 index 00000000..f4ad5de0 --- /dev/null +++ b/src/main/java/jnr/ffi/annotations/Variadic.java @@ -0,0 +1,20 @@ +package jnr.ffi.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Specifies that a non-varargs function binding will call a variadic C function with the specified number of fixed + * arguments. + * + * Platforms that pass variadic arguments differently than fixed arguments will need this annotation if the Java binding + * does not itself use varargs. Without Java varargs or this annotation, there's no way for jnr-ffi to know where fixed + * args end and variadic arguments begin, causing them all to be passed the way fixed arguments get passed on the given + * platform. + * + * See https://github.com/jnr/jnr-ffi/pull/292 + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface Variadic { + int fixedCount(); +} diff --git a/src/main/java/jnr/ffi/provider/jffi/AsmLibraryLoader.java b/src/main/java/jnr/ffi/provider/jffi/AsmLibraryLoader.java index cbb9c682..61cc90f2 100644 --- a/src/main/java/jnr/ffi/provider/jffi/AsmLibraryLoader.java +++ b/src/main/java/jnr/ffi/provider/jffi/AsmLibraryLoader.java @@ -21,6 +21,7 @@ import com.kenai.jffi.Function; +import jnr.ffi.annotations.Variadic; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; @@ -126,9 +127,11 @@ private T generateInterfaceImpl(final NativeLibrary library, Class interf InterfaceScanner scanner = new InterfaceScanner(interfaceClass, typeMapper, libraryCallingConvention); for (NativeFunction function : scanner.functions()) { - if (function.getMethod().isVarArgs()) { - ObjectField field = builder.getObjectField(invokerFactory.createInvoker(function.getMethod()), Invoker.class); - generateVarargsInvocation(builder, function.getMethod(), field); + Method method = function.getMethod(); + + if (method.isVarArgs() || method.isAnnotationPresent(Variadic.class)) { + ObjectField field = builder.getObjectField(invokerFactory.createInvoker(method), Invoker.class); + generateVarargsInvocation(builder, method, field); continue; } @@ -137,13 +140,13 @@ private T generateInterfaceImpl(final NativeLibrary library, Class interf try { long functionAddress = library.findSymbolAddress(functionName); - FromNativeContext resultContext = new MethodResultContext(runtime, function.getMethod()); - SignatureType signatureType = DefaultSignatureType.create(function.getMethod().getReturnType(), resultContext); - ResultType resultType = getResultType(runtime, function.getMethod().getReturnType(), + FromNativeContext resultContext = new MethodResultContext(runtime, method); + SignatureType signatureType = DefaultSignatureType.create(method.getReturnType(), resultContext); + ResultType resultType = getResultType(runtime, method.getReturnType(), resultContext.getAnnotations(), typeMapper.getFromNativeType(signatureType, resultContext), resultContext); - ParameterType[] parameterTypes = getParameterTypes(runtime, typeMapper, function.getMethod()); + ParameterType[] parameterTypes = getParameterTypes(runtime, typeMapper, method); boolean saveError = jnr.ffi.LibraryLoader.saveError(libraryOptions, function.hasSaveError(), function.hasIgnoreError()); @@ -152,7 +155,7 @@ private T generateInterfaceImpl(final NativeLibrary library, Class interf for (MethodGenerator g : generators) { if (g.isSupported(resultType, parameterTypes, function.convention())) { - g.generate(builder, function.getMethod().getName(), jffiFunction, resultType, parameterTypes, !saveError); + g.generate(builder, method.getName(), jffiFunction, resultType, parameterTypes, !saveError); break; } } @@ -161,7 +164,7 @@ private T generateInterfaceImpl(final NativeLibrary library, Class interf String errorFieldName = "error_" + uniqueId.incrementAndGet(); cv.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, errorFieldName, ci(String.class), null, ex.getMessage()); generateFunctionNotFound(cv, builder.getClassNamePath(), errorFieldName, functionName, - function.getMethod().getReturnType(), function.getMethod().getParameterTypes()); + method.getReturnType(), method.getParameterTypes()); } } diff --git a/src/main/java/jnr/ffi/provider/jffi/DefaultInvokerFactory.java b/src/main/java/jnr/ffi/provider/jffi/DefaultInvokerFactory.java index 5ad69176..1a75e597 100644 --- a/src/main/java/jnr/ffi/provider/jffi/DefaultInvokerFactory.java +++ b/src/main/java/jnr/ffi/provider/jffi/DefaultInvokerFactory.java @@ -32,6 +32,7 @@ import jnr.ffi.annotations.Meta; import jnr.ffi.annotations.StdCall; import jnr.ffi.annotations.Synchronized; +import jnr.ffi.annotations.Variadic; import jnr.ffi.mapper.DataConverter; import jnr.ffi.mapper.DefaultSignatureType; import jnr.ffi.mapper.FromNativeContext; @@ -130,8 +131,17 @@ public Invoker createInvoker(Method method) { if (method.isVarArgs()) { invoker = new VariadicInvoker(runtime, functionInvoker, typeMapper, parameterTypes, functionAddress, resultType, saveError, callingConvention); } else { - Function function = new Function(functionAddress, - getCallContext(resultType, parameterTypes, callingConvention, saveError)); + Function function; + // check if method is all-fixed but calling a variadic function + Variadic variadic = method.getAnnotation(Variadic.class); + + if (variadic != null) { + function = new Function(functionAddress, + getCallContext(resultType, variadic.fixedCount(), parameterTypes, callingConvention, saveError)); + } else { + function = new Function(functionAddress, + getCallContext(resultType, parameterTypes, callingConvention, saveError)); + } Marshaller[] marshallers = new Marshaller[parameterTypes.length]; for (int i = 0; i < marshallers.length; ++i) { @@ -365,7 +375,7 @@ public final Object invoke(Object self, Object[] parameters) { // platforms where we have not rebuilt the jffi stub we still set up this trailing NULL to be compatible // with ffi_prep_cif and the common va_arg layout. Once all platforms have been rebuilt to use // ffi_prep_cif_var, this NULL and the +1 on variableArgs allocation above can be removed. - + argTypes[fixedParameterTypes.length + variableArgsCount - 1] = new ParameterType( Pointer.class, Types.getType(runtime, Pointer.class, Collections.emptyList()).getNativeType(), diff --git a/src/test/java/jnr/ffi/VarargsTest.java b/src/test/java/jnr/ffi/VarargsTest.java index 7a31b154..54642012 100644 --- a/src/test/java/jnr/ffi/VarargsTest.java +++ b/src/test/java/jnr/ffi/VarargsTest.java @@ -2,6 +2,7 @@ import jnr.ffi.annotations.Encoding; import jnr.ffi.annotations.Meta; +import jnr.ffi.annotations.Variadic; import jnr.ffi.provider.FFIProvider; import jnr.ffi.types.size_t; import org.junit.jupiter.api.AfterAll; @@ -15,6 +16,8 @@ public class VarargsTest { public static interface C { + @Variadic(fixedCount = 3) + public int snprintf(Pointer buffer, @size_t long bufferSize, String format, long value); public int snprintf(Pointer buffer, @size_t long bufferSize, String format, Object... varargs); } @@ -39,6 +42,14 @@ public static void tearDownClass() throws Exception { assertEquals("12345", result); } + @Test public void testSizeTNoVarargs() { + Pointer ptr = Runtime.getRuntime(c).getMemoryManager().allocate(5000); + int size = c.snprintf(ptr, 5000, "%zu", 12345); + assertEquals(5, size); + String result = ptr.getString(0, size, Charset.defaultCharset()); + assertEquals("12345", result); + } + @Test public void testMetaAscii() throws UnsupportedEncodingException { Pointer ptr = Runtime.getRuntime(c).getMemoryManager().allocate(5000); int size = c.snprintf(ptr, 5000, "%s", AsciiEncoding.class, "\u7684");