Qt应用开发(安卓篇)——调用ioctl、socket等C函数

本文介绍了在QtforAndroid中由于权限限制无法直接访问底层设备,通过JNI接口间接调用系统函数和设备驱动的方法。详细展示了JNI接口的编写、规范以及如何在Qt层和Java层之间调用。同时提到了JNI接口对跨平台性的影响和日志调试技巧。
摘要由CSDN通过智能技术生成

一、前言

        在 Qt for Android 中没办法像在嵌入式linux中一样直接使用 ioctl 等底层函数,这是因为因为 Android 平台的安全性权限限制

        在 Android 中,访问设备硬件和系统资源需要特定的权限,并且需要通过 Android 系统提供的 API 来进行。Android 平台为了保障系统的安全性和稳定性,限制了应用程序对底层硬件和系统的直接访问。

        Qt for Android 是建立在 Android NDK 和 Java 层之上的,它提供了一种跨平台的开发框架,允许开发者使用 C++ 和 Qt API 来开发 Android 应用程序。但是,由于 Android 平台的限制,Qt for Android 也受到了 Android 平台的限制,无法直接访问底层设备或调用底层系统函数。

        我们需要通过 Java 层的 JNI 接口来间接访问,通过 JNI 接口调用底层的系统函数或设备驱动程序,下面我们通过一个socketcan的调用实例才讲解。

二、编写 JNI 接口

        在Android路径下,新建一个jni文件夹,新建文件socketcan_native.c,部分代码内容如下:

#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <net/if.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <fcntl.h>
#include <string.h>

#define	STATUS_OK					0
#define STATUS_ERR					-1

static int sock_fd;
static int m_isopen;

void sockcan_close()
{
    close(sock_fd);
    system(ip_cmd_can_close);
    m_isopen = STATUS_ERR;
}
int sockcan_open(int bitrate)
{
    //创建套接口
    sock_fd = socket(AF_CAN,SOCK_RAW,CAN_RAW);
    if(sock_fd < 0)
    {
        return STATUS_ERR;
    }
    //绑定can0设备与套接口
    struct ifreq ifr;
    struct sockaddr_can addr;

    strcpy(ifr.ifr_name,"can0");
    ioctl(sock_fd,SIOCGIFINDEX,&ifr);

    ifr.ifr_ifindex = if_nametoindex(ifr.ifr_name);
    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;
    if(bind(sock_fd,(struct sockaddr *)&addr,sizeof(addr))<0)
    {
        perror("bind error!\n");
        return STATUS_ERR;
    }
    //配置
    int flags;
    flags = fcntl(sock_fd,F_GETFL,0);
    // flags |= O_NONBLOCK;//非阻塞
    flags &= ~O_NONBLOCK;//阻塞
    fcntl(sock_fd,F_SETFL,flags);

    m_isopen = STATUS_OK;
    return STATUS_OK;
}

JNIEXPORT jint JNICALL
Java_com_example_socketcan_SocketCANJNI_socketCanOpen(JNIEnv *env, jobject thiz, jint baudrate)
{
    return sockcan_open(baudrate);
}

JNIEXPORT jint JNICALL
Java_com_example_socketcan_SocketCANJNI_socketCanWrite
(JNIEnv *env, jobject thiz, jint canId,jint dataLen,jint externFlag,jint remoteFlag,jbyteArray datas)
{
     if(sock_fd <= 0)
        return -1;
     if(dataLen > 8)
        return -2;
     //获取实例的变量array的值
     int nArrLen = (*env)->GetArrayLength(env,datas);
     char *chArr = (char*)(*env)->GetByteArrayElements(env,datas, 0);
     struct can_frame txframe;
     memcpy(txframe.data, chArr, nArrLen);
     txframe.can_id = canId;
     if(externFlag)
        txframe.can_id |= CAN_EFF_FLAG;
     if(remoteFlag)
        txframe.can_id |= CAN_RTR_FLAG;
     txframe.can_dlc = dataLen;
     return write(sock_fd, &txframe, sizeof(struct can_frame));
}

2.1. jni名称规范

        jni接口的名称比如Java_com_example_socketcan_SocketCANJNI_socketCanWrite,这里是需要规范的,我们接下来会用两种方法调用,第一种传入JNIEnv指针直接调用,这种情况接口名称无所谓,第二种我们把jni接口编译成so,通过java层调用,这时候名称就很重要的,Java本地方法名以Java_开头,后面跟着类名、下划线和方法名,也就是Java_包名_类名_方法名。方法名要驼峰写法,比如setBaud(),不能写set_baud()。

2.2. 封装jni接口

        在Android/jni文件夹下,新建文件Android.mk,内容如下:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := socketcan_native
LOCAL_SRC_FILES := socketcan_native.c

include $(BUILD_SHARED_LIBRARY)

        然后在该目录使用ndk对其进行打包生成so文件,在libs文件夹下,会生成四个不同环境下的的so文件。

5625fc6983dd4a5b99fb3600a07984be.png

 a4e6576586c54f4bba493d17270690ce.png

60f5cd37710c45fdb5b1ecf78e08673b.png

 858f5940bab048dd90563643ecc09d6a.png

986cfd8307964db8838e8c742541e157.png

三、直接调用Jni接口

        Qt侧声明接口,直接调用!!!

extern "C" JNIEXPORT jint JNICALL
JJava_com_example_socketcan_SocketCANJNI_socketCanOpen
(JNIEnv *env, jobject thiz, jint baudrate);

extern "C" JNIEXPORT jint JNICALL
Java_com_example_socketcan_SocketCANJNI_socketCanWrite
(JNIEnv *env, jobject thiz, jint canId,jint dataLen,jint externFlag,jint remoteFlag,jbyteArray datas);
jint attachResult = QAndroidJniEnvironment::javaVM()->AttachCurrentThread(reinterpret_cast<JNIEnv**>(&env), NULL);
if (attachResult != JNI_OK) {
    qDebug() << "Failed to attach current thread to JVM";
}
JJava_com_example_socketcan_SocketCANJNI_socketCanOpen(env, NULL, 500000);

         这种方法不需要使用jni编译的so库,等于通过jni调用的c代码,使用JNI接口可能会降低Qt应用程序的跨平台性,因为它会依赖于Java虚拟机的存在和正确配置,并且如果发生不正确的内存管理可能会导致内存泄漏或者内存错误。

四:Java层调用

        右键项目新建一个SocketCANJNI.java文件

         按示例我们需要把java文件要放在android/src/com/example/socketcan文件夹下,然后我们编辑文件,内容如下

package com.tbbpower.android;
import org.qtproject.qt5.android.bindings.QtActivity;
/***********************************
jint 对应 Java 层的 int
jboolean 对应 Java 层的 boolean
jbyte 对应 Java 层的 byte
jchar 对应 Java 层的 char
jshort 对应 Java 层的 short
jlong 对应 Java 层的 long
jfloat 对应 Java 层的 float
jdouble 对应 Java 层的 double
jobject 对应 Java 层的 Object 类型
jstring 对应 Java 层的 String 类型
jarray 对应 Java 层的数组类型(例如 jintArray 对应 int[])
***********************************/
public class SocketCANJNI{
    static {
            System.loadLibrary("socketcan_native");
        }
        public static native int open(int baudrate);
        public static native int close();
        public static native int setFilter(int extFrame, int startCanId,int endCanId);
        public static native int write(int canId,int dataLen,int externFlag,int remoteFlag,byte[] datas);
        public static native int read(int[] paramDatas,byte[] datas);
}

        在示例中,备注了jni接口的方法名和java层的方法的类型对应

        在示例中,java层的方法名和jni的方法名一定要对应,否则会崩溃哦。

        libsocketcan_native.so文件要放在android/libs文件夹下一起编译。

        QT层调用:

QAndroidJniObject::callStaticMethod<jint>("com/tbbpower/android/SocketCANJNI",
                                                                "open",
                                                                "(I)I",
                                                                9600);

        几个参数都简单,解释一下第三个参数(I)I,在Qt中,该方法用于调用Java类中的静态方法。第三个参数是一个字符串,它规定了被调用的 Java 方法的签名。Java 方法的签名描述了方法的参数类型和返回类型,签名的格式如下:

(参数类型1, 参数类型2, ...)返回类型

其中,参数类型和返回类型使用特定的字母代码表示,如下所示:

  • Z:boolean
  • B:byte
  • C:char
  • S:short
  • I:int
  • J:long
  • F:float
  • D:double
  • V:void
  • [:数组(后跟数组元素类型的签名,如[I表示int[]

对于引用类型,使用L作为前缀L后面跟着类的完全限定名,以分号结尾,如Ljava/lang/String;表示String类型。

举例来说,如果你要调用一个方法,它接受一个整数和一个字符串作为参数,并返回一个整数,你的方法签名将如下所示:

(ILjava/lang/String;)I

        其中,I表示整数,Ljava/lang/String;表示字符串

五:接口调试

        在 JNI 中,无法直接使用 printf 打印输出,我们需要使用Android NDK 提供的控制台输出函数进行log打印,可以输出多种类型的日子,常用的有debug\info\warn\error。

#include <jni.h>
#include <android/log.h>

JNIEXPORT void JNICALL
Java_com_example_package_ClassName_methodName(JNIEnv *env, jobject thiz) {
    // 输出日志信息
    __android_log_print(ANDROID_LOG_INFO, "Tag", "Your log message");
}

         而Java层,我们要使用java自带的打印函数进行打印,方法类似。

import android.util.Log;
public class USBReceiver extends BroadcastReceiver {

    private static final String TAG = "USBReceiver";
    @Override
    public void onReceive(Context context, Intent intent) {
        try
        {
            Log.d(TAG, "onReceive");
        }
        catch (Exception ex)
        {
            Log.d(TAG, "onReceive-failed");
        }
    }
}
  • 14
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

波塞冬~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值