Rockchip系列之CAN 新增framework系统jni接口访问(2)

简介: Rockchip系列之CAN 新增framework系统jni接口访问(2)

Rockchip系列之深度分析CAN接口系列(1)_一歲抬頭的博客-CSDN博客

Rockchip系列之CAN 新增framework系统jni接口访问(2)-CSDN博客

Rockchip系列之CAN 新增framework封装service+manager访问(3)-CSDN博客

Rockchip系列之CAN APP测试应用实现(4)_一歲抬頭的博客-CSDN博客

Rockchip CAN 部分波特率收发不正常解决思路_一歲抬頭的博客-CSDN博客

Android JNI与CAN通信遇到的问题总结_android can通信-CSDN博客

Android 内核关闭CAN 串口设备回显功能_一歲抬頭的博客-CSDN博客

在这篇博客中,将介绍如何在Android系统源码中增加JNI来实现CAN(Controller Area Network)通信的功能。

JNI层的结构和组成

为了实现CAN通信的功能,需要在JNI层编写一些本地方法,这些方法会被Java层的CanService类调用。需要完成以下几个步骤:

  • 在Java层定义本地方法,使用native关键字标识 (下一篇讲)
  • 在C/C++层实现本地方法,使用JNI提供的数据结构和函数来操作Java对象和本地资源
  • 在C/C++层注册本地方法,使用jniRegisterNativeMethods函数将本地方法和Java方法关联起来

下面来看一下具体的代码实现。

Java层的代码

在frameworks/base/services/core/jni/com_android_server_CanService.cpp文件中定义了CanService类,这个类是一个服务类,它提供了一些公开的方法供其他应用程序调用。其中有五个本地方法,分别是:

  • init:初始化JNI层,目前没有具体的实现(没需求 留着)
  • native_dev_openCan:打开指定的CAN设备,并返回一个文件描述符
  • native_dev_closeCan:关闭指定的文件描述符
  • native_dev_sendCan:向指定的文件描述符发送一帧CAN数据
  • native_dev_receiveCan:从指定的文件描述符接收一帧CAN数据,并返回一个长整型数组

这些本地方法都是私有的,只能在CanService类内部调用。它们的具体实现在C/C++层完成。看一下其中一个本地方法的定义:

private native int native_dev_openCan(String canx);

这个方法接受一个字符串参数canx,表示要打开的CAN设备的名称,例如"can0"或"can1"。它返回一个整型值,表示打开设备后得到的文件描述符,如果打开失败,则返回负数。

C/C++层的代码

在C/C++层实现了Java层定义的本地方法,以及注册本地方法的函数。需要包含以下几个头文件:

#include <jni.h> // JNI头文件,包含了JNI相关的数据结构和函数
#include <nativehelper/JNIHelp.h> // JNI辅助头文件,包含了jniRegisterNativeMethods函数
#include <android_runtime/AndroidRuntime.h> // Android运行时头文件,包含了Android相关的数据结构和函数
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h> // CAN相关头文件,包含了CAN相关的数据结构和常量
 
//#define LOG_TAG "com_android_server_CanService" //这玩意不要定义 有冲突
#include <utils/Log.h> // 日志头文件,包含了ALOGI等日志函数
//使用了android命名空间,以便于使用Android相关的类和函数:
 
namespace android {
// ...
}

看一下其中一个本地方法的实现:

static jint dev_openCan(JNIEnv *env, jobject thiz, jstring canx) {
    ALOGI("dev_openCan");
    int fd;
    struct ifreq ifr;
    struct sockaddr_can addr;
 
    /* open socket */
    if ((fd = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
        return -1;
    }
 
    const char *str = env->GetStringUTFChars(canx, 0);
    strcpy(ifr.ifr_name, str);
    ioctl(fd, SIOCGIFINDEX, &ifr);
 
    memset(&addr, 0, sizeof(addr));
    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;
 
    if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        return -2;
    }
 
    return fd;
}

这个函数实现了打开CAN设备的功能,它的逻辑如下:

  • 首先加一条打印日志信息,表示进入了本地方法(调试打印
  • 然后创建一个原始套接字(socket),用于与CAN设备通信,如果创建失败,则返回-1
  • 接着获取Java层传入的字符串参数canx,并将其转换为C风格的字符串str,使用env->GetStringUTFChars函数
  • 然后将str复制到ifr.ifr_name中,并使用ioctl函数获取CAN设备的索引号ifr.ifr_ifindex
  • 接着初始化一个sockaddr_can结构体addr,并设置其can_family为AF_CAN,can_ifindex为ifr.ifr_ifindex
  • 最后将套接字fd绑定到addr上,如果绑定失败,则返回-2
  • 如果一切顺利,则返回fd作为文件描述符

注意,在使用完env->GetStringUTFChars函数返回的字符串后,应该调用env->ReleaseStringUTFChars函数来释放其内存,以避免内存泄漏。在这里,没有这样做,是因为知道str只是一个临时变量,不会被其他地方引用。

其他四个本地方法的实现逻辑类似。

注册本地方法

为了让Java层能够调用本地方法,需要在C/C++层注册本地方法。使用jniRegisterNativeMethods函数来完成这个任务。这个函数需要三个参数:

  • env:指向JNIEnv结构体的指针
  • className:要注册本地方法的Java类的完整名称
  • gMethods:一个JNINativeMethod结构体数组,表示要注册的本地方法及其签名和函数指针

定义了一个JNINativeMethod结构体数组gMethods,包含了五个元素,分别对应于五个本地方法:

static const JNINativeMethod gMethods[] = {
        /* name, signature, funcPtr */
        { "init", "()V", (void*) init },
        { "native_dev_openCan", "(Ljava/lang/String;)I", (void*) dev_openCan },
        { "native_dev_closeCan", "(I)I", (void*) dev_closeCan },
        { "native_dev_sendCan", "(IJJJI[I)I", (void*) dev_sendCan },
        { "native_dev_receiveCan", "(I)[J", (void*) dev_receiveCan },
};

然后,定义了一个函数register_android_server_CanService,用于调用jniRegisterNativeMethods函数:

int register_android_server_CanService(JNIEnv* env)
{
     return jniRegisterNativeMethods(env,
            "com/android/server/CanService",
            gMethods,
            NELEM(gMethods));
}

最后在framework native中注册这个JNI , 基操 就不赘述。

我验证过后的完整代码:

#include <jni.h>
#include <nativehelper/JNIHelp.h>
#include <android_runtime/AndroidRuntime.h>
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
 
//#define LOG_TAG "com_android_server_CanService"
#include <utils/Log.h>
 
namespace android {
 
static void init(JNIEnv *env, jobject thiz) {
    ALOGI("init");
}
 
static jint dev_openCan(JNIEnv *env, jobject thiz, jstring canx) {
    ALOGI("dev_openCan");
    int fd;
    struct ifreq ifr;
    struct sockaddr_can addr;
 
    /* open socket */
    if ((fd = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
        return -1;
    }
 
    const char *str = env->GetStringUTFChars(canx, 0);
    strcpy(ifr.ifr_name, str);
    ioctl(fd, SIOCGIFINDEX, &ifr);
 
    memset(&addr, 0, sizeof(addr));
    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;
 
    if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        return -2;
    }
 
    return fd;
}
 
static jint dev_closeCan(JNIEnv *env, jobject thiz, jint fd) {
    ALOGI("dev_closeCan");
    return close(fd);
}
 
static jint dev_sendCan(JNIEnv *env, jobject thiz, jint fd, jlong canid,
        jlong eff, jlong rtr, jint len, jintArray data) {
    ALOGI("dev_sendCan");
    struct can_frame frame;
    frame.can_id = (eff << 31) | (rtr << 30) | canid;
    frame.can_dlc = len;
    jint *pdata = env->GetIntArrayElements(data, 0);
    for(uint8_t i = 0; i < len; i++) {
        frame.data[i] = pdata[i] & 0xFF;
    }
    int ret =  write(fd, &frame, sizeof(struct can_frame));
    env->ReleaseIntArrayElements(data, pdata, 0);
    return  ret;
}
 
static jlongArray dev_receiveCan(JNIEnv *env, jobject thiz,
        jint fd) {
    ALOGI("dev_receiveCan");
    jlongArray ret;
    ret = env->NewLongArray(12);
    struct can_frame frame;
    read(fd, &frame, sizeof(struct can_frame));
    int64_t data[12];
    data[0] = frame.can_id & 0x1FFFFFFF;
    data[1] = (frame.can_id >> 31) & 0x1;
    data[2] = (frame.can_id >> 30) & 0x1;
    data[3] = frame.can_dlc;
    for(uint8_t i = 0; i < frame.can_dlc; i++) {
        data[i + 4] = frame.data[i];
    }
    env->SetLongArrayRegion(ret, 0, 12, data);
    return ret;
}
 
static const JNINativeMethod gMethods[] = {
        /* name, signature, funcPtr */
        { "init", "()V", (void*) init },
        { "native_dev_openCan", "(Ljava/lang/String;)I", (void*) dev_openCan },
        { "native_dev_closeCan", "(I)I", (void*) dev_closeCan },
        { "native_dev_sendCan", "(IJJJI[I)I", (void*) dev_sendCan },
        { "native_dev_receiveCan", "(I)[J", (void*) dev_receiveCan },
};
 
int register_android_server_CanService(JNIEnv* env)
{
     return jniRegisterNativeMethods(env,
            "com/android/server/CanService",
            gMethods,
            NELEM(gMethods));
}
 
};

通过这篇文章,我介绍了如何在Rockchip系列芯片中(其他平台也一样 我只是在rockchip验证而已),使用JNI来实现CAN 通讯。如果你有任何问题或者想法,欢迎在评论区留言 三连支持 。

相关文章
|
1月前
|
存储 Java Android开发
Rockchip系列之UART 新增framework系统jni+service接口访问(2)
Rockchip系列之UART 新增framework系统jni+service接口访问(2)
31 1
|
1月前
|
Java Android开发
Rockchip系列之VendorStorage 新增framework系统jni+service接口访问(3)
Rockchip系列之VendorStorage 新增framework系统jni+service接口访问(3)
32 0
|
1月前
|
安全 Java Android开发
Rockchip系列之客制化GPIO接口jni+service接口访问(4)
Rockchip系列之客制化GPIO接口jni+service接口访问(4)
26 0
|
1月前
|
存储 传感器 JSON
Rockchip系列之VendorStorage 新增framework封装VendorStorageManager访问(4)
Rockchip系列之VendorStorage 新增framework封装VendorStorageManager访问(4)
31 0
|
8月前
|
C# Windows
[记录]c#.net framework 4.5调用运行时库
[记录]c#.net framework 4.5调用运行时库
|
1月前
|
Java Shell Android开发
Rockchip系列之CAN 新增framework封装service+manager访问(3)
Rockchip系列之CAN 新增framework封装service+manager访问(3)
26 2
|
1月前
|
Java API Android开发
[NDK/JNI系列04] JNI接口方法表、基础API与异常API
[NDK/JNI系列04] JNI接口方法表、基础API与异常API
25 0
|
JSON 物联网 网络性能优化
NET Core 跨平台物联网开发 SDK属性、方法、委托、类(四)
NET Core 跨平台物联网开发 SDK属性、方法、委托、类(四)
253 0
|
编译器 C语言
QT应用编程: 快速创建指定大小的空文件
QT应用编程: 快速创建指定大小的空文件
372 0