Java调用C/C++ 动态库dll操作串口实例

大家好,今天分享的是使用C/C++编写一个读取串口数据的代码,然后将其编译成Windows下的动态链接库(.dll文件),然后写一个简单的java demo来调用C/C++接口。

众所周知,java开发项目会比较方便,尤其是在一些大型项目中,java开发效率会比较高,但是一些底层的东西和一些追求效率的东西,依然会倾向使用C/C++,这是他们不可替代的优势。所以有时会需要两者混合起来,C/C++完成一部分较底层的功能,提供接口给java调用。

由于本人主要是从事嵌入式相关,对java也不是很熟,所以今天主要是把整个流程过一遍,把整个流程打通,搞清楚如何制作动态库,如何被java调用即可。

还有就是今天所有的操作都是在命令行中完成,不会使用任何IDE,这也符合我们嵌入式开发的习惯,能用命令行就没必要去安装臃肿的IDE软件,所以大家需要先在自己的DOS窗口中安装好必要的工具,g++用于编译c++代码,安装java的jdk以提供java环境(如果你需要接入电商系统,API接口联系V:ling18306660726),另外把nmake路径添加到环境变量中就可以使用Makefile了。

一、编写cpp功能函数

这一部分就是具体的功能实现,比如在本次中,我们需要读取串口数据,那么至少涉及四个接口:打开串口,设置波特率等参数,读取数据,关闭串口。我们需要使用c++代码把这四个接口的具体实现写出来,新建一个文件夹,用于存放文件,在里面新建一个dllApi.cpp和dllApi.h文件。

dllApi.h:

#ifndef DLLAPI_H
#define DLLAPI_H
#include <WinSock2.h>
#include <Windows.h>
#include <process.h>
#include <iostream>
using namespace std;
class ComHelper  {
public:
    // bool Open(void);      //打开串口
    void DLL_API_Set(int baud);    //设置串口信息
    char* DLL_API_Read(char str[],int length);
    bool DLL_API_Close(void);
    bool DLL_API_OPEN(void);
};

复制

dllApi.cpp:

#include "dllApi.h"
HANDLE  hCom ;
bool ComHelper::DLL_API_OPEN(void)
{
    int num;
    const char* com="COM";
    char buf[100]={0};
    cout<<"请输入要打开的串口号,输入1打开COM1"<<endl;
    cin>>num;
    sprintf(buf,"%s%d",com,num);
    hCom=CreateFile(
//        "COM1",   // COM1
        buf,
        GENERIC_READ | GENERIC_WRITE ,  //允许读和写
        0 ,  //独占方式
        NULL ,
        OPEN_EXISTING ,  //打开而不是创建
        0 ,  //同步方式
        NULL 
    );
    if ( hCom == ( HANDLE ) - 1 )
    {
        cout<<"打开COM失败"<<endl;
        return  FALSE ;
    }
    return  TRUE ;
}

void ComHelper::DLL_API_Set(int baud=115200)
{
    SetupComm ( hCom , 1024 , 1024 ) ; //输入缓冲区和输出缓冲区的大小都是1024
    COMMTIMEOUTS TimeOuts ; 
    //设定读超时
    TimeOuts . ReadIntervalTimeout = 1000 ;
    TimeOuts . ReadTotalTimeoutMultiplier = 500 ;
    TimeOuts . ReadTotalTimeoutConstant = 5000 ; 

    //设定写超时
    TimeOuts . WriteTotalTimeoutMultiplier = 500 ;
    TimeOuts . WriteTotalTimeoutConstant = 100 ;
    SetCommTimeouts ( hCom , & TimeOuts ) ; //设置超时
    DCB dcb ;
    GetCommState ( hCom , & dcb ) ;
    dcb . BaudRate = baud ; //波特率为115200
    dcb . ByteSize = 8 ; //每个字节有8位
    dcb . Parity = NOPARITY ; //无奇偶校验位
    dcb . StopBits = ONESTOPBIT ; //一个停止位
    SetCommState ( hCom , & dcb ) ;
    PurgeComm ( hCom , PURGE_TXCLEAR | PURGE_RXCLEAR ) ;
}

char* ComHelper::DLL_API_Read(char str[],int length)
{
    DWORD wCount = (DWORD)length; //读取的字节数
    bool bReadStat;
    bReadStat=ReadFile(hCom,str,wCount,&wCount,NULL);
    if(!bReadStat)
    {
        cout<<"Not Ready!"<<endl;
    }
//    cout<<"Read success"<<endl;
//    cout<<str<<endl;
    return str;
}

bool ComHelper::DLL_API_Close(void)
{
    bool result = CloseHandle(hCom);
    if(result)
    {
        return true;
    }
    else {
        return false;
    }
}

复制

这里面主要是涉及两个很重要的函数,CreateFile 和 ReadFile 函数,这两个函数是Windows下的API,可以直接调用,关于具体的函数功能及用法,这里暂时不讨论,其实和linux下的驱动是很类似的。

二、编写一个java的demo

Java2cpp.java :

public class Java2cpp
{
static
{
System.loadLibrary("javaCallcpp");
}

public native boolean DLL_OPEN();
public native void DLL_Set(int baud);    //设置串口信息
public native String DLL_Read(char str[],int length);
public native boolean DLL_Close();

public static void main(String args[])
{

    System.out.println("code test....");
    boolean ret;
    char buf[]={0};
    String str;
    Java2cpp com = new Java2cpp();
    ret=com.DLL_OPEN();
    if(!ret)
    {
        System.out.println("打开串口失败");
        return;
    }
    System.out.println("打开串口成功");
    com.DLL_Set(115200);
    while(true)
    {
        str=com.DLL_Read(buf, 100);
        System.out.println(str);
    }
}
}

复制

这里主要注意两个点,一个是使用System.loadLibrary("javaCallcpp");导入了一个库,这个库的名字是javaCallcpp 也就是说待会儿我们要生成一个javaCallcpp.dll的文件。第二点是public native boolean DLL_OPEN();等几个API。

使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用。使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用。这些函数的实现体在DLL中,JDK的源代码中并不包含,你应该是看不到的。对于不同的平台它们也是不同的。这也是java的底层机制,实际上java就是在不同的平台上调用不同的native方法实现对操作系统的访问的。

这个时候还没有dll文件,因此有了第三步:

三、生成dll文件

首先进入到文件目录,在命令行中使用

javac -h ./ Java2cpp.java

复制

命令生成Java2cpp.h文件,这个头文件是不可修改的,大概长这样

接下来新建一个Java2cpp.cpp文件,在这个文件中调用第一步中实现的接口

Java2cpp.cpp :

#include "Java2cpp.h"
#include "dllApi.h"
JNIEXPORT jboolean JNICALL Java_Java2cpp_DLL_1OPEN
  (JNIEnv *, jobject)
{
    ComHelper com;
    bool var=0;
    var=com.DLL_API_OPEN();
    return var;
}

JNIEXPORT void JNICALL Java_Java2cpp_DLL_1Set
  (JNIEnv *, jobject, jint baud)
{
    ComHelper com;
    com.DLL_API_Set(115200);
    return;
}

JNIEXPORT jstring JNICALL Java_Java2cpp_DLL_1Read
  (JNIEnv * env, jobject, jcharArray ay, jint)
{
    char array[1024];
    char* buf=array;
    int len=strlen(buf);
    
    jstring ret;
    ComHelper com;
    com.DLL_API_Read(array,100);

    //将char[] 转化为jstring
    //定义java String类 strClass
     jclass strClass = (env)->FindClass("Ljava/lang/String;");
     //获取java String类方法String(byte[],String)的构造器,用于将本地byte[]数组转换为一个新String
    jmethodID ctorID = (env)->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
    //建立byte数组
    jbyteArray bytes = (env)->NewByteArray((jsize)strlen(buf));
    //将char* 转换为byte数组
    (env)->SetByteArrayRegion(bytes, 0, (jsize)strlen(buf), (jbyte*)buf);
    //设置String, 保存语言类型,用于byte数组转换至String时的参数
    jstring encoding = (env)->NewStringUTF("gbk"); 
    //将byte数组转换为java String,并输出
    ret= (jstring)(env)->NewObject(strClass, ctorID, bytes, encoding);
    return ret;
}

JNIEXPORT jboolean JNICALL Java_Java2cpp_DLL_1Close
  (JNIEnv *, jobject)
{
    bool ret;
    ComHelper com;
    ret=com.DLL_API_Close();
    return ret;
}

复制

实际上你也可以直接在这里实现具体的功能代码,这样就省掉第一步了,不过为了一个分层的思想,更方便维护,还是不要省掉第一步比较好。

在这个文件中,使用#include "dllApi.h" 来调用第一步中的接口,然后这个文件是被java程序调用的,这里要稍微注意一下数据类型的转化。比如char[] 转化为jstring。

然后在命令行中将前面的dllApi.cpp 和这个Java2cpp.cpp同时编译成dll文件。

g++ -shared -fPIC Java2cpp.cpp dllApi.cpp -o javaCallcpp.dll -I "F:\Program Files\Java\jdk-11.0.12\include"  -I "F:\Program Files\Java\jdk-11.0.12\include\win32"

复制

这样在目录中就出现了javaCallcpp.dll文件。

四、编译并运行java程序

在命令行中输入

javac Java2cpp.java

复制

生成Java2cpp.class文件,.class文件就是java编译后的可执行文件

最后在命令行中输入

java Java2cpp  //注意没有.class后缀

复制

就可以运行java程序了。

这样就成功实现了java调用dll库,我们也可以将上面那些命令写成Makefile文件,和linux下的Makefile是一样的,只不过在Windows下不是make命令,而是nmake,使用时需要将nmake的路径添加到系统环境变量中。

总结:

1、编写cpp具体的功能接口代码

2、编写java程序,使用native关键字声明调用本地接口

3、javac -h (在旧版本中直接使用javah)生成头文件,根据头文件编写对应cpp源文件

4、使用g++ 编译生成.dll文件

5、使用javac xxx.java生成xxx.class文件并执行

当然如果不习惯使用命令行,也可以结合Visual Studio 和 Eclipse 两个IDE进行操作,在这里不做阐述。

本文参与 腾讯云自媒体分享计划,欢迎热爱写作的你一起参与!

本文章分享自微信公众号

电子技术研习社

作者:小小飞飞哥

原始发表时间:2021-07-28

如有侵权,请联系 [email protected] 删除。

猜你喜欢

转载自blog.csdn.net/TinagirlAPI/article/details/130129594