Nick
Nick

Reputation: 4248

Converting byte array to C struct using SWIG

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?

Upvotes: 2

Views: 205

Answers (1)

Nick
Nick

Reputation: 4248

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);
}

Upvotes: 2

Related Questions