Назад
Chapter 10
256
In some cases, such as when looking for a HID-class device with a specific Ven-
dor ID and Product ID, the application may need to request more information
before deciding whether a retrieved device interface is the desired one.
8$ Definitions
Public Structure SP_DEVICE_INTERFACE_DATA
Dim cbSize As Int32
Dim InterfaceClassGuid As Guid
Dim Flags As Int32
Dim Reserved As IntPtr
End Structure
<DllImport("setupapi.dll", SetLastError:=True)> _
Shared Function SetupDiEnumDeviceInterfaces _
(ByVal DeviceInfoSet As IntPtr, _
ByVal DeviceInfoData As IntPtr, _
ByRef InterfaceClassGuid As System.Guid, _
ByVal MemberIndex As Int32, _
ByRef DeviceInterfaceData As SP_DEVICE_INTERFACE_DATA) _
As Boolean
End Function
Use
Dim memberIndex As Int32 = 0
Dim MyDeviceInterfaceData As SP_DEVICE_INTERFACE_DATA
Dim success As Boolean
MyDeviceInterfaceData.cbSize = Marshal.SizeOf(MyDeviceInterfaceData)
success = SetupDiEnumDeviceInterfaces _
(deviceInfoSet, _
IntPtr.Zero, _
myGuid, _
memberIndex, _
MyDeviceInterfaceData)
Detecting Devices
257
8% Definitions
internal struct SP_DEVICE_INTERFACE_DATA
{
internal Int32 cbSize;
internal Guid InterfaceClassGuid;
internal Int32 Flags;
internal IntPtr Reserved;
}
[DllImport("setupapi.dll", SetLastError = true)]
internal static extern Boolean SetupDiEnumDeviceInterfaces
(IntPtr DeviceInfoSet,
IntPtr DeviceInfoData,
ref System.Guid InterfaceClassGuid,
Int32 MemberIndex,
ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData);
Use
Int32 memberIndex = 0;
MyDeviceInterfaceData = new SP_DEVICE_INTERFACE_DATA();
Boolean success = false;
MyDeviceInterfaceData.cbSize = = Marshal.SizeOf( MyDeviceInterfaceData );
success = SetupDiEnumDeviceInterfaces
(deviceInfoSet,
IntPtr.Zero,
ref myGuid,
memberIndex,
ref MyDeviceInterfaceData);
&GVCKNU
In the SP_DEVICE_INTERFACE_DATA structure, the cbSize parameter is
the size of the structure in bytes. The Marshal.SizeOf method returns the struc-
tures size.
The myGuid and deviceInfoSet parameters are values retrieved previously. The
DeviceInfoData parameter can be a pointer to an SP_DEVINFO_DATA struc-
ture that limits the search to a particular device instance or a null pointer. The
memberIndex parameter is an index to a structure in the deviceInfoSet array.
The MyDeviceInterfaceData parameter is a pointer to the
SP_DEVICE_INTERFACE_DATA structure that the function returns. The
Chapter 10
258
structure identifies a device interface of the requested type. The function
returns true on success.
4GSWGUVKPIC5VTWEVWTGYKVJVJG&GXKEG2CVJ0COG
The SetupDiGetDeviceInterfaceDetail function returns a structure that con-
tains a device path name for a device interface identified in an
SP_DEVICE_INTERFACE_DATA structure.
When calling this function for the first time, you dont know the size in bytes of
the DeviceInterfaceDetailData structure to pass in the DeviceInterfaceDetail-
DataSize parameter. Yet the function wont return the structure unless the func-
tion call passes the correct size. The solution is to call the function twice. The
first time, GetLastError returns the error The data area passed to a system call is
too small, but the RequiredSize parameter contains the correct value for Device-
InterfaceDetailDataSize. The second call passes the returned size value, and the
function returns the structure.
The code below doesnt pass a structure for the DeviceInterfaceDetailData
parameter. Instead, the code reserves a generic buffer, passes a pointer to the
buffer, and extracts the device path name directly from the buffer. The code
thus doesnt require a structure declaration, but I’ve included one to show the
contents of the returned buffer.
8$ Definitions
Public Structure SP_DEVICE_INTERFACE_DETAIL_DATA
Dim cbSize As Int32
Dim DevicePath As String
End Structure
<DllImport("setupapi.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Shared Function SetupDiGetDeviceInterfaceDetail _
(ByVal DeviceInfoSet As IntPtr, _
ByRef DeviceInterfaceData As SP_DEVICE_INTERFACE_DATA, _
ByVal DeviceInterfaceDetailData As IntPtr, _
ByVal DeviceInterfaceDetailDataSize As Int32, _
ByRef RequiredSize As Int32, _
ByVal DeviceInfoData As IntPtr) _
As Boolean
End Function
Detecting Devices
259
Use
Dim bufferSize As Int32
Dim detailDataBuffer As IntPtr
Dim success As Boolean
success = SetupDiGetDeviceInterfaceDetail _
(deviceInfoSet, _
MyDeviceInterfaceData, _
IntPtr.Zero, _
0, _
bufferSize, _
IntPtr.Zero)
detailDataBuffer = Marshal.AllocHGlobal(bufferSize)
Marshal.WriteInt32 _
(detailDataBuffer,
ConvertToInt32(IIf((IntPtr.Size = 4), 4 + Marshal.SystemDefaultCharSize, 8)))
success = SetupDiGetDeviceInterfaceDetail _
(deviceInfoSet, _
MyDeviceInterfaceData, _
detailDataBuffer, _
bufferSize, _
bufferSize, _
IntPtr.Zero)
8%
Definitions
internal struct SP_DEVICE_INTERFACE_DETAIL_DATA
{
internal Int32 cbSize;
internal String DevicePath;
}
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern Boolean SetupDiGetDeviceInterfaceDetail
(IntPtr DeviceInfoSet,
ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData,
IntPtr DeviceInterfaceDetailData,
Int32 DeviceInterfaceDetailDataSize,
ref Int32 RequiredSize,
IntPtr DeviceInfoData);
Chapter 10
260
Use
Int32 bufferSize = 0;
IntPtr detailDataBuffer;
Boolean success = false;
success = SetupDiGetDeviceInterfaceDetail
(deviceInfoSet,
ref MyDeviceInterfaceData,
IntPtr.Zero,
0,
ref bufferSize,
IntPtr.Zero);
detailDataBuffer = Marshal.AllocHGlobal( bufferSize );
Marshal.WriteInt32
(detailDataBuffer, (IntPtr.Size == 4) ? (4 + Marshal.SystemDefaultCharSize) : 8);
success = SetupDiGetDeviceInterfaceDetail
(deviceInfoSet,
ref MyDeviceInterfaceData,
detailDataBuffer,
bufferSize,
ref bufferSize,
IntPtr.Zero);
&GVCKNU
After calling SetupDiGetDeviceInterfaceDetail, bufferSize contains the value to
pass in the DeviceInterfaceDetailDataSize parameter in the next call. But before
calling the function again, the code needs to take care of a few things.
The second function call returns a pointer (detailDataBuffer) to an
SP_DEVICE_INTERFACE_DETAIL_DATA structure in unmanaged mem-
ory. The Marshal.AllocGlobal method uses the returned bufferSize value to
allocate memory for the structure.
The cbSize member of the structure passed in detailDataBuffer equals four
bytes for cbSize plus the width of one character for the device path name
(which is empty when passed to the function). The Marshal.WriteInt32
method copies the cbSize value into the first member of detailDataBuffer. The
IIf function (Visual Basic) or “?” conditional operator (Visual C#) selects the
correct value for 32- and 64-bit systems.
Detecting Devices
261
The second call to SetupDiGetDeviceInterfaceDetail passes the pointer to
detailDataBuffer and sets the deviceInterfaceDetailDataSize parameter equal to
the bufferSize value returned previously in RequiredSize.
When the function returns after the second call, detailDataBuffer points to a
structure containing a device path name.
'ZVTCEVKPIVJG&GXKEG2CVJ0COG
In detailDataBuffer, the first four bytes are the cbSize member. The string con-
taining the device path name begins at the fifth byte.
8$ Dim devicePathName As String = ""
Dim pDevicePathName As IntPtr = New IntPtr(detailDataBuffer.ToInt32 + 4)
devicePathName = Marshal.PtrToStringAuto(pDevicePathName)
Marshal.FreeHGlobal(detailDataBuffer)
8% String devicePathName = "";
IntPtr pDevicePathName = new IntPtr( detailDataBuffer.ToInt32() + 4 );
devicePathName = Marshal.PtrToStringAuto(pDevicePathName);
Marshal.FreeHGlobal(detailDataBuffer);
&GVCKNU
The pDevicePathName variable points to the string in the buffer. The Mar-
shal.PtrToString method retrieves the string from the buffer. When finished
with the buffer, Marshal.FreeHGlobal frees the memory previously allocated for
the buffer.
%NQUKPI%QOOWPKECVKQPU
When finished using the DeviceInfoSet returned by SetupDiGetClassDevs, the
application should call SetupDiDestroyDeviceInfoList to free resources.
8$ Definitions
<DllImport("setupapi.dll", SetLastError:=True)> _
Shared Function SetupDiDestroyDeviceInfoList _
(ByVal DeviceInfoSet As IntPtr) _
As Int32
End Function
Chapter 10
262
Use
SetupDiDestroyDeviceInfoList (deviceInfoSet)
8%
Definitions
[DllImport("setupapi.dll", SetLastError = true)]
internal static extern Int32 SetupDiDestroyDeviceInfoList
(IntPtr DeviceInfoSet);
Use
SetupDiDestroyDeviceInfoList( deviceInfoSet );
1DVCKPKPIC*CPFNG
An application can use a retrieved device path name to obtain a handle that
enables communicating with the device. Table 10-2 shows API functions
related to requesting a handle.
4GSWGUVKPIC%QOOWPKECVKQPU*CPFNG
After retrieving a device path name, an application is ready to open communi-
cations with the device. The CreateFile function requests a handle to an object,
which can be a file or another resource managed by a driver that supports han-
dle-based operations. For example, applications can request a handle to use in
exchanging reports with HID-class devices. For devices that use the WinUSB
driver, CreateFile obtains a handle the application uses to obtain a WinUSB
device handle for accessing a device.
The call to CreateFile can pass a SECURITY_ATTRIBUTES structure that
can limit access to the handle or IntPtr.Zero if the function doesnt need to limit
access.
Table 10-2: Applications can use CreateFile to request a handle to a device and
CloseHandle to free the resources used by a handle.
#2+(WPEVKQP &.. 2WTRQUG
CloseHandle kernel32 Free resources reserved by CreateFile. To close handles
for the SafeHandle and derived classes, use the Close
method, which calls CloseHandle internally.
CreateFile kernel32 Retrieve a handle for communicating with a device.
Detecting Devices
263
8$ Definitions
Friend Const FILE_ATTRIBUTE_NORMAL As Int32 = &H80
Friend Const FILE_FLAG_OVERLAPPED As Int32 = &H40000000
Friend Const FILE_SHARE_READ As Int32 = 1
Friend Const FILE_SHARE_WRITE As Int32 = 2
Friend Const GENERIC_READ As UInt32 = &H80000000UL
Friend Const GENERIC_WRITE As UInt32 = &H40000000
Friend Const OPEN_EXISTING As Int32 = 3
<DllImport("kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
Shared Function CreateFile _
(ByVal lpFileName As String, _
ByVal dwDesiredAccess As UInt32, _
ByVal dwShareMode As Int32, _
ByVal lpSecurityAttributes As IntPtr, _
ByVal dwCreationDisposition As Int32, _
ByVal dwFlagsAndAttributes As Int32, _
ByVal hTemplateFile As Int32) _
As SafeFileHandle
End Function
Use
Dim deviceHandle As SafeFileHandle
deviceHandle = CreateFile _
(devicePathName, _
GENERIC_WRITE Or GENERIC_READ, _
FILE_SHARE_READ Or FILE_SHARE_WRITE, _
IntPtr.Zero, _
OPEN_EXISTING, _
FILE_ATTRIBUTE_NORMAL Or FILE_FLAG_OVERLAPPED, _
0)
8%
Definitions
internal const Int32 FILE_ATTRIBUTE_NORMAL = 0X80;
internal const Int32 FILE_FLAG_OVERLAPPED = 0X40000000;
internal const Int32 FILE_SHARE_READ = 1;
internal const Int32 FILE_SHARE_WRITE = 2;
internal const UInt32 GENERIC_READ = 0X80000000;
internal const UInt32 GENERIC_WRITE = 0X40000000;
internal const Int32 OPEN_EXISTING = 3;
Chapter 10
264
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern SafeFileHandle CreateFile
(String lpFileName,
UInt32 dwDesiredAccess,
Int32 dwShareMode,
IntPtr lpSecurityAttributes,
Int32 dwCreationDisposition,
Int32 dwFlagsAndAttributes,
Int32 hTemplateFile);
Use
internal SafeFileHandle deviceHandle;
deviceHandle = CreateFile
(devicePathName,
(GENERIC_WRITE | GENERIC_READ),
FILE_SHARE_READ | FILE_SHARE_WRITE,
IntPtr.Zero,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
0);
&GVCKNU
The function passes a pointer to the devicePathName string returned by Setup-
DiGetDeviceInterfaceDetail. The dwDesiredAccess parameter requests
read/write access to the device. The dwShareMode parameter allows other pro-
cesses to access the device while the handle is open. The lpSecurityAttributes
parameter is a null pointer (or a pointer to a SECURITY_ATTRIBUTES
structure). The dwCreationDisposition parameter must be OPEN_EXISTING
for devices. For use with the WinUSB driver, the dwFlagsAndAttributes param-
eter must use FILE_FLAG_OVERLAPPED. The
FILE_ATTRIBUTE_NORMAL attribute indicates that no other attributes
such as hidden, read-only, or encrypted are set. The example passes zero for the
unused hTemplate parameter. The function returns a SafeFileHandle object.
%NQUKPIVJG*CPFNG
When finished communicating with a device, the application should free the
resources reserved by CreateFile.
8$ deviceHandle.Close()
Detecting Devices
265
8% deviceHandle.Close();
&GVCKNU
SafeFileHandle objects support the Close method, which marks the handle for
releasing and freeing resources. The method calls the CloseHandle API func-
tion internally.
&GVGEVKPI#VVCEJOGPVCPF4GOQXCN
Many applications find it useful to know when a device has been attached or
removed. On detecting when a device is attached, the application can begin
communicating with the device. On detecting when a device has been removed,
the application can stop attempting to communicate until detecting reattach-
ment. Windows provides device-notification functions for this purpose.
#DQWV&GXKEG0QVKHKECVKQPU
To request to be informed when a device is attached or removed, an applica-
tions form can register to receive notification messages for devices in a device
interface class. The operating system passes WM_DEVICECHANGE mes-
sages to the forms WndProc method (called WindowProc in C). An application
can override WndProc in a forms base class with a method that processes the
messages and then passes them to the base classs WndProc method. (The code
below shows how to do this.) Each notification contains a device path name
that the application can use to identify the device that the notification applies
to. Table 10-3 lists the API functions used in registering for device notifications.
The example that follows shows how to use the functions.
4GIKUVGTKPIHQT&GXKEG0QVKHKECVKQPU
Applications use the RegisterDeviceNotification function to request to receive
notification messages. The function requires a handle for the window or service
that will receive the notifications, a pointer to a
DEV_BROADCAST_DEVICEINTERFACE structure that holds information
about the request, and flags to indicate whether the handle is for a window or
service.
In the DEV_BROADCAST_DEVICEINTERFACE structure passed to Regis-
terDeviceNotification, the dbcc_devicetype member is set to
DBT_DEVTYP_DEVICEINTERFACE to specify that the application wants