NDK学习笔记:java类封装c++类_vonchenchen1的博客-程序员宅基地

技术标签: java  jni  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
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/lidec/article/details/72872037

智能推荐

java perf函数调用_google-perftools 分析JAVA 堆外内存_026后勤处的博客-程序员宅基地

原文转自:http://koven2049.iteye.com/blog/1142768,所有权利归原作者所有最近线上运行的Hbase发现分配了16g内存,但是实际使用了22g,堆外内存达到6g。感觉非常诡异。堆外内存用一般的工具很难查看,可以通过google-perftools来跟踪:http://code.google.com/p/google-perftools/downloads/list...

2019-9-25:渗透测试,基础学习,medusa爆破学习_aizhuwei6274的博客-程序员宅基地

Medusa(美杜莎)暴力破解使用一、简介medusa(美杜莎)是一个速度快,支持大规模并行,模块化,爆破登陆,可以同时对多个主机,用户或是密码执行强力测试,medusa和hydra一样,同样属于在线破解工具,不同的是,medusa的稳定性相较于hydra要好很多但是支持的模块相对于hydra少一些。二,语法参数medusha [-h host主机 或 -H file主机...

Appium进行多设备兼容性测试_测试-八戒的博客-程序员宅基地_appium udid

在日常测试过程中,产品的基础功能逻辑测试通过后,经常需要根据软件的特性进行分辨率或者系统的不同,进行多设备的兼容性测试。兼容性测试,不仅要兼容不同的系统,也要兼容不同的分辨率,例如安卓就有6.X、7.X、8.X、9.X等多个系统,分辨率也有720p、1080p等等。这些兼容性测试如果单纯靠人工进行的话,不仅测试人员会觉得枯燥无味,而且浪费时间和精力,花大把的时间去避免那千分之一的可能存在的错误。解决方法:利用appium进行多设备自动化测试本文主要介绍通过appium同时启动多设备(多设备启

AXIS2中OMElement和Java对象相互转换_jintonghuoya的博客-程序员宅基地

最近在使用AXIS2部署Web Service,关于如果使用axis2部署Web Service的方法不想在这里多说,网站有很多相关的文章,这里只想谈谈AXIOM对象与Java中的对象的转换方法。关于AXIOM的介绍,下面这个片段已经说得很明白了:AXIOMAXIs 对象模型 (AXIOM) 是一个 XML 对象模型,设计用于提高 XML 处理期间的内存使用率和性能,基于

longformer代码结构解读_唐僧爱吃唐僧肉的博客-程序员宅基地_longformer

Longformer模型难点解读_sliding_chunks_query_key_matmul函数之中的结构变换longformer在延伸maxlen的同时,结构上也存在着很多的难点,这里逐步分析出来。_sliding_chunks_query_key_matmul函数之中的结构变换这里最难懂的是这样的几句query_size = list(query.size())query_size[1] = query_size[1]*2-1query_stride = list(query.strid

java.lang.NumberFormatException: For input string: 错误及解决办法_许你常欢的博客-程序员宅基地

这种问题我遇到过两次不同的,一种是 String 转换为 int 型报错,另一种是 String 转换为 double 报错,其实是同一类型的错误,只是我运用的不同场景,忽略了本质。第一种、错误提示信息如下:Exception in thread "main" java.lang.NumberFormatException: For input string: "1 " at java.lang.NumberFormatException.forInputString(NumberFormatExce

随便推点

乐优商城: 域名端口配置_xiaoshitou_2015的博客-程序员宅基地_乐优设置域名

域名解析:1,通过SwitchHosts文件配置,实际修改的是系统的host文件2, 配置端口, 80端口为nginx监听,通过端口反向代理到对应的对应的微服务 server { listen 80; server_name manage.leyou.com; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Serve.

【Go语言踩坑系列(四)】字典_NoSayOvO的博客-程序员宅基地_go字典添加元素

声明本系列文章并不会停留在Go语言的语法层面,更关注语言特性、学习和使用中出现的问题以及引起的一些思考。要点本文关注Go语言map相关的语言特性。map初始化与内存分配首先,必须给map分配内存空间之后,才可以往map中添加元素:func main() { var m map[int]int // 使用var语法声明一个map,不会分配内存 m[1] = 1 // 报错:assig...

ADO连接数据库字符串大全_zhuanghe_xing的博客-程序员宅基地_ado连接数据库字符串

<br />  在VB环境中用ADO方式连接数据库的字符串格式,其中包括ODBC DSN/DSN-Less,OLE DB Providers<br />第一部分:DSN连接方式<br />ODBC DSN Connections DSN<br />File DSN<br />第二部分:ODBC连接方式<br />ODBC Driver for Access<br />ODBC Driver for dBASE<br />ODBC Driver for Excel<br />ODBC Driver for

kubernetes Service(SVC)几种4层代理的不同用法,ClusterIP、Headless、NodePort、LoadBalancer_ 清欢渡.的博客-程序员宅基地

kubernetes Service(SVC)个人学习记录kubernetes Service(SVC)service 概念service 类型SVC 访问流程组件VIP 和 Service 代理代理模式分类Ⅰ、userspace 代理模式Ⅱ、Iptables 代理模式Ⅲ、ipvs 代理模式ipvs 对比 iptablesCluster IP( service 的暴露IP,提供内部负载均衡 )Headless Service(DNS解析)NodePort (将内部暴露外部)LoadBalancer(从内部

如何在当前目录下启动python解释器,导入相应函数_Lambert_robot的博客-程序员宅基地

如何在当前目录下启动python解释器,导入相应函数在初学定义函数中,看到这个教程link。其中提到将my_abs()函数定义保存为abstest.py文件,可以在该文件的当前目录下启动Python解释器,用from abstest import my_abs来导入my_abs()函数,注意abstest是文件名。这一步如何在当前目录下启动python解释器呢?将定义函数的.py文件保存在p...

SpringMVC访问项目名配置默认访问controller_freelk的博客-程序员宅基地

假设项目首页就需要从数据库获取数据并展示,怎么配置默认访问的controller。从Struts2转到SpringMVC过程中,产生了一个疑问,怎么像Struts2那样,通过配置default-action来确定项目默认访问的action。现在发现有两种方法:首先看controller代码,这块代码是不变的(service层和dao层代码没有影响,这里就不写了)@Control

推荐文章

热门文章

相关标签