针对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;ii<DetailData一1;ii++)
{
DevieePathName=DevicePathName
DetailDataBuffer.getAsByte(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(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;k<Capabilities.getAsShoa(4);k++){
byte t=ReadBuffer.getAsByte(k);
System.out.println(k+”:”+t+”十六进制:”
+Integer.toHexString(t&0x00tXl00ff));
}
4结论
本程序运行在WindowsXP系统下,通过程序实验测试,HID设备与主机运行稳定、可靠,数据通信准确无误。随着计算机上的USB接口的普及、性能
的改进和成本的下降,越来越多的产品将会采用它作为通讯接口,本文对于设计HID类USB数据通信接口有较高的参考价值。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)