嘗試用3種.NET方法寫出下面的C/C++指標作業碼。注意所有.NET方法均不直接引用外部函數資源(DllImport),都是使用.NET中原生態提供的方法。
注意:所有代碼輸出僅代表在32位和Little-Endian的CPU環境的運行結果。
來看這一段典型的C/C++指標作業碼:
/****************************
* C/C++ 代碼
****************************/
int i = 0xAA00FF;
//i的記憶體儲存形式:FF 00 AA 00
unsigned char *p = (unsigned char*)&i;
for(int i = 0; i < sizeof(i); i++)
cout<<hex<<uppercase<<setw(2)<<setfill('0')<<(int)(*p++)<<" ";
//換行
puts("");
//輸出:FF 00 AA 00
//注意p在迴圈後的位置,p-3 指向變數i在記憶體中第二個位元組
*(p-3) = 0xCC;
//現在記憶體儲存形式:FF CC AA 00
//i變成xAACCFF = 11193599
cout << dec << i << endl;
//輸出:11193599
邏輯上注釋已經解釋地很清楚了,上面代碼會輸出:
FF 00 AA 00
11193599
下面看.NET(C#)的執行,要求邏輯上和輸出就要和上面代碼一樣!
注意:
由於.NET對象記憶體分布是不同於非託管的C/C++的,因此並不是所有類型都可以這樣做。
目錄
- 1. 直接操作託管堆:Buffer類
- 2. 非託管堆:Marshal類
- 3. 託管函數棧:unsafe和stackalloc關鍵字
返回目錄
1. 直接操作託管堆:Buffer類
這個很驚奇,直接在託管堆中就可以幹這樣的事情。就靠這個Buffer類(System命名空間內),簡而言之Buffer類是以連續位元組流的形式操作未經處理資料類型。用他的GetByte和SetByte方法也可以輕鬆實現上面C/C++代碼。
/****************************
* C# 代碼(託管堆:Buffer類)
****************************/
int[] arr = new int[] { 0xAA00FF };
//記憶體儲存形式:FF 00 AA 00
for (int i = 0; i < sizeof(int); i++)
{
Console.Write("{0:X2} ", Buffer.GetByte(arr, i));
}
//換行
Console.WriteLine();
Buffer.SetByte(arr, 1, 0xCC);
//修改成:FF CC AA 00
//arr[0] = 0xAACCFF = 11193599
Console.WriteLine(arr[0]);
邏輯和輸出都一樣!
返回目錄
2. 非託管堆:Marshal類
接下來使用平台或者COM調用常用到的Marshal類(在System.Runtime.InteropServices命名空間內)。它主要用來在於非託管調用中環境對象的封送處理。
因此要處理我們程式的資料,需要先在非託管環境中建立這塊資料,首先使用Marshal類的AllocHGlobal方法,這個方法調用了kernel32.dll的LocalAlloc函數,從而在進程的非託管堆中進行記憶體配置。然後就可以使用Marshal類的Readxxx和Writexxx方法來進行資料操作了。最後別忘了需要手動釋放非託管堆中的資源!
代碼:
/* **************************
* C#代碼(非託管堆:Marshal類)
* **************************/
//+ using System.Runtime.InteropServices;
//分配空間
var p = Marshal.AllocHGlobal(4);
//賦值
Marshal.WriteInt32(p, 0xAA00FF);
//記憶體儲存形式:FF 00 AA 00
for (int i = 0; i < Marshal.SizeOf(typeof(int)); i++)
{
Console.Write("{0:X2} ", Marshal.ReadByte(p, i));
}
//換行
Console.WriteLine();
//修改成:FF CC AA 00
//p = 0xAACCFF = 11193599
Marshal.WriteByte(p, 1, 0xCC);
Console.WriteLine(Marshal.ReadInt32(p));
//清理非託管堆中的資源
Marshal.FreeHGlobal(p);
同樣,邏輯相同,輸出相同。
返回目錄
3. 託管函數棧:unsafe和stackalloc關鍵字
事實上這是大多數人想到用C#寫類似指標代碼的方式,這裡unsafe關鍵字是必須的。stackalloc僅為了示範聲明一個在本地函數棧的數組。
這個方法缺點是:必須使用unsafe代碼。
優點是:指標邏輯代碼和C/C++太像了。其次是stackalloc聲明數組的空間是在棧中的,是非常有效率的,減少GC的壓力。
代碼:
//標記unsafe關鍵字。編譯需要Visual Studio允許不安全的程式碼或者csc的unsafe參數
unsafe static void Main()
{
/* **************************
* C#代碼(函數棧:unsafe代碼)
* **************************/
//分配空間
//這裡直接聲明變數也可以
//僅為做樣本,我們把變數包在數組裡!
//注意這裡的數組不在堆裡,而在本地函數棧中!
int* arr = stackalloc int[1];
*arr = 0xAA00FF;
//指標
byte* p = (byte*)arr;
//記憶體儲存形式:FF 00 AA 00
for (int i = 0; i < Marshal.SizeOf(typeof(int)); i++)
{
Console.Write("{0:X2} ", (int)(*p++));
}
//換行
Console.WriteLine();
//修改成:FF CC AA 00
//p = 0xAACCFF = 11193599
*(p - 3) = 0xCC;
Console.WriteLine(*arr);
}
同樣邏輯和輸出是和C/C++代碼一樣的!