72

I've got some C functions which I am calling through JNI which take a pointer to a structure, and some other functions which will allocate/free a pointer to the same type of structure so that it is a bit easier to deal with my wrapper. Surprisingly, the JNI documentation says very little about how to deal with C structures.

My C header file looks like so:

typedef struct _MyStruct {
  float member;
} MyStruct;

MyStruct* createNewMyStruct();
void processData(int *data, int numObjects, MyStruct *arguments);

The corresponding JNI C wrapper file contains:

JNIEXPORT jobject JNICALL
Java_com_myorg_MyJavaClass_createNewMyStruct(JNIEnv *env, jobject this) {
  return createNewMyStruct();
}

JNIEXPORT void JNICALL
Java_com_myorg_MyJavaClass_processData(JNIEnv *env, jobject this, jintArray data,
                                       jint numObjects, jobject arguments) {
  int *actualData = (*env)->GetIntArrayElements(env, data, NULL);
  processData(actualData, numObjects, arguments);
  (*env)->ReleaseIntArrayElements(env, data, actualData, NULL);
}

...and finally, the corresponding Java class:

public class MyJavaClass {
  static { System.loadLibrary("MyJniLibrary"); }

  private native MyStruct createNewMyStruct();
  private native void processData(int[] data, int numObjects, MyStruct arguments);

  private class MyStruct {
    float member;
  }

  public void test() {
    MyStruct foo = createNewMyStruct();
    foo.member = 3.14159f;
    int[] testData = new int[10];
    processData(testData, 10, foo);
  }
}

Unfortunately, this code crashes the JVM right after hitting createNewMyStruct(). I'm a bit new to JNI and have no idea what the problem could be.

Edit: I should note that the C code is very vanilla C, is well-tested and was ported from a working iPhone project. Also, this project is using the Android NDK framework, which lets you run native C code from an Android project from within JNI. However, I don't think that this is strictly an NDK issue... it seems like a JNI setup/initialization error on my part.

Nik Reiman
  • 39,067
  • 29
  • 104
  • 160
  • Something more specific about the error? Any error message? – Petar Minchev Oct 13 '10 at 12:06
  • Nope, it just crashes the JRE. That's one pitfall about dealing with JNI... – Nik Reiman Oct 13 '10 at 12:12
  • 1
    The CheckJNI feature was added to find common code errors before they turn into Heisenbugs. It's enabled by default in the emulator. See the jni-tips and embedded-vm-control docs at http://android.git.kernel.org/?p=platform/dalvik.git;a=tree;f=docs;h=b0d3023109f548626cce1a9a586c95e82fb012ac;hb=HEAD for info on enabling it on a device. – fadden Oct 15 '10 at 21:59
  • Have a look at JNA (and [see also section](http://en.wikipedia.org/wiki/Java_Native_Access#See_also)). – dma_k Jan 17 '12 at 10:36
  • You can also pass nio.DirectByteBuffers through JNI and parse the memory on Java side using for example https://github.com/marc-christian-schulze/structs4java – Marc-Christian Schulze Sep 10 '16 at 10:24

4 Answers4

51

You need to create a Java class with the same members as C struct, and 'map' them in the C code via methods env->GetIntField, env->SetIntField, env->GetFloatField, env->SetFloatField, and so on - in short, lots of manual labor, hopefully there already exist programs that do it automatically: JNAerator (http://code.google.com/p/jnaerator) and SWIG (http://www.swig.org/). Both have their pros and cons, the choice is up to you.

André Puel
  • 8,741
  • 9
  • 52
  • 83
iirekm
  • 8,890
  • 5
  • 36
  • 46
  • 17
    Could you paste some simple code examples of such integration? – Centurion Nov 28 '12 at 09:26
  • 5
    @Centurion I know URLs are generally frowned upon, but this is a comment and I found this web page Very helpful in figuring this stuff out: http://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html#zz-5.1 – James Nov 22 '13 at 23:20
  • @James ...and the REASON they are frowned upon is because it's aggravating as hell when someone comes along years later seeking the same data and finds 404 after 404. – NerdyDeeds Feb 27 '23 at 02:54
10

It's crashing because Java_com_myorg_MyJavaClass_createNewMyStruct is declared to return jobject, but is actually returning struct MyStruct. If you ran this with CheckJNI enabled, the VM would complain loudly and abort. Your processData() function is also going to be fairly upset about what it gets handed in arguments.

A jobject is an object on the managed heap. It can have extra stuff before or after the declared fields, and the fields don't have to be laid out in memory in any particular order. So you can't map a C struct on top of a Java class.

The most straightforward way to deal with this was identified in an earlier answer: manipulate the jobject with JNI functions. Allocate the objects from Java or with NewObject, Get/Set the object fields with appropriate calls.

There are various ways to "cheat" here. For example, you could include a byte[] in your Java object that holds sizeof(struct MyStruct) bytes and then use GetByteArrayElements to get a pointer to it. A bit ugly, especially if you want to access the fields from the Java side as well.

fadden
  • 51,356
  • 5
  • 116
  • 166
  • Using a byte[] would only work if the struct layout matched the java Object layout, surely? Which seems unlikely to be the case. – barneypitt Sep 27 '22 at 13:14
  • @barneypitt: they don't overlap. The idea is to create a Java `byte[]` of the appropriate size and store the C data there, effectively serializing the C struct into memory that Java can see. This requires that the Java code know the size, byte order, and field alignment of the struct, and if you don't pin the array then the C code has to access it carefully. The approach in the accepted answer, where Java "owns" the data and C accesses fields via JNI, is generally better than letting C "own" the data in a jobject and having Java extract it from bytes... but I like to show all options. – fadden Sep 27 '22 at 18:01
  • Okay, I misunderstood. I'm actually looking at how to properly match the layouts of a java class and a c++ class, with the ultimate goal of having shared-memory objects. I think it's possible only if the C is generated by the Java... because Java makes no guarantees about layout, C does - in particular that they remain in declaration order. So you can use "jol" library to determine the layout of the java class, then make sure the C struct order matches that from jol. To ensure padding matches, you can insert dummy byte variables and #pragma pack 4. I haven't put it into practice yet though! – barneypitt Sep 27 '22 at 21:17
7

C structure is the collection of variables (some are function pointer). Pass to java is not a good idea. In general, it is the problem how to pass more complex type to java, like pointer.

In JNI book, to keep the pointer/structure in native and export manipulation to java is recommended. You can read some useful articles. The JavaTM Native Interface Programmer's Guide and Specification, I have read. 9.5 Peer Classes have a solution to deal with it.

qrtt1
  • 7,746
  • 8
  • 42
  • 62
  • 5
    Link is dead -- looks like Oracle took the contents down. There are a few sites with mirrors, or you can use archive.org: http://web.archive.org/web/20070101174413/http://java.sun.com/docs/books/jni/ – fadden Jul 21 '15 at 22:09
0
  1. Make the class on both the Java and C++ sides, just putting in the member variables. C++ structs are really just classes with public data members. If you are really in pure C, stop reading now.
  2. Use your IDE(s) to make setters and getters for the member variables automatically.
  3. Use javah to generate the C header file from the Java class.
  4. Do some editing on the C++ side to make the setters and getters match the generated header file.
  5. Put in the JNI code.

This is not an ideal solution, but it may save you a little time, and it will at least give you a skeleton that you can edit. This functionality could be added to an IDE, but without a big demand, it probably won't happen. Most IDEs don't even support mixed language projects, let alone having them talk to each other.

Bill
  • 536
  • 5
  • 7