0

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.

Sid
  • 15
  • 6
  • Is it possible this is due to padding concerns? In the `Data_t` definition, it starts with and `unsigned char` followed by an `int`, but most of us would not expect the second struct member to start on the second byte: the compiler often includes padding to align the `int` on its own boundary. That would mean several bytes of invisible padding in the structure that are not accounted for by reading the struct definition directly. Perhaps that's involved? – Steve Friedl Dec 19 '19 at 15:11
  • @Steve Friedl No, I don't think that it is due to this issue. The problem what I think is the definition of Callback function which I have to translate in Java. – Sid Dec 19 '19 at 15:31
  • Where is `BL_signal_t` created? I'd check on C side whether `Data_t *data` is a null pointer (prolly exists early in that case). – jerch Dec 19 '19 at 15:38
  • @jerch I edited my post, this is not `BL_signal_t` actually it is `Data_t` – Sid Dec 19 '19 at 15:46
  • @Sid You should remove the `.ByReference` from the method declaration in Java side and just provide a Data_t variable created beforehand, JNA will automatically deal with the pointer underneath. – jerch Dec 19 '19 at 15:51
  • Note on that: `new Data_t .ByReference();` creates a pointer type `Data_t *` but is not linked to any memory location. So to stick with it youd have to do: `Data_t mydata; mydata.ByReference();...` – jerch Dec 19 '19 at 16:00
  • @jerch What about the callback function header `FR_callback_func ` ? in which `Data_t` is getting called ? Have I translated that in Java correctly ? – Sid Dec 19 '19 at 16:14
  • Yes this looks good, but dont forget to initialize during startup, also you have to keep a ref around to not get cleanup by the gc. – jerch Dec 19 '19 at 18:46
  • @jerch Thank you. I am having a slight difficulty on understanding your point for initialization. Could you please give me some sample code through which I can understand this ? – Sid Dec 19 '19 at 19:33
  • @Sid You have to initialize the function pointer with a callback somewhere, before any C code tries to access the function (otherwise it will segfault). See https://github.com/java-native-access/jna/blob/master/www/CallbacksAndClosures.md – jerch Dec 19 '19 at 20:36
  • @jerch thanks for your help . I'll try and let you know then. However, I followed advice for removing `.By reference ` from method. After that when I am trying to create `Data_t my data ; mydata.Byreference(); ` then it is not getting created, What should I do in this case ? – Sid Dec 19 '19 at 20:51
  • Nah, the second line is not needed, just pass `my_data` into `GetData` (and also remove the byReference there). ByReference is the default behavior for function interfaces, thus its not needed. You only have to make it explicitly if you want changed behavior (byValue vs byRef). – jerch Dec 19 '19 at 21:17
  • @jerh where should I create `mydata` ? Is it should be like this ? `public static void main(String[] args) throws IOException { Data_t mydata_t ; int data = 0; int val = 0; device h = (device) Native.load("Library", device.class); data = h.getData (val, mydata); }` If I make this so, then I have to initialize `mydata = null` , right ? – Sid Dec 19 '19 at 21:40
  • @Sid See my answer below. – jerch Dec 20 '19 at 00:13

1 Answers1

2

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.

jerch
  • 682
  • 4
  • 9
  • @jerh many thanks for your efforts. Just one more clarification needed, in my C code I have written `typedef void (FR_callback_func)(Data_t frame);` and in your C code it is `typedef Foo* (*FooCallback)(int, int);`. So, if I refer my C code then it is also a callback function pointer as yours in the code ? since it doesn't have * sign. – Sid Dec 20 '19 at 08:39
  • 1
    @Sid Dereferencing a function pointer is the same as the function pointer itself (thus`*callback == callback` in the example). If you typedef a function pointer as `void (fp)()` instead of `void (*fp)()` you have to put the (*..) around any variable definition of that type, which I find more ugly to read. Beside that its the same. – jerch Dec 20 '19 at 12:53
  • 1
    @Sid One more note about callback arguments I didnt show in the example above: Make sure to correctly lifecycle C allocated arguments. Make sure to decouple memory state for C/Java side, if the variable has to survive somehow. Currently your callback passes a `Data_t` type to Java, which is call by value, thus properly decoupled. If you have to deal with pointer types instead (shared memory), youd either have to copy things over or need some memory pooling semantics spanning from C into Java. – jerch Dec 20 '19 at 13:13
  • Good find on the IC2 bus question. – jww Dec 27 '19 at 01:33