C# 連蒙帶騙不知所以然的搞定USB下位機讀寫

来源:https://www.cnblogs.com/catzhou/archive/2018/06/08/9156863.html
-Advertisement-
Play Games

公司用了一臺發卡機,usb介面,半雙工,給了個dll,不支持線程操作,使得UI線程老卡。 懊惱了,想自己直接通過usb讀寫,各種百度,然後是無數的坑,最終搞定。 現將各種坑和我自己的某些猜想記錄一下,也供各位參考。 一、常量定義 主要用於CreateFile時用。 二、結構、枚舉、類定義 都是從各種 ...


公司用了一臺發卡機,usb介面,半雙工,給了個dll,不支持線程操作,使得UI線程老卡。

懊惱了,想自己直接通過usb讀寫,各種百度,然後是無數的坑,最終搞定。

現將各種坑和我自己的某些猜想記錄一下,也供各位參考。

一、常量定義

        private const short INVALID_HANDLE_VALUE = -1;
        private const uint GENERIC_READ = 0x80000000;
        private const uint GENERIC_WRITE = 0x40000000;
        private const uint FILE_SHARE_READ = 0x00000001;
        private const uint FILE_SHARE_WRITE = 0x00000002;
        private const uint CREATE_NEW = 1;
        private const uint CREATE_ALWAYS = 2;
        private const uint OPEN_EXISTING = 3;
        private const uint FILE_FLAG_OVERLAPPED = 0x40000000;
        private const uint FILE_ATTRIBUTE_NORMAL = 0x00000080; 

主要用於CreateFile時用。

 

二、結構、枚舉、類定義

        private struct HID_ATTRIBUTES
        {
            public int Size;
            public ushort VendorID;
            public ushort ProductID;
            public ushort VersionNumber;
        }
        private struct SP_DEVICE_INTERFACE_DATA
        {
            public int cbSize;
            public Guid interfaceClassGuid;
            public int flags;
            public int reserved;
        }
        [StructLayout(LayoutKind.Sequential)]
        private class SP_DEVINFO_DATA
        {
            public int cbSize = Marshal.SizeOf<SP_DEVINFO_DATA>();
            public Guid classGuid = Guid.Empty;
            public int devInst = 0;
            public int reserved = 0;
        }
        [StructLayout(LayoutKind.Sequential, Pack = 2)]
        private struct SP_DEVICE_INTERFACE_DETAIL_DATA
        {
            internal int cbSize;
            internal short devicePath;
        }
        private enum DIGCF
        {
            DIGCF_DEFAULT = 0x1,
            DIGCF_PRESENT = 0x2,
            DIGCF_ALLCLASSES = 0x4,
            DIGCF_PROFILE = 0x8,
            DIGCF_DEVICEINTERFACE = 0x10
        }
        [StructLayout(LayoutKind.Sequential)]
        private struct HIDP_CAPS
        {
            /// <summary>
            /// Specifies a top-level collection's usage ID.
            /// </summary>
            public System.UInt16 Usage;
            /// <summary>
            /// Specifies the top-level collection's usage page.
            /// </summary>
            public System.UInt16 UsagePage;
            /// <summary>
            /// 輸入報告的最大節數數量(如果使用報告ID,則包含報告ID的位元組)
            /// Specifies the maximum size, in bytes, of all the input reports (including the report ID, if report IDs are used, which is prepended to the report data).
            /// </summary>
            public System.UInt16 InputReportByteLength;
            /// <summary>
            /// Specifies the maximum size, in bytes, of all the output reports (including the report ID, if report IDs are used, which is prepended to the report data).
            /// </summary>
            public System.UInt16 OutputReportByteLength;
            /// <summary>
            /// Specifies the maximum length, in bytes, of all the feature reports (including the report ID, if report IDs are used, which is prepended to the report data).
            /// </summary>
            public System.UInt16 FeatureReportByteLength;
            /// <summary>
            /// Reserved for internal system use.
            /// </summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)]
            public System.UInt16[] Reserved;
            /// <summary>
            /// pecifies the number of HIDP_LINK_COLLECTION_NODE structures that are returned for this top-level collection by HidP_GetLinkCollectionNodes.
            /// </summary>
            public System.UInt16 NumberLinkCollectionNodes;
            /// <summary>
            /// Specifies the number of input HIDP_BUTTON_CAPS structures that HidP_GetButtonCaps returns.
            /// </summary>
            public System.UInt16 NumberInputButtonCaps;
            /// <summary>
            /// Specifies the number of input HIDP_VALUE_CAPS structures that HidP_GetValueCaps returns.
            /// </summary>
            public System.UInt16 NumberInputValueCaps;
            /// <summary>
            /// Specifies the number of data indices assigned to buttons and values in all input reports.
            /// </summary>
            public System.UInt16 NumberInputDataIndices;
            /// <summary>
            /// Specifies the number of output HIDP_BUTTON_CAPS structures that HidP_GetButtonCaps returns.
            /// </summary>
            public System.UInt16 NumberOutputButtonCaps;
            /// <summary>
            /// Specifies the number of output HIDP_VALUE_CAPS structures that HidP_GetValueCaps returns.
            /// </summary>
            public System.UInt16 NumberOutputValueCaps;
            /// <summary>
            /// Specifies the number of data indices assigned to buttons and values in all output reports.
            /// </summary>
            public System.UInt16 NumberOutputDataIndices;
            /// <summary>
            /// Specifies the total number of feature HIDP_BUTTONS_CAPS structures that HidP_GetButtonCaps returns.
            /// </summary>
            public System.UInt16 NumberFeatureButtonCaps;
            /// <summary>
            /// Specifies the total number of feature HIDP_VALUE_CAPS structures that HidP_GetValueCaps returns.
            /// </summary>
            public System.UInt16 NumberFeatureValueCaps;
            /// <summary>
            /// Specifies the number of data indices assigned to buttons and values in all feature reports.
            /// </summary>
            public System.UInt16 NumberFeatureDataIndices;
        }

都是從各種地方複製過來的。最後的結構註釋從微軟那裡複製了英文,翻譯了一句中文。因為這個坑最大。

三、Dll封裝

        /// <summary>
        /// 過濾設備,獲取需要的設備
        /// </summary>
        /// <param name="ClassGuid"></param>
        /// <param name="Enumerator"></param>
        /// <param name="HwndParent"></param>
        /// <param name="Flags"></param>
        /// <returns></returns>
        [DllImport("setupapi.dll", SetLastError = true)]
        private static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, uint Enumerator, IntPtr HwndParent, DIGCF Flags);
        /// <summary>
        /// 獲取設備,true獲取到
        /// </summary>
        /// <param name="hDevInfo"></param>
        /// <param name="devInfo"></param>
        /// <param name="interfaceClassGuid"></param>
        /// <param name="memberIndex"></param>
        /// <param name="deviceInterfaceData"></param>
        /// <returns></returns>
        [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern Boolean SetupDiEnumDeviceInterfaces(IntPtr hDevInfo, IntPtr devInfo, ref Guid interfaceClassGuid, UInt32 memberIndex, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData);
        /// <summary>
        /// 獲取介面的詳細信息 必須調用兩次 第1次返回長度 第2次獲取數據
        /// </summary>
        /// <param name="deviceInfoSet"></param>
        /// <param name="deviceInterfaceData"></param>
        /// <param name="deviceInterfaceDetailData"></param>
        /// <param name="deviceInterfaceDetailDataSize"></param>
        /// <param name="requiredSize"></param>
        /// <param name="deviceInfoData"></param>
        /// <returns></returns>
        [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
        private static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr deviceInfoSet, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData, IntPtr deviceInterfaceDetailData, int deviceInterfaceDetailDataSize, ref int requiredSize, SP_DEVINFO_DATA deviceInfoData);
        /// <summary>
        /// 刪除設備信息並釋放記憶體
        /// </summary>
        /// <param name="HIDInfoSet"></param>
        /// <returns></returns>
        [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern Boolean SetupDiDestroyDeviceInfoList(IntPtr HIDInfoSet);


        /// <summary>
        /// 獲取設備文件
        /// </summary>
        /// <param name="lpFileName"></param>
        /// <param name="dwDesiredAccess">access mode</param>
        /// <param name="dwShareMode">share mode</param>
        /// <param name="lpSecurityAttributes">SD</param>
        /// <param name="dwCreationDisposition">how to create</param>
        /// <param name="dwFlagsAndAttributes">file attributes</param>
        /// <param name="hTemplateFile">handle to template file</param>
        /// <returns></returns>
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, uint lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, uint hTemplateFile);
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)]
        private static extern bool WriteFile(System.IntPtr hFile, byte[] lpBuffer, uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten, IntPtr lpOverlapped);
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern int CloseHandle(int hObject);

        /// <summary>
        /// 獲得GUID
        /// </summary>
        /// <param name="HidGuid"></param>
        [DllImport("hid.dll")]
        private static extern void HidD_GetHidGuid(ref Guid HidGuid);
        [DllImport("hid.dll")]
        private static extern Boolean HidD_GetPreparsedData(IntPtr hidDeviceObject, out IntPtr PreparsedData);
        [DllImport("hid.dll")]
        private static extern uint HidP_GetCaps(IntPtr PreparsedData, out HIDP_CAPS Capabilities);
        [DllImport("hid.dll")]
        private static extern Boolean HidD_FreePreparsedData(IntPtr PreparsedData);
        [DllImport("hid.dll")]
        private static extern Boolean HidD_GetAttributes(IntPtr hidDevice, out HID_ATTRIBUTES attributes);

 

四、幾個屬性

        private int _InputBufferSize;
        private int _OutputBufferSize;
        private FileStream _UsbFileStream = null;

五、幾個方法

Usb設備的讀寫跟磁碟文件的讀寫沒區別,需要打開文件、讀文件、寫文件,最後關閉文件。

磁碟文件大家都清楚,比如“c:\data\hello.txt”就是個文件名,前面加“\\電腦A”則是其他電腦上的某文件名,Usb設備也有文件名,如我的設備的文件名就是“\\?\hid#vid_5131&pid_2007#7&252e9bc9&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}”。

哪弄來的?頭大了吧,我也頭大,什麼鬼?百度了,未果,所以乾脆不管了。先來一個列出全部Usb設備文件名的方法

(一)獲取所有Usb設備文件名

        /// <summary>
        /// 獲取所有Usb設備文件名
        /// </summary>
        /// <returns></returns>
        public static List<string> GetUsbFileNames()
        {
            List<string> items = new List<string>();

            //通過一個空的GUID來獲取HID的全局GUID。
            Guid hidGuid = Guid.Empty;
            HidD_GetHidGuid(ref hidGuid);

            //通過獲取到的HID全局GUID來獲取包含所有HID介面信息集合的句柄。
            IntPtr hidInfoSet = SetupDiGetClassDevs(ref hidGuid, 0, IntPtr.Zero, DIGCF.DIGCF_PRESENT | DIGCF.DIGCF_DEVICEINTERFACE);

            //獲取介面信息。
            if (hidInfoSet != IntPtr.Zero)
            {

                SP_DEVICE_INTERFACE_DATA interfaceInfo = new SP_DEVICE_INTERFACE_DATA();
                interfaceInfo.cbSize = Marshal.SizeOf(interfaceInfo);

                uint index = 0;
                //檢測集合的每個介面
                while (SetupDiEnumDeviceInterfaces(hidInfoSet, IntPtr.Zero, ref hidGuid, index, ref interfaceInfo))
                {
                    int bufferSize = 0;
                    //獲取介面詳細信息;第一次讀取錯誤,但可取得信息緩衝區的大小
                    SP_DEVINFO_DATA strtInterfaceData = new SP_DEVINFO_DATA();
                    var result = SetupDiGetDeviceInterfaceDetail(hidInfoSet, ref interfaceInfo, IntPtr.Zero, 0, ref bufferSize, null);
                    //第二次調用傳遞返回值,調用即可成功
                    IntPtr detailDataBuffer = Marshal.AllocHGlobal(bufferSize);
                    Marshal.StructureToPtr(
                        new SP_DEVICE_INTERFACE_DETAIL_DATA
                        {
                            cbSize = Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DETAIL_DATA))
                        }, detailDataBuffer, false);


                    if (SetupDiGetDeviceInterfaceDetail(hidInfoSet, ref interfaceInfo, detailDataBuffer, bufferSize, ref bufferSize, null))// strtInterfaceData))
                    {
                        string devicePath = Marshal.PtrToStringAuto(IntPtr.Add(detailDataBuffer, 4));
                        items.Add(devicePath);
                    }
                    Marshal.FreeHGlobal(detailDataBuffer);
                    index++;
                }
            }
            //刪除設備信息並釋放記憶體
            SetupDiDestroyDeviceInfoList(hidInfoSet);
            return items;
        }

一般會返回好幾個文件名,那哪個是你要的呢?方法有二:

1.先獲取一次文件名列表,然後插拔或者禁用啟用一次Usb設備,變化的那個就是

2.輪流寫然後讀一次文件名,獲取到正確結果的就是

我採用2,然後User.config裡面把他記下來。

要讀寫,首先要打開

(二)打開Usb設備

        /// <summary>
        /// 構造
        /// </summary>
        /// <param name="usbFileName">Usb Device Path</param>
        public UsbApi(string usbFileName)
        {
            if (string.IsNullOrEmpty(usbFileName))
                throw new Exception("文件名不能為空");

            var fileHandle = CreateFile(
                 usbFileName,
                 GENERIC_READ | GENERIC_WRITE,// | GENERIC_WRITE,//讀寫,或者一起
                 FILE_SHARE_READ | FILE_SHARE_WRITE,//共用讀寫,或者一起
                 0,
                 OPEN_EXISTING,//必須已經存在
                 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
                 0);
            if (fileHandle == IntPtr.Zero || (int)fileHandle == -1)
                throw new Exception("打開文件失敗");

            HidD_GetAttributes(fileHandle, out var attributes);// null);// out var aa);
            HidD_GetPreparsedData(fileHandle, out var preparseData);
            HidP_GetCaps(preparseData, out var caps);
            HidD_FreePreparsedData(preparseData);
            _InputBufferSize = caps.InputReportByteLength;
            _OutputBufferSize = caps.OutputReportByteLength;

            _UsbFileStream = new FileStream(new SafeFileHandle(fileHandle, true), FileAccess.ReadWrite, System.Math.Max(caps.OutputReportByteLength, caps.InputReportByteLength), true);
        }

打開Usb設備我是在構找函數裡面完成的,我的類名叫UsbApi。

(三)寫

        /// <summary>
        /// 寫數據
        /// </summary>
        /// <param name="array"></param>
        public void Write(byte[] data)
        {
            if (_UsbFileStream == null)
                throw new Exception("Usb設備沒有初始化");
            if (data.Length > _OutputBufferSize)
                throw new Exception($"數據太長,超出緩衝區長度({_OutputBufferSize})");
            byte[] outBuffer = new byte[_OutputBufferSize];
            Array.Copy(data, 0, outBuffer, 1, data.Length);
            _UsbFileStream.Write(outBuffer, 0, _OutputBufferSize);
        }

(四)讀

        /// <summary>
        /// 同步讀
        /// </summary>
        /// <param name="array"></param>
        public byte[] Read()
        {

             if (_UsbFileStream == null)
                 throw new Exception("Usb設備沒有初始化");

            byte[] inBuffer = new byte[_InputBufferSize];
            _UsbFileStream.Read(inBuffer, 0, _InputBufferSize);
            return inBuffer;
        }

我的Usb設備是半雙工的,並且數據只有64位元組,所有用了同步讀。

(五)關閉

        public void Close()
        {
            if (_UsbFileStream != null)
                _UsbFileStream.Close();
        }

六、最後

最後寫了幾行代碼測試,巨坑:

1.CreateFile參數的坑

 var fileHandle = CreateFile(
                 usbFileName,
                 GENERIC_READ | GENERIC_WRITE,// | GENERIC_WRITE,//讀寫,或者一起
                 FILE_SHARE_READ | FILE_SHARE_WRITE,//共用讀寫,或者一起
                 0,
                 OPEN_EXISTING,//必須已經存在
                 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
                 0);
這些參數是針對我的Usb設備,各種調整後達到了能讀寫、能非同步。

2.FileStream參數的坑

_UsbFileStream = new FileStream(new SafeFileHandle(fileHandle, true), FileAccess.ReadWrite, System.Math.Max(caps.OutputReportByteLength, caps.InputReportByteLength), true);

緩衝區大小最終採用 System.Math.Max(caps.OutputReportByteLength, caps.InputReportByteLength)

太小讀寫錯誤,大點似乎沒關係

 3.Write的巨坑

public void Write(byte[] data)
這個data長度必須與緩衝區大寫一樣,而且數據要從data[1]開始寫,如你要寫“AB
”,
var data=new byte[]{0,(byte)'A',(byte)'B'};
事後發現HIDP_CAPS裡面的某個值可能告訴我了。

趟了這些坑後,搞定了,能用線程了^-^,發文紀念。

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 有話要說: 這次準備講述後臺伺服器的搭建以及前臺訪問到數據的過程。 成果: 準備: 搭建伺服器: 用eclipse直接創建一個web工程,並將運行環境設置為Tomcat7 接著定義了四個類來實現了一個簡單的介面(通過servlet的方式),下麵來看看這四個類 NewsBean.java 該類是段子類 ...
  • 4、為何需要進行url去重? 運行爬蟲時,我們不需要一個網站被下載多次,這會導致cpu浪費和增加引擎負擔,所以我們需要在爬取的時候對url去重,另一方面:當我們大規模爬取數據時,當故障發生時,不需要進行url鏈接重跑(重跑會浪費資源、造成時間浪費) 5、如何確定去重強度? 這裡使用去重周期確定強度: ...
  • Brotli是一種全新的數據格式,可以提供比Zopfli高20-26%的壓縮比。據谷歌研究,Brotli壓縮速度同zlib的Deflate實現大致相同,而在Canterbury語料庫上的壓縮密度比LZMA和bzip2略大。 鏈接:Google開源Brotli壓縮演算法 微軟使用了一種基於谷歌提供的C代 ...
  • 1、訂閱序列using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Reactive;using System.Reactive.Linq;using System.... ...
  • 原理知識準備 對於已經熟悉了session原理的同學來說,我們都清楚:在瀏覽器端我們會存儲一個sessionId,用它來作為憑證,在伺服器端得到有關本次瀏覽器與伺服器會話的所有信息,這些信息是儲存在伺服器端的存儲空間中的,它完全可以用來判斷一個瀏覽器端的登錄狀態,因為它是由伺服器端來掌控的,是安全的 ...
  • 前段時間我有一個朋友面試公司的時候遇到這個面試題,他也給了份原題給我瞧瞧,並沒有什麼特別的要點,關於這一類問題,如何在網格上的單元格嵌入多個控制項(如按鈕、超鏈接等)問題,我在網上搜索了下這類問題,發現很多解答但是都雜亂,本篇文章幫助大家瞭解如何應對這類問題。 微軟提供的DataGirdView網格控 ...
  • 百度百科 冒泡排序是筆試面試經常考的內容,雖然它是這些演算法里排序速度最慢的 原理:從頭開始,每一個元素和它的下一個元素比較,如果它大,就將它與比較的元素交換,否則不動。 這意味著,大的元素總是在向後慢慢移動直到遇到比它更大的元素。所以每一輪交換完成都能將最大值 冒到最後。 原出處:https://w ...
  • 一款用C#開發的APP開源項目。這款開源項目名為SmoSec,目前包含資產管理、耗材管理兩大類。並且,未來會不斷迭代,持續增加盤點、標簽列印和倉庫管理等功能。 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...