Search code examples
javaceclipsedlldynamic-dll-import

Callback function translation in C-DLL to java JNA


I am having a problem in mapping/translating a callback function from C-DLL to Java using JNA.

on C header file following callback function is written:

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

The structure of the above Data_t is as follow:

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

The function in which Data_t structure is getting called:

int getData(int val,Data_t *data);

Now I translated in my JAVA code which is as follows:

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

Then I tried to use it in my main function which is as follow:

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

My question is that am I translating the above C code correctly ? especially the callback function ? Since the C code can't be manipulated. Hence I have to translate the provided C-DLL code in JAVA.

Your advice will be highly appreciated.


Solution

  • Did a rough writeup of the needed bits you are still missing above:

    • C example lib
    // 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 example code
    // 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";
      }
    }
    

    Output

    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
    

    Note that the GC needs some special care if there are callbacks esp. with non primitive data types. Therefore I put in the example a callback returning a struct itself back to C (foo2). Since the object is returned by reference it must survive the C code dealing with it after the callback. I dont know whether JNA correctly moves structs returned by value prior any GC interaction can happen. You'd have to look that up in the JNA code.

    Hope this helps.

    Edit: On a sidenote - I kinda find JNI easier to work with in conjunction with C, mainly for being closer to the memory (more C friendly). Although it needs slightly more work initially (for the wrapper in C) it pays off later with better performance.