Question

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);
Était-ce utile?

La 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.

Autres conseils

Maybe it might not be relevant for the author here, but something that might be useful for anyone new who comes across this problem as I did. Most of these issues are related with callbacks for logging and in such cases, it might be easy to just use C and another JNA call to our advantage.

For instance, if there is a callback function that needs to be provided that has a C signature as log_write(const char* format, va_list args); then one can have a JNA callback that will make another call to C's vsprintf to construct the final string.

public interface CLib extends Library {
  CLib INSTANCE = (CLib) Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"), CLib.class);
  int vsprintf(byte[] buffer, String format, Pointer va_list);
} // interface CLib

public static String format(final String format, final Pointer va_args) {
  CLib jnaLib = CLib.INSTANCE;
  byte[] buffer = new byte[2048];
  jnaLib.vsprintf(buffer, format, va_args);
  return new String(buffer);
}

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top