5

Update:

Error: jbyte* elements = (*env)->GetByteArrayElements(env, array, NULL); returns only 8 bytes. Provide any alternative to way to retrieve byte form jbytearray.


I'm new in JNI so I'm not familiar in JNI and also English.

Now I try the simple JNI program on File reading in Java and write it into file using C.

File reading java code:

public class FileIO {
   static {
      System.loadLibrary("io");         
   }

   private native void writeToFile(byte[] msg);

   public static void main(String[] args) throws IOException { 
      FileInputStream fileInputStream=null;

      File file = new File("version1-1.png");

      byte[] bFile = new byte[(int) file.length()];

      try {
         //convert file into array of bytes
         fileInputStream = new FileInputStream(file);
         fileInputStream.read(bFile);
         fileInputStream.close();
         System.out.println("Done");

      } catch(Exception e){
         e.printStackTrace();
      }

      new FileIO().writeToFile(bFile); 
   }
}

File write C code:

#include <jni.h>
#include <stdio.h>
#include <string.h>
#include "FileIO.h"

JNIEXPORT void JNICALL Java_FileIO_writeToFile (JNIEnv *env, jobject job, jbyteArray array ){

    char * buf ; 

   // here what i do ???    :(

    FILE *file = fopen("test123.png", "w");

    int results = fputs(buf, file);
    if (results == EOF) {
        //Failed to write do error code here.
    }
    fclose(file);
}

I have tried many solutions (below link) but no luck in writing it to the file. Please provide the correct solution and best JNI tutorial site.

Already tried solution: (But not success)

A correct way to convert byte[] in java to unsigned char* in C++, and vice versa?

Converting jbyteArray to a character array, and then printing to console

How to convert jbyteArray to native char* in jni?

int len = (*env)->GetArrayLength (env , array );
printf(" Length of the bytearray %d\n", len );
unsigned char * string ;
string = (char *)malloc((len + 1) * sizeof(char)) ;

jbyte* b = (*env)->GetByteArrayElements(env, array, &isCopy);

After GetByteArrayElements jbyte length should be 8 but the GetArrayLength returns bytearray length is 50,335.

What i try :

JNIEXPORT void JNICALL Java_HelloJNI_sayHello (JNIEnv *env, jobject job, jbyteArray array ){

    jsize num_bytes = (*env)->GetArrayLength(env, array);
    char *buffer = malloc(num_bytes + 1);

    printf("Number of jByte element    : %d\n", (int) num_bytes);

    if (!buffer) 
        printf("Buff Fail\n");

    jbyte* elements = (*env)->GetByteArrayElements(env, array, NULL);
    if (!elements)
        printf("Element Fail\n");

    printf ("Number of Byte elements   : %d\n", (int) strlen (elements));

    memcpy(buffer, elements, num_bytes);
    buffer[num_bytes] = 0;

    printf("Number of buffer elements  : %d\n", (int) strlen(elements));

    (*env)->ReleaseByteArrayElements(env, array, elements, JNI_ABORT);

    FILE *fp;
    fp = fopen( "file.txt" , "w" );
    fwrite(buffer , 1 , sizeof(buffer) , fp );
    fclose(fp);
    return;
}

AND the Output:

Done
Number of jByte element   : 50335
Number of Byte elements   : 8
Number of buffer elements : 8
Community
  • 1
  • 1
Muthu GM
  • 281
  • 3
  • 11
  • You can't convert an array to a pointer. A pointer is not an array! And from the links, your code **is** a dup, so VTC. If you don't understand the answers, drop a comment for clarification or specify **why** the answers don't work for you **specifically**. – too honest for this site Aug 04 '16 at 13:32
  • Possible duplicate of [Converting jbyteArray to a character array, and then printing to console](http://stackoverflow.com/questions/17491752/converting-jbytearray-to-a-character-array-and-then-printing-to-console) – too honest for this site Aug 04 '16 at 13:32
  • Post complete code that reproduces problem. Now I see only some snippets of native sources. – Sergio Aug 04 '16 at 13:33
  • @Olaf, the question you propose as a dupe does address the same problem. Unfortunately, the accepted answer appears to be buggy (which may be why it didn't work for this question's OP). The other answer really isn't on point, so I can't support your dupe proposal. – John Bollinger Aug 04 '16 at 13:37
  • 2
    Why should the byte length be 8? It's not an 8-byte PNG file is it? – samgak Aug 04 '16 at 13:38
  • @JohnBollinger: Sorry, I don't "java", just had to judge from the keywords. Anyway, then either this should be answered and the older made a dup or the older answered and this left as dup. As this one seems to be the more general, I actually think we should keep this one and CV the other as dup. Feel free to drop a comment for me if you agree, I'll CV the other question then and retract mine here. – too honest for this site Aug 04 '16 at 13:40
  • It's not the focus of your question, but your Java code is either buggy or incomplete. You are instantiating a class named HelloJNI and invoking a method on that object, but the class you present is named FileIO. It appears to be the latter that you intend to instantiate. – John Bollinger Aug 04 '16 at 14:18
  • code bug fixed @JohnBollinger – Muthu GM Aug 04 '16 at 14:34
  • @Serhio I provide my full try and output is added end of the question. – Muthu GM Aug 04 '16 at 15:00
  • It's not clear why you say "jbyte length should be 8". The length of the byte array should be the same as the length of the file, and if that file is a PNG then 50335 is a far more likely length than 8. – John Bollinger Aug 04 '16 at 15:05
  • You appear to be running into at least two distinct problems in that code. In the first place, the data likely contain null bytes. In that event, `strlen()` gives you the number of bytes before the first null, which is not the same as the overall size of the data. In the second place, `sizeof(buffer)` is completely wrong for your use. `buffer` is a *pointer*, and its size has nothing to do with the size of the data to which it points. Anyway, you already know that size, so why are you trying to compute it again? – John Bollinger Aug 04 '16 at 15:06
  • jbytearray len is 5033. After GetByteArrayElements buffer size is want to same but it is not equals to jbytearray. And the File size is also only 8 byte(4kb) but original image size 53 kb. – Muthu GM Aug 04 '16 at 15:12
  • I don't follow you at all. The JNI code is telling you that the length of the byte array is 50335. The other outputs it prints do not reflect the same thing because your code is buggy, as I already detailed. And I have no clue why you're equating 8 bytes with 4 kilobytes. – John Bollinger Aug 04 '16 at 15:18

1 Answers1

5

As @Olaf observed, pointers and arrays are altogether different kinds of objects. There is no meaningful way to convert one to another. The first step, therefore, is to better characterize what you actually mean.

Since your ultimate objective appears to be to write the bytes of the Java array to a file via fputs(), it seems that what you want to do is to create a C string whose contents are the same as the Java byte array's. This is exactly what Olaf's proposed dupe, Converting jbyteArray to a character array, and then printing to console, requests, and what the accepted answer purports to provide. You claim, however, that you already tried that solution and it didn't work. You don't describe the nature of the failure, but I can believe that it does fail, because the solution in the accepted answer is a bit buggy. In what follows, I'll refer to that answer as "the historic answer".

The historic answer is quite correct on several points. In particular, a C string must be null-terminated, and the contents of a Java byte array are not terminated, unless accidentally. Also, the jbytearray pointer is not a pointer directly to the data, so you need to use appropriate JNI functions to get the data themselves. You'll need to create a new buffer large enough for the byte array's contents plus the terminator, copy the bytes into it, and add the terminator.

For example:

// determine the needed length and allocate a buffer for it
jsize num_bytes = GetArrayLength(env, array);
char *buffer = malloc(num_bytes + 1);

if (!buffer) {
    // handle allocation failure ...
}

// obtain the array elements
jbyte* elements = GetByteArrayElements(env, array, NULL);

if (!elements) {
    // handle JNI error ...
}

// copy the array elements into the buffer, and append a terminator
memcpy(buffer, elements, num_bytes);
buffer[num_bytes] = 0;

// Do not forget to release the element array provided by JNI:
ReleaseByteArrayElements(env, array, elements, JNI_ABORT);

After that, you have in the space pointed to by buffer a null-terminated copy of the contents of the byte array, which you can, for example, pass to fputs():

int result = fputs(buffer, file);

Also, once you successfully allocate the buffer, you must be certain to free it before returning from the function, including via any error-handling return path:

free(buffer);

The main problem I see with the historic answer is that it suggests using strlen() to compute the length of the array data so as to be able to allocate a sufficiently large temporary buffer. But that could work only if the byte array were already null terminated, in which case it wouldn't be necessary in the first place.

Update

The above answers the question as posed -- how to convert the data to a C string. Note, however, that the question itself is premised on the supposition that converting the data to a C string and outputting them via fputs() is an appropriate mechanism in the first place. As @Michael observed, that is not the case if the data contain null bytes, as may be difficult to rule out if they originally come from a binary file.

If the overall objective is simply to write the bytes to a file, then first converting them to a C string is pointless. There are alternative mechanisms for outputting the data without first performing such a conversion. If the data can be relied upon not to contain internal null bytes, then you can use fprintf() to write them:

fprintf(file, "%*s", (int) num_bytes, (char *) elements);

On the other hand, if the data may contain nulls then you should use an appropriate low-level output function. That might look like this:

#include <stdio.h>
#include <jni.h>
#include "FileIO.h"

JNIEXPORT void JNICALL Java_FileIO_writeToFile(JNIEnv *env, jobject job,
        jbyteArray array) {
    FILE *fp = fopen( "file.txt" , "w" );

    if (!fp) {
        // handle failure to open the file ...
    }

    // determine the needed length and allocate a buffer for it
    jsize num_bytes = GetArrayLength(env, array);

    // obtain the array elements
    jbyte* elements = GetByteArrayElements(env, array, NULL);

    if (!elements) {
        // handle JNI error ...
    }

    // output the data
    if (fwrite(elements, 1, num_bytes, fp) != num_bytes) {
        // handle I/O error ...
    }

    // Do not forget to release the element array provided by JNI:
    ReleaseByteArrayElements(env, array, elements, JNI_ABORT);

    fclose(fp);
}
John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • Since the Java code appears to pass the contents of a PNG file to the native function, I doubt that the OP actually wants to write a string using `fputs`. Seems more likely that the `fputs` part comes from some code example that the OP didn't fully understand. Using `fwrite` would make more sense in this case. – Michael Aug 04 '16 at 14:18
  • @Michael, I answered the question more or less as posed, but you are right that the OP's premise is likely mistaken. I have updated the answer to address that. – John Bollinger Aug 04 '16 at 14:48
  • `jsize num_bytes = GetArrayLength(env, array);` is throws Undefined symbols for architecture x86_64: "_GetArrayLength", referenced from: so I try `(*env)->GetArrayLength();` and also same prob in `GetByteArrayElements` and `_ReleaseByteArrayElements` – Muthu GM Aug 04 '16 at 14:51
  • @MuthuGM, you tagged your question [c]. The code I presented is correct for the C JNI interface. Your alternative is written for the C++ JNI interface. If you are compiling the code as C++ then you should tag it that way. – John Bollinger Aug 04 '16 at 14:54
  • @JohnBollinger My whole project is pure C lang and I don't know C++ knowledge. – Muthu GM Aug 04 '16 at 14:58
  • @MuthuGM then it would be wise to build your code with a C compiler. You are not doing so. The C API for JNI uses the function signatures I wrote. The ones you say work for you are those used when you compile your code as C++ (i.e. with a C++ compiler). – John Bollinger Aug 04 '16 at 15:10
  • @JohnBollinger ur updated ans is also not worked. the output file size is also same 8 byte. – Muthu GM Aug 04 '16 at 15:22
  • @MuthuGM, I'm sorry, but I don't believe you. If the code ran successfully and did not generate any errors (which in the above form it does not report) then the size of the contents of file it outputs -- confusingly named "file.txt" -- will be the same as the size of the contents of the file that was originally read. If you think you observe different behavior then your observation is flawed. Note in particular my comments in the question's comment thread about how your debug printing is itself buggy. Examine the output file itself. – John Bollinger Aug 04 '16 at 15:28
  • @JohnBollinger: I retracted the CV. Do you think we should CV the other then? – too honest for this site Aug 04 '16 at 17:21
  • @Olaf, I think everything in the other question and answers are also in this one, and then some. On the other hand, the OP of this one has some bugs in his C code and some other misunderstandings that might cloud the main issue. I'm honestly uncertain how best to proceed. – John Bollinger Aug 04 '16 at 19:32
  • If you addressed these errors (as far as I understand, you do), I think the older questin is redundant. This one also has the more general title, so I think we can dup-vote the other one. Let's clean up at least some redundancy. – too honest for this site Aug 04 '16 at 20:27
  • The problem is only for media file (Images, audio and Video). And all other file formats are working correctly and output files are created perfectly. Now the problem is media file cannot be accessed by in C code. – Muthu GM Aug 05 '16 at 09:06
  • A wrinkle is that JNI functions like `GetByteArrayElements` are, in my experience, likely to allocate and copy an additional copy of the array, which is incredible wasteful given that we're about to allocate and copy it again. I've found I can avoid the extra copy by using `GetPrimitiveArrayCritical` instead. I don't know whether this is considered a good idea or not. – Steve Summit Nov 24 '21 at 13:07
  • @SteveSummit, if one is prepared to accept the semantic restrictions on use of `GetPrimitiveArrayCritical()` then I see no reason why it should not be used for something like this. In most VMs, it will not make a copy, whereas `GetByteArrayElements()` is more likely to do. I did not want to get into a discussion of the restrictions on use of the former when I wrote this answer, and since the objective is to output the data to a file, it seemed unlikely to me that performance of preparing that data for I/O was a major concern. – John Bollinger Nov 24 '21 at 13:21