close

在 C# 中很少需要使用指標,但在某些情況下卻需要使用。舉例來說,下列的情況可以使用不安全的內容來允許指標:

  • 處理磁碟上現有的結構
  • COM 或平台叫用的進階案例 (其中包含具有指標的結構)
  • 非常注重效能的程式碼

我們不建議在其他狀況下使用不安全的內容。尤其,不應該使用不安全的內容來嘗試在 C# 中撰寫 C 程式碼。

警告   使用不安全內容所撰寫的程式碼無法被驗證是否安全,因此只有在程式碼被完全信任時才會執行這種程式碼。換句話說,不安全的程式碼無法在未受信任的環境中執行。舉例來說,您無法從網際網路直接執行不安全的程式碼。

這個教學課程包含下列範例:

  • 範例 1   使用指標來複製位元組陣列。
  • 範例 2   顯示如何呼叫 Windows ReadFile 函式。
  • 範例 3   顯示如何列印可執行檔的 Win32 版本。

範例 1

下列範例使用指標將位元組陣列從 src 複製到 dst。請使用 /unsafe 選項來編譯這個範例。

// 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");
    }
}

範例輸出

The first 10 elements are:
0 1 2 3 4 5 6 7 8 9

程式碼討論

  • 請小心使用 unsafe 關鍵字,因為它允許在 Copy 方法內使用指標。
  • fixed 陳述式是用來宣告指向來源和目標陣列的指標。它會固定 srcdst 物件在記憶體中的位置,使它們不會因記憶體回收而被移動。當 fixed 區塊完成時,該物件的固定就會解除。
  • 您可以為不安全的程式碼取消陣列界限檢查,提高其執行效能。

範例 2

這個範例顯示如何從 Platform SDK 呼叫 Windows ReadFile 函式。這項作業必須使用不安全的內容,因為讀取緩衝區需要指標做為參數。

// 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;
            }
      }
}

範例輸入

當您編譯並執行這個範例時,下列來自 readfile.txt 的輸入會產生範例輸出所顯示的輸出。

line 1
line 2

範例輸出

line 1
line 2

程式碼討論

傳遞到 Read 函式的位元組陣列為 Managed 型別。這表示 Common Language Runtime 記憶體回收行程可依所需重新配置陣列所使用的記憶體。fixed 陳述式可讓您取得指向位元組陣列所使用記憶體的指標,並標記該執行個體,使它們不會因為記憶體回收行程而移動。

執行個體會被標記在 fixed 區塊尾端,這樣才可以移動它。這項功能稱為宣告式固定。固定的可取之處在於虛耗的空間很少,除非記憶體回收發生在 fixed 區塊內 (不太可能發生)。

範例 3

這個範例會讀取並顯示可執行檔的 Win32 版本號碼,這個號碼與本範例的組件版本號碼相同。這個範例中的可執行檔是 printversion.exe。本範例會使用 Platform SDKVerQueryValue、GetFileVersionInfoSize 和 GetFileVersionInfo 函式,以便從指定的版本資訊資源中擷取指定的版本資訊。

這個範例之所以使用指標,是因為它可以簡化方法 (其簽名碼會使用指標的指標) 的使用,這種情形常見於 Win32 API。

// 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;
      }
}

範例輸出

ProductVersion == 4.3.2.1
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 11 的頭像
    11

    冠霖的部落格

    11 發表在 痞客邦 留言(0) 人氣()