Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Accessing Device Drivers from C#


We can combine the ideas and code snippets discussed to create Example 8. In the downloadable file, DeviceDriver.cs, I have a few more constants than I strictly need for this sample. They have been cut from Example 8 for brevity. Note that in this listing, I’ve extended the namespace System.IO rather than System.Runtime.InteropServices shown previously simply to show that any namespace could be created or extended. As shown, when declaring constants “int” for 0x80000000 (or larger) we used “unchecked”.

Example 8: Combining the code snippets
namespace System.IO {
    using System;
    using System.Runtime.InteropServices;
    using System.IO;
    [
    System.Runtime.InteropServices.ComVisible(false), 
    System.Security.SuppressUnmanagedCodeSecurityAttribute()
    ]
    public class Win32Methods     {
        public const int 
            INVALID_HANDLE_VALUE    = (-1),
            NULL                = 0,
            ...
            ERROR_SUCCESS        = 0,
            FILE_READ_DATA        = (0x0001),
            ...            
            FILE_SHARE_READ        = 0x00000001,
            ...
            OPEN_EXISTING        = 3,
            GENERIC_READ              = unchecked((int)0x80000000),
            ...            
            METHOD_BUFFERED         = 0,
            ...                
            METHOD_NEITHER          = 3,
            FILE_ANY_ACCESS         = 0,
            ...
            FILE_DEVICE_VIRTUAL_DISK = 0x00000024;

    [DllImport("Kernel32.dll", ExactSpelling=true, CharSet=CharSet.Auto, 
      SetLastError=true)]public static extern bool CloseHandle(int 
    hHandle); 
  
    // CreateFile is is Overloaded for having SecurityAttributes or not 

    [DllImport("Kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
    public static extern int CreateFile(String lpFileName, 
    int dwDesiredAccess, int dwShareMode,IntPtr lpSecurityAttributes, 
    int dwCreationDisposition,int dwFlagsAndAttributes, 
    int hTemplateFile);

    [DllImport("Kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
    public static extern int CreateFile(String lpFileName, int 
    dwDesiredAccess, int dwShareMode, SECURITY_ATTRIBUTES 
    lpSecurityAttributes, int dwCreationDisposition,int 
    dwFlagsAndAttributes,int hTemplateFile);

    // DeviceIoControl is Overloaded for byte or int data

    [DllImport("Kernel32.dll", CharSet=CharSet.Auto, 
    SetLastError = true)] public static extern bool DeviceIoControl(
    int hDevice, int dwIoControlCode,  byte[] InBuffer, int     
    nInBufferSize, byte[] OutBuffer,int nOutBufferSize,ref int 
    pBytesReturned, int pOverlapped);

    [DllImport("Kernel32.dll", CharSet=CharSet.Auto, 
    SetLastError = true)]public static extern bool DeviceIoControl(
    int hDevice, int dwIoControlCode,  int[] InBuffer, int nInBufferSize, 
    int[] OutBuffer,int nOutBufferSize,ref int pBytesReturned, int 
    pOverlapped);

    // These replace Macros in winioctl.h

    public static int CTL_CODE( int DeviceType, int Function, 
    int Method, int Access ) {
      return (((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) 
        | (Method) ) ;
    }     
    public int DEVICE_TYPE_FROM_CTL_CODE(int ctrlCode)     { 
        return    (int)(( ctrlCode & 0xffff0000) >> 16) ;
    }
    [ StructLayout( LayoutKind.Sequential, CharSet=CharSet.Auto )]
    public struct SECURITY_ATTRIBUTES     {
        public int        nLength ;             // DWORD
        public IntPtr       lpSecurityDescriptor;    // LPVOID
        public int          bInheritHandle;        // BOOL
    }                                                
    }  // End win32methods
}    // end

Now we need a simple test program to use this code. With Visual Studio .NET you can build a project and include two files based on Example 8 and Example 9. Alternately, you can include both files (combined into DeviceDriver.cs, available online) in one file and build them in the IDE or on the command line.

Example 9: CallDriver class
public class CallDriver {
   public static void Main() {
    // Here's how we'd declare the sa, though we
    // won't be using it here
    Win32Methods.SECURITY_ATTRIBUTES sa 
        = new Win32Methods.SECURITY_ATTRIBUTES();
    int hFileHandle = new int();
    hFileHandle = Win32Methods.INVALID_HANDLE_VALUE;

    hFileHandle = Win32Methods.CreateFile("\\\\.\\MyDriver",
        Win32Methods.GENERIC_READ | Win32Methods.GENERIC_WRITE,
        0, (IntPtr) 0, Win32Methods.OPEN_EXISTING,0,Win32Methods.NULL);

    if (hFileHandle == Win32Methods.INVALID_HANDLE_VALUE) {    
        MessageBox.Show("Cannot Open theDriver!", "LAME");
        // This is usually a place to throw an exception, perhaps by:
        // throw new FileNotFoundException(Res.GetString(Res.IOError));
        return ;
    }
    try     {            
        int IOCTL_READ_FILE = new int();
        // Note you get to define whatever code you want for the IOCTL
        IOCTL_READ_FILE = Win32Methods.CTL_CODE (
           Win32Methods.FILE_DEVICE_UNKNOWN, (int) 0x969, 
           Win32Methods.METHOD_BUFFERED,Win32Methods.FILE_ANY_ACCESS);

        byte[] sOutput = new byte[512];
        byte[] Input = new byte[8] ;
        int bytesReturned = new int();
                
        if (Win32Methods.DeviceIoControl(hFileHandle, 
           IOCTL_READ_FILE,Input,0,sOutput,512,ref bytesReturned,0 ))
            MessageBox.Show("Success", "IOCTL = ?" );
        else
            MessageBox.Show("Failure", "IOCTL = ?" );
            // show what's in sOutput
    }  // try - note normally we'd have an except to handle errors
    finally { // cleanup
        Win32Methods.CloseHandle(hFileHandle);            
    }            
   }
}

If you’re a driver writer then you already have a driver you can try this on. If not, you can try any driver sample with virtually any IOCTL. Changing this to write data to a Driver should be clear, and I hope the overloading of types shows how to change the format of the data that will get sent to the driver.

One simple example you can try this on is the FILEIO driver sample from the Walter Oney book Programming the Windows Driver Model, from Microsoft Press. There are also many simple driver examples in the various Windows DDK’s (NT, W2K, XP, and so on).

SetupApi

To open a driver more typically one uses the SetupDiXXX functions. They are in Setupapi.dll. For Example:

    SetupDiEnumDeviceInfo()
    SetupDiGetClassDevs()
    etc.

We can convert these APIs in the same manner as before, using Overloading as needed (see Example 10).

Example 10: Converting APIs
[DllImport("Setupapi.dll", CharSet=CharSet.Auto, SetLastError = true)]
public static extern bool SetupDiEnumDeviceInfo(
int DeviceInfoSet,int MemberIndex,ref SP_DEVINFO_DATA DeviceInfoData);

[DllImport("Setupapi.dll", CharSet=CharSet.Auto, SetLastError = true)]
public static extern int SetupDiGetClassDevs(
ref Guid ClassGuid, ref String Enumerator,int hwndParent,int Flags );
            
[DllImport("Setupapi.dll", CharSet=CharSet.Auto, SetLastError = true)]
public static extern int SetupDiGetClassDevs(
IntPtr ClassGuid, ref String Enumerator,int hwndParent,int Flags );
...

Some of the APIs need to return data unmanaged as shown in Example 11.

Example 11: Some of the APIs need to return data unmanaged

[DllImport("Setupapi.dll", CharSet=CharSet.Auto, SetLastError = true)]
public static extern bool SetupDiGetDeviceInstanceId(
int DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData,
[MarshalAs(UnmanagedType.LPWStr)] String DeviceInstanceId,
int DeviceInstanceIdSize, ref int RequiredSize);

GUIDS are supported by the .NET Framework:

Guid GUID_DEVINTERFACE_TOASTER = new 
Guid(“781EF630-72B2-11d2-B852-00C04FAD5171”);

An example showing the use of many of these functions is TalkToToaster.cs (available online), which uses the Toaster sample driver from the Windows DDK and can be downloaded as part of this month’s source code.

Loose Ends

There are several loose ends that we have not covered here. We didn’t present an example of using SecurityAttributes, though this should be straightforward with the material presented here. We also didn’t present an example of using Overlapped IO.

There are a couple ways to do this. There is support for C# Overlapped structures in System.Threading. There are classes System.Threading.Overlapped and System.Threading.NativeOverlapped. There are methods Overlapped.Pack and Overlapped.Unpack to transfer data from a managed “Overlapped” class to an unmanaged “NativeOverlapped” structure. In managed code, these should be used if at all possible.

The RTM (final released version) documentation has the following information on the Overlapped Class:

“The Overlapped type supports the .NET Framework infrastructure and is not intended to be used directly from your code.”

Despite this comment, there is a good sample called HandleRef using Overlapped IO that uses PInvoke as well as the keyword “null” to avoid having to Overload the definitions of some classes if the need was solely to deal with a NULL passed in place of some data type.

When we get a buffer back from ReadFile or DeviceIoControl, we might have a block of data that we need to decode. We can use a Structure, or choose to parsing data directly. One way is to use System.Runtime.InteropServices.Marshal.ReadInt32 (or ReadInt64). We can extract what we want using, based on knowing where the data actually is. For example:

 
 public const int Offset0 = 0;
 public const int Offset4 = 4;
 byte[] pDataBuf = new byte[256];
 // ... code to fill this from
 // DeviceIoControl or
 // etc. goes here...
 int Value1 =
     Marshal.ReadInt32(
     (int)pDataBuf,(int)Offset0);
 int Value2 =
     Marshal.ReadInt32(
     (IntPtr)((((long)pDataBuf)+
     (((long)Offset4)))));
 //... etc;

Finally, it is common to use an int or IntPtr to store the results of a native handle. In most cases this is sufficient. There is a class, System.Runtime.Interopservices.HandleRef, which can be used to wrap the handle returned via PInvoke. This will keep the managed object from being garbage collected and ensure the handle is valid for further use with PInvoke.

In summary, PInvoke, the Platform Invocation Services, combined with Overloading allows for reusable access to all the Win32 system calls from inside a managed language like C#. In particular, it easily allows for all the ones we’d ever need to communicate with a Device Driver.


David Union is a Senior Software Engineer at Vibren Technologies Inc. He can be reached at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.