2

从本机库接收到的 va_list 参数,如何使用 Java 处理它?

我正在使用一个有助于通过回调函数进行日志记录的 C 库。该库是libghoto2,我正在使用 JNA 包装器libgphoto2-java来访问其功能。有关应如何进行日志记录的示例,请参见此 C 文件errordumper中的方法。

我已经设法使用库的gp_log_add_func. 唯一的问题是,回调函数的签名包含一个va_list我不知道如何处理的参数。

正如前面的 C 示例所示,va_list args直接传递到vfprintf. 阅读vfprintf手册会很清楚,它是某种可迭代的数据结构,已经“用va_start宏初始化”并且在迭代之后需要使用va_arg清理va_end。但是我发现防止 JVM 崩溃的唯一方法是使args参数类型为com.sun.jna.Pointerwhile a String[]orObject[]更合适。

如何从此 va_list 中获取数据?

注意为了访问gp_log_add_func,我添加了一些 Java 代码:

org.gphoto2.jna.GPhoto2Native 的补充

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

创建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);
}

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);
4

2 回答 2

2

这是一个示例 varargs 实现,其中包含一些关于 varargs 宏正在做什么的提示:

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

}

所以它基本上是一堆使用堆栈指针的指针算术。带有 JNA 的有问题的部分是您无法访问堆栈指针,并且您可能需要在本机级别扩展 JNA 的回调机制以专门处理可变参数回调以提供堆栈指针。

即使这样也可能存在问题。正如您从上面的示例中看到的那样,您实际上需要可变参数函数签名的最后一个命名参数的地址才能访问可变参数。一般来说,这将是非常棘手的。

于 2012-07-12T22:41:53.710 回答
0

也许它可能与这里的作者无关,但对于像我一样遇到这个问题的任何新人来说可能会有用。大多数这些问题都与日志记录回调有关,在这种情况下,使用 C 和另一个 JNA 调用对我们有利可能很容易。

例如,如果需要提供一个具有 C 签名的回调函数,log_write(const char* format, va_list args);则可以有一个 JNA 回调,该回调将再次调用 C 的 vsprintf 以构造最终字符串。

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);
}

于 2020-03-21T22:39:55.230 回答