0.Для примера кода:
using System;
using System.Diagnostics;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
1.Демонстрационная структура
[Serializable, StructLayout(LayoutKind.Sequential, Pack = 128)]
public struct T
{
public int a;
public byte b;
public int c;
public String d;
public short e;
};
2.Вычитание управляемых указателей:
/// Return byte-offset between managed addresses of struct instances 'hi' and 'lo'
public static long IL<T1,T2>.RefOffs(ref T1 hi, ref T2 lo) { ... }
public static class IL<T1, T2>
{
public delegate long _ref_offs(ref T1 hi, ref T2 lo);
public static readonly _ref_offs RefOffs;
static IL()
{
var dm = new DynamicMethod(
Guid.NewGuid().ToString(),
typeof(long),
new[] { typeof(T1).MakeByRefType(), typeof(T2).MakeByRefType() },
typeof(Object),
true);
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Sub);
il.Emit(OpCodes.Conv_I8);
il.Emit(OpCodes.Ret);
RefOffs = (_ref_offs)dm.CreateDelegate(typeof(_ref_offs));
}
};
3.Выявить управляемую структуру внутренней структуры:
static class demonstration
{
/// Helper thunk that enables automatic type-inference from argument types
static long RefOffs<T1,T2>(ref T1 hi, ref T2 lo) => IL<T1,T2>.RefOffs(ref hi, ref lo);
public static void Test()
{
var t = default(T);
var rgt = new T[2];
Debug.Print("Marshal.Sizeof<T>: {0,2}", Marshal.SizeOf<T>());
Debug.Print("&rgt[1] - &rgt[0]: {0,2}", RefOffs(ref rgt[1], ref rgt[0]));
Debug.Print("int &t.a {0,2}", RefOffs(ref t.a, ref t));
Debug.Print("byte &t.b {0,2}", RefOffs(ref t.b, ref t));
Debug.Print("int &t.c {0,2}", RefOffs(ref t.c, ref t));
Debug.Print("String &t.d {0,2}", RefOffs(ref t.d, ref t));
Debug.Print("short &t.e {0,2}", RefOffs(ref t.e, ref t));
}
};
4.Результаты и обсуждение
Параметр StructLayout(..., Pack)
можно добавить к объявлению struct T
с любым из следующих значений: {0, 1, 2, 4, 8, 16,32, 64, 128} .Значение по умолчанию, когда Pack
не указано или эквивалентно Pack=0
, устанавливает упаковку равной IntPtr.Size
(4
на x86, 8
на x64).
Результаты выполнениявышеприведенная программа показывает, что значение Pack
влияет только на размер маршалинга , о котором сообщает Marshal.SizeOf
, а не на фактический размер одного T
образа памяти, предполагаемого как смещение в байтах между физически смежнымиэкземпляров.Тестовый код измеряет это с помощью диагностического управляемого массива new T[2]
, присвоенного rgt .
========= x86 ==========
========= x64 ==========
-------- Pack=1 --------
-------- Pack=1 --------
Marshal.Sizeof(): 15
Marshal.Sizeof(): 19
&rgt[1] - &rgt[0]: 16
&rgt[1] - &rgt[0]: 24
-------- Pack=2 --------
-------- Pack=2 --------
Marshal.Sizeof(): 16
Marshal.Sizeof(): 20
&rgt[1] - &rgt[0]: 16
&rgt[1] - &rgt[0]: 24
--- Pack=4/0/default ---
-------- Pack=4 --------
Marshal.Sizeof(): 20
Marshal.Sizeof(): 24
&rgt[1] - &rgt[0]: 16
&rgt[1] - &rgt[0]: 24
-------- Pack=8 --------
--- Pack=8/0/default ---
Marshal.Sizeof(): 20
Marshal.Sizeof(): 32
&rgt[1] - &rgt[0]: 16
&rgt[1] - &rgt[0]: 24
-- Pack=16/32/64/128 ---
-- Pack=16/32/64/128 ---
Marshal.Sizeof(): 20
Marshal.Sizeof(): 32
&rgt[1] - &rgt[0]: 16
&rgt[1] - &rgt[0]: 24
Как уже отмечалось, мы находим, что для каждой архитектуры ( x86 , x64 ), структура управляемого поля соответствует независимо от настройки Pack
.Вот фактические смещения управляемого поля, опять же как для 32-, так и для 64-битного режима, как указано в приведенном выше коде:
┌─offs─┐
field type size x86 x64
===== ====== ==== === ===
a int 4 4 8
b byte 1 14 18
c int 4 8 12
d String 4/8 0 0
e short 2 12 16
Самое важное, на что следует обратить внимание в этомТаблица заключается в том, что (как отмечал Ганс ), сообщаемые смещения полей являются немонотонными по отношению к порядку их объявления.Поля ValueType
экземпляров всегда переупорядочиваются, поэтому все поля ссылочного типа идут первыми.Мы можем видеть, что поле String
d имеет смещение 0.
Дальнейшее переупорядочение оптимизирует порядок полей для того, чтобы разделить избыточный внутренний отступ, который в противном случае был бы потрачен впустую.Мы можем видеть это с byte
полем b , которое было перемещено из второго объявленного поля, вплоть до последнего.
Естественно, сортируя строкиВ предыдущей таблице мы можем раскрыть истинную внутреннюю управляемую структуру .NET ValueType
.Обратите внимание, что мы можем получить этот макет, несмотря на пример структуры T
, содержащей управляемую ссылку (String d
) и, следовательно, non-blittable :
============= x86 ============
============= x64 ============
field type size offs end
field type size offs end
===== ====== ==== ==== ===
===== ====== ==== ==== ===
d String 4 0 … 4
d String 8 0 … 8
a int 4 4 … 8
a int 4 8 … 12
c int 4 8 … 12
c int 4 12 … 16
e short 2 12 … 14
e short 2 16 … 18
b byte 1 14 … 15
b byte 1 18 … 19
internal padding: 1 15 … 16
internal padding: 5 19 … 24
x86 managed total size: 16
x64 managed total size: 24
Ранее мы определяли размер отдельного экземпляра управляемой структуры, вычисляя разницу смещения байтов между соседними экземплярами.Принимая это во внимание, последние строки предыдущей таблицы тривиально показывают заполнение, которое CLR внутренне применяет к концу примера struct T
.Помните еще раз, конечно, что это внутреннее заполнение установлено CLR и полностью вне нашего контроля.
5.Кода
ДляДля полноты этой последней таблицы показано количество дополнений, которые будут синтезированы на лету в течение маршалинг .Обратите внимание, что в некоторых случаях это Marshal
заполнение является отрицательным по сравнению с внутренним управляемым размером.Например, даже если внутренний управляемый размер T
в x64 составляет 24 байта, структура, передаваемая при маршалинге, может составлять 19 или 20 байтов с Pack=1
или Pack=2
соответственно.*
pack size offs end
pack size offs end
============= ==== ==== ===
============= ==== ==== ===
1 0 15 … 15
1 0 19 … 19
2 1 15 … 16
2 1 19 … 20
4/8/16/32/64… 5 15 … 20
4/8/16/32/64… 5 19 … 24