This tutorial demonstrates how to use unsafe code (code using pointers) in C#.
- A Unsafe code
- unsafe
- fixed Statement
Tutorial
The use of pointers is rarely required in C#, but there are some situations that require them. As examples, using an unsafe context to allow pointers is warranted by the following cases:
- Dealing with existing structures on disk
- Advanced COM or Platform Invoke scenarios that involve structures with pointers in them
- Performance-critical code
The use of unsafe context in other situations is discouraged. Specifically, an unsafe
context should not be used to attempt to write C code in C#.
Caution:- Code written using an unsafe context cannot be verified to be safe, so it
will be executed only when the code is fully trusted. In other words, unsafe code
cannot be executed in an untrusted environment. For example, you cannot run unsafe
code directly from the Internet.
This tutorial includes the following examples:
- Example 1 Uses pointers to copy an array of bytes.
- Example 2 Shows how to call the Windows ReadFile function.
- Example 3 Shows how to print the Win32 version of the executable file.
The following example uses pointers to copy an array of bytes from src to dst. Compile
// fastcopy.cs
// compile with: /unsafe
using System;
class Test
{
// The unsafe keyword allows pointers to be used within
// the following method:
static unsafe void Copy(byte[] src, int srcIndex,
byte[] dst, int dstIndex, int count)
{
if (src == null || srcIndex < 0 ||
dst == null || dstIndex < 0 || count < 0)
{
throw new ArgumentException();
}
int srcLen = src.Length;
int dstLen = dst.Length;
if (srcLen - srcIndex < count ||
dstLen - dstIndex < count)
{
throw new ArgumentException();
}
// The following fixed statement pins the location of
// the src and dst objects in memory so that they will
// not be moved by garbage collection.
fixed (byte* pSrc = src, pDst = dst)
{
byte* ps = pSrc;
byte* pd = pDst;
// Loop over the count in blocks of 4 bytes, copying an
// integer (4 bytes) at a time:
for (int n =0 ; n < count/4 ; n++)
{
*((int*)pd) = *((int*)ps);
pd += 4;
ps += 4;
}
// Complete the copy by moving any bytes that weren't
// moved in blocks of 4:
for (int n =0; n < count%4; n++)
{
*pd = *ps;
pd++;
ps++;
}
}
}
static void Main(string[] args)
{
byte[] a = new byte[100];
byte[] b = new byte[100];
for(int i=0; i<100; ++i)
a[i] = (byte)i;
Copy(a, 0, b, 0, 100);
Console.WriteLine("The first 10 elements are:");
for(int i=0; i<10; ++i)
Console.Write(b[i] + " ");
Console.WriteLine("\n");
}
}
Example Output
The first 10 elements are:
0 1 2 3 4 5 6 7 8 9
Code Discussion
- Notice the use of the unsafe keyword, which allows pointers to be used within the Copy method.
- The fixed statement is used to declare pointers to the source and destination arrays. It pins the location of the src and dst objects in memory so that they will not be moved by garbage collection. The objects will be unpinned when the fixed block completes
- Unsafe code increases the performance by getting rid of array bounds checks.
This example shows how to call the Windows ReadFile function from the Platform
SDK, which requires the use of an unsafe context because the read buffer requires a
pointer as a parameter.
// readfile.cs
// compile with: /unsafe
// arguments: readfile.cs
// Use the program to read and display a text file.
using System;
using System.Runtime.InteropServices;
using System.Text;
class FileReader
{
const uint GENERIC_READ = 0x80000000;
const uint OPEN_EXISTING = 3;
IntPtr handle;
[DllImport("kernel32", SetLastError=true)]
static extern unsafe IntPtr CreateFile(
string FileName, // file name
uint DesiredAccess, // access mode
uint ShareMode, // share mode
uint SecurityAttributes, // Security Attributes
uint CreationDisposition, // how to create
uint FlagsAndAttributes, // file attributes
int hTemplateFile // handle to template file
);
[DllImport("kernel32", SetLastError=true)]
static extern unsafe bool ReadFile(
IntPtr hFile, // handle to file
void* pBuffer, // data buffer
int NumberOfBytesToRead, // number of bytes to read
int* pNumberOfBytesRead, // number of bytes read
int Overlapped // overlapped buffer
);
[DllImport("kernel32", SetLastError=true)]
static extern unsafe bool CloseHandle(
IntPtr hObject // handle to object
);
public bool Open(string FileName)
{
// open the existing file for reading
handle = CreateFile(
FileName,
GENERIC_READ,
0,
0,
OPEN_EXISTING,
0,
0);
if (handle != IntPtr.Zero)
return true;
else
return false;
}
public unsafe int Read(byte[] buffer, int index, int count)
{
int n = 0;
fixed (byte* p = buffer)
{
if (!ReadFile(handle, p + index, count, &n, 0))
return 0;
}
return n;
}
public bool Close()
{
// close file handle
return CloseHandle(handle);
}
}
class Test
{
public static int Main(string[] args)
{
if (args.Length != 1)
{
Console.WriteLine("Usage : ReadFile <FileName>");
return 1;
}
if (! System.IO.File.Exists(args[0]))
{
Console.WriteLine("File " + args[0] + " not found.");
return 1;
}
byte[] buffer = new byte[128];
FileReader fr = new FileReader();
if (fr.Open(args[0]))
{
// Assume that an ASCII file is being read
ASCIIEncoding Encoding = new ASCIIEncoding();
int bytesRead;
do
{
bytesRead = fr.Read(buffer, 0, buffer.Length);
string content = Encoding.GetString(buffer,0,bytesRead);
Console.Write("{0}", content);
}
while ( bytesRead > 0);
fr.Close();
return 0;
}
else
{
Console.WriteLine("Failed to open requested file");
return 1;
}
}
}
Example Input:-
The following input from readfile.txt produces the output shown in Example Output when you compile and run this sample:
line 1
line 2
Example Output
line 1
line 2
Code Discussion
Ø The byte array passed into the Read function is a managed type. This means that the common language runtime garbage collector could relocate the memory used by the array at will.
Ø The fixed statement allows you to both get a pointer to the memory used by the byte array and to mark the instance so that the garbage collector won't move it.
Ø At the end of the fixed block, the instance will be marked so that it can be moved. This capability is known as declarative pinning. The nice part about pinning is that there is very little overhead unless a garbage collection occurs in the fixed block, which is an unlikely occurrence.
Example 3
Ø This example reads and displays the Win32 version number of the executable file, which is the same as the assembly version number in this example. The executable file, in this example, is printversion.exe.
Ø The example uses the Platform SDK functions VerQueryValue,GetFileVersionInfoSize, andGetFileVersionInfo to retrieve specified version information from the specified version-information resource.
Ø This example uses pointers because it simplifies the use of methods whose signatures use pointers to pointers, which are common in the Win32 APIs.
// printversion.cs
// compile with: /unsafe
using System;
using System.Reflection;
using System.Runtime.InteropServices;
// Give this assembly a version number:
[assembly:AssemblyVersion("4.3.2.1")]
public class Win32Imports
{
[DllImport("version.dll")]
public static extern bool GetFileVersionInfo (string sFileName,
int handle, int size, byte[] infoBuffer);
[DllImport("version.dll")]
public static extern int GetFileVersionInfoSize (string sFileName,
out int handle);
// The third parameter - "out string pValue" - is automatically
// marshaled from ANSI to Unicode:
[DllImport("version.dll")]
unsafe public static extern bool VerQueryValue (byte[] pBlock,
string pSubBlock, out string pValue, out uint len);
// This VerQueryValue overload is marked with 'unsafe' because
// it uses a short*:
[DllImport("version.dll")]
unsafe public static extern bool VerQueryValue (byte[] pBlock,
string pSubBlock, out short *pValue, out uint len);
}
public class C
{
// Main is marked with 'unsafe' because it uses pointers:
unsafe public static int Main ()
{
try
{
int handle = 0;
// Figure out how much version info there is:
int size =
Win32Imports.GetFileVersionInfoSize("printversion.exe",
out handle);
if (size == 0) return -1;
byte[] buffer = new byte[size];
if (!Win32Imports.GetFileVersionInfo("printversion.exe", handle, size, buffer))
{
Console.WriteLine("Failed to query file version information.");
return 1;
}
short *subBlock = null;
uint len = 0;
// Get the locale info from the version info:
if (!Win32Imports.VerQueryValue (buffer, @"\VarFileInfo\Translation", out subBlock, out len))
{
Console.WriteLine("Failed to query version information.");
return 1;
}
string spv = @"\StringFileInfo\" + subBlock[0].ToString("X4") + subBlock[1].ToString("X4") + @"\ProductVersion";
byte *pVersion = null;
// Get the ProductVersion value for this program:
string versionInfo;
if (!Win32Imports.VerQueryValue (buffer, spv, out versionInfo, out len))
{
Console.WriteLine("Failed to query version information.");
return 1;
}
Console.WriteLine ("ProductVersion == {0}", versionInfo);
}
catch (Exception e)
{
Console.WriteLine ("Caught unexpected exception " + e.Message);
}
return 0;
}
}
Example Output
ProductVersion == 4.3.2.1
No comments:
Post a Comment