For help in determining if a variable is a value or reference types, Visual Basic provides the IsReference function.. For example, when calling ReadFile to read data from a device, the a
Trang 1string 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;
Trang 2unManagedBuffer = 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
T h i s i s a n e x a m p l e d e c l a r a t i o n f o r t h e A P I f u n c t i o n 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)
Trang 3• 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
Trang 4understand 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 00000011b 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
Trang 5For 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 funcinforma-tion 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
Trang 6value, 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 ) ]
Trang 7Some 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.
Trang 88$ 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.
Trang 98% 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);
Trang 10internal 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 applicainforma-tion can increment the index until the function returns zero, indicating that the array has no more interfaces