首页 > 学院 > 开发设计 > 正文

应用程序与硬件进行交互(实现硬件绑定、应用程序与硬件通信)

2019-11-17 03:19:21
字体:
来源:转载
供稿:网友

应用程序与硬件进行交互(实现硬件绑定、应用程序与硬件通信)

一、前言 在无操作系统的裸机中,或者类似如DOS这样操作系统中,应用程序和硬件的交互是非常简单的。如果没有操作系统,我们访问硬件空间可能只需一条指令就行,甚至可以访问任意的内存空间或者IO空间。但是在WINDOWS操作系统中,应 用程序与硬件间被完全隔离开来,应用程序与软件的通信就必须依赖于依赖驱动程序。有点类似于现在的网上购物,卖家相当于硬件,而卖家相当于应用程序,淘宝等类似的购物网站相当于驱动程序,而卖家与买家之间的买卖就得依赖 于淘宝等类似的购物网站。至于为什么要把硬件层和应用程序隔开,答案是——安全。允许应用程序随意访问硬件是一件很危险的事情,除了可能会造成蓝屏死机之类的问题,还会发生密码的安全性问题。那在我们日常开发过程当中哪 些场景需要涉及到硬件与软件的交互呢? 二、应用程序与硬件交互的场景 1.读取硬件信息 当应用程序需要与硬件进行捆绑时,就需要获取对应的硬件信息,例如触摸框绑定、MCU绑定等等,需要读取出对应硬件的信息,并与预期的信息进行比较来判断是否进行了绑定。获取MCU版本号等等。 2.与硬件进行通信 应用程序与硬件进行通信,比单纯的读取硬件信息稍微复杂一点。例如通过MCU来进行定时开关机,首先需要根据对应的信息查找指定的MCU设备,然后将对应的定时开关机指定发送给MCU,MCU收到信息之后,发送信息给应用程序,告之 定时开关机的指定是否设置成功。这其中包含了应用程序与硬件之间的通信。 3.硬件的管理 例如磁盘的管理,我们可以自己编写应用程序对磁盘进行一系列的管理,磁盘分区、磁盘大小等等。移动设备管理工具等等都可以实现。 4.其他 当然还有很多很多啦.... 那么,既然我们提到了这么多得应用场景,那我们如何来实现它们呢?那接下来就不得不提SetupDi这一系列API了。 三、SetupDi API 1.HidD_GetHidGuid 函数定义 BOOL Hidd_GetHidGuid( &guidHID 指向GUID类型的指针 ); HID类设备是通过GUID类型值作标识的,调用函数HidD_GetHidGuid颗获得HID设备的标识 2.SetupDiGetClassDevs 函数定义   HDEVINFO   SetupDiGetClassDevs(   const GUID *ClassGuid,//HID类设备是通过GUID类型值作标识的,如图1.图Guid示例,通过指向Guid的指针,获取对应的设备列表。   PCTSTR Enumerator,   HWND hwndParent,   DWord Flags//Flags,当值为DIGCF_ALLCLASSES,该函数会将*ClassGuid忽略掉。   ); 获取一个指定类别或全部类别的所有已安装设备的信息,其中两个参数需要注意一下。返回值,如果函数运行成功,返回设备信息结构的句柄,该结构包含与指定参数匹配的所有已安装设备。如果失败,则返回INVALID_HANDLE_VALUE。 3.SetupDiEnumDeviceInterfaces 函数定义 BOOL SetupDiEnumDeviceInterfaces( HDEVINFO DeviceInfoSet, //一个指向设备信息集合的句柄,包含设备接口返回信息,通常是SetupDiGetClassDevs的返回 PSP_DEVINFO_DATA DeviceInfoData, //指向特定设备的SP_DEVINFO_DATA 类型的指针, const GUID *InterfaceClassGuid, //指向制定设备接口类的GUID指针 DWORD MemberIndex, //设备信息中接口列表的索引值(初始值为0) PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData //指向调用者分配的SP_DEVICE_INTERFACE_DATA类型的内存区域的指针,调用前必须先配置DeviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA) ); 获取设备列表中指定接口的信息,通过次接口只能获取接口信息,需要取得接口的详细信息需要调用SetupDiGetDeviceInterfaceDetail这个接口。返回值,ture则成功,false为失败。 4.SetupDiGetDeviceInterfaceDetail BOOL SetupDiGetDeviceInterfaceDetail( IN HDEVINFO DeviceInfoSet,//指向设备信息集的指针,它包含了所要接收信息的接口。该句柄通常由SetupDiGetClassDevs函数返回。 IN PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData,//一个指向SP_DEVICE_INTERFACE_DETAIL_DATA结构的指针,该结构用于接收指定接口的信息 OUT PSP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData..OPTIONAL, IN DWORD DeviceInterfaceDetailDataSize,//缓冲的大小。该缓冲的大小不能小于 (offsetof(SP_DEVICE_INTERFACE_DETAIL_DATA, DevicePath) + sizeof(TCHAR)) 字节。 OUT PDWORD RequiredSize..OPTIONAL, OUT PSP_DEVINFO_DATA DeviceInfoData OPTIONAL ); 返回设备接口的详细信息。这里需要特别提一下的是,该接口需要调用两次,因为该接口主要是获取设备的路径而路径会放到DeviceDetailData面, 然而每一个设备的路径是不一样的, 所以大小不一定, 所以需要调用两次,第一次 获取路径的大小,第二次获取具体的路径信息。返回值,ture则成功,false为失败。 5.CreateFile 函数定义 HANDLE CreateFile(   LPCTSTR lpFileName, //设备路径   DWORD dwDesiredaccess, //访问模式(写/读)   DWORD dwShareMode, //共享模式   LPSECURITY_ATTRIBUTES lpSecurityAttributes, //指向安全属性的指针   DWORD dwCreationDisposition, //如何创建   DWORD dwFlagsAndAttributes, //文件属性   HANDLE hTemplateFile //用于复制文件句柄   ); 与设备建立连接,获取相应的属性(设备的供应商标识与产品标识等),应用程序就能够与对应的设备进行通信了。如果调用成功,该函数返回文件的句柄;如果调用失败,则返回INVALID_HANDLE_VALUE,在打开通信设备时,应该以独占方式打开。 6.HidD_GetAttributes 函数定义 HidD_GetAttributes( SafeFileHandle hidDeviceObject,//对应与选定设备的句柄 out HiddAttributes attributes,//指向HIDD_ATTRIBUTES类型的指针 ); //设备属性结构体 struct HiddAttributes { int Size; ushort VendorId; ushort PRoductId; ushort VersionNumber; }; 获取设备的属性 已经介绍了这么多SetupDi API,那我们如何来利用这些API来实现我们的需求呢?下面就来介绍一下这些API一般情况下使用的步骤。 四、Setup API使用步骤 以下相关代码片段为C#。 1.调用HidD_GetHidGuid,获取Hid设备的Guid。当然,假如我们只需要获取磁盘相关设备的列表,且知道磁盘的Guid为53f56307-b6bf-11d0-94f2-00a0c91efb8b。则无须调用HidD_GetHidGuid函数。 示例代码: var hUsb = Guid.Empty; // 取得hid设备全局id HidD_GetHidGuid(ref hUsb); 2.执行第1步,或者说知道指定的Guid,调用SetupDiGetClassDevs函数,获取Guid对应的设备集合。 示例代码: //取得一个包含所有HID接口信息集合的句柄 var hidInfoSet = SetupDiGetClassDevs(ref hUsb, 0, IntPtr.Zero, Digcf.DIGCF_PRESENT | Digcf.DIGCF_DEVICEINTERFACE); 3.通过第2步,获取对应的设备列表,我们就可以获取其中某个接口的相关信息,这个时候,需要调用SetupDiEnumDeviceInterfaces来获取接口的信息了。 4.执行完第3步之后,则需要调用SetupDiGetDeviceInterfaceDetail来获取接口的详细信息,主要是获取对应设备的路径,此函数必须连续调用两次,详情见接口说明。 第3、4步的示例代码: for (index = 0; index < MaxUsbDevices; index++) { //得到第index个接口信息 if (SetupDiEnumDeviceInterfaces(hidInfoSet, IntPtr.Zero, ref hUsb, index, ref interfaceInfo)) { int buffsize = 0; // 取得接口详细信息:第一次读取错误,但可以取得信息缓冲区的大小 SetupDiGetDeviceInterfaceDetail(hidInfoSet, ref interfaceInfo, IntPtr.Zero, buffsize, ref buffsize, null); //构建接收缓冲 var pDetail = Marshal.AllocHGlobal(buffsize); var detail = new SpDeviceInterfaceDetailData { cbSize = Marshal.SizeOf(typeof(SpDeviceInterfaceDetailData)) }; Marshal.StructureToPtr(detail, pDetail, false); if (SetupDiGetDeviceInterfaceDetail(hidInfoSet, ref interfaceInfo, pDetail, buffsize, ref buffsize, null)) { deviceList.Add(Marshal.PtrToStringAuto((IntPtr)((int)pDetail + 4))); } Marshal.FreeHGlobal(pDetail); } } 5.执行完第4步后,可以获取到设备的路径,通过调用CreateFile与设备创建连接之后调用HidD_GetAttributes获取设备对应的属性,执行完这一步,我们就可以完成上述应用场景中的MCU、触摸框等相关硬件的绑定,以及与应用程序通信 了,如图2. PID,VID属性。例如MCU的供应商标识为0x1ff7,产品标识为0x0f13,应用程序实现MCU绑定了。 示例代码: //建立和设备的连接 _device = CreateFile(device, Desiredaccess.GENERIC_READ | Desiredaccess.GenericWrite, 0, 0, Creationdisposition.OPEN_EXISTING, Flagsandattributes.FILE_FLAG_OVERLAPPED, 0); if (_device.IsInvalid) continue; HiddAttributes attributes; //获取连接属性 HidD_GetAttributes(_device, out attributes); if (attributes.VendorId == vId && attributes.ProductId == pId) { IntPtr preparseData; HidpCaps caps; HidD_GetPreparsedData(_device, out preparseData); //获取设备具体信息 HidP_GetCaps(preparseData, out caps); HidD_FreePreparsedData(preparseData);//?? OutputReportLength = caps.OutputReportByteLength;//获取设备接收到字节的长度 InputReportLength = caps.InputReportByteLength;//获取设备发送的字节的长度 //根据设备初始化FileStream实例,通过流来实现数据接收与发送 _hidDevice = new FileStream(_device, FileAccess.ReadWrite, InputReportLength, true); _deviceOpened = true; return true; } 总结:

通过调用上述API,以及执行对应的步骤,就能够应用程序实现硬件绑定,与硬件进行交互,当然,其中可能会遇到一些问题,需要有耐心,一步一步来。关于磁盘管理、识别大容量的移动设备等,在本文中没有讲到, 由于时间关系,过几天另起一章总结一下磁盘相关的知识。

相关图片:

QQ2mEnZRq0XUBEWhFSEJWY5wCeQgQO7d953fH/Pu7Ox77SbZTUJ8v/DZzDvvM888M/vufN9nToQxhihg27Z3b54/H2NACDAAAsAYPnjv3fkL5kcjOx3ke8QMBEHgGfh8Pp/P5/WDXJJ4QRDYhL1dFB06dOjoBBBCCCGO4wwGA8dxsbGxBoMhJiaGfLIwMOA4DjEgesLMMSZKJcFYqD1zBgC2HFtW1Rz3q3QnABL0RjnKUGRNypcdHR0dHR2tra3t7e1tbW2UPglxUvrs7ULo0KFDRydAWTMmJiY2NnbAgAFxcXGxDHieFwRBEITY2Fg2IcdxVEOncowWcfI8jzF+97330BQEANvefW/B/O/yPK+ewm1DFicJWl3YYe563m4bsjiDdHiKs0155QAAWUXVZXYjI+wpzjblAY0NSHbXip4EdTfJp+AHZc2Ojo729vbW1tampqZmP9rb2wl36sSpQ4eOixeEOAlrxsfHJyQkDBw4MCEhIT4+Pi4uzufzDRgwgLRvbBJClsTvxBjTz3ByjB5xCiSQM/aVHATbat7DGFSJ00912CxeZhebgvmNwFOcbapYrcFnhAat1qxg7aa8TBcuMwO4bWhZcU5Atad42WbIYiSXwUaMjaImm/sioU7yldMw+e4lDwHGuKWlpbGxsb6+vr6+/syZM5s2bWI1SAI6Ig76HSm+neivLDp0dBM5OTnJyckjR44kXMP5wfN8TEwMy50kIOmkjXxX7fbt2z/77LOHHnooTHmvz4cQYDLICQAYI4R8vE9J1m2TOIhmR1kX+cpoL8N28BRnOysC6kucWUXVRKE5v6hgWanH7ncvl21esnF1halAnrExZ0nW5ioPmOX03bfBepx0XLO9vb2lpaW+vv706dONjY3PP/98bGzsr3/96x//+Me9bW9k0POs080cO5U8fOFIqY34re6ja6904acKKRmmqs7aGalX1R5+5Y2Pj7/nFvOrW9/tyUzVcNfC+S9vcb/00ktFRUVer/f2228fO3Ys6UuTdKQR35QEBEFgBzg7hbCI88MPP1y5cqXX6w1JnBUVFf/6178++OCDnTt3xsbGjho92nPgwALzAgAAwB0dXoU0LLEF4CnONm1eIvageoqzTZshq7y8HAAsyCl2uAZ6d2U9sBoor6gGMPpps8xYbVMUq64oz8y9qFhTMjOI7aptb29vamqqr69vbGxcs2bN73//+1tvvbW2tnb//v0Q/JOL1M8vIm1omEoi6MCFo6oLl/Sny/6GBUGQBNgw2/0eToDNV9G1lfTny3OUmCcxG4IfMMWsiSp5HapVkeLDJomUyyg+rtqRIQPy6SGKl2rxIcXUTFWUVLzUrge1GDki9X4wZcoUgfed+aK85egRAxL9Iw6AAwDABgAEwAEgAAQAIDDh0CDOoAAAwGEADCAAYAAeAAAJAOSRxRh4DAMvGy/wvrNnz1599dXbt28vLS1dtWrVsmXLQFZvpFeWfhLQR7pTTV9o4iSsmZmZuXv37pDCTz755NixYx966KFvvjl983dvWrPmzxcuXHjmmWdybrkVAHm9SsSpDKN9tTWvgDiHntLN5dbV2GFmu2rdNlSQUc30q6ap9KuaMrLK8wrddocZwF2YVw5WAABP8bK8zNXYCFCtlMhts+wtqnaEbW6vg23aJB5nR0dHS0tLU1NTbW2t0+n8/e9//53vfKempkY+nSxSHbbdZ83uUGY4ydUIUrvg4VMCyNhLwp0YY/LmKwiC5NerSE4agTApU0MgZFnYsQDwPx7UWiovqT2WklkBKqbIoNjfh6Z4KYmhYVaVJFIe0CiXoqQkPuSlvHIktyQFZDWoVYJi7UkqXF5XcoT5kIfTAvACCD4efF7ez5ECEgCAE5kPBACDX1ig

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表