.net中如何以純二進(jìn)制的形式在內(nèi)存中繪制一個(gè)對(duì)象
一個(gè)對(duì)象總是映射一塊連續(xù)的內(nèi)存序列(不考慮對(duì)象之間的引用關(guān)系),如果我們知道了引用類型實(shí)例的內(nèi)存布局,以及變量引用指向的確切的地址,我們不僅可以采用純“二進(jìn)制”的方式在內(nèi)存“繪制”一個(gè)指定引用類型的實(shí)例,還可以修改某個(gè)變量的“值”指向它,還能直接通過改變二進(jìn)制內(nèi)容來更新實(shí)例的狀態(tài)。
一、引用類型實(shí)例的內(nèi)存布局
從內(nèi)存布局的角度來看,一個(gè)引用類型的實(shí)例由如下圖所示的三部分組成:ObjHeader + TypeHandle + Fields。前置的ObjHeader用來緩存哈希值和同步狀態(tài)(《如何將一個(gè)實(shí)例的內(nèi)存二進(jìn)制內(nèi)容讀出來?》具有對(duì)此的詳細(xì)介紹),TypeHandle部分存儲(chǔ)類型對(duì)應(yīng)方法表(Method Table)的地址,方法表可以視為針對(duì)類型的描述。也正是這部分內(nèi)容的存在,運(yùn)行時(shí)可以確定任何一個(gè)實(shí)例的真實(shí)類型,所以我們才說引用類型的實(shí)例是自描述(Self Describing)的。Fields用于存儲(chǔ)實(shí)例每個(gè)字段的內(nèi)容。
對(duì)于32位(x86)的機(jī)器來說,ObjHeader 和 TypeHandle的長度都是4字節(jié)。如果是64位(x64)的機(jī)器,用于存儲(chǔ)方法表地址的TypeHandle 需要8個(gè)字節(jié)來存儲(chǔ),但是ObjHeader 依然是4個(gè)直接。考慮到內(nèi)存對(duì)齊,需要前置4個(gè)字節(jié)的Padding。對(duì)于一個(gè)不為null的應(yīng)用類型變量來說,它存儲(chǔ)的是實(shí)例的內(nèi)存地址。但是這個(gè)地址并不是實(shí)例所在內(nèi)存的“首地址(ObjHeader)”,而是TypeHandle部分的地址。
二、以二進(jìn)制的形式創(chuàng)建對(duì)象
既然我們已經(jīng)知道了引用類型實(shí)例的內(nèi)存布局,也知道了引用指向的確切的地址,我們不僅可以采用純“二進(jìn)制”的方式在內(nèi)存“繪制”一個(gè)指定引用類型的實(shí)例,還可以修改某個(gè)變量的“值”指向它。具體的實(shí)現(xiàn)體現(xiàn)在如下所示的Create方法中,該方法根據(jù)指定的屬性值創(chuàng)建一個(gè)Foobar對(duì)象。除了用來提供兩個(gè)屬性值的foo、bar參數(shù)之外,它還通過輸出參數(shù)bytes返回整個(gè)實(shí)例的字節(jié)序列。
var foobar = Create(1, 2, out var bytes); Debug.Assert(foobar.Foo == 1); Debug.Assert(foobar.Bar == 2); static unsafe Foobar Create(int foo, int bar, out byte[] bytes) { Foobar foobar = null!; bytes = new byte[24]; BinaryPrimitives.WriteInt64LittleEndian(bytes.AsSpan(8), typeof(Foobar).TypeHandle.Value.ToInt64()); BinaryPrimitives.WriteInt32LittleEndian(bytes.AsSpan(16), foo); BinaryPrimitives.WriteInt32LittleEndian(bytes.AsSpan(20), bar); Unsafe.Write(Unsafe.AsPointer(ref foobar), new IntPtr(Unsafe.AsPointer(ref bytes[8]))); return foobar; } public class Foobar { public int Foo { get; set; } public int Bar { get; set; } }
根據(jù)上述針對(duì)內(nèi)存布局的介紹,我們知道任何一個(gè)Foobar實(shí)例在x64機(jī)器中都映射位一段連續(xù)的24字節(jié)內(nèi)存,所以Create方法創(chuàng)建了一個(gè)長度位24的字節(jié)數(shù)組。我們保持ObjHeader為空,所以我們從第8(zero based)個(gè)字節(jié)開始寫入Foobar類型對(duì)應(yīng)TypeHandle的值(8字節(jié)),然后將指定的數(shù)據(jù)成員的值(int類型占據(jù)4個(gè)字節(jié))填充到最后8個(gè)字節(jié)(由于兩個(gè)字段的類型均為int,所以不需要添加額外的“留白”來確保內(nèi)存對(duì)齊)。自此我們將“憑空”在內(nèi)存中“繪制”了一個(gè)Foobar對(duì)象。由于x86機(jī)器采用“小端字節(jié)序”,所以二進(jìn)制的寫入最終是通過調(diào)用BinaryPrimitives的WriteInt32/64LittleEndian方法來完成的。
接下來我們定義一個(gè)Foobar類型的變量,并讓它指向這個(gè)繪制的Foobar對(duì)象。我們?cè)谏厦嬲f過,它指向的不是實(shí)例內(nèi)存的首字節(jié),而是TypleHandle部分。對(duì)于我們的例子來說,它指向的就是我們創(chuàng)建的字節(jié)數(shù)組的第8(zero based)的元素。針對(duì)變量內(nèi)容(目標(biāo)對(duì)象的地址)的改寫是通過調(diào)用Unsafe的靜態(tài)方法Write實(shí)現(xiàn)的。我們的演示程序調(diào)用了Create創(chuàng)建了一個(gè)Foo和Bar屬性分別為1和2的Foobar對(duì)象,并得到它真正映射在內(nèi)存中的字節(jié)序列。
三、字節(jié)數(shù)組與實(shí)例狀態(tài)的同一性
對(duì)于我們定義的Create方法來說,由于通過輸出參數(shù)返回的字節(jié)數(shù)字就是返回的Foobar對(duì)象在內(nèi)存中的映射,所以Foobar的狀態(tài)(Foo和Bar屬性)發(fā)生改變后,字節(jié)數(shù)組的內(nèi)容也會(huì)發(fā)生改變。這一點(diǎn)可以通過如下的程序來驗(yàn)證。
var foobar = Create(1, 1, out var bytes); Console.WriteLine(BitConverter.ToString(bytes)); foobar.Foo = 255; foobar.Bar = 255; Console.WriteLine(BitConverter.ToString(bytes));
輸出結(jié)果
00-00-00-00-00-00-00-00-D8-11-30-17-F9-7F-00-00-01-00-00-00-01-00-00-00
00-00-00-00-00-00-00-00-D8-11-30-17-F9-7F-00-00-FF-00-00-00-FF-00-00-00
既然返回的字節(jié)數(shù)據(jù)和Foobar對(duì)象具有同一性,我們自然也可以按照如下的方式通過修改字節(jié)數(shù)組的內(nèi)容來到達(dá)改變實(shí)例狀態(tài)的目的。
var foobar = Create(1, 1, out var bytes); Debug.Assert(foobar.Foo == 1); Debug.Assert(foobar.Bar == 1); BinaryPrimitives.WriteInt32LittleEndian(bytes.AsSpan(16), 255); BinaryPrimitives.WriteInt32LittleEndian(bytes.AsSpan(20), 255); Debug.Assert(foobar.Foo == 255); Debug.Assert(foobar.Bar == 255);
四、ObjHeader針對(duì)哈希被同步狀態(tài)的緩存
我們可以進(jìn)一步利用這種方式驗(yàn)證實(shí)例的ObjHeader針對(duì)哈希值和同步狀態(tài)的緩存。如下面的代碼片段所示,我們調(diào)用Create創(chuàng)建了一個(gè)Foobar對(duì)象并將得到的字節(jié)數(shù)組打印出來。然后我們調(diào)用其GetHashCode方法觸發(fā)哈希值的計(jì)算,并再次打印字節(jié)數(shù)組。接下來我們創(chuàng)建一個(gè)新的Foobar對(duì)象,分別對(duì)它進(jìn)行加鎖和解鎖狀態(tài)打印字節(jié)數(shù)組。
var foobar = Create(1, 2, out var bytes); Console.WriteLine($"{BitConverter.ToString(bytes)}[Original]"); foobar.GetHashCode(); Console.WriteLine($"{BitConverter.ToString(bytes)}[GetHashCode]"); foobar = Create(1, 2, out bytes); lock (foobar) { Console.WriteLine($"{BitConverter.ToString(bytes)}[Enter lock]"); } Console.WriteLine($"{BitConverter.ToString(bytes)}[Exit lock]");
從如下所示的輸出結(jié)果可以看出,在GetHashCode方法調(diào)用和被“鎖住”之后,承載Foobar對(duì)象的ObjHeader字節(jié)(4-7字節(jié))都發(fā)生了改變,實(shí)際上運(yùn)行時(shí)就是利用它來存儲(chǔ)計(jì)算出的哈希值和同步狀態(tài)。至于ObjHeader具體的字節(jié)布局,我的另一篇文章《如何將一個(gè)實(shí)例的內(nèi)存二進(jìn)制內(nèi)容讀出來?》提供了系統(tǒng)的說明。
00-00-00-00-00-00-00-00-90-1C-30-17-F9-7F-00-00-01-00-00-00-02-00-00-00[Original]
00-00-00-00-C7-D5-9F-0D-90-1C-30-17-F9-7F-00-00-01-00-00-00-02-00-00-00[GetHashCode]
00-00-00-00-01-00-00-00-90-1C-30-17-F9-7F-00-00-01-00-00-00-02-00-00-00[Enter lock]
00-00-00-00-00-00-00-00-90-1C-30-17-F9-7F-00-00-01-00-00-00-02-00-00-00[Exit lock]
到此這篇關(guān)于以純二進(jìn)制的形式在內(nèi)存中繪制一個(gè)對(duì)象的文章就介紹到這了,更多相關(guān)二進(jìn)制形式繪制一個(gè)對(duì)象內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Net?core中使用System.Drawing對(duì)上傳的圖片流進(jìn)行壓縮(示例代碼)
這篇文章主要介紹了Net?core中使用System.Drawing對(duì)上傳的圖片流進(jìn)行壓縮,本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08ASP.NET網(wǎng)站使用Kindeditor富文本編輯器配置步驟
首先下載編輯器然后部署編輯器最后在網(wǎng)頁中加入(ValidateRequest="false")引入腳本文件,具體配置步驟如下,有需求的朋友可以了解下哈2013-06-06asp.net 文件上傳 實(shí)時(shí)進(jìn)度
在swfupload的基礎(chǔ)上增加一些個(gè)性化東西.附圖2張.2009-11-11Asp.net Core 初探(發(fā)布和部署Linux)
這篇文章主要介紹了Asp.net Core 初探(發(fā)布和部署Linux),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-12-12.NET Core結(jié)合Nacos實(shí)現(xiàn)配置加解密的方法
當(dāng)我們把應(yīng)用的配置都放到配置中心后,很多人會(huì)想到這樣一個(gè)問題,配置里面有敏感的信息要怎么處理呢?本文就詳細(xì)的介紹了.NET Core Nacos配置加解密,感興趣的可以了解一下2021-06-06基于Dapper實(shí)現(xiàn)分頁效果 支持篩選、排序、結(jié)果集總數(shù)等
這篇文章主要為大家詳細(xì)介紹了基于Dapper實(shí)現(xiàn)分頁效果,支持篩選,排序,結(jié)果集總數(shù),多表查詢,非存儲(chǔ)過程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07ASP.NET:一段比較經(jīng)典的多線程學(xué)習(xí)代碼
ASP.NET:一段比較經(jīng)典的多線程學(xué)習(xí)代碼...2006-09-09在SQL Server中使用CLR調(diào)用.NET方法實(shí)現(xiàn)思路
在.NET中新建一個(gè)類,并在這個(gè)類里新建一個(gè)方法,然后在SQL Server中調(diào)用這個(gè)方法,接下來我們將實(shí)現(xiàn)這個(gè)功能做了以下幾個(gè)步驟,詳細(xì)看下本文,感興趣的你可不要錯(cuò)過了哈2013-02-02