Search code examples
javacallbackjnavariadic-functionslibgphoto2

processing va_list argument in JNA callback function


How can I process a va_list argument with Java, having received it from the native library?

I am using a C library that facilitates logging through a callback function. The library is libghoto2 and I am using a JNA wrapper libgphoto2-java to access its features. See the errordumper method in this C file for an example of how the logging should be done.

I've managed to add a Java-written callback function using the library's gp_log_add_func. The only problem is, the callback function's signature contains a va_list argument that I don't know how to process.

As the C example from earlier shows, va_list args is passed directly into vfprintf. Reading the vfprintf manual it becomes clear that it is some sort of iterable data structure that has "been initialized with the va_start macro" and after iterating using va_arg a cleanup with va_end is required. But the only way I found for keeping the JVM from crashing, is to make the args parameter of type com.sun.jna.Pointer while a String[] or Object[] would be more suitable.

How do I get the data out of this va_list?

N.B. In order to get access to gp_log_add_func, I added some Java code:

Additions to org.gphoto2.jna.GPhoto2Native:

int gp_log_add_func(int logLevel, LogFunc func, Pointer data);

Created org.gphoto2.jna.LogFunc:

public interface LogFunc extends Callback {
    public static final int GP_LOG_ERROR = 0;
    public static final int GP_LOG_VERBOSE = 1;
    public static final int GP_LOG_DEBUG = 2;
    public static final int GP_LOG_DATA = 3;
    public static final int GP_LOG_ALL = GP_LOG_DATA;

    //the args argument is a va_list 
    public void log(int logLevel, String domain, String format, Pointer args, Pointer data);
}

The implementation and usage of org.gphoto2.jna.LogFunc:

LogFunc callback = new LogFunc() {
        public void log(int logLevel, String domain, String format, Pointer args, Pointer data) {
            System.out.println("[" + domain + "] " + format);
            System.out.println(args.toString());
        }
};
GPhoto2Native.INSTANCE.gp_log_add_func(LogFunc.GP_LOG_ALL, callback, null);

Solution

  • Here is an example varargs implementation, with some hints about what the varargs macros are doing:

    int printf(const char* fmt, ...) {
      va_list argp;
    
      va_start(argp, fmt); // usually something like: argp = (char *)&fmt - sizeof(void *);
    
      int arg1 = va_arg(argp, int); // *(int *)argp; argp += sizeof(int);
      void *arg2 = va_arg(argp, void*); // *(void **)argp; argp += sizeof(void *);
      float arg3 = va_arg(argp, float); // *(float *)argp; argp += sizeof(float);
    
      va_end(argp); // no-op
    
    }
    

    So it's basically a bunch of pointer arithmetic working with the stack pointer. The problematic piece w/r/t JNA is that you don't have access to the stack pointer, and you'd probably need to extend JNA's callback mechanism at the native level to specially handle variadic callbacks to provide the stack pointer.

    Even that is potentially problematic. As you can see from the above example, you actually need the address of the last named argument of the variadic function signature in order to access the variadic arguments. That would be very tricky to do generically.