【问题标题】:Callback function translation in C-DLL to java JNA将 C-DLL 中的回调函数转换为 java JNA
【发布时间】:2020-04-12 04:49:30
【问题描述】:

我在使用 JNA 将回调函数从 C-DLL 映射/转换为 Java 时遇到问题。

在C头文件中写入如下回调函数:

// ! callback function header whenever a data report is received from a device
typedef void (FR_callback_func)(Data_t frame);

上述Data_t的结构如下:

// ! Carries information about one signal.
typedef struct
{
    unsigned char index;
    int isval;
    unsigned short val;
    int arr_Length;
    unsigned char array[8];
} Data_t ;

Data_t 结构被调用的函数:

int getData(int val,Data_t *data);

现在我翻译成我的JAVA代码如下:

public interface device extends Library 
{
    public interface FR_callback_func extends Callback
    {
        void invoke(Data_t signal);
    }

    public class Data_t extends Structure implements com.sun.jna.Structure.ByReference 
    {
        public static class ByReference extends Data_t implements Structure.ByReference { }
        public byte index;
        public int isval;
        public  short val;
        public int arr_Length;
        public byte[] array = new byte[8];
        @Override
        protected java.util.List<java.lang.String> getFieldOrder()
        {    
            return Arrays.asList(new String[] {"index","isval","val","arr_Length","array"});
        }
    } 

    public int getData (int val,Data_t.ByReference data);
}

然后我尝试在我的主要功能中使用它,如下所示:

public static void main(String[] args) throws IOException 
{
    Data_t .ByReference data_t = new Data_t .ByReference();
    int data = 0;
    int val = 0;

    device h = (device) Native.load("Library", device.class);

    data = h.getData (val, data_t);
}

我的问题是我是否正确翻译了上面的 C 代码?特别是回调函数?由于无法操纵C代码。因此我必须在 JAVA 中翻译提供的 C-DLL 代码。

我们将非常感谢您的建议。

【问题讨论】:

  • 这可能是由于填充问题吗?在Data_t 定义中,它以和unsigned char 开头,后跟int,但我们大多数人不希望第二个结构成员从第二个字节开始:编译器通常包含填充以对齐int在自己的边界上。这意味着结构中存在几个字节的不可见填充,这些字节不能通过直接读取结构定义来解决。或许与此有关?
  • @Steve Friedl 不,我不认为这是由于这个问题。我认为的问题是我必须用Java翻译的回调函数的定义。
  • BL_signal_t 是在哪里创建的?我会在 C 端检查 Data_t *data 是否为空指针(在这种情况下很早就存在)。
  • @jerch 我编辑了我的帖子,这不是BL_signal_t 实际上是Data_t
  • @Sid 你应该从Java端的方法声明中删除.ByReference,并提供一个预先创建的Data_t变量,JNA会自动处理下面的指针。

标签: java c eclipse dll dynamic-dll-import


【解决方案1】:

您是否在上面仍然缺少所需的部分:

  • C 示例库
// gcc -c -Wall -Werror -fPIC foo.c
// gcc -shared -o libfoo.so foo.o

#include <stdio.h>

typedef struct {
  int x;
  int y;
} Foo;

typedef Foo* (*FooCallback)(int, int);

void printFoo(Foo *foo) {
  printf("foo: %p x: %d y: %d\n", foo, foo->x, foo->y);
}

FooCallback callback = NULL;

void setCallback(FooCallback cb) {
  callback = cb;
}

void runCallback() {
  if (callback) {
    Foo *foo = callback(123, 456);
    printf("foo from callback: %p x: %d y: %d\n", foo, foo->x, foo->y);
  } else {
    printf("callback not set!\n");
  }
}
  • java示例代码
// javac -classpath .:jna.jar Foo.java 
// java -classpath .:jna.jar Foo

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Structure;
import com.sun.jna.Callback;


public class Foo {
  public interface CFoo extends Library {
    public class SFoo extends Structure {
      public static class ByReference extends SFoo implements Structure.ByReference { }
      public static class ByValue extends SFoo implements Structure.ByValue { }
      public int x;
      public int y;
    }

    void printFoo(SFoo foo);

    public interface FooCallback extends Callback {
      SFoo invoke(int x, int y);
    }

    void setCallback(FooCallback cb);
    void runCallback();
  }

  public static void main(String[] args) {
    CFoo cfoo = (CFoo) Native.loadLibrary("libfoo.so", CFoo.class);

    // test construction of SFoo
    CFoo.SFoo foo = new CFoo.SFoo();
    foo.x = 23;
    foo.y = 42;

    // test auto ByReference
    System.out.println("foo pointer: " + foo.getPointer());
    cfoo.printFoo(foo);

    // callback test
    // no callback set yet
    cfoo.runCallback();

    // set callback
    // declare return value outside to "survive" callback scope
    // (this is needed for ByReference return values)
    CFoo.SFoo foo2 = new CFoo.SFoo();
    System.out.println("foo2 pointer: " + foo2.getPointer());
    CFoo.FooCallback cb = new CFoo.FooCallback() {
      public CFoo.SFoo invoke(int x, int y) {
        System.out.println("values from C x: " + x + " y: " + y);
        // Is it safe to initialize ByValue inside the callback?
        // CFoo.SFoo.ByValue foo2 = new CFoo.SFoo.ByValue();
        foo2.x = x;
        foo2.y = y;
        foo2.write();   // explicitly write to memory needed here
        return foo2;
      }
    };
    cfoo.setCallback(cb);

    // rerun with set callback
    cfoo.runCallback();

    // hack to avoid gc for cb and foo2
    assert cb != null : "Oops";
    assert foo2 != null : "Oops";
  }
}

输出

foo pointer: auto-allocated@0x7f5588284050 (8 bytes)
foo: 0x7f5588284050 x: 23 y: 42
callback not set!
foo2 pointer: auto-allocated@0x7f55882cfa50 (8 bytes)
values from C x: 123 y: 456
foo from callback: 0x7f55882cfa50 x: 123 y: 456

请注意,如果有回调,特别是 GC 需要特别注意。具有非原始数据类型。因此,我在示例中放置了一个回调,将结构本身返回给 C (foo2)。由于对象是通过引用返回的,因此它必须在回调后处理它的 C 代码中存活下来。我不知道 JNA 是否在任何 GC 交互发生之前正确移动由值返回的结构。您必须在 JNA 代码中查找。

希望这会有所帮助。

编辑:附带说明 - 我发现 JNI 更容易与 C 结合使用,主要是因为更接近内存(更 C 友好)。虽然它最初需要做更多的工作(对于 C 中的包装器),但它会在以后获得更好的性能。

【讨论】:

  • @jerh 非常感谢您的努力。只需要再澄清一点,在我的 C 代码中我写了typedef void (FR_callback_func)(Data_t frame);,而在你的 C 代码中它是 typedef Foo* (*FooCallback)(int, int);。那么,如果我引用我的 C 代码,那么它也是代码中的回调函数指针吗?因为它没有 * 符号。
  • @Sid 解引用函数指针与函数指针本身相同(因此示例中为*callback == callback)。如果您将函数指针键入为void (fp)() 而不是void (*fp)(),则必须将 (*..) 放在该类型的任何变量定义周围,我觉得这更难读。除此之外,它是一样的。
  • @Sid 关于我在上面的示例中没有显示的回调参数的另一个注意事项:确保正确地生命周期 C 分配的参数。如果变量必须以某种方式生存,请确保解耦 C/Java 端的内存状态。当前,您的回调将 Data_t 类型传递给 Java,它是按值调用的,因此可以正确解耦。如果您必须处理指针类型(共享内存),您要么必须复制内容,要么需要一些从 C 到 Java 的内存池语义。
  • IC2 总线问题上的好发现。
猜你喜欢
  • 2015-07-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-29
  • 2016-02-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多