NDK学习笔记:java类封装c++类

Posted vonchenchen1

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NDK学习笔记:java类封装c++类相关的知识,希望对你有一定的参考价值。

背景

在最近的开发中遇到了这样的一个场景,使用ffmpeg同时解码多路h264流,之前解码一路视频时,可以直接在jni文件中定义一个包装了ffmpeg解码功能的c++类的对象,如果继续采取这种写法必须在jni中定义多个对象,使得程序很不灵活。如果能把一个java类直接和c++类建立关系,则可以在多路解码时分别创建java对象,在使用完毕后由java进行gc,这样代码就灵活了许多。下面来研究一下java类与c++类之间如何建立关系。

实现

jni文件为我们提供了java层和c/c++层通信的接口,但是这些接口都是c函数的形式,也就是正常情况下要想调用一个c++类的方法,我们需要在jni中建立一个全局的c++对象。如果想要直接使用c++类,必须建立一个c++对象,如果建立全局对象肯定不符合我们的要求,这时可以提供一个jni函数,用来在堆中创建一个c++对象。

正常情况下我们是用一个指针指向堆中创建的c++对象,由于指针本身是一个地址值,所以可以将其当作一个数值。把这个地址值当作数字返回给java层,java就拿到了这个c++对象的句柄,便可以对这个对象进行操作。

java包装类实现

之所以称其为包装类,是因为其实这个java类是包装了c++类,其功能实现是由c++实现。下面我们看下这个java包装的实现。

package com.vonchenchen.jnitest;

import android.util.Log;

import static android.content.ContentValues.TAG;

/**
 * Created by lidechen on 6/5/17.
 */

public class JavaClassDemoWrapper 

    private static final String TAG = "JavaClassDemoWrapper";

    static 
        System.loadLibrary("demo");
    

    private int mCppObjWapper;

    public JavaClassDemoWrapper()
        mCppObjWapper = getCppObjWrapper();
    

    public void setTag(String tag)
        setTag(tag, mCppObjWapper);
    

    public String getTag()
        return getTag(mCppObjWapper);
    

    @Override
    protected void finalize() throws Throwable 

        try 

            Log.e(TAG, "finalize()");

            if (mCppObjWapper != 0) 
                release(mCppObjWapper);
                mCppObjWapper = 0;
            
        finally 
            super.finalize();
        
    

    //获取cpp对象指针
    public native int getCppObjWrapper();

    //调用cpp对象中对应的方法
    public native void setTag(String tag, int cppObjWapper);
    public native String getTag(int cppObjWapper);

    //释放cpp对象
    public native void release(int cppObjWapper);

这个类主要包含以下几个功能

1.getCppObjWrapper() 这个方法是一个jni接口,用来返回jni中创建的c++对象的指针,并由java类记录。

2.jni 中具体实现功能的方法,这些方法都需要传入c++对象的句柄,用来标识执行哪个对象的方法。

3.release()方法用来释放c++中的资源,该类实现finalize()方法,也就是在虚拟机gc的时候单独将这个类放入一个列表,分别执行这个方法,用来释放资源,这时我们就把c++中相关的内容一起释放。

jni接口实现

直接上代码

#include <jni.h>
/* Header for class com_vonchenchen_jnitest_JavaClassDemoWrapper */

#ifndef _Included_com_vonchenchen_jnitest_JavaClassDemoWrapper
#define _Included_com_vonchenchen_jnitest_JavaClassDemoWrapper

#include "CppClassDemo.h"

#include "log.h"

#ifdef __cplusplus
extern "C" 
#endif

//对象包装类 此处包含被包装的功能类CppClassDemo对象指针和ctx指针
// ctx用于保存一些中间变量
class CppClassWrapper
public:
    CppClassDemo *obj;
    void *ctx;
;

/*
 * Class:     com_vonchenchen_jnitest_JavaClassDemoWrapper
 * Method:    getCppObj
 * Signature: ()I
 *
 * JNIEXPORT jint JNICALL Java_com_vonchenchen_jnitest_JavaClassDemoWrapper_getCppObjWrapper
 * 修改返回值类型为CppClassWrapper *
 */
JNIEXPORT CppClassWrapper * JNICALL Java_com_vonchenchen_jnitest_JavaClassDemoWrapper_getCppObjWrapper
  (JNIEnv *env, jobject obj)

    CppClassWrapper *wrapper = new CppClassWrapper();
    wrapper->obj = new CppClassDemo();
    return wrapper;


/*
 * Class:     com_vonchenchen_jnitest_JavaClassDemoWrapper
 * Method:    setTag
 * Signature: (Ljava/lang/String;I)V
 */
JNIEXPORT void JNICALL Java_com_vonchenchen_jnitest_JavaClassDemoWrapper_setTag
        (JNIEnv *env, jobject obj, jstring tag, CppClassWrapper *wrapper)

    char *cTag = env->GetStringUTFChars(tag, JNI_FALSE);

    wrapper->obj->setTag(cTag);

    env->ReleaseStringUTFChars(tag, cTag);
    env->DeleteLocalRef(tag);


/*
 * Class:     com_vonchenchen_jnitest_JavaClassDemoWrapper
 * Method:    getTag
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_vonchenchen_jnitest_JavaClassDemoWrapper_getTag
  (JNIEnv *env, jobject obj, CppClassWrapper *wrapper)

    char *tag = wrapper->obj->getTag();
    return env->NewStringUTF(tag);



/*
 * Class:     com_vonchenchen_jnitest_JavaClassDemoWrapper
 * Method:    release
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_com_vonchenchen_jnitest_JavaClassDemoWrapper_release
        (JNIEnv *env, jobject obj, CppClassWrapper *wrapper)

    if(wrapper != NULL)
        wrapper->obj->release();
        delete wrapper;
        wrapper = NULL;
    


#ifdef __cplusplus

#endif
#endif

这里注意我们并没有直接创建需要调用的c++对象,而是把这个对象进行了一层包装,因为在实际开发中发现有许多中间变量需要存储,但是我们不能将这些变量定义为全局,否则多个java对象就会共用这些全局变量。这里使用CppClassWrapper封装,增加了一个void *ctx指针,我们可以根据需求定义其他存储结构,用ctx指针指向这些结构。

c++对象

#include "CppClassDemo.h"

char* CppClassDemo::getTag() 
    return mTag;


void CppClassDemo::setTag(char *tag) 

    if(mTag != NULL)
        free(mTag);
        mTag = NULL;
    
    int len = strlen(tag);
    mTag = (char *)malloc(sizeof(char) * len + 1);
    strcpy(mTag, tag);


void CppClassDemo::release() 
    if(mTag != NULL)
        free(mTag);
        mTag = NULL;
    

简单实现了一个字符串存储的功能。

调用示例

        JavaClassDemoWrapper wrapper1 = new JavaClassDemoWrapper();
        JavaClassDemoWrapper wrapper2 = new JavaClassDemoWrapper();
        JavaClassDemoWrapper wrapper3 = new JavaClassDemoWrapper();

        wrapper1.setTag("I am wrapper1");
        wrapper2.setTag("I am wrapper2");
        wrapper3.setTag("I am wrapper3");

        Log.i(TAG, "wrapper1 "+wrapper1.getTag());
        Log.i(TAG, "wrapper2 "+wrapper2.getTag());
        Log.i(TAG, "wrapper3 "+wrapper3.getTag());

可以发现此时我们定义的三个java对象中存储了不同的字符串。我们可以在release中打印标示信息,手动触发gc,可以发现在垃圾回收的时候release方法会被调用。

代码链接:http://download.csdn.net/detail/lidec/9861699

勘误

代码在一部分机器上运作直接崩溃,经过排查发现在32位的arm机器上可以正常运作,但是换成x86或者64位arm都会崩溃。这里打印地址我们会发现64位机器返回c++对象的地址数值比较大,会超出32位,也就是java的int类型不可以用来保存c++对象的地址。此处必须使用long类型接收c++对象的地址。

修改

在JavaClassDemoWrapper类中,需要做如下修改,创建对象后直接返回一个long类型,同时填入对象参数也是long类型

    //用long类型保存c++类对象的地址
    private long mCppObjWapper;
    ......

 //获取cpp对象指针
    public native long getCppObjWrapper();

    //调用cpp对象中对应的方法
    public native void setTag(String tag, long cppObjWapper);
    public native String getTag(long cppObjWapper);

    //释放cpp对象
    public native void release(long cppObjWapper);

jni文件也需要将对应的int型改为long型

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_vonchenchen_jnitest_JavaClassDemoWrapper */

#ifndef _Included_com_vonchenchen_jnitest_JavaClassDemoWrapper
#define _Included_com_vonchenchen_jnitest_JavaClassDemoWrapper

#include "CppClassDemo.h"

#include "log.h"

#ifdef __cplusplus
extern "C" 
#endif

//对象包装类 此处包含被包装的功能类CppClassDemo对象指针和ctx指针
// ctx用于保存一些中间变量
class CppClassWrapper
public:
    CppClassDemo *obj;
    void *ctx;
;

/*
 * Class:     com_vonchenchen_jnitest_JavaClassDemoWrapper
 * Method:    getCppObj
 * Signature: ()I
 *
 * JNIEXPORT jint JNICALL Java_com_vonchenchen_jnitest_JavaClassDemoWrapper_getCppObjWrapper
 * 修改返回值类型为CppClassWrapper *
 */
JNIEXPORT jlong JNICALL Java_com_vonchenchen_jnitest_JavaClassDemoWrapper_getCppObjWrapper
  (JNIEnv *env, jobject obj)

    CppClassWrapper *wrapper = new CppClassWrapper();
    wrapper->obj = new CppClassDemo();

    LOGE("getCppObjWrapper addr %p", wrapper);

    return wrapper;


/*
 * Class:     com_vonchenchen_jnitest_JavaClassDemoWrapper
 * Method:    setTag
 * Signature: (Ljava/lang/String;I)V
 */
JNIEXPORT void JNICALL Java_com_vonchenchen_jnitest_JavaClassDemoWrapper_setTag
        //(JNIEnv *env, jobject obj, jstring tag, CppClassWrapper *wrapper)
        (JNIEnv *env, jobject obj, jstring tag, long handle)

    LOGE("setTag addr %p", handle);

    //int real_handle = (int)handle;
    long real_handle = handle;
    CppClassWrapper *wrapper = real_handle;

    char *cTag = env->GetStringUTFChars(tag, JNI_FALSE);

    wrapper->obj->setTag(cTag);

    env->ReleaseStringUTFChars(tag, cTag);
    env->DeleteLocalRef(tag);


/*
 * Class:     com_vonchenchen_jnitest_JavaClassDemoWrapper
 * Method:    getTag
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_vonchenchen_jnitest_JavaClassDemoWrapper_getTag
  //(JNIEnv *env, jobject obj, CppClassWrapper *wrapper)
  (JNIEnv *env, jobject obj, long handle)

    //int real_handle = (int)handle;
    long real_handle = handle;
    CppClassWrapper *wrapper = real_handle;

    char *tag = wrapper->obj->getTag();
    return env->NewStringUTF(tag);



/*
 * Class:     com_vonchenchen_jnitest_JavaClassDemoWrapper
 * Method:    release
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_com_vonchenchen_jnitest_JavaClassDemoWrapper_release
        //(JNIEnv *env, jobject obj, CppClassWrapper *wrapper)
        (JNIEnv *env, jobject obj, long handle)

    //int real_handle = (int)handle;
    long real_handle = handle;
    CppClassWrapper *wrapper = real_handle;

    if(wrapper != NULL)
        wrapper->obj->release();
        delete wrapper;
        wrapper = NULL;
    


#ifdef __cplusplus

#endif
#endif

以上是关于NDK学习笔记:java类封装c++类的主要内容,如果未能解决你的问题,请参考以下文章

C++中的类与封装

java JDBC编程学习笔记

java JDBC编程学习笔记

java学习笔记:继承

C++ Primer 0x07 学习笔记

Java 学习笔记 - Spring工具类:FileCopyUtilsStreamUtils