Pointers

Until now you’ve seen no code using C/C++ style pointers. Only here, in the final paragraphs of the final pages of the book, does this topic arise, even though pointers are central to the C family of languages. In C#, pointers are relegated to unusual and advanced programming; typically they are used only when interoperating with COM.

C# supports the usual C pointer operators, listed in Table 22-1.

Table 22-1. C# pointer operators

Operator

Meaning

&

The address-of operator returns a pointer to the address of a value.

*

The dereference operator returns the value at the address of a pointer.

->

The member access operator is used to access the members of a type.

The use of pointers is almost never required, and is nearly always discouraged. When you do use pointers, you must mark your code with the C# unsafe modifier. The code is marked unsafe because with pointers you can manipulate memory locations directly, a feat otherwise impossible within a C# program. In unsafe code you can directly access memory, perform conversions between pointers and integral types, take the address of variables, and so forth. In exchange, you give up garbage collection and protection against uninitialized variables, dangling pointers, and accessing memory beyond the bounds of an array. In essence, unsafe code creates an island of C++-code within your otherwise safe C# application.

As an example of when this might be useful, you’ll read a file to the console by invoking two Win32 API calls: CreateFile and ReadFile . ReadFile takes, as its second parameter, a pointer to a buffer. The declaration of the two imported methods is not unlike those shown in Example 22-11.

Example 22-11. Declaring Win32 API methods for import into a C# program

[DllImport("kernel32", SetLastError=true)]
static extern unsafe int CreateFile(
   string filename,
   uint desiredAccess,
   uint shareMode,
   uint attributes,   
   uint creationDisposition,
   uint flagsAndAttributes,
   uint templateFile);

[DllImport("kernel32", SetLastError=true)]
static extern unsafe bool ReadFile(
   int hFile,
   void* lpBuffer, 
   int nBytesToRead,
   int* nBytesRead, 
   int overlapped);

You will create a new class APIFileReader whose constructor will invoke the CreateFile( ) method. The constructor takes a filename as a parameter, and passes that filename to the CreateFile( ) method:

public APIFileReader(string filename)
{
   fileHandle = CreateFile(
      filename,      // filename
      GenericRead,   // desiredAccess 
      UseDefault,    // shareMode
      UseDefault,    // attributes
      OpenExisting,  // creationDisposition 
      UseDefault,    // flagsAndAttributes
      UseDefault);   // templateFile
}

The APIFileReader class implements only one other method, Read( ), which invokes ReadFile( ), passing in the file handle created in the class constructor, along with a pointer into a buffer, a count of bytes to retrieve, and a reference to a variable which will hold the number of bytes read. It is the pointer to the buffer which is of interest to us here. To use this API call you must use a pointer.

Because you will access it with a pointer, the buffer needs to be pinned in memory; the .NET Framework cannot be allowed to move the buffer during garbage collection. To accomplish this, you will use the C# fixed keyword. Fixed allows you to get a pointer to the memory used by the buffer, and also to mark that instance so that the garbage collector won’t move it.

The block of statements following the fixed keyword creates a scope, within which the memory will be pinned. At the end of the fixed block the instance will be marked so that it can be moved. This is known as declarative pinning :

public unsafe int Read(byte[] buffer, int index, int count) 
{
   int bytesRead = 0;
   fixed (byte* bytePointer = buffer) 
   {
      ReadFile(
        fileHandle, 
        bytePointer +     index, 
        count, 
        &bytesRead, 0);
   }
   return bytesRead;
}

Notice that the method must be marked with the unsafe keyword. This allows you to create pointers and creates an unsafe context. To compile this you must use the /unsafe compiler option.

The test program instantiates the APIFileReader and an ASCIIEncoding object. It passes the filename to the constructor of the APIFileReader and then creates a loop to repeatedly fill its buffer by calling the Read( ) method which invokes the ReadFile API call. What it gets back is an array of bytes, which it converts to a string using the ASCIIEncodingObjects’s GetString( ) method. It passes that string to the Console.Write( ) method, to be displayed on the console. The complete source is shown in Example 22-12.

Example 22-12. Using pointers in a C# program

using System;
using System.Runtime.InteropServices;
using System.Text;

class APIFileReader
{
   [DllImport("kernel32", SetLastError=true)]
   static extern unsafe int CreateFile(
      string filename,
      uint desiredAccess,
      uint shareMode,
      uint attributes,   
      uint creationDisposition,
      uint flagsAndAttributes,
      uint templateFile);

   [DllImport("kernel32", SetLastError=true)]
   static extern unsafe bool ReadFile(
      int hFile,
      void* lpBuffer, 
      int nBytesToRead,
      int* nBytesRead, 
      int overlapped);

   // constructor opens an existing file
   // and sets the file handle member 
   public APIFileReader(string filename)
   {
      fileHandle = CreateFile(
         filename,      // filename
         GenericRead,   // desiredAccess 
         UseDefault,    // shareMode
         UseDefault,    // attributes
         OpenExisting,  // creationDisposition 
         UseDefault,    // flagsAndAttributes
         UseDefault);   // templateFile
   }

   public unsafe int Read(byte[] buffer, int index, int count) 
   {
      int bytesRead = 0;
      fixed (byte* bytePointer = buffer) 
      {
         ReadFile(
            fileHandle,             // hfile
            bytePointer + index,    // lpBuffer
            count,                  // nBytesToRead
            &bytesRead,             // nBytesRead
            0);                     // overlapped
      }
      return bytesRead;
   }

   const uint GenericRead = 0x80000000;
   const uint OpenExisting = 3;
   const uint UseDefault = 0;
   int fileHandle;
}

class Test
{
   public static void Main(  )
   {
      // create an instance of the APIFileReader, 
      // pass in the name of an existing file
      APIFileReader fileReader = 
        new APIFileReader("myTestFile.txt");

      // create a buffer and an ASCII coder      
      const int BuffSize = 128;
      byte[] buffer = new byte[BuffSize];
      ASCIIEncoding asciiEncoder = new ASCIIEncoding(  );

      // read the file into the buffer and display to console
      while (fileReader.Read(buffer, 0, BuffSize) != 0)
      {
         Console.Write("{0}", asciiEncoder.GetString(buffer));
      }
   }
}

The key section of code is shown in bold, where you create a pointer to the buffer and fix that buffer in memory using the fixed keyword. You need to use a pointer here because the API call demands it, though you’ve seen in Chapter 21 that all this can be done without the API call at all.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.71.211