如何在JNI中将Java字节数组的内容转换为C字符串?

5

更新:
错误:`jbyte* elements = (*env)->GetByteArrayElements(env, array, NULL);` 只返回 8 个字节。请提供其它检索 jbytearray 的方法。
我是JNI的新手,对JNI和英语都不太熟悉。
现在我正在尝试用Java读取文件并使用C将其写入文件的简单JNI程序。
Java文件读取代码:
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); 
   }
}

文件写入的C代码:
#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);
}

我已经尝试了许多解决方案(下面的链接),但是在将其写入文件时没有成功。请提供正确的解决方案和最佳JNI教程网站。
已经尝试的解决方案:(但没有成功) 在Java中正确地将byte[]转换为C++中的unsigned char*,反之亦然? 将jbyteArray转换为字符数组,然后打印到控制台 如何在jni中将jbyteArray转换为本地char*?
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);

在调用GetByteArrayElements函数之后,jbyte的长度应该是8,但是GetArrayLength返回的bytearray的长度为50,335。
我尝试过的方法:
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输出:

Done
Number of jByte element   : 50335
Number of Byte elements   : 8
Number of buffer elements : 8

你不能将数组转换为指针。指针不是数组!从链接中可以看出,你的代码是一个副本,所以 VTC。如果你不理解答案,请在评论中说明或具体说明为什么答案对你不起作用。 - too honest for this site
可能是重复的问题:将jbyteArray转换为字符数组,然后打印到控制台 - too honest for this site
请发布能够复现问题的完整代码。我目前只看到了一些本地源代码片段。 - Sergio
2
为什么字节长度应该是8?它不是一个8字节的PNG文件,是吗? - samgak
@JohnBollinger:抱歉,我不懂“Java”,只能从关键词判断。无论如何,要么这个问题应该得到回答,旧的问题就成了重复的,要么旧的问题得到回答,这个问题就留作重复的。由于这个问题似乎更为普遍,我认为我们应该保留这个问题,并将另一个标记为重复。如果您同意,请给我留言,我会将另一个问题标记为重复并在此处撤回我的问题。 - too honest for this site
显示剩余8条评论
1个回答

5
正如@Olaf所观察到的,指针和数组是完全不同类型的对象。因此,没有有意义的方式将一个转换为另一个。因此,第一步是更好地描述您实际想要的内容。
由于您的最终目标似乎是通过在文件中写入Java数组的字节,因此您想要做的是创建一个C字符串,其内容与Java字节数组相同。这正是Olaf提出的重复问题:将jbyteArray转换为字符数组,然后打印到控制台所要求的,并且被接受的答案所声称提供。但是,您声称已经尝试了该解决方案,但它没有起作用。您没有描述失败的性质,但我可以相信它会失败,因为已接受答案中的解决方案有点有问题。在接下来的内容中,我将把那个答案称为“历史答案”。
历史答案在几个方面是正确的。特别是,C字符串必须以空值结尾,并且Java字节数组的内容没有终止符,除非意外。此外,指针不是直接指向数据的指针,因此您需要使用适当的JNI函数获取数据本身。您需要创建一个足够大的新缓冲区来容纳字节数组的内容以及终止符,将字节复制到其中,并添加终止符。
例如:
// 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);

之后,您在由buffer指向的空间中拥有了一个以null结尾的字节数组副本,您可以将其传递给例如fputs()函数:

int result = fputs(buffer, file);

另外,在成功分配缓冲区后,您必须确保在函数返回之前释放它,包括通过任何错误处理返回路径释放:

free(buffer);

我看到历史答案的主要问题是建议使用strlen()来计算数组数据的长度,以便能够分配足够大的临时缓冲区。但是,这只有在字节数组已经以空字符结尾的情况下才有效,而在这种情况下,首先就不需要这样做。
更新
上面回答了提出的问题——如何将数据转换为C字符串。然而,请注意,问题本身是基于这样的假设:将数据转换为C字符串并通过fputs()输出它们是一种合适的机制。正如@Michael所观察到的那样,如果数据包含空字节,则情况并非如此,因为如果它们最初来自二进制文件,则可能很难排除。
如果总体目标仅是将字节写入文件,则首先将其转换为C字符串是无意义的。有替代机制可用于在执行此类转换之前输出数据。如果可以确信数据不包含内部空字节,则可以使用fprintf()将其写入:
fprintf(file, "%*s", (int) num_bytes, (char *) elements);

另一方面,如果数据可能包含空值,则应使用适当的低级输出函数。这可能看起来像这样:

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

由于Java代码似乎将PNG文件的内容传递给本地函数,我怀疑OP实际上并不想使用fputs来编写字符串。更有可能是fputs部分来自OP没有完全理解的某些代码示例。在这种情况下,使用fwrite会更有意义。 - Michael
@Michael,我基本上回答了问题,但你说得对,原帖的前提可能是错误的。我已经更新了答案来解决这个问题。 - John Bollinger
jsize num_bytes = GetArrayLength(env, array); 这段代码会抛出 Undefined symbols for architecture x86_64 错误: "_GetArrayLength",因此我尝试使用 (*env)->GetArrayLength();,但在 GetByteArrayElements_ReleaseByteArrayElements 中也遇到了同样的问题。 - Muthu GM
@JohnBollinger 我的整个项目是纯C语言,我不懂C++知识。 - Muthu GM
@JohnBollinger,您更新的答案也没有起作用。输出文件大小仍然是8字节。 - Muthu GM
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接