C# unsafe模式内存操作深入探索
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication1 { class CTile { public CTileData _dat; public int x; } //结构体可能分配在堆上,也可能分配在栈上 //1,结构体中无引用类型,则: //a:若该结构体类型的变量X是类的内部成员,由于类是引用类型,则X分配在堆上 //b:若非a的情况,则结构体分配在栈上 unsafe struct CTileData//为了避开C#数组,因为它是一个引用类型。 { public int var1; public float var2; public fixed sbyte name[6]; //使用C++风格的定长数组,避免C#风格的引用数组 public float var3; } //2,结构体中有引用类型,则该结构体类型的变量分配在堆上 struct CTileData2 { public int var1; public float var2; public string name;//有引用类型,结构体无论如何都分配在堆上了 public float var3; } unsafe class Program { [DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)] static extern void MemCopy(void* dest, void* src, int count); static void Main(string[] args) { unsafe { var tile = new CTile(); var dat = new CTileData(); //栈上的结构体,可以直接取地址,堆上的则不行,因为堆上对象的地址是不定的(原因是内存管理) //同理,堆上的任何对象都不可直接取地址,必须使用fixed才行 CTileData* ptd = &dat; var ms = new MemoryStream(); var binWr = new BinaryWriter(ms, Encoding.ASCII); binWr.Write(10); binWr.Write(3.2f); binWr.Write("hello");//先写入1字节长度(也就是说字符串长度最大256???),然后写入hello binWr.Write(109.9f); var bts = ms.GetBuffer(); fixed (void* pbts = bts)//堆对象,必须使用fixed语法才能取地址 { var sz = sizeof(CTileData); MemCopy(ptd, pbts, sz); fixed (void* pt = &tile._dat)//堆上的结构体(堆对象),必须使用fixed语法才能取地址 { MemCopy(pt, pbts, sz); } } var v1 = ptd->var1; //10 var v2 = ptd->var2; //3.2 var strlen = *(ptd->name); //取一字节,字符串长度 5 var straddr = ptd->name + 1; //跳过一字节,到达字符串起始地址 string name = new string(straddr, 0, strlen); //hello var v3 = ptd->var3; //这里数据不对,原因???? //结论:C#真不适合做内存操作,若使用marshal,虽然方便了一些,但要经过一次内存申请和一次内存释放,一次转换到C#结构的过程,很蹩脚 } } } }