147 lines
9.5 KiB
Markdown
147 lines
9.5 KiB
Markdown
|
|
---
|
|||
|
|
source_files:
|
|||
|
|
- Common/DTS.Common.IConnection/USBConnection/HIDUSBConnection/HIDUSBConnection.cs
|
|||
|
|
generated_at: "2026-04-16T02:10:22.619594+00:00"
|
|||
|
|
model: "Qwen/Qwen3-Coder-Next-FP8"
|
|||
|
|
schema_version: 1
|
|||
|
|
sha256: "4a9eeb7b8043999a"
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# Documentation: `HIDUSBConnection` Class
|
|||
|
|
|
|||
|
|
## 1. Purpose
|
|||
|
|
`HIDUSBConnection` is a concrete implementation of the `IConnection` interface that enables communication with a specific HID-class USB device (identified by Vendor ID `0x1CB9` and Product ID `0x0003`, named *HIDSLICE*) using Windows file I/O APIs (`CreateFile`, `ReadFile`, `WriteFile` via overlapped I/O). It abstracts low-level HID report handling (input/output reports) over USB, providing asynchronous send/receive semantics consistent with .NET’s `IAsyncResult` pattern. This module exists to support direct, low-latency data transfer to/from embedded hardware (e.g., a data acquisition recorder) where standard socket-based communication is not applicable.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. Public Interface
|
|||
|
|
|
|||
|
|
### Constructors & Finalizer
|
|||
|
|
- **`HIDUSBConnection()`**
|
|||
|
|
Initializes the instance, sets up `SECURITY_ATTRIBUTES` for `CreateFile`, and initializes `_Connected = false`.
|
|||
|
|
|
|||
|
|
- **`~HIDUSBConnection()`**
|
|||
|
|
Finalizer that invokes `Dispose(false)` to release unmanaged resources.
|
|||
|
|
|
|||
|
|
### Disposal
|
|||
|
|
- **`void Dispose()`**
|
|||
|
|
Performs deterministic cleanup: calls `Dispose(true)` and suppresses finalization.
|
|||
|
|
|
|||
|
|
- **`protected virtual void Dispose(bool disposing)`**
|
|||
|
|
Releases all handles (`_HIDHandle`, `_ReadHandle`, `_WriteHandle`) via `FileIODeclarations.CloseHandle`, disposes `_MyHID`, and sets `_Connected = false`. Idempotent (`disposed` flag prevents double-disposal).
|
|||
|
|
|
|||
|
|
### Connection Management
|
|||
|
|
- **`void Create(string ConnectString)`**
|
|||
|
|
Stores the device path (`ConnectString`) for later use in `EndConnect`. Does *not* open the device.
|
|||
|
|
|
|||
|
|
- **`IAsyncResult BeginConnect(AsyncCallback cb, object state)`**
|
|||
|
|
Initiates asynchronous connection. Enqueues a work item to invoke `NetCallbackFix`, which in turn invokes the callback on the thread pool. *Actual device opening occurs in `EndConnect`*.
|
|||
|
|
|
|||
|
|
- **`void EndConnect(IAsyncResult ar)`**
|
|||
|
|
Opens the device using `ConnectString` (set via `Create`) via three `CreateFile` calls:
|
|||
|
|
- `_HIDHandle`: for attribute queries (no access rights)
|
|||
|
|
- `_ReadHandle`: for reading input reports (`GENERIC_READ`)
|
|||
|
|
- `_WriteHandle`: for writing output reports (`GENERIC_WRITE`)
|
|||
|
|
Retrieves device attributes (`HidD_GetAttributes`), capabilities (`GetDeviceCapabilities`), and input report buffer size (`GetInputReportBufferSize`). Flushes the input queue. Sets `_Connected = true`. Throws on handle failure or attribute retrieval failure.
|
|||
|
|
|
|||
|
|
- **`IAsyncResult BeginDisconnect(bool reuseSocket, AsyncCallback cb, object state)`**
|
|||
|
|
Asynchronous disconnect. Enqueues a work item to invoke the callback. *Actual cleanup is deferred to `EndDisconnect`*.
|
|||
|
|
|
|||
|
|
- **`void EndDisconnect(IAsyncResult asyncResult)`**
|
|||
|
|
Closes all three handles (`_HIDHandle`, `_ReadHandle`, `_WriteHandle`) and sets `_Connected = false`.
|
|||
|
|
|
|||
|
|
### Properties
|
|||
|
|
- **`bool Connected { get; }`**
|
|||
|
|
Returns `_Connected`.
|
|||
|
|
|
|||
|
|
- **`string ConnectString { get; }`**
|
|||
|
|
Returns `Device_Name` (set via `Create`).
|
|||
|
|
|
|||
|
|
- **`System.Net.Sockets.SocketFlags Flags { get; set; }`**
|
|||
|
|
Property required by `IConnection` interface; unused in this implementation.
|
|||
|
|
|
|||
|
|
- **`string GetConnectionData()`**
|
|||
|
|
Returns `""` (empty string). No meaningful data exposed.
|
|||
|
|
|
|||
|
|
- **`double GetCurrentDownloadRate()`**
|
|||
|
|
Returns `0D`. Rate tracking not implemented.
|
|||
|
|
|
|||
|
|
- **`double GetCurrentUploadRate()`**
|
|||
|
|
Returns `0D`. Rate tracking not implemented.
|
|||
|
|
|
|||
|
|
### Static Utility
|
|||
|
|
- **`static string GetFirstConnectString()`**
|
|||
|
|
Scans all HID devices on the system to find the first device matching `DTS_VID` (`0x1CB9`) and `HIDSLICE_PID` (`0x0003`). Returns the device’s path string (e.g., `\\?\hid#vid_1cb9&pid_0003#...`) or `string.Empty` if not found. Uses `HIDDeclarations.HidD_GetHidGuid`, `DeviceManagement.FindDeviceFromGuid`, and `HIDDeclarations.HidD_GetAttributes`.
|
|||
|
|
|
|||
|
|
### I/O Operations
|
|||
|
|
- **`IAsyncResult BeginSend(byte[] buffer, int offset, int size, AsyncCallback cb, object state)`**
|
|||
|
|
Initiates asynchronous send. Validates `_ReadHandle` and `_WriteHandle` are valid. Enqueues callback via `ThreadPool.QueueUserWorkItem`. *Actual transmission occurs in `EndSend`*.
|
|||
|
|
|
|||
|
|
- **`int EndSend(IAsyncResult ar)`**
|
|||
|
|
Writes `buffer[offset..offset+size)` to the device in chunks, respecting `OutputReportByteLength`.
|
|||
|
|
- Prepends each chunk with report ID `0x00` at index `0`.
|
|||
|
|
- Uses `HIDevice.OutputReport.Write` for each chunk.
|
|||
|
|
Returns `size` (number of bytes sent). Throws on invalid handles or write failure.
|
|||
|
|
|
|||
|
|
- **`IAsyncResult BeginReceive(byte[] buffer, int offset, int size, AsyncCallback cb, object state)`**
|
|||
|
|
Initiates asynchronous receive. Validates handles. Enqueues callback. *Actual read occurs in `EndReceive`*.
|
|||
|
|
|
|||
|
|
- **`int EndReceive(IAsyncResult ar)`**
|
|||
|
|
Reads one input report into `InputReportBuffer` via `HIDevice.InputReport.Read`. Copies data (skipping report ID at index `0`) into `buffer[offset..]`. Returns `size` if successful, `0` otherwise. Sets `IsCompleted = true` and signals `AsyncWaitHandle`.
|
|||
|
|
|
|||
|
|
### Unsupported Operations (Throw `NotSupportedException`)
|
|||
|
|
- `BeginAccept`, `EndAccept`, `Bind(int)`, `Listen(int)`
|
|||
|
|
These methods are implemented only to satisfy `IConnection` but are not applicable to HID device communication.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. Invariants
|
|||
|
|
|
|||
|
|
- **Device Identity**: Only devices with `VendorID == 0x1CB9` and `ProductID == 0x0003` are accepted.
|
|||
|
|
- **Handle State**: `_HIDHandle`, `_ReadHandle`, and `_WriteHandle` must be valid (non-`INVALID_HANDLE_VALUE`) before `BeginSend`/`BeginReceive` succeeds.
|
|||
|
|
- **Connection State**: `_Connected` is `true` only after successful completion of `EndConnect`, and `false` otherwise (including after `EndDisconnect` or disposal).
|
|||
|
|
- **Report Structure**:
|
|||
|
|
- Input reports: Data starts at index `1`; index `0` is the report ID (ignored).
|
|||
|
|
- Output reports: Data starts at index `1`; index `0` is report ID (set to `0`).
|
|||
|
|
- **Buffer Size**: `InputReportBuffer` is sized to `_MyHID.Capabilities.InputReportByteLength` during `EndConnect`.
|
|||
|
|
- **Disposal Safety**: `Dispose` is idempotent (`disposed` flag prevents re-entry).
|
|||
|
|
- **Asynchronous Pattern**: All async operations (`BeginConnect`, `BeginSend`, etc.) use `HIDUSBRecAsyncResult` and delegate callback invocation to `NetCallbackFix`, which runs on a thread pool thread.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. Dependencies
|
|||
|
|
|
|||
|
|
### External Dependencies
|
|||
|
|
- **Windows API (via interop)**:
|
|||
|
|
- `FileIODeclarations.CreateFile`, `CloseHandle`, `FILE_SHARE_*`, `GENERIC_*`, `INVALID_HANDLE_VALUE`, `OPEN_EXISTING`
|
|||
|
|
- `HIDDeclarations.HidD_GetHidGuid`, `HidD_GetAttributes`
|
|||
|
|
- **`DTS.DASLib.Connection.USBFramework`**:
|
|||
|
|
- `HIDevice` class (used for device attributes, capabilities, input/output report handling)
|
|||
|
|
- `DeviceManagement` class (used in `GetFirstConnectString`)
|
|||
|
|
- **`DTS.DASLib.DASResource`**:
|
|||
|
|
- `Strings` resource class (for localized error messages, e.g., `Strings.HIDUSBConnection_EndConnect_Err1`)
|
|||
|
|
- **.NET Framework Core**:
|
|||
|
|
- `System.Runtime.InteropServices` (for `Marshal.SizeOf`, `SECURITY_ATTRIBUTES`)
|
|||
|
|
- `System.Threading` (for `ThreadPool`, `ManualResetEvent`)
|
|||
|
|
- `System.Windows.Forms.MessageBox` (used in `NetCallbackFix` for exception reporting—*potential runtime dependency on WinForms*).
|
|||
|
|
|
|||
|
|
### Implemented Interfaces
|
|||
|
|
- `IConnection` (interface defining the contract; not shown in source but referenced).
|
|||
|
|
|
|||
|
|
### Inferred Consumers
|
|||
|
|
- Any code requiring `IConnection` to communicate with the *HIDSLICE* device (e.g., data acquisition logic, device enumeration UI).
|
|||
|
|
- `GetFirstConnectString` is likely called by device discovery components.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. Gotchas
|
|||
|
|
|
|||
|
|
- **Misleading Async Pattern**: `BeginConnect`/`BeginSend`/`BeginReceive` do *not* perform actual I/O; they only enqueue a callback. All work happens in `End*` methods. This deviates from typical .NET async patterns where `Begin*` initiates the operation.
|
|||
|
|
- **No Overlapped I/O**: Despite using `CreateFile` with overlapped semantics (implied by `_ReadHandle`/`_WriteHandle`), the code does *not* use `OVERLAPPED` structures or `ReadFile`/`WriteFile` with completion callbacks. Instead, it relies on synchronous `HIDevice.InputReport.Read`/`Write`, which may block the thread pool thread.
|
|||
|
|
- **Hardcoded Report ID**: Output reports always use `0x00` as the report ID. This assumes the device expects report ID `0`; mismatched IDs will cause silent failures or device errors.
|
|||
|
|
- **WinForms Dependency in Error Handling**: `NetCallbackFix` shows a `MessageBox` on exceptions—a severe anti-pattern for non-UI threads or server environments.
|
|||
|
|
- **`GetFirstConnectString` Hack**: The method scans *all* HID devices and stops at the first match. If multiple *HIDSLICE* devices are connected, behavior is undefined (first found wins).
|
|||
|
|
- **Buffer Size Mismatch in `EndReceive`**: `EndReceive` copies `InputReportBuffer.Length - 1` bytes into the user buffer, but returns `rar.size` regardless. If `rar.size < InputReportBuffer.Length - 1`, the caller may read uninitialized buffer data beyond the actual received payload.
|
|||
|
|
- **No Timeout Handling**: No mechanism for read/write timeouts; operations may block indefinitely.
|
|||
|
|
- **`Flags` Property Unused**: The `SocketFlags` property is implemented but never used.
|
|||
|
|
- **`BeginAccept`/`EndAccept` Misuse**: Throwing `NotSupportedException` for connection-oriented methods suggests this class was adapted from a socket-based `IConnection` implementation without full refactoring.
|