7

I have no problem with simple callbacks when free function passed as parameter to another, thanks to @flexo.

But assume bit more difficult C interface:

typedef struct
{
    int id;
    const char* name;
} Item;

typedef struct
{
    int value;
    Items_Callback callback;
    void *context;
} Items_Call;

typedef int (*Items_Callback)(const Item *item, void *context);

int Items_create(const Item *item, Items_Call *call) {
  ...
  call->callback(item, call->context);
  ...
}

I intent to generate some nice Java wrapper for code like this. I assume to have as result

class Item {
  public int id;
  public String name;
}

class Items_Call {
  public int value;
  public Object context;
  public Interface callback;
  public void setInterface(Interface i){ callback=i; };
}

public interface Interface {
  public int Items_Callback(Item item, Object context);
} 

int Items_create(Item item, Items_Call call) {
  ...
  call.callback.Items_Callback(item, call.context);
  ...
}

I realize that SWIG have some problem with generation of pure Java interfaces, but I believe it's not major problem. The problem is I have no idea how to reinterpret such nested structure to acceptable Java code.

Community
  • 1
  • 1
triclosan
  • 5,578
  • 6
  • 26
  • 50
  • Consider using [JNA](http://github.com/twall/jna). You can map a callback to an interface, then either instantiate that interface manually or JNA will generate one if it finds one in a native structure. The callback is invokable by both Java and native code. – technomage Dec 20 '13 at 12:50
  • @technomage, thanks but performance and portability are crucial for me. Thus whould be nice to use JNI. – triclosan Dec 20 '13 at 17:37

1 Answers1

4

Not SWIG, but the following works with JavaCPP (which does not come with the kind of overhead JNA has, and works wherever JNI works):

// items.h
typedef struct
{
    int id;
    const char* name;
} Item;

typedef int (*Items_Callback)(const Item *item, void *context);

typedef struct
{
    int value;
    Items_Callback callback;
    void *context;
} Items_Call;

int Items_create(const Item *item, Items_Call *call) {
//  ...
    call->callback(item, call->context);
//  ...
    return 0;
}

And in Java:

import com.googlecode.javacpp.*;
import com.googlecode.javacpp.annotation.*;

@Platform(include="items.h")
public class Items {
    static { Loader.load(); }

    public static class Item extends Pointer {
        public Item() { allocate(); }
        private native void allocate();

        public native int id();           public native Item id(int id);
        @Cast("const char*")
        public native BytePointer name(); public native Item name(BytePointer name);
    }

    public static class Items_Callback extends FunctionPointer {
        protected Items_Callback() { allocate(); }
        private native void allocate();

        public native int call(@Const Item item, Pointer context);
    }

    public static class Items_Call extends Pointer {
        public Items_Call() { allocate(); }
        private native void allocate();

        public native int value();               public native Items_Call value(int value);
        public native Pointer context();         public native Items_Call context(Pointer context);
        public native Items_Callback callback(); public native Items_Call callback(Items_Callback value);

        public void setInterface(Items_Callback i) { callback(i); }
    }

    public static native void Items_create(Item item, Items_Call call);

    public static void main(String[] args) {
        BytePointer s = new BytePointer("Hello");
        Item i = new Item();
        i.id(42);
        i.name(s);

        Items_Callback cb = new Items_Callback() { 
            public int call(Item item, Pointer context) {
                System.out.println(item.id() + " " + item.name().getString());
                return 0;
            }
        };
        Items_Call ic = new Items_Call();
        ic.callback(cb);

        Items_create(i, ic);

        // if we remove these references, the GC may prematurely deallocate them
        s.deallocate();
        cb.deallocate();
    }
}

Which outputs the expected result:

42 Hello

Disclaimer: I'm the author of JavaCPP :)

Samuel Audet
  • 4,964
  • 1
  • 26
  • 33
  • I have few question. (1) Is JavaCPP thread safe against callbacks coming in on multiple threads? (2) What about performance? Does framework cache method- and objectID to invoke call or retrieve they on echo call? Thanks. – triclosan Jan 13 '14 at 11:35
  • @triclosan Yes, the method ID and object reference get cached. I guess it is thread safe, as long as JNI is, but I haven't done some stress testing to make sure... – Samuel Audet Jan 14 '14 at 13:15
  • Not exactly as I know about thread-safety. Please refer `Attaching to the VM` here http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html – triclosan Jan 14 '14 at 13:27
  • Or am I missing something? – triclosan Jan 14 '14 at 14:38
  • @triclosan `AttachCurrentThread()` does get called as necessary. If there is a bug, I would be happy to fix it, obviously. Performance-wise, however, since we can't do anything to guarantee that the native side is going to call `DetachCurrentThread()`, it gets called defensively as necessary after the callback. We can work around that by making sure the callbacks come from threads initialized in Java... – Samuel Audet Jan 15 '14 at 01:27
  • One of my callbacks on C side is `int fn (const Item *item, void *context) { *(int*)context = item->id*10; return item->id; }`. How can I achieve the same behaviour with `Pointer context` ? – triclosan Jan 18 '14 at 09:45
  • @triclosan We can "cast" it, e.g.: `new IntPointer(context).put(item.id()*10); return item.id();` – Samuel Audet Jan 19 '14 at 00:32
  • Is it necessary to use `deallocate` method for each `Pointer`-based class. If I ignore to invoke `deallocate` will GC handle all allocated resources? Does C/C++ side allocate some memory which may be leaked in case of `deallocate` method wasn't called? – triclosan Jan 21 '14 at 21:07
  • @triclosan It doesn't need to be `deallocate()`, it can be anything that references the object. The problem is that `s` and `cb` are not referenced in Java when making the call to `Items_create(i, ic)`, so they may get garbage collected before the call, and that would lead to a crash. Try to call `System.gc()` before the call, that might reproduce the issue. – Samuel Audet Jan 22 '14 at 01:34
  • @triclosan BTW, keeping a reference of the objects in a field also does the trick, because the virtual machine cannot guarantee that those will never be accessed at some point by some other object. – Samuel Audet Jan 22 '14 at 03:23