寫這篇文章是有一個(gè)起因的... 最近在學(xué)socket編程 翻源代碼的時(shí)候 無(wú)意發(fā)現(xiàn)一個(gè)沒有用過(guò)的函數(shù)Marshal.UnsafeAddrOfPinnedArrayElement() 它的原形如下: public static IntPtr UnsafeAddrOfPinnedArrayElement(Array arr, int index);
這個(gè)靜態(tài)函數(shù)的作用是返回一個(gè)數(shù)組第index個(gè)元素的首地址 而且沒有值類型和引用類型的限制 沒有數(shù)組索引越界的檢查 簡(jiǎn)單的說(shuō) 它是一個(gè)托管的C#數(shù)組與非托管指針的一個(gè)合法的轉(zhuǎn)換接口 這樣 我們就很容易寫出以下代碼了: byte[] array = new byte[1024]; byte* pArray = (byte*)Marshal.UnsafeAddrOfPinnedArrayElement(array, 0); for (int i = 0, j = array.Length; i < j; i++)
他的好處是 在改變數(shù)組值的時(shí)候沒有進(jìn)行索引越界檢查 效率會(huì)略微提高 當(dāng)然 它只在極端需要效率的場(chǎng)合值得一用 或者是密集計(jì)算的場(chǎng)合懶得用C++寫底層然后dllImport的時(shí)候用 它比普通的數(shù)組遍歷能提高5%~15%的效能 畢竟 指針玩不好終究是個(gè)挺危險(xiǎn)的東西.. 另外一個(gè)有趣的函數(shù)是Buffer.BlockCopy() 原形如下: public static void BlockCopy(Array src, int srcOffset, Array dst, int dstOffset, int count);
它也是用來(lái)復(fù)制內(nèi)存區(qū)塊的 和Array.Copy()不同 它最后一個(gè)參數(shù)count是表示復(fù)制的字節(jié)長(zhǎng)度 而不是復(fù)制元素的數(shù)量 當(dāng)然 這個(gè)函數(shù)的源數(shù)組和目的數(shù)組 類型可以不同 但是必須是基本類型(原始的各種數(shù)值型) 大致用法是這樣: double[] src = new double[5] { 1, 2, 3, 4, 5 }; byte[] dst = new byte[40]; Buffer.BlockCopy(src, 0, dst, 0, 40);
于是它就會(huì)按照double的IEEE內(nèi)存標(biāo)準(zhǔn) 原封不動(dòng)的復(fù)制到byte[]里去了... 事實(shí)上用上面提供的數(shù)組轉(zhuǎn)指針 以及Marshal.Copy()方法也可以做到 不過(guò)這函數(shù)方便了很多 BitConverter類中提供了一系列靜態(tài)函數(shù) 如GetBytes()和ToInt32()等 可以在基本類型和byte[]間進(jìn)行轉(zhuǎn)換 它比較適合單一轉(zhuǎn)換的場(chǎng)合 如果需要循環(huán)轉(zhuǎn)換的話還是使用BlockCopy或者直接unsafe指針讀取更為方便 Marshal.Copy()提供了一系列的重載 可以提供托管數(shù)組和IntPtr之間的內(nèi)存復(fù)制功能... 于是經(jīng)常我們會(huì)看到一段GDI圖像處理代碼如下: Bitmap bitmap = (Bitmap)Bitmap.FromFile(fileName); BitmapData data = bitmap.LockBits(new Rectangle(new Point(), bitmap.Size), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); byte[] buffer = new byte[data.Stride * data.Height]; Marshal.Copy(data.Scan0, buffer, 0, buffer.Length); for (int h = 0; h < data.Height; h++) for (int w = 0; w < data.Width; w++) Marshal.Copy(buffer, 0, data.Scan0, buffer.Length);
事實(shí)上 直接使用指針可以在不創(chuàng)建緩沖區(qū)的情況下對(duì)非托管數(shù)據(jù)進(jìn)行操作 如果是對(duì)一定批量的非托管數(shù)據(jù)進(jìn)行處理 可以使用緩沖區(qū)(如上面的情況可以圖像逐行復(fù)制處理) 或者直接使用unsafe代碼 更為高效 另外一件比較討厭的事 Marshal.Copy()沒有提供從指針到指針復(fù)制的函數(shù)重載 不過(guò)我們可以用API來(lái)實(shí)現(xiàn)它: [DllImport("kernel32.dll")] public static extern void CopyMemory(IntPtr Destination, IntPtr Source, int Length);
IntPtr當(dāng)然也可以使用void*來(lái)替代或者重載 以達(dá)到更好的靈活性 結(jié)合數(shù)組取地址的方式 這個(gè)函數(shù)可以提供不同類型的 非基礎(chǔ)類型數(shù)組之間的復(fù)制 總之 因?yàn)镃#的特殊支持 可以很輕松的直接處理內(nèi)存 寫出類似C++的指針運(yùn)算代碼 在保證足夠安全性的前提下 它會(huì)帶來(lái)意想不到的語(yǔ)法便利 以及高效的處理速率
|