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.
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.
18.117.71.211