基于JAVA的USB-HID设备通信设计与实现

基于JAVA的USB-HID设备通信设计与实现,第1张

针对Java语言本身没有指针的类型,使用Java语言调用API与USB HID设备通信显得尤为困难的问题,介绍了如何使用JNative框架实现在windows平台下访问USB—HID设备,使java开发人员可以直接调用API完成程序与设备的通信。

0 引言

Java调用DLL的常用方法大致为几种,JNI,JNA,JNArI’IVE等,但实现与易用性差距还是很大…。JNI使用最繁琐,先要编写带有native声明的方法的Java类,使用javac命令编译所编写的Java类,然后使用javah+java类名生成扩展名为h的头文件,再使用C/C++实现本地方法将C/C++编写的文件生成动态连接库。JNA相对简单些,需要设计接口,接口中的方法需要和dll文件中的函数一一对应,但在实现函数原型时,java和C之间的类型无法一一对应,特别是C中的指针类型。JNative是一种能够使Java语言使调用DLL的一种技术,对JNI进行了封装,让开发人员能够利用Java代码访问n.ative libraries(DLL和lib.SO)的开源类库,并且不需要编译任何一行C/C++代码。
1 访问HID设备涉及的Windows API在Windows系统中有一整套对HID设备进行访问的API(如表1所示),包含在hid.dll、setupapi.dU、kernel32.dll这3个DLL文档中,分别起到与HID设备通信、寻找与识别设备、交换数据的作
用日J,为应用程序访问HID设备提供了强大的支持。

2 Java上位机编写思路
应用程序要访问HID设备就必须先枚举到设备,枚举成功后根据返回的设备句柄,就可以用ReadFile和WriteFile来读写设备的数据了‘4I,访问流程如图1所示。

3 Java访问HID设备程序的实现
3.1使用HidD—GetHidGuid()函数获取HID设备的接口类GUID
GUID是HID设备接口类的标识,是一个128位(16字节)的整刻5|。通过GUID可以在众多的HID硬件中找到自己的设备。获取GUID的函数原
型如下:
void stdcall HidD—GetHidGuid(OUT LPGUIDhidGuid);
参数hidGuid是一个GUID的结构体,可定义在此函数之前,即GUID hidGuid。
代码如下:

JNative jnative=new JNative(”hid.dll”,”HidDGetHidGuid);
jnative.setRetVal(Type.INT);//设置此函数的返回值
Pointer HidGuid=new Pointer(MemoryBlockFactorycreateMemoryBlock(16));
jnative.setParameter(0HidGuid);//赋予参数值
jnative.invoke();//函数执行

3.2通过GUID,使用SetupDiGetClassDevs()函数获取HID类中所有设备的信息集合
函数setupDiGetClassDevs返回设备信息集合句
柄,包括本地机器的设备信息元素,使用的函数如
下:

HDEVINFO SetupDiGetClassDevs(const GUIDClassGuid,PCTSTR EnumeratorHWNDhwndParent,DWORD Flags)

调用该函数将返回由ClassGuid指定的所有设备的一个信息集合的句柄。当信息集合使用完毕后,需要调用函数SetupDiDestroyDevicelnfoList()函数销毁。人口参数ClassGuid就是上一个函数获取的设备的GUID。人El参数Enumerator是可选的,当其值为NULL时,将搜索全部设备。hwndParent为
父窗口句柄,可以为NULL。第4个参数为DIGCF—DEVIcEINTERFAcE,即使用设备接口类进行访问。
代码如下:

JNative jnative2=new JNative(”setupapi.dll”,”SetupDiGetClassDevsA);
jnative2.setRetVal(Type.INT);//设置返回值类型
//参数赋值
jnative2.setParameter(0HidGuid);
jnative2.setParameter(1Type.INT,”0);
jnative2.setParameter(2Type.INT,”0);
jnative2.setParameter(3Type.INT,”18);
jnative2.invoke();//函数执行
DevicelnfoSet=IntegerparseInt(jnative2.getRetVal());//接收信息集合句柄返回值

3.3 在该设备信息集合中,使用SetupDiEnumDevi.ceInterfaces()函数。获取单个设备的接口信息从设备信息集合中获取单个设备接口信息,调
用函数如下:

BOOL SetupdiEnumDeviceInterfaces(HDEVINFO DevicelnfoSet,PSPDEVINFODATA DevicelnfoDataconst GUIDInterfaceClassGuid,DWORD Memberlndex,PSPDEVICEINTERFACEDATA DeviceInterfaeeData)

当该函数调用成功时,返回非0值;调用失败时,将返回0值。参数DevicelnfoSet是上一步中的SetupDiGetClassDevs函数的返回值。参数DeviceIn—foData是一个PSP—DEVINFO—DATA型的数据结构变量,可用来强制获取某个设备的信息。该参数是可选的,值为NULL表示不使用该参数。参数Inter.faceClassGuid是一个指向设备的接口类GUID的指针。Memberlndex是整型变量,表示设备集合中某个设备的索引序号。可在循环中调用此函数,修改此参数,以便获取所有的特定设备信息。
Devi.ceInterfaceData是一个SP—DEVICE—INTERFACE—DARTA的结构体,用来接收设备的信息。在调用该函数前,必须设置好该结构体的大小。
代码如下:


int Memberlndex=0;
Pointer MyDeviceInterfaceData=new Pointer(MemoryBlockFactory.createMemoryBloek(4+16+4+4));
JNative jnative3=new JNative(”setupapi.dⅡ”,”
SetupDiEnumDevieeInterfaees”);
MyDeviceInterfaceData.setIntAt(028);
jnative3.setRetVal(Type.INT);
jnative3.setParameter(0,Type.INT,DevicelnfoSet+””);
jnative3.setParameter(1,Type.INT,”0);
jnative3.setParameter(2,HidGuid);
jnative3.setParameter(3,Type.INT,MemberIndex+””);
jnative3.setParameter(4,
MyDeviceInterfaceData);
jnative3.invoke();//函数执行
int result=Integer.parseInt(jnative3.getRetVal());//接收函数返回值

3.4如果找到HID设备接口信息。使用SetupDi—GetDeviceInterfaceDetail()函数获取上一步中设备的更详细信息(设备路径名)这个函数要调用2次,以便得到一个存储信息的结构体。第1次是得到信息结构体的缓冲区大小;第2次获取完整的信息。这里获取指定设备接口的详细信息,函数如下:

BOOL SetupDiGetDeviceInterfaceDetail(HDEVINFO DeviceInfoSet,PSP—DEVICE—INTER—FACE—DATA DeviceInterfaceData,PSP—DEVICE—IN—TERFACE—DETAIL—DATA DeviceInterfaceDetailDa—ta,DWORD DeviceInterfaceDetailDataSize,PDWORD
equiredSize,PSP_DEVINFO—DATA DeviceInfoData)

入El参数DeviceInfoSet设备信息集合的句柄,由上一个函数返回。入口参数DeviceInterfaceData是保存设备信息的结构体,由上一个函数来获取。这个结构体获取的信息仍然不够详细。我们需要得到设备的路径,通过这个函数会返回另外一个结构体,也就是第3个入口参数DeviceInterfaceDetailDa—ha。在这个结构体中有一个成员DevicePath就保存着设备的路径(或者叫设备接口名)。
代码如下:


Pointer Needed=new Pointer(MemoryBlockFactory.createMemoryBlock(4))int DetailData=0MyDeviceInterfaceDatasetIntAt(028)JNative jnative4=new JNative(”setupapi.dll”,”
SetupDiGetDeviceInterfaceDetailA):
jnative4.setRetVal(Type.INT);
jnative4.setParameter(0Type.INT,DeviceInfoSet+””);
jnative4.setParameter(1MyDeviceInterfaceData);
jnative4.setParameter(2Type.INT,”0);
jnative4.setParameter(3Type.INT,”0);
jnative4.setParameter(4Needed);
jnative4.setParameter(5Type.INT,”0);
jnative4.invoke()int r4=jnative4.getRetValAsInt()DetailData=NeededgetAsInt(0);//获得缓冲
区大小
Pointer MyDeviceInterfaceDetailData=new Pointer(MemoryBlockFactory.createMemoryBlock(4+1))MyDeviceInterfaceDetailDatasetIntAt(05)Pointer DetailDataBuffer=new Pointer(MemoryBlockFactory.createMemoryBlock(NeededgetAsInt(0)))JNative jnativetemp=new JNative(”kernel32.dU”,”RflMoveMemory);
jnativetemp.setRetVal(Type.INT);
jnativetemp.setParameter(0DetailDataBuffer);
jnativetemp.setParameter(1MyDeviceInterface.DetailData);
jnativetemp.setParameter(2Type.INT,”4);
jnativetemp.invoke()JNative jnativetemp2=new JNative(”setupapi.dU”,”SetupDiGetDeviceInterfaceDetailA);
jnativetemp2.setRetVal(Type.INT);
jnativetemp2.setParameter(0Type.INT,Devi·celnfoSet+””);
jnativetemp2.setParameter(1MyDeviceInterfaceData);
jnativetemp2.setParameter(2DetailDataBuffer);
jnativetemp2.setParameter(3Type.INT,DetailData+””);
jnativetemp2.setParameter(4Needed);
jnativetemp2.setParameter(5Type.INT,”0);
jnativetemp2.invoke();
//获得设备路径名
for(int ii=4;ii<DetailData1;ii++)
{
DevieePathName=DevicePathName
DetailDataBuffergetAsByte(ii)}

3.5获取设备信息后。使用CreateFile()函数打开指定的设备CreateFile打开指定设备。这个函数将会用到上一步中的Devicepath作为第1个参数。返回设备句柄,目的是调用函数。
HidD—GetAttributes(handle,&DevAttributes)得到DevAttributes结构体,其中有2个成员DevAt.tributes.VendorID和DevAttributes.ProductlD就是相应设备的产商编号(VID)和设备编号(PID),可以核对这2个值,确认自己的设备后,然后调用Read.File和WriteFile函数进行读写。
代码如下:

JNative jnative5=new JNative(”kemeB2.dll”,”CreateFileA);
jnative5.setRetVal(Type.INT);
jnative5.setParameter(0Type.STRING,DevicePathName);
jnative5. setParameter (1Type. INT,(Ox80000000 IOx40000000)+””);
jnative5.setParameter(2Type.INT,(0xl 0x2)+””)Security s=new Pointer(MemoryBlockFactory.createMemory—
Block(4+4+4));
jnative5.setParameter(3Security);
jnative5.setParameter(4Type.INT,”3);
jnative5.setParameter(5Type.INT,”0);
jnative5.setParameter(6Type.INT,”0);
jnative5.invoke()HIDHandle=jnative5.getRetValAsInt();//获得设备句柄

3.6打开设备后。用HidD—GetAttributes()函数获取设备属性(为结构体)
通过核对属性结构体中的VID、PID以及产品的版本号,找到我们自己的设备,如果找到就退出。没有找到,则切换到下一步,重复(3)~(6)。
代码如下:

Pointer DeviceAttributes =new Pointer(MemoryBlockFaetory.createMemory—
Block(4+2+2+2))DeviceAttributessetIntAt(010)JNative jnative6=new JNative(”hid.dll”,”HidDGetAttributes);
jnative6.setRetVal(Type.INT);
jnative6.setParameter(0Type.INT,HIDHandle
+””);
jnative6.setParameter(1DeviceAttributes);
jnative6.invoke();
//获得HID设备的VID,PID
short vid=DeviceAttributesgetAsShort(4)short pid=DeviceAttributesgetAsShort(6)

3.7找到要 *** 作的设备后,调用Readfile()函数/WriteFile()函数。对HID设备进行读或写 *** 作
代码如下:


//销毁设备信息集合,释放资源
JNative jnative7=new JNative(”setupapi.dll”,”SetupDiDestroyDevicelnfoList);
jnative7.setRetVal(Type.INT);
jnative7.setParameter(0Type.INT,DevicelnfoSet+””);
jnative7.invoke()int r_jnative7=jnative7.getRetValAsInt()if(MyDeviceDetected==true){
FindTheHid=trueGetDeviceCapabilities()JNative jnative—createfile=new JNative(”kerneB2.dU”,”CreateFileA);
jnative_createfile.setRetVal(Type.INT);
jnative—createfile.setParameter(0Type.STRING,DevicePathName);
jnative—createfile.setParameter(1Type.INT,(Ox80000000 IOx40000000)+””);
jnative—createfile.setParameter(2Type.INT,(0xl 10)c2)+””);
jnative_createfile.setParameter(3Security);
jnative—createfile.setParameter(4Type.INT,”3);
jnative—ereatefile.setParameter(5Type.INT,Ox40000000+””);
jnative—createfile.setParameter(6Type.INT,”0);
jnative_createfile.invoke()ReadHandle=jnative—createfile.getRetValAsInt()}

JNative jnative8=new JNative(”kernel32.dll”,”
WriteFile);
jnative8.setRetVal(Type.INT);
jnative8.setParameter(0Type.INT,HIDHandle)Pointer SendBuffer=new Pointer(MemoryBloekFactory.createMemory—
Block(CapabilitiesgetAsInt(6)));
//向HID设备发送指令AD 03 03 01 0l 02 04 08 0D
SendBuffersetByteAt(0(byte)0)SendBuffersetByteAt(1(byte)0xad)SendBuffersetByteAt(2(byte)0x3)SendBuffersetByteAt(3(byte)0x3)SendBuffersetByteAt(4(byte)0x1)SendBuffersetByteAt(5(byte)Oxl)SendBuffersetByteAt(6(byte)0x2)SendBuffersetByteAt(7(byte)0x4)SendBuffersetByteAt(8(byte)Ox8)SendBuffersetByteAt(9(byte)0xd);
jnative8.setParameter(1SendBuffer);

jnative8.setParameter(2Type.INT,Capabili—lapped);
ties.getAsShort(6)+…’); jnative_readfie.invoke()Pointer NumberOfBytesWritten =new Pointer(MemoryBlockFactorycreateMemoryBlock(4));
jnative8.setParameter(3NumberOfBytesWritten);
jnative8.setParameter(4Type.INT,”0);
jnative8.invoke()JNative jnative_readfie=new JNative(”kernel32.dll”,”ReadFile);
jnative—read_fie.setRetVal(Type.INT);
jnative—readfie.setParameter(0Type.INT,ReadHandle+””)Pointer ReadBuffer=new Pointer(MemoryBlockFactory.createMemoryBlock(CapabilitiesgetAsShort(4)));
jnative_readfie.setParameter(1ReadBuffer);
jnative_readfie.setParameter(2Type.INT,CapabilitiesgetAsShort(4)+””)Pointer NumberOfBytesRead=new Pointer(MemoryBlockFactorycreateMemoryBlock(4));
jnativereadfie.setParameter(3NumberOfBytesRead)Pointer HIDOverlapped=new Pointer(MemoryBlockFactory.createMemoryBlock(4+4+4+4+4));
jnative—readfie.setParameter(4HIDOver-lapped);
jnative_readfie.invoke();
int r_jnative—readfie=jnative—readfie.getRetValAsInt();
//查看接收的数据
for(int k=1;k<CapabilitiesgetAsShoa(4);k++){
byte t=ReadBuffergetAsByte(k)System.out.println(k+”:”+t+”十六进制:”
+IntegertoHexString(t&0x00tXl00ff))}

4结论
本程序运行在WindowsXP系统下,通过程序实验测试,HID设备与主机运行稳定、可靠,数据通信准确无误。随着计算机上的USB接口的普及、性能
的改进和成本的下降,越来越多的产品将会采用它作为通讯接口,本文对于设计HID类USB数据通信接口有较高的参考价值。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/726715.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-26
下一篇 2022-04-26

发表评论

登录后才能评论

评论列表(0条)

保存