美文网首页
Java如何调用C代码

Java如何调用C代码

作者: BinaryBang | 来源:发表于2021-04-18 08:55 被阅读0次

    Preface

    image-20210417234903849.png

    在用安卓处理音视频开发时,往往需要我们调用已有的成熟的用C/C++语言编写的库,比如FFmpeg,LAME等.这就牵涉到如何用Java调用C语言编写的库.

    本文处理的是最简单的情形,简单调用一个C文件里的函数.

    想写这篇文章很久了,但是之前遇到了一个坑,今天才万幸走出来.所以赶紧记录一下.

    1 Java调用C代码的整体流程

    Java调用C代码是通过JNI(JavaNativeInterface)这个手段来实现的,具体流程见下图:


    image-20210418001615813.png

    接下来我们用一个实例来说明如何实现Java调用C代码

    2 准备工作

    • 确认开发环境

    小编这里使用的是

    MacOS 11.0.1

    Android Stuidio 4.1.2

    Javac 的版本是14.0.1,这个很重要,影响到了第2步生成jni文件.之前参考帖子上用的都是javah命令,但是在小编这里就是不成功,可能就是版本问题.

    • 明确两个重要工具的目录

    ndk-build程序目录:

    /Users/gikkiares/Library/Android/sdk/ndk/20.0.5594570/ndk-build
    

    javac目录

    /usr/bin/javac
    

    如果在使用命令时,提示command not found:就说明要么工具没有安装,要么安装了,但是没有加入到环境变量.

    如果是前者,重新安装对应工具.

    如果是后者,将工具的目录加入到环境变量,或者使用命令的全路径.

    • 创建一个新的Android项目

    我们在该项目中,实现用java调用c代码.

    3 Java调用C代码示例

    3.1 编写java桥接类

    为了简单起见,我们新建一个CManager.java

    CManager类负责两个事情:

    1,对java层,提供了java形式的接口

    2,实现的方式为c语言.

    该文件内容为:

    package com.tinywind.gajdemo.module.cmanager;
    
    public class CManager {
        public static CManager sharedInstance = new CManager();
        public native String getMessageFromC();
        public native int sum(int a, int b);
    }
    

    我们注意到以下几点:

    • 我们使用了单例模式.
    • 定义了两个用native关键字修饰的方法.

    1.使用native关键字,表示我们实现该方法的语言不是java而是c/c++.

    2,getMessageFromC,简单地用c语言返回一个字符串

    3,sum函数,用c语言实现两个数相加.

    • 我们只声明了函数,并没有实现.

    3.2 生成jni风格的头文件.

    小编之前一直卡在这一步.一直在尝试用javah命令生成,单总是提示找不到类.

    估计可能和小编用的javac版本是14.0.1有关.

    总之,错误的方式成千上万,正确的方式只有那么一种,我们记住正确的就好了,错误的就让他随风而去吧~

    //切换到CManager.java所在的目录
    cd ${ProjectPath}/app/src/main/java/com/tinywind/gajdemo/module/cmanager
    //-h .指定了在当前目录中输出jni风格的头文件
    javac CManager.java -h .
    

    这一步完成之后,我们得到了一个.class文件和.h文件


    image-20210417173817694.png

    .class文件对我们来说没用,可以直接删除.

    这个很长的h文件,就是我们的jhi头文件.

    3.3 编写C文件

    新建目录

    src/main/jni
    

    需要将jni的头文件移动到这个目录中.

    然后新建一个文件CManager.c,内容为:

    #include "com_tinywind_gajdemo_module_cmanager_CManager.h"
    
    
        //C字符串转java字符串
        jstring cstringToJstring(JNIEnv* env, const char* pStr) {
            int        strLen    = strlen(pStr);
            jclass     cls_string   = (*env)->FindClass(env, "java/lang/String");
            // 获取java String类方法String(byte[],String)的构造器,用于将本地byte[]数组转换为一个新String
            jmethodID  methodId  = (*env)->GetMethodID(env, cls_string, "<init>", "([BLjava/lang/String;)V");
            jbyteArray byteArray = (*env)->NewByteArray(env, strLen);
            jstring    encode    = (*env)->NewStringUTF(env, "utf-8");
    
            (*env)->SetByteArrayRegion(env, byteArray, 0, strLen, (jbyte*)pStr);
    
            return (jstring)(*env)->NewObject(env, cls_string, methodId, byteArray, encode);
        }
    
    JNIEXPORT jstring JNICALL Java_com_tinywind_gajdemo_module_cmanager_CManager_getMessageFromC
      (JNIEnv * env, jobject obj) {
        char * str = "Hello,this is a message from c!";
        return cstringToJstring(env, str);
      }
    
      JNIEXPORT jint JNICALL Java_com_tinywind_gajdemo_module_cmanager_CManager_sum
        (JNIEnv * env, jobject obj, jint a, jint b) {
        return a + b;
        }
    
    

    需要注意以下几点:

    • c文件中要引入jni头文件#include "com_tinywind_gajdemo_module_cmanager_CManager.h"
    • 函数的原型从jni头文件中拷贝,但是要注意jni头文件中形参的名称是省略的,我们需要加上去.
    • c语言的字符串和jni的jstring是不同类型的需要转换.

    3.4 生成动态库

    在jni目录中新建Android.mk文件,目录看起来是这个样子:


    image-20210418005701072.png

    在Android.mk文件中输入以下内容:

    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE := libcmanager
    LOCAL_SRC_FILES := ./CManager.c
    include $(BUILD_SHARED_LIBRARY)
    
    • LOCAL_MODULE指定了so库的名字

    要注意的是,写得是libcmanager,但是后期导入时只需要写cmanager.

    • LOCAL_SRC_FILES指定了so库的源文件

    其他的项目暂时不太清楚

    然后执行以下命令:

    //然后切换到目录:
    ${ProjectPath}/app/src/main
    //编译c文件为so动态链接库
    ${NdkDir}/ndk-build
    
    image-20210418074249795.png

    编译成功之后,项目里在src/main目录下会多出libs目录,里面对应不同的架构,产生了对应的so库.

    3.5 加载动态链接库

    加载动态链接库最简单的方法,就是在main目录下创建jniLibs文件夹,然后将libs中目全部加入进去就额可以了.

    系统会自动加载jniLibs里的so动态库.

    3.6 调用Native方法

    最后,我们调用Native方法,只要把桥接的Java类CManager当做普通的java类去调用就可以了:

            String string = CManager.sharedInstance.getMessageFromC();
            int sub = CManager.sharedInstance.sum(1,1);
    

    我们断点查看执行结果

    image-20210418075408617.png

    我们可以看到,c的世界向java的世界发来了贺电"Hello,this is a message from c",然后也友好地教导了我们1+1=2.

    这就是java调用c代码的最简单模型.

    4 总结

    • 关于Markdown的一个问题

    希望markdown有一个没有级别的标题,现在是要么1级标题,2级标题.

    我希望能有一个没有级别的标题.

    • 关于模板

    模板,就是套路.不管是在编程界还是社交界,都是需要各种模板.

    模板够多,才能玩的六.

    所以本文牵涉的内容很简单,但是也是一个很重要的模板

    相关文章

      网友评论

          本文标题:Java如何调用C代码

          本文链接:https://www.haomeiwen.com/subject/afpmlltx.html