Java如何调用C代码

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, "", "([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代码)