2

I have the following struct in C:

typedef struct KFMutableBytes {
    uint8_t * _Nullable bytes;
    size_t length;
    const size_t capacity;
} KFMutableBytes;

This struct is passed as a pointer to functions like so:

KFMutableBytes bytes = ...;
someFunc(&bytes);

The function writes into bytes.bytes up to bytes.capacity, and stores the written length in bytes.length.

So far I've got this:

%typemap(jni) KFMutableBytes * "jbyteArray"
%typemap(jtype) KFMutableBytes * "byte[]"
%typemap(jstype) KFMutableBytes * "byte[]"
%typemap(in) KFMutableBytes * {
    KFMutableBytes *bytes = (KFBytes *)malloc(sizeof(KFMutableBytes));
    if(bytes == NULL) {
        jclass clazz = (*jenv)->FindClass(jenv, "java/lang/OutOfMemoryError");
        (*jenv)->ThrowNew(jenv, clazz, "Not enough memory");
        return $null;
    }
    KFMutableBytes b = KFMutableBytesCreate((uint8_t *) JCALL2(GetByteArrayElements, jenv, $input, 0), 0, (size_t) JCALL1(GetArrayLength, jenv, $input));
    memcpy(bytes, &b, sizeof(b));
    $1 = bytes;
}
%typemap(javain) KFMutableBytes * "$javainput"
/* Prevent default freearg typemap from being used */
%typemap(freearg) KFMutableBytes * {
        JCALL3(ReleaseByteArrayElements, jenv, $input, (jbyte *) $1->bytes, 0);
        free($1);
}

This means in Java, someFunc is someFunc(byte[] bytes). The problem is that you can't get the written length out, and the byte array length can't be modified in C. So really I just need to map byte[] to the bytes and capacity members, and map the length member to long. But I'm not sure how to map the byte array to struct members?

2
  • so this is an uint pointer (1 bvyte) followed by 8 byte size then? Commented Jul 7, 2020 at 21:28
  • 1
    I've updated the question as I developed a partial solution Commented Jul 7, 2020 at 23:04

1 Answer 1

2

I've solved it, but the solution is convoluted. Essentially I ignore the properties of the struct, add my own constructor and properties to the class in Java, pass the java representation of the struct straight to C, and there I pull out the Java properties to create the C struct and after the function is called, write the length back to the java object and release the array.

// ignore the properties of the struct to prevent them being added to the Java class
%ignore KFMutableBytes::bytes;
%ignore KFMutableBytes::capacity;
%ignore KFMutableBytes::length;
// Add our own constructors, properties and methods to the Java class
%extend KFMutableBytes {
#if defined(SWIG)
%proxycode %{
    private byte[] bytes;
    private int length;
    public byte[] getBytes() {
        if(length == 0) { return new byte[0]; }
        return Arrays.copyOfRange(bytes, 0, length);
    }
    public KFMutableBytes(byte[] bytes) {
        this.bytes = bytes;
        length = 0;
    }

    public KFMutableBytes(int capacity) {
        this.bytes = new byte[capacity];
        length = 0;
    }
%}
#endif
}
// Tell SWIG to pass the java object straight to C
%typemap(jni) KFMutableBytes * "jobject"
%typemap(jtype) KFMutableBytes * "KFMutableBytes"
%typemap(jstype) KFMutableBytes * "KFMutableBytes"
// In C, get the added properties from the Java object and set those to our struct
%typemap(in) KFMutableBytes * {
    jclass clazz = JCALL1(FindClass, jenv, "com/example/myapplication/cppinterface/KFMutableBytes");
    jfieldID fid = JCALL3(GetFieldID, jenv, clazz, "bytes", "[B");
    jobject byteArrayObj = JCALL2(GetObjectField, jenv, $input, fid);
    jbyteArray byteArray = *(jbyteArray *)&byteArrayObj;
    jfieldID fid2 = JCALL3(GetFieldID, jenv, clazz, "length", "I");
    int length = JCALL2(GetIntField, jenv, $input, fid2);

    KFMutableBytes *bytes = (KFBytes *)malloc(sizeof(KFMutableBytes));
    if(bytes == NULL) {
        jclass clazz = (*jenv)->FindClass(jenv, "java/lang/OutOfMemoryError");
        (*jenv)->ThrowNew(jenv, clazz, "Not enough memory");
        return $null;
    }
    // Because ->capacity is const, we can't set it directly, we have to create another struct and copy to our allocated struct
    KFMutableBytes b = KFMutableBytesCreate((uint8_t *) JCALL2(GetByteArrayElements, jenv, byteArray, 0), length, (size_t) JCALL1(GetArrayLength, jenv, byteArray));
    memcpy(bytes, &b, sizeof(b));
    $1 = bytes;
}
%typemap(javain) KFMutableBytes * "$javainput"
// When finished with the struct, set the length back to the object and release the byte array before freeing
%typemap(freearg) KFMutableBytes * {
    jclass clazz = JCALL1(FindClass, jenv, "com/example/myapplication/cppinterface/KFMutableBytes");
    jfieldID fid = JCALL3(GetFieldID, jenv, clazz, "bytes", "[B");
    jobject byteArrayObj = JCALL2(GetObjectField, jenv, $input, fid);
    jbyteArray byteArray = *(jbyteArray *)&byteArrayObj;
    jfieldID fid2 = JCALL3(GetFieldID, jenv, clazz, "length", "I");
    JCALL3(SetIntField, jenv, $input, fid2, (int)$1->length);
    JCALL3(ReleaseByteArrayElements, jenv, byteArray, (jbyte *) $1->bytes, 0);

    free($1);
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.