Thursday 25 December 2014

Unsafe Code Tutorial

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.
Example 1
The following example uses pointers to copy an array of bytes from src to dst. Compile
the example with the /unsafe option.
// 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.
Example 2
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