在 C# 中很少需要使用指標,但在某些情況下卻需要使用。舉例來說,下列的情況可以使用不安全的內容來允許指標:
- 處理磁碟上現有的結構
- COM 或平台叫用的進階案例 (其中包含具有指標的結構)
- 非常注重效能的程式碼
我們不建議在其他狀況下使用不安全的內容。尤其,不應該使用不安全的內容來嘗試在 C# 中撰寫 C 程式碼。
警告 使用不安全內容所撰寫的程式碼無法被驗證是否安全,因此只有在程式碼被完全信任時才會執行這種程式碼。換句話說,不安全的程式碼無法在未受信任的環境中執行。舉例來說,您無法從網際網路直接執行不安全的程式碼。
這個教學課程包含下列範例:
範例 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 陳述式是用來宣告指向來源和目標陣列的指標。它會固定
src
和dst
物件在記憶體中的位置,使它們不會因記憶體回收而被移動。當 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 SDK 的 VerQueryValue、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