-1

I want to pass a structure pointer from Java to c,where it gets updated with the required values and use those values back at my java code. I searched all over internet but couldn't find any required pages, nor i couldn't get any good examples. Please help me with this. I am new to Java/JNI.

Below is my JNI c file used

typedef struct _struct1{
    char a;
    char b;
    short c;
    char d[20];
    int e;
    int f;
}struct1;

typedef struct _struct2{
    struct1 g[12];
}struct2;

JNIEXPORT void JNICALL Java_com_example_test_GetDataFromC(
        JNIEnv *env,
        jobject /* this */,jobject foo) {
    /* I want to update foo with the values from struct2*/
   test_application(&struct2);
}

Below is the java code

    public class struct1{
        public char a;
        public char b;
        public short c;
        public char []d= new char[20];
        public int e;
        public int f;
    }

    public class struct2{
        public struct1[]g= new struct1[12];
    }

 protected void onCreate(Bundle savedInstanceState) {
    struct2 foo = new struct2();
    GetDataFromC(foo);
    printf("f = %d \n",foo.g[1].f);
 }

 public native void GetDataFromC(struct2);

Botje
  • 26,269
  • 3
  • 31
  • 41
Maddy26
  • 107
  • 1
  • 10
  • 3
    "_I searched all over internet_" - Did you not find a tutorial? Examples of what? Your code does not really show anything you've tried to do. – Ted Lyngmo Jun 21 '19 at 00:48
  • Yes i searched, but couldnt any proper tutorials which explains passing structure to and from Java to C – Maddy26 Jun 21 '19 at 00:51
  • Ok, but it's hard to judge whether you've exhausted the resources that are available or not when you also say that you're new to this. Show a minimalistic example of what you're trying to do, explain your expectations and ... the horrible outcome people usually are eager to help out with. – Ted Lyngmo Jun 21 '19 at 00:56
  • 2
    Not really played with JNI, but offhand I'd say you're going the wrong way round. Java is NEVER going to be able to process a C struct, but JNI provides facilities for a C program to read/write a Java object. – racraman Jun 21 '19 at 01:02
  • @TedLyngmo Updated with my trying. How should i move on from here? – Maddy26 Jun 21 '19 at 01:07
  • @Maddy26 Oh, I personally have no insight in JNI. I just recognized a question that'd be hard to answer without more information. – Ted Lyngmo Jun 21 '19 at 01:13
  • Likewise, I've no insight, but seems to me you want to read from the java object to populate your C struct2. I don't think JNI does that directly. Have you seen https://stackoverflow.com/questions/3923299/how-to-pass-c-structs-back-and-forth-to-java-code-in-jni and http://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html#zz-5. – racraman Jun 21 '19 at 01:40
  • @racraman yeah i checked those, but couldn't understand since those didnt provide with any good examples – Maddy26 Jun 21 '19 at 02:04
  • pls check jni types and data structures https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html – aucd29 Jun 21 '19 at 01:56
  • As far as I know you can't pass C structs to Java. Am I missing something? – user253751 Jun 21 '19 at 04:49
  • For data as simple as this, I would strongly suggest to use a simple data serialization format like JSON, protobuf or capnproto. All have libraries for both C++ and Java, and the remaining JNI effort is passing a byte buffer back and forth. This is ~50 LOC with JSON, I think. If you do not go this way, you will need lots of JNI code to map the Java array to C++ memory and then use JNI field setters to manipulate each field independently. – Botje Jun 21 '19 at 08:01
  • @Botje Using JSON to pass function arguments sounds a bit extreme. If you go that route, then it might be better to not use JNI at all, since it's always a portability, development and debugging hassle. If you want to use JSON, better use separate processes and IPC. – hyde Jun 21 '19 at 09:00
  • Your basic difficulty comes from thinking that you have one struct type. No, you have a Java POJO class `struct1`, and then you have C `struct _struct1`. These completely different types. You need to write the C code (because only C code has access to both) to copy the fields from Java object to C struct and back. I suggest you write 2 functions to do this both ways. "How to get value from Java object in C" and "How to store value in a Java object in C" are the very basics of JNI, about the 2nd thing covered by any tutorial. – hyde Jun 21 '19 at 09:05
  • @hyde Starting a new process is not always desirable or possible (OP uses Android, for example). Using JSON to marshal nontrivial datastructures trades off a bit of performance for a *lot* of convenience. If this function is called multiple times per second I would probably go for a binary protocol, but it is definitely easier to understand and maintain that than the JNI code in my answer below. – Botje Jun 21 '19 at 09:05
  • @Botje It's always trade-offs. Using JSON in C *still* requires getting the fields from JSON parser and storing them in the struct "manually", doesn't it, because there is no reflection? So might as well get them from Java object instead. – hyde Jun 21 '19 at 09:10
  • @hyde Right. But at least in C++ it should be very close to a native struct field copy: `java_foo[x].a = _struct2[x].a` Furthermore, this question is simply about filling the struct from JNI, so C could just produce a JSON string that is exploded in Java. – Botje Jun 21 '19 at 09:13
  • @Botje Yeah, matter of opinion, continuing here is probably not useful. But I gotta add: this is C, so there is no "just ... string" in C, because C doesn't really have *strings* in the same sense as most other languages. It does have string literals, but otherwise it just has a bunch of functions which take pointers and manipulate memory treating 0 byte in a special way. So going through strings in C is usually more complex than just manipulating structs and pointers. – hyde Jun 21 '19 at 10:03

2 Answers2

2

In a nutshell, you need to do the following:

// Get foo.g
jclass cls_struct2 = env->FindClass("struct2");
jfieldID fld_struct2_g = env->GetFieldID(cls_struct2, "g", "[Lstruct2;");
jarray foo_g = (jarray) env->GetObjectField(foo, fld_struct2_g);

// Look up field IDs of struct1
jclass cls_struct1 = env->FindClass("struct1");
jfieldID fld_struct1_a = env->GetFieldID(cls_struct1, "a", "C");
jfieldID fld_struct1_d = env->GetFieldID(cls_struct1, "d", "[C");

// Loop over the array
jsize s = env->GetArrayLength(foo_g);
for (int i = 0; i < s; i++) {
  jobject element = env->GetObjectArrayElement(foo_g, i);
  env->SetCharField(element, fld_struct1_a, _struct2[i].a);
  jcharArray element_d = (jcharArray) env->GetObjectField(element, fld_struct1_d);
  env->SetCharArrayRegion(element_d, 0, sizeof(_struct2[i].d) / sizeof(_struct2[i].d[0]), _struct2[i].d);
}

If the char[20] fields are actually null-terminated strings, you may be better off converting them to String and using NewStringUTF on the C++ side.

EDIT: The answer above is C++ instead of C, but that is mostly cosmetic (env->Method(ARGS) should be (*env)->Method(env, ARGS)

EDIT 2: From your question it is not clear if your Java code is in a namespace. If it is, you may need pkg/path/to/MyOuterClass$struct1.

EDIT 3: Corrected modification of .d

Botje
  • 26,269
  • 3
  • 31
  • 41
  • SetCharArrayRegion declaration type is of : SetCharArrayRegion*: proc(env: ptr JNIEnv, arr: jcharArray, start: jsize, len: jsize, buf: ptr jchar), how can i pass type jfieldID to jcharArray – Maddy26 Jun 24 '19 at 00:01
  • Oh, right. You need to call `env->GetObjectField(element, fld_struct1_d)` first to get at the `char[]`. – Botje Jul 01 '19 at 07:44
  • As a general rule on stack overflow, if an answer helps you, mark it as accepted so others can be helped too. – Botje Jul 02 '19 at 05:39
1

You can use Scapix Java Link to generate C++ headers for your java classes, then manipulating Java objects from C++ becomes very easy:

#include <example/Access.h>         // C++ header generated by scapix_java from Access.class file
#include <example/Access_struct1.h> // C++ header generated by scapix_java from Access$struct1.class file
#include <example/Access_struct2.h> // C++ header generated by scapix_java from Access$struct2.class file

using namespace scapix::java_api;
using namespace scapix::java::link;

void update(ref<example::Access::struct2> s2)
{
    for (auto&& s1 : s2->g()->elements())
    {
        s1->a('A');
        s1->b('B');
        s1->c(33);

        for (auto&& d : s1->d()->elements())
            d = 'D';

        s1->e(44);
        s1->f(55);
    }
}

For more info see example2

Boris Rasin
  • 448
  • 4
  • 12