4

I am trying to use a C++ API in Java with JNA. This API uses callbacks to handle sessions events.

The only resource I found on how to register callbacks with JNA is this, and it deals with C callbacks, and I don't really know how it can be extended to C++ non-static callbacks.

EDIT: I just found this resource, I think the "Revisiting Callbacks" chapter might help.

All the function pointers for the callbacks are stored in the following sp_session_callbacks structure:

/**
 * Session callbacks
 *
 * Registered when you create a session.
 * If some callbacks should not be of interest, set them to NULL.
 */
typedef struct sp_session_callbacks {
    void (__stdcall *logged_in)(sp_session *session, sp_error error);
    void (__stdcall *logged_out)(sp_session *session);
    void (__stdcall *connection_error)(sp_session *session, sp_error error);
    void (__stdcall *message_to_user)(sp_session *session, const char *message);
    // Other callbacks function pointers
} sp_session_callbacks;

The Java class I created to described this structure is the following:

public class sp_session_callbacks extends Structure{
    public Function logged_in;
    public Function logged_out;
    public Function connection_error;
    public Function message_to_user;
}

Does it make sense to represent the function pointers by com.sun.jna.Function objects in this case in your opinion?

Each session is represented by a sp_session object, which is a C++ opaque struct. I do have a handle on the sp_session_callbacks object when I initialize it though.

Here is a code snippet from my main class:

JLibspotify lib = (JLibspotify)Native.loadLibrary("libspotify", JLibspotify.class);

sp_session_config cfg = new sp_session_config();
/* Some cfg config here */
sp_session_callbacks sessCallbacks = new sp_session_callbacks(); // Handle on my sp_session_callbacks object
cfg.callbacks = sessCallbacks;

PointerByReference sessionPbr = new PointerByReference();
int errorId = lib.sessionCreate(cfg, sessionPbr);

sp_session session = new sp_session(sessionPbr.getValue()); // handle on my sp_session object

How should I register those callbacks function to actually do something on the Java side when they are triggered?

Thank you!

EDIT

New code using Callback instead of Function:

public class sp_session_callbacks extends Structure{
    public LoggedIn logged_in;
    /* Other callbacks... */
}



public interface LoggedIn extends StdCallCallback {
    public void logged_in(sp_session session, int error);
}

Main class:

JLibspotify lib = (JLibspotify)Native.loadLibrary("libspotify", JLibspotify.class);

sp_session_config cfg = new sp_session_config();
/* Some cfg config here */
sp_session_callbacks sessCallbacks = new sp_session_callbacks(); // Handle on my sp_session_callbacks object
LoggedIn loggedInCallback = new LoggedIn(){
    public void logged_in(sp_session session, int error){
        System.out.println("It works");
    }
};
sessCallbacks.logged_in = loggedInCallback;
/* Setting all the other callbacks to null */

cfg.callbacks = sessCallbacks;

PointerByReference sessionPbr = new PointerByReference();
int errorId = lib.sessionCreate(cfg, sessionPbr);

sp_session session = new sp_session(sessionPbr.getValue()); // handle on my sp_session object

The sessionCreate() call is throwing a JRE fatal error (EXCEPTION_ACCES_VIOLATION 0x0000005) when cfg.logged_in is not set to null but to an instance of LoggedIn. The weird thing is that the 2 callbacks logged_in and connection-error have the same signature, and when cfg.connection_error is set, it doesn't throw anything.

nbarraille
  • 9,926
  • 14
  • 65
  • 92
  • All of the function pointers in your example are c, no c++ features to mess with jna. – josefx Mar 04 '11 at 14:10
  • @josefx I thought that the fact of having elements as **non-static** fields of a structure (which is an object oriented feature) was typical of C++. – nbarraille Mar 04 '11 at 14:28
  • struct as you use it is part of standard c. I don't get what you mean with non-static fields, a struct with only static fields would be quite useless.(then again i'm mostly a java programmer so i might mix up the meaning of static) – josefx Mar 04 '11 at 17:18
  • @josefx OK. I also am a Java programmer and don't know much about C/C++, that's why I was asking. What I meant with non-static field, is a field that can hold different values/object for 2 different instances of the structure. I though this concept was brought by OOP, so I assumed it was not possible in C which is not a OO language, but I have no certitude. – nbarraille Mar 04 '11 at 17:41

2 Answers2

2

From the javadoc Function represents a native method, if you want to call a java method you have to create a Callback for each function pointer.

If you only use java callbacks and no native Functions you can just replace Function with Callback. (Depending on the calling convention used you might want to use StdCallLibrary.StdCallCallback instead)

public class sp_session_callbacks extends Structure{
    public StdCallCallback logged_in;
    public StdCallCallback logged_out;
    public StdCallCallback connection_error;
    public StdCallCallback message_to_user;
}

sp_session_callbacks calls = new sp_session_callbacks();
calls.logged_out = new StdCallCallback(){

    public void someName(sp_session sp){...}
}
josefx
  • 15,506
  • 6
  • 38
  • 63
  • @josefx Thank you, after reading the second article I cited, I think it is the solution. I added the new code in my initial post. – nbarraille Mar 04 '11 at 14:22
  • @josefx I have a problem in passing the `error` argument, which is of type `sp_error` (an integer enum). When I use int or IntByReference it does not work. How can I do this? (I added the code at the end of initial post). Thanks! – nbarraille Mar 04 '11 at 14:48
  • @josefx Oops, sorry, wrong information in the previous comment. Only logged_in makes the JRE crash. So I suppose it is because it is actually called. – nbarraille Mar 04 '11 at 14:56
  • @nathanb sp_error is not passed by reference, so you should use int itself or create a class containing only an int for type-safety. – josefx Mar 04 '11 at 17:16
  • @nathanb do you pass the sp_session by reference? I forgot to do so in my example code. It should take a Pointer to an sp_session instead an sp_session itself. – josefx Mar 04 '11 at 17:21
  • @josefx Are you sure? As `sp_session` extends `PointerType`, I thought it was already a pointer, so I used to pass `sp_session` wherever the C++ code required a `sp_session*` and a `PointerByReference` whereevr the C++ code required a `sp_session**`. I will try though. – nbarraille Mar 04 '11 at 17:38
  • @nathanb no it should work with a PointerType. If it does not work with (sp_session,int) then I can't say why it fails. You can try to only get a pointer, or try byte and short instead of int (the size of an c/c++ enum depends on compiler and contents) – josefx Mar 04 '11 at 18:12
  • @josefx Ok, that's what I thought. The weird thing is, the two callbacks logged_in and connection_error have the exact same signature, if I implement logged_in, the JRE crashes (while running sessionCreate()), but not if I implement connection_error... – nbarraille Mar 04 '11 at 19:48
  • @nathanb do you keep a reference to the Callback objects from your java code? Garbage collection makes the native pointer invalid. – josefx Mar 04 '11 at 20:01
  • @josefx Could it be because the logged_in callback is actually called, and jna cannot resolve it to a function? (maybe because as they are __stdcall callbacks they have been renamed by GCC with some stupid suffix such as @4 or something? (I had the problem with my regular functions in the first place, they were not mapped properly). – nbarraille Mar 04 '11 at 21:27
  • @nathanb the name is only to get the function pointer from a library, the runtime only sees an address. Try invoking write() on your sp_session_callbacks struct after assigning the callbacks. (after that I'm running out of ideas) – josefx Mar 04 '11 at 21:42
  • @josefx Unfortunately invoking write() on the sp_session_callbacks object did not help. I think I will open a new question with the proper stack trace when I will have the time because I think the problem has moved. Thanks for your help! – nbarraille Mar 05 '11 at 00:08
  • @josefx I posted the new question there: http://stackoverflow.com/questions/5205654/c-callback-wuth-jna-makes-jre-crash, in case you want to take a look at the error trace! Thanks. – nbarraille Mar 05 '11 at 22:35
0

Basically, in order to wire a callback of any kind into a member function of an object, it would have to pass the target function and an instance of the object to satisfy that first invisible parameter. The issue here is that JNA only accepts a particular format of function. So, you have to work around the issue where possible. Sadly, in C++, this can result in the use of temporary global variables, but as long as you focus on transferring control back over to an object, you can maintain good OO design.

It has nothing to do with a "C vs C++" thing. You just need to understand what a member function call entails and basically what it compiles down to.

myObject.memberFunction(); // This is what the programmer sees.
memberFunction(&myObject); // This is what the compiler sees.

A member function is a fancy description of a function that simply takes the object as a first parameter. It's just invisible in the actual parameter list.

void MyClass::memberFunction() // This is the declaration the programmer sees.
void memberFunction(MyClass* this) // This is what the compiler sees.

From there, C++ adds special semantics to make it easy to work in an object-oriented fashion.

In short, you will virtually never be able to wire directly into an object's member function until the JNA itself adds mechanisms to do so.

TheBuzzSaw
  • 8,648
  • 5
  • 39
  • 58