Chapter 10 246 string from the pointer (IntPtr pDevicePathName) returned by an API func- tion: 8$ Dim devicePathName as String = "" devicePathName = Marshal.PtrToStringAuto(pDevicePathName) 8% String devicePathName = ""; devicePathName = Marshal.PtrToStringAuto(pDevicePathName); The MarshalAs attribute defines an array’s size to enable accessing the array in a structure returned by unmanaged code. This example declares a 16-byte array parameter that will hold a GUID from a structure returned by an API function: 8$ <MarshalAs(UnmanagedType.ByValArray, _ ArraySubType:=UnmanagedType.U1, SizeConst:=16)> _ Public dbcc_classguid() _ As Byte 8% [ MarshalAs( UnmanagedType.ByValArray, ArraySubType=UnmanagedType.U1, SizeConst=16 ) ] public Byte[] dbcc_classguid; The GUID is marshaled into the byte array as an UnmanagedType.ByValArray. The ArraySubType field defines the array’s elements as unsigned, 1-byte (U1) values and the SizeConst field sets the array’s size as 16 bytes. In an asynchronous read or write operation, an application may need to ensure that a variable or structure passed to an unmanaged function remains in the same memory location after the function returns. Doing so enables other unmanaged functions to access the variable or structure when completing the asynchronous operation. The Marshal.AllocHGlobal method can help by allo- cating memory that the garbage collector won’t move: 8$ Dim inputReportBuffer(2) As Byte Dim unManagedBuffer As IntPtr unManagedBuffer = Marshal.AllocHGlobal(inputReportBuffer.Length) 8% Byte[] inputReportBuffer = {0,0,0}; IntPtr unManagedBuffer = IntPtr.Zero; Detecting Devices 247 unManagedBuffer = Marshal.AllocHGlobal(inputReportBuffer.Length); The Marshal.FreeHGlobal method frees allocated memory when the applica- tion no longer needs to access the memory: 8$ Marshal.FreeHGlobal(unManagedBuffer) 8% Marshal.FreeHGlobal(unManagedBuffer); To ensure that code to free memory or other resources executes, place the code in the Finally block of a Try Catch Finally statement. The examples in this book omit the Try statements. &GENCTKPIC(WPEVKQP This is an example declaration for the API function HidD_GetNumInputBuffers, which applications can use to learn the number of Input reports that the driver for a HID-class device can store: 8$ <DllImport("hid.dll", SetLastError:=True)> _ Shared Function HidD_GetNumInputBuffers _ (ByVal HidDeviceObject As SafeFileHandle, _ ByRef NumberBuffers As Int32) _ As Boolean End Function 8% [ DllImport( "hid.dll", SetLastError=true ) ] internal static extern Boolean HidD_GetNumInputBuffers ( SafeFileHandle HidDeviceObject, ref Int32 NumberBuffers ); The declaration contains this information: • A DllImport attribute that names the file that contains the function’s exe- cutable code (hid.dll). The optional SetLastError field is set to true to enable retrieving error codes using the GetLastWin32Error method. Instead of DllImport, Visual Basic applications can use a Declare state- ment, but Dllimport offers more control. • The function’s name (HidD_GetNumInputBuffers). • The parameters the function will pass to the operating system (HidDevice- Object, NumberBuffers). • The data types of the values passed (SafeFileHandle, Int32). Chapter 10 248 • Whether the function passes parameters by value or by reference. The default is by value. Visual Basic supports the optional ByVal modifier. To pass by reference, precede the parameter name with ByRef (Visual Basic) or ref (Visual C#). The function passes HidDeviceObject by value and Num- berBuffers by reference. • The data type of the value returned for the function (Boolean). A few API calls have no return value, and Visual Basic can declare these functions as subroutines. In Visual Basic, the declaration must be in the Declarations section of a file. In Visual C#, the extern modifier indicates that the function resides in a differ- ent file. %CNNKPIC(WPEVKQP After declaring a function and any parameters to be passed, an application can call the function. This is a call to the HidD_GetNumInputBuffers function declared above: 8$ Dim success As Boolean success = HidD_GetNumInputBuffers _ (hidDeviceObject, _ numberOfInputBuffers) 8% Boolean success = false; success = HidD_GetNumInputBuffers (hidDeviceObject, ref numberOfInputBuffers); The hidDeviceObject parameter is a SafeFileHandle returned previously by the CreateFile function, and numberOfInputBuffers is an Int32. The Visual C# code must use the ref modifier to pass numberOfInputBuffers by reference. If the function returns with success = True, numberOfInputBuffers contains the number of Input buffers. /CPCIKPI&CVC Understanding how to pass data to API functions and use data returned by API functions requires understanding .NET’s data types and how the CLR passes them to unmanaged code. The explanations below provide a background to Detecting Devices 249 understand the example code in this and later chapters. If the details seem obscure at this point, you can skip ahead and come back as needed. &CVC6[RGU The header files for API functions use many data types that the .NET Frame- work doesn’t support. To specify a variable’s type for an API call, in many cases you can use a .NET type of the same length. For example, a DWORD is a 32-bit integer, so a .NET application can declare a DWORD as an Int32. A GUID translates to .NET’s System.Guid type. For pointers, .NET provides the IntPtr type, whose size adjusts as needed to 32 or 64 bits depending on the plat- form. IntPtr.Zero is a null pointer. A parameter defined in C as a HANDLE can use an IntPtr, but a safer and more reliable option for some handles is a SafeHandle object. With an IntPtr reference to a handle, in some situations, an exception can “leak” a handle, and a finalizer can corrupt a handle still in use in an asynchronous operation. Recy- cling of IntPtr handles can expose data that belongs to another resource. Safe- Handle objects don’t have these vulnerabilities. The SafeHandle class is abstract. To use a SafeHandle object, you can use one of the provided classes derived from SafeHandle or derive a new class from Safe- Handle. Devices accessed via ReadFile and WriteFile can use the SafeFileHan- dle class. 2CUUKPI8CTKCDNGU Every parameter passed to a function has both an element type and a passing mechanism. The element type is value or reference, and the passing mechanism is by value or by reference. The element type determines in part the effect of the passing mechanism. A value type contains data. For example, a Byte variable assigned a value of 3 consists of one byte with the value 00000011 b . Value types include all numeric data types; the Boolean, Char, and Date types; structures, even if their members are reference types; and enumerations. A reference type contains a reference, or pointer, that specifies the location of the variable’s data, which resides elsewhere in memory. A 2-byte array variable contains the location where the array’s 2 bytes are stored. Reference types include Strings; arrays, even if their elements are value types; classes; and delegates. Chapter 10 250 For help in determining if a variable is a value or reference types, Visual Basic provides the IsReference function. The function returns true if a variable is a reference type or false if a value type. Whether to pass a parameter by value or by reference depends on what informa- tion the function expects, the element type being passed, and in some cases whether the type is blittable (defined below). Sometimes multiple ways can achieve the same result. Passing a value type by value passes a copy of the variable’s value. If the called function changes the value of the variable or its members, the calling function doesn’t see the change. For example, when calling ReadFile to read data from a device, the application passes an Int32 variable that contains the number of bytes requested from the device. The called function uses the passed value but doesn’t have to return the value to the calling application, so the application can pass the variable, which is a value type, by value. Passing a value type by reference passes a pointer to the variable’s data. If the called function changes the variable or its members, the calling application sees the changes. An example, again using ReadFile, is passing an Int32 variable by reference to hold the number of bytes the function returns. The called function writes a value to the variable, and when the function returns, the calling appli- cation sees the value written. Passing a reference type by value also passes a pointer to the variable’s data, but the effect varies depending on whether the type is blittable. A blittable type is a .NET data type that managed and unmanaged code represent in the same way. Blittable types include Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Single, Double, and IntPtr as well as SafeHandles used as IN parameters. When an application passes a blittable, reference type by value to an unman- aged function, the application passes a reference to the original variable. To pre- vent the garbage collector from moving the variable while the function executes, the CLR pins the variable in memory. The calling application sees changes to the variable’s value but not changes to the variable’s instance. Passing a reference to the original variable in this way reduces overhead and improves performance compared to passing the variable by value. An example of passing a blittable, reference type by value is passing a Byte array in a synchronous call to ReadFile, which expects a pointer to an array that the function will fill with data read from the device. Because a Byte array is a refer- ence type and a Byte is a blittable type, if the application passes the array by Detecting Devices 251 value, the called function receives a pointer to the original array. The function writes the data to the array, and when the function returns, the calling applica- tion can access the new data. (For non-blittable types, the CLR converts the data to a format the function accepts and passes a pointer to the converted data.) The calling application doesn’t see changes the called function makes to the variable’s instance, only changes to its value. For example, if the called function sets the variable to Nothing/null, the calling application doesn’t see the change. Passing a reference type by reference passes a pointer that points to a pointer to the variable’s data. The calling application sees changes to the variable and to the variable’s instance. The examples in this book don’t use this passing mecha- nism. 2CUUKPI5VTWEVWTGU Some API functions pass and return structures that can contain multiple items of different types. The header files contain declarations for the structures in C syntax. A .NET application can usually declare an equivalent structure (a Visual Basic Structure or Visual C# struct) or a class that contains the items in the structure. To ensure that the managed and unmanaged code agree on the layout and alignment of the structure’s members, a structure’s declaration or class defini- tion can set the StructLayout attribute to LayoutKind.Sequential. 8$ <StructLayout(LayoutKind.Sequential)> 8% [ StructLayout( LayoutKind.Sequential ) ] The Visual Basic and Visual C# compilers always specify LayoutKind.Sequen- tial for value types, which include structures but not classes, so specifying Lay- outKind.Sequential in code is optional for structures. The optional CharSet field can determine whether strings are converted to ANSI or Unicode before being passed to unmanaged code. CharSet.Auto selects 8-bit ANSI or 16-bit Unicode characters depending on the target plat- form. A DllImport attribute can also use the CharSet field. 8$ <StructLayout(LayoutKind.Sequential, CharSet:=CharSet. Auto)> 8% [ StructLayout( LayoutKind.Sequential, CharSet=CharSet. Auto ) ] Chapter 10 252 Some structures are difficult or impractical to duplicate in Visual Basic or Visual C#. A solution is to use a generic buffer of the expected size. The appli- cation can fill the buffer before passing it and extract returned data from the buffer as needed. (KPFKPI;QWT&GXKEG The Windows API provides a series of SetupDi_ API functions that enable applications to find all devices in a device interface class and to obtain a device path name for each device. The CreateFile function can use the device path name to obtain a handle for accessing the device. As Chapter 8 explained, these functions can be useful in finding devices that use the WinUSB driver, HID-class devices that perform vendor-specific functions, and some devices with vendor-specific drivers. Obtaining a device path name requires these steps: 1. Obtain the device interface GUID. 2. Request a pointer to a device information set with information about all installed and present devices in the device interface class. 3. Request a pointer to a structure that contains information about a device interface in the device information set. 4. Request a structure containing a device interface’s device path name. 5. Extract the device path name from the structure. The application can then use the device path name to open a handle for com- municating with the device. Table 10-1 lists the API functions that applications can use to perform these tasks. The following code shows how to use API functions to find a device and obtain its device path name. For complete Visual C# and Visual Basic applications that demonstrate how to use these functions, visit www.Lvr.com. 1DVCKPKPIVJG&GXKEG+PVGTHCEG)7+& As Chapter 8 explained, for many drivers, applications can obtain a device interface GUID from a C header file or other declaration provided with a driver. The device’s INF file should contain the same GUID. For the HID class, Windows provides an API function to obtain the GUID defined in hidclass.h. Detecting Devices 253 8$ Definitions <DllImport("hid.dll", SetLastError:=True)> Sub HidD_GetHidGuid (ByRef HidGuid As System.Guid) End Sub Use Dim hidGuid As System.Guid HidD_GetHidGuid(hidGuid) 8% Definitions [ DllImport( "hid.dll", SetLastError=true ) ] public static extern void HidD_GetHidGuid( ref System.Guid HidGuid ); Use System.Guid hidGuid; HidD_GetHidGuid( ref hidGuid ); For other GUIDs, you can specify a a constant GUID value as a string and con- vert the string to a System.Guid object. 8$ Definitions Public Const WINUSB_DEMO_GUID_STRING As String = _ "{42CA71EC-CE1C-44c2-82DE-87D8D8FF6C1E}" Use Dim myGuid As New System.Guid(WINUSB_DEMO_GUID_STRING) Table 10-1: Applications use these functions to find devices and obtain device path names to enable accessing devices. #2+(WPEVKQP & 2WTRQUG HidD_GetHidGuid hid Retrieve the device interface GUID for the HID class. SetupDiDestroyDeviceInfoList setupapi Free resources used by SetupDiGetClassDevs. SetupDiGetClassDevs setupapi Retrieve a device information set for the devices in a specified class. SetupDiGetDeviceInterfaceDetail setupapi Retrieve a device path name. SetupDiEnumDeviceInterfaces setupapi Retrieve information about a device in a device information set. Chapter 10 254 8% Definitions public const string WINUSB_DEMO_GUID_STRING = "{42CA71EC-CE1C-44c2-82DE-87D8D8FF6C1E}"; Use System.Guid myGuid = new System.Guid( WINUSB_DEMO_GUID_STRING ); 4GSWGUVKPIC2QKPVGTVQC&GXKEG+PHQTOCVKQP5GV The SetupDiGetClassDevs function can return a pointer to an array of struc- tures containing information about all devices in the device interface class spec- ified by a GUID. 8$ Definitions <DllImport("setupapi.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _ Shared Function SetupDiGetClassDevs _ (ByRef ClassGuid As System.Guid, _ ByVal Enumerator As IntPtr, _ ByVal hwndParent As IntPtr, _ ByVal Flags As Int32) _ As IntPtr End Function Use Public Const DIGCF_PRESENT As Int32 = 2 Public Const DIGCF_DEVICEINTERFACE As Int32 = &H10 Dim deviceInfoSet As IntPtr deviceInfoSet = SetupDiGetClassDevs _ (myGuid, _ IntPtr.Zero, _ IntPtr.Zero, _ DIGCF_PRESENT Or DIGCF_DEVICEINTERFACE) 8% Definitions [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)] internal static extern IntPtr SetupDiGetClassDevs (ref System.Guid ClassGuid, IntPtr Enumerator, IntPtr hwndParent, Int32 Flags); Detecting Devices 255 Use internal const Int32 DIGCF_PRESENT = 2; internal const Int32 DIGCF_DEVICEINTERFACE = 0X10; IntPtr deviceInfoSet; deviceInfoSet = SetupDiGetClassDevs ( ref myGuid, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE ); &GVCKNU For HID-class devices, the ClassGuid parameter is the HidGuid value returned by HidD_GetHidGuid. For other drivers, the application can pass a reference to the appropriate GUID. The example passes null pointers for Enumerator and hwndParent. The Flags parameter uses system constants defined in setu- papi.h. The flags in the example cause the function to look for device interfaces that are currently attached and enumerated members of the class identified by the ClassGuid parameter. The returned deviceInfoSet value is a pointer to a device information set that contains information about all attached and enumerated devices in the specified device interface class. The device information set contains a device information element for each device in the set, or array. Each device information element contains a handle to a device’s devnode (a structure that represents the device) and a linked list of device interfaces associated with the device. When finished using the device information set, the application should free the resources used by calling SetupDiDestroyDeviceInfoList as described later in this chapter. +FGPVKH[KPIC&GXKEG+PVGTHCEG A call to SetupDiEnumDeviceInterfaces retrieves a pointer to a structure that identifies a device interface in the previously retrieved deviceInfoSet array. The call passes an array index that specifies a device interface. To retrieve informa- tion about all devices in an array, an application can increment the index until the function returns zero, indicating that the array has no more interfaces. . object. 8$ Definitions Public Const WINUSB_DEMO_GUID_STRING As String = _ "{42CA71EC-CE1C-44c2-82DE-87D8D8FF6C1E}" Use Dim myGuid As New System.Guid(WINUSB_DEMO_GUID_STRING) Table 10-1:. Definitions public const string WINUSB_DEMO_GUID_STRING = "{42CA71EC-CE1C-44c2-82DE-87D8D8FF6C1E}"; Use System.Guid myGuid = new System.Guid( WINUSB_DEMO_GUID_STRING ); 4GSWGUVKPIC2QKPVGTVQC&GXKEG+PHQTOCVKQP5GV The. device. As Chapter 8 explained, these functions can be useful in finding devices that use the WinUSB driver, HID-class devices that perform vendor-specific functions, and some devices with vendor-specific