【问题标题】:processing va_list argument in JNA callback function在 JNA 回调函数中处理 va_list 参数
【发布时间】:2012-06-14 04:30:25
【问题描述】:

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

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

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

正如C example from earlier 所示,va_list args 直接传递给vfprintf。阅读vfprintf manual 可以清楚地看出它是某种“已使用va_start 宏初始化”的可迭代数据结构,并且在使用va_arg 进行迭代之后,需要使用va_end 进行清理。 但我发现防止 JVM 崩溃的唯一方法是设置 com.sun.jna.Pointer 类型的 args 参数,而 String[]Object[] 会更合适。

如何从这个 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);

【问题讨论】:

  • 您必须查看相关平台上 va_list 的实际实现(从头文件 (vararg.h) 开始,并可能深入研究编译器细节)。 va_list通常是一个指向堆栈的指针,各种提取参数只是将指针移动适当大小的偏移量的偏移操作。您必须使用指针手动提取内容,使用各种数据提取方法,如 Pointer.getInt(int offset),具体取决于您尝试访问的类型。
  • 这就是我害怕的。从纯粹主义者的角度来看,JNA/Java 业务应该与任何本地业务分开。这尤其令人担忧,因为我目前正在 openSuse 上进行开发,以后可能想在 OS X 上进行部署。不幸的是,我对 C/C++ 了解不多,但可能有一种方法可以使用第二个本机库来转换 va_list进入更易于 JNA 管理的东西?
  • JNA 应该为 va_list 处理提供映射当然是一个有争议的案例。随意打开一个问题。
  • 今天我又有了一些空闲时间来做这件事;我想我可能已经找到了一种方法来提取数据并将整个东西变成一个 Java List。我现在需要知道的唯一两件事是 a) 我如何访问 va_arg,以及 b) 我如何将类型作为参数传递给它?
  • 如果你有 va_start(va_list 地址)的结果,你可以使用指针函数来执行 va_arg 的等价(va_end 不可能做任何事情)。获取可变参数的地址是困难的部分。

标签: java callback jna variadic-functions libgphoto2


【解决方案1】:

也许它可能与这里的作者无关,但对于像我一样遇到这个问题的任何新人来说可能会有用。这些问题中的大多数都与日志记录回调有关,在这种情况下,使用 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);
}

【讨论】:

    【解决方案2】:

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

    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 的回调机制以专门处理可变参数回调以提供堆栈指针。

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

    【讨论】:

    • 我想我将恢复为仅调用 gphoto2 cli 并结合匹配 stdout 和 stderr 的“聪明的正则表达式”。它可能不是最巧妙的解决方案,但至少它更有可能奏效。
    • @derabbink 仅供参考,使用 JavaCPP 更容易解决这个问题:它已经拥有自动构建、捆绑和加载 JNI 库的基础设施。我最近不得不为 FFmpeg 这样做:github.com/bytedeco/javacpp-presets/commit/…
    猜你喜欢
    • 1970-01-01
    • 2011-05-01
    • 2023-03-17
    • 2017-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多