针对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”,”HidD—GetHidGuid”); jnative.setRetVal(Type.INT);//设置此函数的返回值 Pointer HidGuid=new Pointer(MemoryBlockFactory.createMemoryBlock(16)); jnative.setParameter(0,HidGuid);//赋予参数值 jnative.invoke();//函数执行
3.2通过GUID,使用SetupDiGetClassDevs()函数获取HID类中所有设备的信息集合
函数setupDiGetClassDevs返回设备信息集合句
柄,包括本地机器的设备信息元素,使用的函数如
下:
HDEVINFO SetupDiGetClassDevs(const GUIDClassGuid,PCTSTR Enumerator,HWNDhwndParent,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(0,HidGuid); jnative2.setParameter(1,Type.INT,”0”); jnative2.setParameter(2,Type.INT,”0”); jnative2.setParameter(3,Type.INT,”18”); jnative2.invoke();//函数执行 DevicelnfoSet=Integer.parseInt(jnative2.getRetVal());//接收信息集合句柄返回值
3.3 在该设备信息集合中,使用SetupDiEnumDevi.ceInterfaces()函数。获取单个设备的接口信息从设备信息集合中获取单个设备接口信息,调
用函数如下:
BOOL SetupdiEnumDeviceInterfaces(HDEVINFO DevicelnfoSet,PSPDEVINFODATA DevicelnfoData,const 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(0,28); 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=0; MyDeviceInterfaceData.setIntAt(0,28); JNative jnative4=new JNative(”setupapi.dll”,” SetupDiGetDeviceInterfaceDetailA”): jnative4.setRetVal(Type.INT); jnative4.setParameter(0,Type.INT,DeviceInfoSet+””); jnative4.setParameter(1,MyDeviceInterfaceData); jnative4.setParameter(2,Type.INT,”0”); jnative4.setParameter(3,Type.INT,”0”); jnative4.setParameter(4,Needed); jnative4.setParameter(5,Type.INT,”0”); jnative4.invoke(); int r4=jnative4.getRetValAsInt(); DetailData=Needed.getAsInt(0);//获得缓冲 区大小 Pointer MyDeviceInterfaceDetailData=new Pointer(MemoryBlockFactory.createMemoryBlock(4+1)); MyDeviceInterfaceDetailData.setIntAt(0,5); Pointer DetailDataBuffer=new Pointer(MemoryBlockFactory.createMemoryBlock(Needed.getAsInt(0))); JNative jnativetemp=new JNative(”kernel32.dU”,”RflMoveMemory”); jnativetemp.setRetVal(Type.INT); jnativetemp.setParameter(0,DetailDataBuffer); jnativetemp.setParameter(1,MyDeviceInterface.DetailData); jnativetemp.setParameter(2,Type.INT,”4”); jnativetemp.invoke(); JNative jnativetemp2=new JNative(”setupapi.dU”,”SetupDiGetDeviceInterfaceDetailA”); jnativetemp2.setRetVal(Type.INT); jnativetemp2.setParameter(0,Type.INT,Devi·celnfoSet+””); jnativetemp2.setParameter(1,MyDeviceInterfaceData); jnativetemp2.setParameter(2,DetailDataBuffer); jnativetemp2.setParameter(3,Type.INT,DetailData+””); jnativetemp2.setParameter(4,Needed); jnativetemp2.setParameter(5,Type.INT,”0”); jnativetemp2.invoke(); //获得设备路径名 for(int ii=4;ii3.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(0,Type.STRING,DevicePathName); jnative5. setParameter (1,Type. INT,(Ox80000000 IOx40000000)+””); jnative5.setParameter(2,Type.INT,(0xl 0x2)+””); Security s=new Pointer(MemoryBlockFactory.createMemory— Block(4+4+4)); jnative5.setParameter(3,Security); jnative5.setParameter(4,Type.INT,”3”); jnative5.setParameter(5,Type.INT,”0”); jnative5.setParameter(6,Type.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)); DeviceAttributes.setIntAt(0,10); JNative jnative6=new JNative(”hid.dll”,”HidD —GetAttributes”); jnative6.setRetVal(Type.INT); jnative6.setParameter(0,Type.INT,HIDHandle +””); jnative6.setParameter(1,DeviceAttributes); jnative6.invoke(); //获得HID设备的VID,PID short vid=DeviceAttributes.getAsShort(4); short pid=DeviceAttributes.getAsShort(6);3.7找到要操作的设备后,调用Readfile()函数/WriteFile()函数。对HID设备进行读或写操作
代码如下://销毁设备信息集合,释放资源 JNative jnative7=new JNative(”setupapi.dll”,”SetupDiDestroyDevicelnfoList”); jnative7.setRetVal(Type.INT); jnative7.setParameter(0,Type.INT,DevicelnfoSet+””); jnative7.invoke(); int r_jnative7=jnative7.getRetValAsInt(); if(MyDeviceDetected==true){ FindTheHid=true: GetDeviceCapabilities(); JNative jnative—createfile=new JNative(”kerneB2.dU”,”CreateFileA”); jnative_createfile.setRetVal(Type.INT); jnative—createfile.setParameter(0,Type.STRING,DevicePathName); jnative—createfile.setParameter(1,Type.INT,(Ox80000000 IOx40000000)+””); jnative—createfile.setParameter(2,Type.INT,(0xl 10)c2)+””); jnative_createfile.setParameter(3,Security); jnative—createfile.setParameter(4,Type.INT,”3”); jnative—ereatefile.setParameter(5,Type.INT,Ox40000000+””); jnative—createfile.setParameter(6,Type.INT,”0”); jnative_createfile.invoke(); ReadHandle=jnative—createfile.getRetValAsInt(); } JNative jnative8=new JNative(”kernel32.dll”,” WriteFile”); jnative8.setRetVal(Type.INT); jnative8.setParameter(0,Type.INT,HIDHandle); Pointer SendBuffer=new Pointer(MemoryBloekFactory.createMemory— Block(Capabilities.getAsInt(6))); //向HID设备发送指令AD 03 03 01 0l 02 04 08 0D SendBuffer.setByteAt(0,(byte)0); SendBuffer.setByteAt(1,(byte)0xad); SendBuffer.setByteAt(2,(byte)0x3); SendBuffer.setByteAt(3,(byte)0x3); SendBuffer.setByteAt(4,(byte)0x1); SendBuffer.setByteAt(5,(byte)Oxl); SendBuffer.setByteAt(6,(byte)0x2); SendBuffer.setByteAt(7,(byte)0x4); SendBuffer.setByteAt(8,(byte)Ox8); SendBuffer.setByteAt(9,(byte)0xd); jnative8.setParameter(1,SendBuffer); jnative8.setParameter(2,Type.INT,Capabili—lapped); ties.getAsShort(6)+…’); jnative_readfie.invoke(); Pointer NumberOfBytesWritten =new Pointer(MemoryBlockFactory.createMemoryBlock(4)); jnative8.setParameter(3,NumberOfBytesWritten); jnative8.setParameter(4,Type.INT,”0”); jnative8.invoke(); JNative jnative_readfie=new JNative(”kernel32.dll”,”ReadFile”); jnative—read_fie.setRetVal(Type.INT); jnative—readfie.setParameter(0,Type.INT,ReadHandle+””); Pointer ReadBuffer=new Pointer(MemoryBlockFactory.createMemoryBlock(Capabilities.getAsShort(4))); jnative_readfie.setParameter(1,ReadBuffer); jnative_readfie.setParameter(2,Type.INT,Capabilities.getAsShort(4)+””); Pointer NumberOfBytesRead=new Pointer(MemoryBlockFactory.createMemoryBlock(4)); jnativereadfie.setParameter(3,NumberOfBytesRead); Pointer HIDOverlapped=new Pointer(MemoryBlockFactory.createMemoryBlock(4+4+4+4+4)); jnative—readfie.setParameter(4,HIDOver-lapped); jnative_readfie.invoke(); int r_jnative—readfie=jnative—readfie.getRetValAsInt(); //查看接收的数据 for(int k=1;k4结论
本程序运行在WindowsXP系统下,通过程序实验测试,HID设备与主机运行稳定、可靠,数据通信准确无误。随着计算机上的USB接口的普及、性能
的改进和成本的下降,越来越多的产品将会采用它作为通讯接口,本文对于设计HID类USB数据通信接口有较高的参考价值。



