JNI的HelloWorld实战

1. Java文件生成头文件

先编写一个Java文件 HelloJNI.java

public class HelloJNI {

    static {
        System.loadLibrary("hello");
    }

    public native String sayHello();

}

接着生成 C/C++ 头文件 HelloJNI.h,
其中“ . ”是指当前目录(生成的头文件放在当前目录)

javac -h . HelloJNI.java

该命令会生成一个 HelloJNI.h,这个头文件描述了我们需要实现的函数

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

#ifndef _Included_com_example_hellojnidemo_HelloJNI
#define _Included_com_example_hellojnidemo_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_hellojnidemo_HelloJNI
 * Method:    sayHello
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_hellojnidemo_HelloJNI_sayHello
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
  1. 生成的函数中有两个参数:
  • JNIEnv:JNIEnv 内部提供了很多函数,方便我们进行 JNI 编程。

    C 代码中,JNIEnv 是指向 JNINativeInterface 结构的指针,为了访问任何一个 JNI 函数,该指针需要首先被解引用。因为 C 代码中的 JNI 函数不了解当前的 JNI 环境, JNIEnv
    实例应该作为第一个参数传递给每一个 JNI 函数调用调用者,调用格式如下:
    (*env)->NewStringUTF(env,"Hello from JNI !");

    在 C++代码中,JNIEnv实际上是 C++ 类实例,JNI 函数以成员函数的形式存在,因此 JNI 函数调用不要求 JNIEnv 实例作参数。在C++中,完成同样功能的调用代码格式如下:
    env->NewstringUTF ( "Hello from JNI ! ");

  • jobject: 指向 “this” 的 Java 对象
    如果 java 中的 native 函数是 static 的,那第二个参数是 jclass,代表了 java 中的 Class 类

  1. extern “C”
    告诉 C++ 编译器以 C 的方式来编译这个函数,以方便其他 C 程序链接和访问该函数。C 和 C++ 有着不同的命名协议,因为 C++ 支持函数重载,用了不同的命名协议来处理重载的函数。在 C 中函数是通过函数名来识别的,而在 C++ 中,由于存在函数的重载问题,函数的识别方式通过函数名,函数的返回类型,函数参数列表三者组合来完成的。因此两个相同的函数,经过C,C++编绎后会产生完全不同的名字。所以,如果把一个用 C 编绎器编绎的目标代码和一个用 C++ 编绎器编绎的目标代码进行链接,就会出现链接失败的错误。

  2. JNIEXPORT、JNICALL 两个宏在 linux 平台的定义如下:

//该声明的作用是保证在本动态库中声明的方法 , 能够在其他项目中可以被调用
#define JNIEXPORT  __attribute__ ((visibility ("default")))
//一个空定义
#define JNICALL

2. 调用

MainActivity

	HelloJNI helloJNI = new HelloJNI();
    String s = helloJNI.sayHello();
    Log.d("JNI", s);

3. 编写C文件,native的具体实现

新建HelloJNI.c

#include "com_example_hellojnidemo_HelloJNI.h"
#include <jni.h>
#include <stdio.h>

JNIEXPORT jstring JNICALL Java_com_example_hellojnidemo_HelloJNI_sayHello
        (JNIEnv *env, jobject obj) {
    return (*env)->NewStringUTF(env, "Hello From JNI");
}

4. 编译生成.so库

gcc -fpic -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libhello.so HelloJNI.c

-fpic:表示生成位置无关的代码。这样一来,产生的代码中就没有绝对地址了,全部使用相对地址,所以代码可以被加载器加载到内存的任意位置,都可以正确的执行。这是动态链接库所必需的。

-I:用于指定头文件的搜索路径。该参数后面跟着一个目录路径,编译器在编译源文件时会在指定的目录中查找头文件。

-I"$JAVA_HOME/include":表示包含JAVA的头文件。

-I"$JAVA_HOME/include/linux":表示包含JAVA在linux下的特定的头文件。

-shared:表示生成共享库。

-o 自定义 原文件:自定义生成可执行程序名称

5. 编写CMakeLists.txt

在app/下新建CMakeLists.txt

# 设置cmake最低版本
cmake_minimum_required(VERSION 3.4.1)

# add_library(库文件名称 STATIC/SHARED 文件) 
# 静态库的扩展名一般为 *.a 或 *.lib;动态库的扩展名一般为 *.so 或 *.dll
# 注意,库文件名称通常为libxxx.so,在这里只要写xxx即可
add_library(
        hello
        SHARED
        src/main/java/com/example/hellojnidemo/HelloJNI.c)

# find_library用于查找使用android ndk中系统库
# 查找log库,且重新别名为log-lib
find_library(
        log-lib
        log
)

# link_libraries 关联需链接的库
# 在linux中c/c++的编译一般都是用gcc来编译的,c/c++编译时会产生.o文件要通过make工具来把这些.o文件链接起来,
# 这样才能得一个可执行程序。所以.so在编译时要把所有库链接起来才能编
target_link_libraries(hello ${log-lib})

将其放入build编译的大环境中,这样代码才会生效,在app/build.gradle文件的android{}函数作用域中添加externalNativeBuild

externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }

最后运行程序!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值