Java 之JNI基础篇(三)

从源码开始

上一篇博客讲了Java代码如何调用本地C代码,这一篇则主要讲C代码如何访问Java的变量和方法,那么我们继续从JDK源码入手,从源码中学习,学会JNI真正的使用方式和使用场景,而不是想当然的写几个简单的demo,那样是没有意义的,知道API和会用API并不是一回事,源码的JNI使用方式是高效的经得起考验的,如果我们仅仅只是看了几个demo,查了一下文档就去写JNI,那样很可能就是在给自己埋坑。

好了,进入主题,看到我们今天的主角,使用Java的zip压缩与解压,之所以从这里入手,是因为此处的代码逻辑比较简单,且正真实现压缩与解压的是本地C库,所以非常适合学习JNI,以及Java与C的大数据量交互。
打开openjdk的Java源码/jdk/src/share/classes/java/util/zip/Deflater.java
这里写图片描述

可以看到,在该类的开头就做了一个介绍,并贴心的给出了一段demo,实际上这个demo太过于简单,真实的代码中并不是这样用,我们这里就以此demo做个小测试,简单改造一下

try {
		// Encode a String into bytes
		String inputString = "Beautiful is better than ugly.Explicit is better than implicit.";

		byte[] input = inputString.getBytes();
		System.out.println("原始长度:"+input.length);

		// Compress the bytes
		byte[] output = new byte[100];
		Deflater compresser = new Deflater();
		compresser.setInput(input);
		compresser.finish();
		int compressedDataLength = compresser.deflate(output);
		compresser.end();
		System.out.println("压缩之后:"+compressedDataLength);

		// Decompress the bytes
		Inflater decompresser = new Inflater();
		decompresser.setInput(output, 0, compressedDataLength);
		byte[] result = new byte[100];
		int resultLength = decompresser.inflate(result);
		decompresser.end();
		System.out.println("解压长度:"+resultLength);
		// Decode the bytes into a String
		String outputString = new String(result, 0, resultLength);
		System.out.println(outputString);
	} catch (Exception ex) {
		ex.printStackTrace();
	} 

这一段程序主要做了两件事,先压缩字节,再解压恢复,这里压缩使用Deflater 类,而解压则用到Inflater 类,从名字也很好理解。大家可以运行上述代码,看看一行字符串,压缩之后减少了多少字节。

首先将上述程序分开来看,先看压缩部分,主要干事的代码如下

Deflater compresser = new Deflater();
compresser.setInput(input);
compresser.finish();
int compressedDataLength = compresser.deflate(output);
compresser.end();

源码其实不是很多,这里仅摘取相关部分

private final ZStreamRef zsRef;
private byte[] buf = new byte[0];
private int off, len;
private int level, strategy;
private boolean setParams;
private boolean finish, finished;

...

    static {
        /* Zip library is loaded from System.initializeSystemClass */
        initIDs();
    }

    /**
     * Sets input data for compression. This should be called whenever
     * needsInput() returns true indicating that more input data is required.
     * @param b the input data bytes
     * @param off the start offset of the data
     * @param len the length of the data
     * @see Deflater#needsInput
     */
    public void setInput(byte[] b, int off, int len) {
        if (b== null) {
            throw new NullPointerException();
        }
        if (off < 0 || len < 0 || off > b.length - len) {
            throw new ArrayIndexOutOfBoundsException();
        }
        synchronized (zsRef) {
            this.buf = b;
            this.off = off;
            this.len = len;
        }
    }

    /**
     * Sets input data for compression. This should be called whenever
     * needsInput() returns true indicating that more input data is required.
     * @param b the input data bytes
     * @see Deflater#needsInput
     */
    public void setInput(byte[] b) {
        setInput(b, 0, b.length);
    }

...

	/**
     * When called, indicates that compression should end with the current
     * contents of the input buffer.
     */
    public void finish() {
        synchronized (zsRef) {
            finish = true;
        }
    }

...

    /**
     * Compression flush mode used to achieve best compression result.
     *
     * @see Deflater#deflate(byte[], int, int, int)
     * @since 1.7
     */
    public static final int NO_FLUSH = 0;
    
    public int deflate(byte[] b) {
        return deflate(b, 0, b.length, NO_FLUSH);
    }

    public int deflate(byte[] b, int off, int len, int flush) {
        if (b == null) {
            throw new NullPointerException();
        }
        if (off < 0 || len < 0 || off > b.length - len) {
            throw new ArrayIndexOutOfBoundsException();
        }
        synchronized (zsRef) {
            ensureOpen();
            if (flush == NO_FLUSH || flush == SYNC_FLUSH ||
                flush == FULL_FLUSH)
                return deflateBytes(zsRef.address(), b, off, len, flush);
            throw new IllegalArgumentException();
        }
    }

...

    private static native void initIDs();
    private native static long init(int level, int strategy, boolean nowrap);
    private native static void setDictionary(long addr, byte[] b, int off, int len);
    private native int deflateBytes(long addr, byte[] b, int off, int len,
                                    int flush);
    private native static int getAdler(long addr);
    private native static long getBytesRead(long addr);
    private native static long getBytesWritten(long addr);
    private native static void reset(long addr);
    private native static void end(long addr);

简单浏览一下代码就知道,前几行代码其实都是一些准备工作,真正做压缩的是deflate方法,而对应的其实是native修饰的deflateBytes方法。

我们打开/jdk/src/share/native/java/util/zip/Deflater.c 源码 ,按照Java层的代码调用顺序看

JNI访问Java成员变量
  • Java静态代码块
static {
       initIDs();
}

本地实现

JNIEXPORT void JNICALL
Java_java_util_zip_Deflater_initIDs(JNIEnv *env, jclass cls)
{
    levelID = (*env)->GetFieldID(env, cls, "level", "I");
    strategyID = (*env)->GetFieldID(env, cls, "strategy", "I");
    setParamsID = (*env)->GetFieldID(env, cls, "setParams", "Z");
    finishID = (*env)->GetFieldID(env, cls, "finish", "Z");
    finishedID = (*env)->GetFieldID(env, cls, "finished", "Z");
    bufID = (*env)->GetFieldID(env, cls, "buf", "[B");
    offID = (*env)->GetFieldID(env, cls, "off", "I");
    lenID = (*env)->GetFieldID(env, cls, "len", "I");
}

这个初始化方法是干什么的呢?我们从最直观的角度就能判断,这个方法应该是在获取类的成员变量,因为这个方法里出现了Deflater类的成员变量level、strategy、finish等等,再看到函数名GetFieldID,如果非常熟悉Java反射机制的话,那么对于Java的Field域是再熟悉不过了,这一段代码就跟Java反射获取类成员是高度相似的,唯一的不同是,这里获取的Field的ID

在JNI官方文档中查询GetFieldID
这里写图片描述

通过阅读文档,我们可以知道,本地方法访问Java实例变量,有两个步骤。 首先,调用GetFieldID函数获取字段的ID,第二步,在以上示例代码中则是调用GetObjectClass函数来访问实例变量。

看到函数原型

jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig); 

前三个参数都很好理解,由于Java中initIDs方法是static方法,所有这里第二个参数传的是jclass类型,主要是最后一个参数不好理解,该参数实际上是一个Field描述符,或者理解为一种类型签名,实际上它是一个字符串,8种基本类型和引用类型都有对应的类型签名,先看一下八种基本类型,最后再做一下总结

Z  boolean  
B  byte  
C  char  
S  short  
I  int  
J  long  
F  float  
D  double  

其中带"["的表示数组,如示例中bufID = (*env)->GetFieldID(env, cls, "buf", "[B");

回到Deflater.c 源码,在函数的外层定义了这些全局静态变量,由此很好理解在Java静态代码块所做的事情,实际上就是在类加载的时候,将类成员的FieldId保存到本地代码的全局静态变量中,这样做的目的就是提高效率,以免本地代码访问类成员时频繁获取FieldId

static jfieldID levelID;
static jfieldID strategyID;
static jfieldID setParamsID;
static jfieldID finishID;
static jfieldID finishedID;
static jfieldID bufID, offID, lenID;
  • 真正处理压缩的方法

在Deflater类的构造方法中也做了一些事情,主要是对native层的zlib库的一些准备,关于zlib库如何使用就不在本文讨论范围了,实际上也就是函数的调用,看一下zlib库的示例很好理解,接下来看到关键代码

JNIEXPORT jint JNICALL
Java_java_util_zip_Deflater_deflateBytes(JNIEnv *env, jobject this, jlong addr,
                                         jarray b, jint off, jint len, jint flush)
{
    z_stream *strm = jlong_to_ptr(addr);
    
    /* 通过之前加载的FieldId 获取类成员变量*/
    jarray this_buf = (*env)->GetObjectField(env, this, bufID);
    jint this_off = (*env)->GetIntField(env, this, offID);
    jint this_len = (*env)->GetIntField(env, this, lenID);
    jbyte *in_buf;
    jbyte *out_buf;
    int res;
    if ((*env)->GetBooleanField(env, this, setParamsID)) {
        int level = (*env)->GetIntField(env, this, levelID);
        int strategy = (*env)->GetIntField(env, this, strategyID);

		/* 获取待压缩的字节数组*/
        in_buf = (*env)->GetPrimitiveArrayCritical(env, this_buf, 0);
        if (in_buf == NULL) {
            // Throw OOME only when length is not zero
            if (this_len != 0)
                JNU_ThrowOutOfMemoryError(env, 0);
            return 0;
        }

		/* 获取压缩后输出的字节数组*/
        out_buf = (*env)->GetPrimitiveArrayCritical(env, b, 0);
        if (out_buf == NULL) {
            (*env)->ReleasePrimitiveArrayCritical(env, this_buf, in_buf, 0);
            if (len != 0)
                JNU_ThrowOutOfMemoryError(env, 0);
            return 0;
        }

		/* 调用zlib库做压缩之前,需要做的一些准备工作*/
        strm->next_in = (Bytef *) (in_buf + this_off);
        strm->next_out = (Bytef *) (out_buf + off);
        strm->avail_in = this_len;
        strm->avail_out = len;

		/* C库中,真正做压缩的方法,调用完成后返回一个结果值,用以判断是否成功*/
        res = deflateParams(strm, level, strategy);
        /* 释放资源*/
        (*env)->ReleasePrimitiveArrayCritical(env, b, out_buf, 0);
        (*env)->ReleasePrimitiveArrayCritical(env, this_buf, in_buf, 0);

        switch (res) {
        case Z_OK:
            (*env)->SetBooleanField(env, this, setParamsID, JNI_FALSE);
            this_off += this_len - strm->avail_in;
            (*env)->SetIntField(env, this, offID, this_off);
            (*env)->SetIntField(env, this, lenID, strm->avail_in);
            return len - strm->avail_out;
        case Z_BUF_ERROR:
            (*env)->SetBooleanField(env, this, setParamsID, JNI_FALSE);
            return 0;
        default:
            JNU_ThrowInternalError(env, strm->msg);
            return 0;
        }
    } else {
        /* 此处省略另一种策略下的压缩逻辑,实际上这个else中的逻辑与if中的类似,只是具体调用的方法不同*/
    }
}

从这段源码中,可以学会大数据量数组的传递,以及在本地方法中访问Java变量并修改,特别关键的一点是可以学习标准的JNI编写套路,这里Java层的数组并不是直接通过参数传递到本地代码中的,而是先将字节数组的引用保存到成员变量中,在本地代码中去获取这个类成员变量,从而获取到该数组的指针,前面的博客已经详细说明过,GetPrimitiveArrayCritical并不一定获取到原始数组的指针,也可能是副本的指针,所以一定要调用ReleasePrimitiveArrayCritical进行缓冲区释放,回写数组。

访问Java成员变量小结

从这个源码示例进行引申,将JNI官方文档中,对于Java实例变量与静态变量做一个归纳总结

  • 获取实例变量的FieldID

函数原型

jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig); 
  • 通过FieldID获取实例变量值
    根据8种基本类型和引用类型,分别对应不同的函数

函数原型

NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID); 

这里写图片描述

  • 修改实例变量的值

函数原型

void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID,NativeType value); 

这里写图片描述

除了实例变量,还有用static修饰的静态变量,看看静态变量如何访问

  • 获取静态变量的FieldID
    可以看到,和获取实例变量的使用是一样的,只是函数名不同

函数原型

jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig); 
  • 通过FieldID获取静态变量值
    获取静态变量值和获取实例变量值稍有一点不同,可以看该函数的第二参数不是jobject类型,而是jclass类型,这说明该函数调用的时候需要一个字节码对象,而不是类的实例对象

函数原型

NativeType GetStatic<type>Field(JNIEnv *env, jclass clazz,jfieldID fieldID); 

这里写图片描述

  • 修改静态变量的值

函数原型

void SetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID, NativeType value); 

这里写图片描述

在JNI本地代码中创建Java对象和反调Java方法

由于Java的JNI接口被设计出来,主要是用于提升Java性能,复用已有的C/C++库,所以在JDK中没有很好的示例学习在本地代码中创建Java对象和反调Java方法,然而这样的情况在Android NDK开发中则大为不同,JNI成为了本地运行环境和Java层的重要桥梁,因而JNI在Android平台被发扬光大了。在后续的Android NDK相关博客中再从源码角度学习,这里先从官方文档示例入手学习

首先我们回顾一下Java反射机制,在我的Java入门基础博客里曾有写过

所谓反射,简单说就是通过一个class文件对象来使用该class文件中的构造方法,成员变量,成员方法。获取Class类对象通常有三种方法

//1.Object类中的getClass()方法
Person p = new Person();
Class c1 = p.getClass();
 
//2.通过数据类型的一个静态的class属性
Class c2= Person.class;
 
//3.通过Class类的一个静态方法forName()
Class c3 = Class.forName("com.test.Person");

在Java中,通常得到了Class对象就可以反射构造方法从而创建一个该类的实例对象,看到文档给出的示例

jstring MyNewString(JNIEnv *env, jchar *chars, jint len)
{
	jclass stringClass;
	jmethodID cid;
	jcharArray elemArr;
	jstring result;

	/*获取jclass对象*/
	stringClass = (*env)->FindClass(env, "java/lang/String");
	if (stringClass == NULL) {
	    return NULL; 
	}
	
	/*获取 String(char[]) 构造函数的MethodID*/
	cid = (*env)->GetMethodID(env, stringClass,"<init>", "([C)V");
	
	if (cid == NULL) {
	    return NULL;
	}
	
	/*创建一个char[]数组作为String类的内容 */
	elemArr = (*env)->NewCharArray(env, len);
	
	if (elemArr == NULL) {
	    return NULL; 
	}
	(*env)->SetCharArrayRegion(env, elemArr, 0, len, chars);
	
	/*创建一个java.lang.String对象 */
	result = (*env)->NewObject(env, stringClass, cid, elemArr);
	
	/* 释放本地引用*/
	(*env)->DeleteLocalRef(env, elemArr);
	(*env)->DeleteLocalRef(env, stringClass);
	return result;
}

从这个示例中可以看出,本地代码创建对象的步骤和Java反射其实是一致的

  1. 获得字节码文件对象,即获得Class对象
  2. 获得类的成员属性、方法或者构造函数
  3. 调用构造函数创建对象

我们通过查询文档,学习示例中的相关方法

  • 获取jclass对象
    FindClass函数类似Java中的forName方法,传入完整包名类名,注意中间使用"/"分隔符,该方法加载的是CLASSPATH路径下的类

函数原型

jclass FindClass(JNIEnv *env, const char *name);

除了该函数外,还有另一个函数也可以用来或许jclass对象,这一点和Java的getClass()方法也是高度相似的,都是通过一个实例对象获取自身的class对象

jclass GetObjectClass(JNIEnv *env, jobject obj); 
  • 获取MethodID
    与获取FieldID一样,调用Java的方法,也需要先得到MethodID

函数原型

jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig); 

该函数的使用与GetFieldID是一样的,最后两个参数传入方法名和方法签名,需要注意的一点是,如果获取的是构造方法,那么方法名统一使用"<init>",这里还有一个知识点就是方法签名的使用,我们在最后做一个总结

  • 调用构造方法创建对象

函数原型

jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodID,...); 

可以看到这个函数后面是不定参,传入的正是Java构造方法中需要的参数。处理NewObject函数用来创建对象,还有另外两个函数可用,只是传参的形式稍有不同,一个是传入jvalue数组,另一个则传入va_list 类型

jobject NewObjectA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args); 
jobject NewObjectV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args); 

接下再看看如何调用Java成员方法,先看的官方示例

class InstanceMethodCall {
	private native void nativeMethod();
	private void callback() {
		System.out.println("In Java");
	}
	public static void main(String args[]) {
		InstanceMethodCall c = new InstanceMethodCall();
		c.nativeMethod();
	}
	static {
		System.loadLibrary("InstanceMethodCall");
	}
}
JNIEXPORT void JNICALL
Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj)
{
	jclass cls = (*env)->GetObjectClass(env, obj);
	jmethodID mid = (*env)->GetMethodID(env, cls, "callback", "()V");
	if (mid == NULL) {
		return; /* method not found */
	}
	printf("In C\n");
	(*env)->CallVoidMethod(env, obj, mid);
}

与访问成员变量步骤相同,也是两步,首先获得Java方法的MethodID,最后调用JNI函数执行该方法。此示例中,调用的是CallVoidMethod函数,它表示执行一个无返回值的Java方法。我们知道Java方法返回值除了Void类型,还可以是8种基本类型和引用类型,下面看到Call函数的原型

<NativeType> Call<Type>Method(JNIEnv *env,jobject obj, jmethodID methodID, ...);

后面的不定参表示被调用的Java方法需要传入的参数
这里写图片描述

除了实例方法,Java还有静态方法,静态方法的调用步骤与实例方法也是相同的

  • 获取MethodID
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig); 
  • 根据MethodID执行Java方法
<NativeType> CallStatic<Type>Method(JNIEnv *env, jclass clazz,jmethodIDmethodID, ...);

在这里插入图片描述

在本地代码调用Java方法中,最后还有一个点需要注意,即在父类中定义但在子类中已被覆盖的实例方法。在Java中我们直接通过关键字super.来调用,在JNI中也有单独处理,而调用步骤和上面讲的也是相同的,都是先获取MethodID,只是执行Java方法的函数是不同的

NativeType CallNonvirtual<type>Method(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); 

这里写图片描述

Java类型签名与方法签名总结

在前面无论是访问Java成员变量还是调用Java方法,都涉及到一个描述符的东西,也就是签名,它是一个字符串,有固定的写法规则

  • 类型签名
    这里写图片描述
    这里写图片描述

可以看到,引用类型必须以L开头,并带上完整包名类名,其包名中的分隔符使用/,而数组则必须以[开头,如果是二维数组,则用[[,三维数组用[[[,最后还有需要特别注意的一点,签名的后面一定要以;结尾

  • 方法签名
    与签名的类型前不同的是,方法签名是有返回值的,其固定格式如下

     (形参列表)返回值类型
    

需要注意的是,形参之间是没有分隔符号的,紧跟在一起
这里写图片描述

当我们手写方法签名的时候很容易出错,JDK是有提供工具自动生成签名的,就和javah工具一样,生成签名的工具是javap命令,如需生成InstanceMethodCall类的签名,执行如下命令
javap -s -p InstanceMethodCall

JNI字符串与对象数组的操作

在上一篇博客我们谈了基本类型数组的使用,这一篇的最后就补充一下关于字符串和对象类型数组的使用

  • 字符串操作
    先看到文档的示例代码
JNIEXPORT jstring JNICALL
Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt)
{
	char buf[128];
	const jbyte *str;
	str = (*env)->GetStringUTFChars(env, prompt, NULL);
	if (str == NULL) {
		return NULL; /* OutOfMemoryError already thrown */
	}
	printf("%s", str);
	(*env)->ReleaseStringUTFChars(env, prompt, str);
	
	/* We assume here that the user does not type more than 127 characters */
	scanf("%s", buf);
	return (*env)->NewStringUTF(env, buf);
}

实际上在C语言中是没有字符串类型的,字符串就是一个字符数组,因此对于字符串的操作,和数组是非常类似的,但是为了和Java的类型对接起来,JNI对Java的字符串提供了专有的函数操作,我们学习了前面的基本类型数组的操作,对于字符串的操作函数就非常好理解了。

从官方示例中可以看出来,这段代码进行了两种操作,1.获取Java层传下来的字符串,并转换为本地代码中的字符数组;2.在本地代码中创建一个Java字符串对象返回。

const jbyte * GetStringUTFChars(JNIEnv *env,jstring string, jboolean *isCopy);

如果有可能,该函数会返回一个指向Java字符串的UTF-8类型字符数组的指针,最后一个参数,我们在之前讲数组操作时已经讲过,注意传值方式,传入的是一个指针。它我们之前探讨的数组操作一样,也有可能会返回一个副本的指针,因此在最后一定要调用对应的Release函数释放副本,回写改变后的值到Java原始字符串中。

void ReleaseStringUTFChars(JNIEnv *env,jstring string, const char *utf);

与我们之前创建的字符串对象不同,这里没有去获取MethodID然后调用构造方法去创建,而是使用NewStringUTF函数,从UTF-8字符数组构去造一个新的String对象

jstring NewStringUTF(JNIEnv *env, const char *bytes); 

除了这些函数之外,还有一些字符串操作的函数,这些函数基本上与数组操作函数完全对应

JNI FunctionDescriptionSince
GetStringChars
ReleaseStringChars
以Unicode格式获取或释放指向字符串内容的指针。 可能会返回字符串的副本。JDK1.1
GetStringUTFChars
ReleaseStringUTFChars
获取或释放指向UTF-8格式的字符串内容的指针。可能返回字符串的副本。JDK1.1
GetStringLength返回Unicode字符串中的字符长度。JDK1.1
GetStringUTFLength获取 UTF-8 编码字符串的长度,也可以通过标准 C 函数 strlen 获取JDK1.1
NewString通过给定的Unicode C字符串创建一个Java String字符串JDK1.1
NewStringUTF通过给定的UTF-8编码C字符串创建一个Java String字符串JDK1.1
GetStringCritical
ReleaseStringCritical
获取Unicode格式的字符串内容的指针。 可能会返回字符串的副本。 与我们之前数组操作一样,在Get / ReleaseStringCritical调用之间不得进行阻塞操作JDK1.2
GetStringRegion
SetStringRegion
以Unicode格式将字符串的内容复制到预分配的C缓冲区或从预分配的C缓冲区中回写修改的内容到原始字符串中。JDK1.2
GetStringUTFRegion
SetStringUTFRegion
以UTF-8 格式将字符串的内容复制到预分配的C缓冲区或从预分配的C缓冲区中回写修改的内容到原始字符串中。JDK1.2
  • 对象数组
jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index); 
void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value); 

示例

JNIEXPORT jobjectArray JNICALL Java_com_test_modifyStringArr(JNIEnv * env, jclass jc,jobjectArray strArray )
{
    jstring jstr;
    jsize len = (*env)->GetArrayLength(env, strArray);
    char **pstr = (char **) malloc(len*sizeof(char *));
             
    int i=0;      
    for (i=0 ; i< len ;i++){
	    jstr = (*env)->GetObjectArrayElement(env, strArray, i);
        *pstr[i] = (char *)(*env)->GetStringUTFChars(env, jstr, 0);                     
    }    
    
    jclass stringClass = (*env)->FindClass(env, "java/lang/String");
    jobjectArray  strArr = (*env)->NewObjectArray(env, len , stringClass, NULL); 
    if( strArr  == NULL){
	    return NULL;
    }   

	 for (i=0 ; i< len ;i++){
		jstring js = (*env)->NewStringUTF(env,*pstr[i])
		(*env)->SetObjectArrayElement(env, strArr , i, iarr,js);
	 }  
     
     free(pstr);
     return strArr ;
}
  • JNI字符串操作乱码问题
    由于编码不统一就会出现乱码问题,本地代码中,中文等使用的是Unicode 编码,而在Java中通常使用UTF-8编码,解决乱码问题需要进行编码转换,在本地代码进行编码转换通常会比较麻烦。一般有两种比较简便的解决方法
    1.调用Java层String的构造方法指定编码表进行转换 String(byte[] data, String charsetName)
    2.使用LibIconv库,在本地代码中进行转换。在NDK开发中已经支持了该库,如需在Windows上使用,可进入官网下载静态或动态库 地址 该库的使用也非常简单
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Chap1:JNI完全手册... 3 Chap2:JNI-百度百科... 11 Chap 3:javah命令帮助信息... 16 Chap 4:用javah产生一个.h文件... 17 Chap5:jni教程(very very good) 19 Chap6: JNI传递返回值... 26 15.2.2.3 传递字符串... 28 15.2.2.4 传递整型数组... 29 15.2.2.5 传递字符串数组... 30 15.2.2.6 传递对象数组... 31 Chap7:Jni中C++和Java的参数传递... 33 Chap8:如何将java传递过来的jbyteArray转换成C/C++中的BYTE数组... 47 Chap5:使用JNI技术实现java程序调用第方dll(c/c++)文件的功能... 47 Chap9:如何编写jni方法(转载)... 55 1、实例一:在jni中调用标准c中自带的函数printf(): 57 2、实例二、调用c 语言用户定义的函数... 58 3、实例、在jni函数中访问java类中的对象实例域... 58 4、实例四:在jni函数中访问类的静态实例域... 60 5、实例五:在jni函数中调用java对象的方法... 60 6、实例六:在jni函数中调用java类的静态方法... 61 7、实例七:jni函数中传递基本数据类型参数... 62 8、实例八:在jni函数中传递对象类型参数... 62 9、实例九:在jni函数中处理字符串... 63 10、实例十:在jni函数中处理数组... 64 11、实例十一:在jni中的返回值问题... 65 12、实例十二:在jni中创建java类对象:... 66 Chap10:在 Windows 中实现 Java 本地方法... 66 1.Java 调用 C. 67 2.调试... 76 3.其他信息... 79 Chap11:如何在C/C++中调用Java. 80 1.环境搭建... 81 2.初始化虚拟机... 83 3.访问类方法... 85 4访问类属性... 87 5.访问构造函数... 88 6.数组处理... 89 7.中文处理... 89 8.异常... 91 9.线程和同步访问... 91 10.时间... 92 Chap12:基本JNI调用技术(c/c++与java互调) 93 Chap13:JNI的c代码中,另外一个线程获取 JNIEnv. 96 chap 14:当JNI遇到多线程--java对象如何被C++中的多个线程访问?. 97 chap 15:JNI在多线程中的应用... 101 chap 16:JNI限制(多线程)... 105 chap 17:使用 Java Native Interface 的最佳实践... 106 1.性能缺陷... 107 2.正确性缺陷... 117 3.避免常见缺陷... 121 4.结束语... 128 Chap18:JNI设计实践之路... 129 一、 前言... 129 二、 JNI基础知识简介... 130 Java程序调用非Java程序... 131 四、 C/C++访问Java成员变量和成员方法... 138 五、 异常处理... 140 六、 MFC程序中嵌入Java虚拟机... 142 Chap19:JNI编程系列之基础... 148 System.loadLibrary("HelloWorld"); 149 JNIEXPORT void JNICALL Java_HelloWorld_print (JNIEnv *, jobject); 150 Chap20:JNI编程系列之中级(上)... 151 1. Java基本类型的传递... 151 2. String参数的传递... 151 3. 数组类型的传递... 153 4. 二维数组和String数组... 154 Chap21:JNI编程系列之高级... 155 1. 在一般的Java类中定义native方法... 156 2. 访问Java类的域和方法... 156 3. 在native方法中使用用户定义的类... 157 4. 异常处理... 158

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程之路从0到1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值