JNI之Java调用C++获取打印机状态

JNI是什么?

维基百科的解释:JNI (Java Native Interface,Java本地接口)是一种编程框架,使得Java虚拟机中的Java程序可以调用本地应用/或库,也可以被其他程序调用。 本地程序一般是用其它语言(C、C++或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序。
因此可以使用JNI实现Java和C++之间的相互调用。

简单实现

** 第一步 Java代码编写**

  1. 新建一个文件夹 JniTest,在文件夹里面新建一个java文件Hello.java,在里面写上如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Hello
    {
    //native声明,用于生成c/c++代码
    public native void sayHelloWorld();
    //加载c/c++编译好的库
    static
    {
    System.loadLibrary("Hello");
    }
    public static void main(String[] args)
    {
    new Hello().sayHelloWorld();
    }
    }
  2. 在该目录下的命令行窗口中编译java文件,输入javac Hello.java,将在该目录中生成Hello.class文件

  3. 在命令窗口中输入javah Hellojavah -jni Hello,将生成Hello.h文件

  4. 打开Hello.h文件你会在末尾看到如下一行:

    1
    2
    JNIEXPORT void JNICALL Java_Hello_sayHelloWorld
    (JNIEnv *, jobject);

这个就是刚才的Hello.java里面的sayHelloWorld()方法加了native关键字生成的方法

第二步 C++代码的编写

  1. 打开VS 2017,选择动态链接库(DLL)新建一个DLL工程
    7-24-1.jpg

  2. 把第一步生成的Hello.h文件复制到C++的项目文件中,然后将jni.hjni.md.h也复制到C++的项目文件中,其中jni.h位于JDK安装目录下的include目录下,即jdk\includejni.md.h位于JDK安装目录下的include的win32目录下,即jdk\include\win32。如下:
    7-24-2

  3. 修改Hello.h中的代码,将第一行的<jni.h>改成"jni.h"

  4. 编写C++代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include "stdafx.h"
    #include "Hello.h"
    #include <stdio.h>

    JNIEXPORT void JNICALL Java_Hello_sayHelloWorld(JNIEnv *env, jobject obj)
    {
    printf("Hello World !");
    return;
    }
  5. 将VS 2017调试器选择64位

  6. 在类视图中选择项目右键,点击生成即可在目录下生成dll文件
    好

  7. 将生成的dll文件拷贝到第一步的java目录下,然后将dll文件的名字改为和System.loadLibrary("Hello");里面加载的名字一样,即Hello.dll

  8. 在命令行中运行java Hello,即可实现java调用c++写的dll文件

Java调用C++获取打印机状态例子

第一步 Java代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class GetPrintStatusUtil {

/**
* -2获取句柄失败 -1打印机名字无效 0获取信息失败 1打印队列空闲 2正在打印
*/
public native static int getPrintStatus(String name);
static
{
System.loadLibrary("PrintStatus");
}
public static void main(String[] args)
{
int a = getPrintStatus("Microsoft Print to PDF");
System.out.println("返回值:"+a);
}
}

第二步 C++获取打印机状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include "stdafx.h"
#include "com_util_GetPrintStatusUtil.h"
#include <string>
#include <iostream>
#include <windows.h>
#include <winspool.h>
using namespace std;

//name为打印机名字
JNIEXPORT jint JNICALL Java_com_util_GetPrintStatusUtil_getPrintStatus(JNIEnv *env, jobject obj, jstring name) {
//将jstring转换成char类型 cname
char* cname = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("GB2312");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray)env->CallObjectMethod(name, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0) {
cname = (char*)malloc(alen + 1);
memcpy(cname, ba, alen);
cname[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);

//char类型转换为string sname
//string sname = cname;

//打印机名字
LPTSTR printerName = (LPTSTR)cname;
//打印机句柄
HANDLE m_hPrinter = NULL;
//获取打印机句柄
if (!::OpenPrinter(printerName, &m_hPrinter, NULL))
{
int status = GetLastError();
if (status == 1801) {
//打印机名字无效
return -1;
}
else {
//获取句柄失败
return -2;
}
}
int level = 2;
//内存指针,用于动态的去获取当前打印机需要获取多大的缓冲区
DWORD dwNeeded = 0;
//初始化一个打印结构体,通过这个来转载打印机的信息
PRINTER_INFO_2 *pPrinterInfo = (PRINTER_INFO_2 *)malloc(0);
//先判断答打印机存在不
if (!::GetPrinter(m_hPrinter, level, 0, 0, &dwNeeded))
{
//std::cout << "需要设置的大小" << dwNeeded << std::endl;
int status2 = GetLastError();
//std::cout << "系统调用状态码:" << status2 << std::endl;
pPrinterInfo = (PRINTER_INFO_2 *)malloc(dwNeeded);//存在的话就把打印机的信息装入指针对象中,这里是重新定义大小
//开始装入
if (!::GetPrinter(m_hPrinter, level, (LPBYTE)pPrinterInfo, dwNeeded, &dwNeeded)) {
int status = GetLastError();
//std::cout << "获取打印机信息失败:" << status << std::endl;
::ClosePrinter(m_hPrinter);
free(pPrinterInfo);
pPrinterInfo = NULL;
return 0; //获取信息失败
}
}
::ClosePrinter(m_hPrinter);
if (pPrinterInfo->cJobs > 0) {
free(pPrinterInfo);
pPrinterInfo = NULL;
return 2; //正在打印
}
free(pPrinterInfo);
pPrinterInfo = NULL;
return 1;//空闲状态
//-2 获取句柄失败 -1打印机名字无效 0获取信息失败 1打印队列空闲 2正在打印
}

第三步 在IntelliJ IDEA中生成.h文件

  1. 在IDEA的setting->Tools->External Tools中新建一个扩展工具,如下:
    5

Program:$JDKPath$\bin\javah
Arguments:-jni -classpath $OutputPath$ -d ./jni $FileClass$
Working directory:$ProjectFileDir$

点击apply然后ok,扩展工具就创建完成啦

  1. 在生成.h文件之前先build一下,生成.class文件过后才能生成.h文件
  2. 选中要生成.h文件的java类,右键,然后找到External Tools,这时会发现刚才生成扩展工具JNI,点击这个工具就可以在项目的jni中生成.h文件,如下:
    6
  3. 将这个生成的.h文件和上述的jni.h,jni.md.h文件放到C++的dll工程目录下面,然后生成.dll文件
  4. 将生成的.dll文件复制到java项目的一级目录下,如下:
    7
  5. 这时就可以在Java项目里面调用C++生成的.dll文件了!

参考文章:
https://blog.csdn.net/change_from_now/article/details/50370748

-------- ♥感谢阅读♥ --------