2

I used SWIG to generate a wrapper for the Spotify library in Java. The header file contains

typedef struct sp_session_config {
   ...
  const void *application_key;           ///< Your application key
  ...

that should allow me to set in Java the application key:

sp_session_config cfg = new sp_session_config();
cfg.setApplication_key(appkey);

The key in C is declared like this

const uint8_t g_appkey[] = {0x01, 0xB4, 0xF9, 0x33, .... }

The problem is that I don't know how to create the key in Java. The type of appkey must be SWIGTYPE_p_void.

I tried to add in the .i file:

%include "carrays.i"
%array_functions(uint8_t, uint8Array);

to be able to create C arrays in Java with these functions:

new_uint8Array
uint8Array_setitem

without success.

How can I resolve my problem? A small simple example would be welcome.

Flexo
  • 87,323
  • 22
  • 191
  • 272
fazerty
  • 21
  • 1

1 Answers1

1

You're on the right lines with carrays.i, although normally I'd prefer to use %array_class instead of %array_functions when given a choice. The problem is that your array_functions/array_class gives you a SWIGTYPE_p_unsigned_char (i.e. proxy for pointer to unsigned char) whereas you need a void pointer and there's no trivial way to convert between the two because of the strong typing in Java. We can work around that though.

To illustrate this answer I created a very simple header file that reproduces your problem:

typedef struct sp_session_config {
  const void *application_key;           ///< Your application key
} session_config;

so the problem is that we can't call setApplication_key() with an array_class/array_function generated uint8_t array because the types don't match.

There's quite a few possible solutions to that. One way might be to tell SWIG to ignore application_key in sp_session_config and instead pretend that it's a uint8_t* application_key. To do that you need to supply a set/get function in C for the wrapper that does the work (trivial in this case) and use %ignore to hide the "real" member, %extend to add the "fake" member and %rename to stop the %ignore ignoring the fake one:

%module test

%{
#include "test.h"
#include <stdint.h>

// Internal helpers for our fake memeber
static void session_config_application_key_set(session_config *cfg, const uint8_t *arr) {
  cfg->application_key = arr;
}

static const uint8_t *session_config_application_key_get(const session_config *cfg) {
  return cfg->application_key;
}
%}

%include <carrays.i>
%include <stdint.i>

%array_class(uint8_t, uint8Array);

%ignore sp_session_config::application_key; // ignore the real one when we see it
%include "test.h"
%rename("%s") sp_session_config::application_key; // unignore for extend
%extend session_config {
  uint8_t *application_key;
}

This is sufficient to allow us to write, in Java:

public class run {
  public static void main(String[] argv) {
    session_config cfg = new session_config();
    uint8Array key = new uint8Array(4);
    key.setitem(0, (short)100); key.setitem(1, (short)101); 
    key.setitem(2, (short)102); key.setitem(3, (short)103);    
    cfg.setApplication_key(key.cast());
  }
}

Alternatively if you'd rather you can use the typemap system to expose the member as a uint8_t* instead:

%module test

%{
#include "test.h"
%}

%include <carrays.i>
%include <stdint.i>

%array_class(uint8_t, uint8Array);

%typemap(jstype) const void *application_key "$typemap(jstype, const uint8_t*)"
%typemap(javain) const void *application_key "$typemap(jstype, const uint8_t*).getCPtr($javainput)"
%typemap(javaout) const void *application_key {
  long cPtr = $jnicall;
  return (cPtr == 0) ? null : new $typemap(jstype, const uint8_t*)(cPtr, $owner);
}
%include "test.h"

which matches only const void *application_key and generates code in the interface to work with the wrapped uint8_t* instead of the void*.

Another possible approach would be to simply claim to SWIG that the member is a uint8_t*, by providing a special definition of the struct inside the interface file:

%module test

%{
#include "test.h"
%}

%include <carrays.i>
%include <stdint.i>

%array_class(uint8_t, uint8Array);

typedef struct sp_session_config {
  const uint8_t *application_key;
} session_config;

// Ignore the one in the header file, use our special version instead
%ignore sp_session_config;
%include "test.h"

Doing it this way is simple, but trickier to maintain - every time sp_session_config changes you'll have to make sure you update the interface file to match it otherwise you risk not exposing, or even incorrectly exposing the struct.

A word of caution: be careful with the ownership in these examples - the array on the Java side retains ownership, so you need to make sure that:

  1. the C library won't try to free it (you'll see a double free() if it does)
  2. the lifetime of the array in Java exceeds the usage of it in the C library.

You could make the C library take ownership of the memory if you wanted, the easiest of these examples to do that with would be the one using typemaps. (You could modify the javain typemap to also take ownership).

Finally for yet another possible solution you could use a little bit of JNI or yet more other techniques as I've answered in the past to use a Java array or generate suitable code for this.

Community
  • 1
  • 1
Flexo
  • 87,323
  • 22
  • 191
  • 272