2009-12-16 29 views
11

.NET值I型具有以下.NET值類型:佈局存儲器

[StructLayout(LayoutKind.Sequential)] 
public struct Date 
{ 
    public UInt16 V; 
} 

[StructLayout(LayoutKind.Sequential)] 
public struct StringPair 
{ 
    public String A; 
    public String B; 
    public String C; 
    public Date D; 
    public double V; 
} 

我有一個指針傳遞到一個值類型非託管代碼的代碼,以通過調用系統發現偏移量沿.Runtime.InteropServices.Marshal.OffsetOf。非託管代碼正在填充日期和雙精度值。

所報道的StringPair結構的偏移量是正是我所期望的:0,8,16,24,32

我有一個測試功能下面的代碼:

FieldInfo[] fields = typeof(StringPair).GetFields(BindingFlags.Instance|BindingFlags.Public); 

for (int i = 0; i < fields.Length; i++) 
{ 
    int offset = System.Runtime.InteropServices.Marshal.OffsetOf(typeof(StringPair), fields[i].Name).ToInt32(); 

    Console.WriteLine(String.Format(" >> field {0} @ offset {1}", fields[i].Name, offset)); 
} 

其中打印出這些偏移。

>> field A @ offset 0 
>> field B @ offset 8 
>> field C @ offset 16 
>> field D @ offset 24 
>> field V @ offset 32 

我然後有一些測試代碼: 的foreach(在對StringPair對) { 日期d = pair.D; double v = pair.V; ...

具有與其在調試器相關聯的以下彙編:

   Date d = pair.D; 
0000035d lea   rax,[rbp+20h] 
00000361 add   rax,20h 
00000367 mov   ax,word ptr [rax] 
0000036a mov   word ptr [rbp+000000A8h],ax 
00000371 movzx  eax,word ptr [rbp+000000A8h] 
00000378 mov   word ptr [rbp+48h],ax 

       double v = pair.V; 
0000037c movsd  xmm0,mmword ptr [rbp+38h] 
00000381 movsd  mmword ptr [rbp+50h],xmm0 

據加載d字段偏移量爲32(0×20)以及V字段偏移量24(0x38-0x20 )。 JIT已經改變了周圍的秩序。 Visual Studio調試器也顯示了這個倒序。

爲什麼?我一直在拉我的頭髮,試圖看看我的邏輯出錯了。如果我在結構中交換D和V的順序,那麼一切都可以正常工作,但是這段代碼需要能夠處理其他開發人員定義結構的插件體系結構,並且他們不能期望記住神祕的佈局規則。

回答

11

您從Marshal類中得到的信息,如果該類型實際上被封纔有意義。託管結構的內部內存佈局不能通過任何記錄的方式來發現,除了可能在彙編代碼處窺視之外。

這意味着CLR可以自由地重新組織布局並優化包裝。由於雙列的對齊要求,交換D和V字段會使您的結構更小。它在你的64位機器上節省了6個字節。

不知道爲什麼這會成爲你的問題,它不應該。考慮Marshal.StructureToPtr()以按照您希望的方式佈局結構。

+1

謝謝 - 我錯過了Marshall。*方法只應用於編組指針的事實。出於性能原因,我希望避免額外的數據副本,但是如果我想支持任意結構 – 2009-12-17 14:03:14

+1

+1來澄清JIT結構佈局是與CLR指定的完全分離的問題,那看起來是不可避免的,因爲 - 原則上 - 它不能被管理程序看到,因此也不會受到影響。我已經注意到JITer似乎在非託管之前放置託管字段。 – 2010-12-07 22:09:56

+0

值類型(的實例)的內存中的確切位佈局可能與決定是否實現IEquatable (以及object.Equals和GetHashCode的推薦覆蓋)有一定關聯性。如果你沒有實現IEquatable,那麼我相信默認生成的代碼會對內存中的位進行按位比較。如果佈局中存在GAPS,那麼您可能會浪費時間來比較那些無關緊要的位(在C++中,您可能還冒着正確性,因爲這些位可能未被初始化 - 但我認爲它們始終位於C#中)。瞭解佈局會告知決定要實施什麼。 – 2011-05-06 17:03:46

13

如果您需要顯式佈局...... 使用顯式佈局......

[StructLayout(LayoutKind.Explicit)] 
public struct StringPair 
{ 
    [FieldOffset(0)] public String A; 
    [FieldOffset(8)] public String B; 
    [FieldOffset(16)] public String C; 
    [FieldOffset(24)] public Date D; 
    [FieldOffset(32)] public double V; 
} 
+2

我可以發誓,我曾試過在過去幾天一些點..但它似乎工作現在。然而,問題在於順序*應該*工作,而框架報告的偏移量與實際使用的偏移量不匹配。我真的不希望這個插件體系結構的用戶必須指定一個屬性,並自己做數學運算才能使其工作。 – 2009-12-16 22:16:04

1

兩件事情:

  • StructLayout(Sequential)不保證包裝。您可能需要使用Pack=1,否則32和64位平臺可能會有所不同。

  • 和字符串是一個引用,而不是一個指針。如果字符串的長度始終是固定的,你可能需要使用固定的字符數組:

    public struct MyArray // This code must appear in an unsafe block 
    { 
        public fixed char pathName[128]; 
    }