Section 22.5. Pointers


22.5. 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 with P/Invoke.

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 you can manipulate memory locations directly with pointers. This is a feat that is 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, and your code will not work in partial-trust scenarios.

As an example of when this might be useful, 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 isn't 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( ). It passes 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 that will hold the number of bytes read. It is the pointer to the buffer that is of interest to us here. To invoke 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 can't be allowed to move the buffer during garbage collection. To accomplish this, 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 un-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 creates an unsafe context and allows you to create pointers. To compile this you must use the /unsafe compiler option. The easiest way to do so is to open the project properties, click the Build tab, and check the Allow Unsafe Code checkbox, as shown in Figure 22-21.

Figure 22-21. Allowing unsafe code


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. An array of bytes is returned, which is converted to a string using the ASCIIEncoding object's GetString( ) method. That string is passed 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
#region Using directives using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; #endregion namespace UsingPointers {    class APIFileReader    {       const uint GenericRead = 0x80000000;       const uint OpenExisting = 3;       const uint UseDefault = 0;       int fileHandle;       [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;       }    }    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 where you create a pointer to the buffer and fix that buffer in memory using the fixed keyword is shown in bold. You need to use a pointer here because the API call demands it.



Programming C#(c) Building. NET Applications with C#
Programming C#: Building .NET Applications with C#
ISBN: 0596006993
EAN: 2147483647
Year: 2003
Pages: 180
Authors: Jesse Liberty

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net