Size of managed structures

2020-02-04 05:59发布

The .NET 4.0 Framework introduces classes for reading and writing memory mapped files. The classes are centred around methods for reading and writing structures. These are not marshalled but copied from and to the file in the form in which they are laid out in managed memory.

Let's say I want to write two structures sequentially to a memory mapped file using these methods:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct Foo
{
    public char C;
    public bool B;
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct Bar
{
}

static void Write<T1, T2>(T1 item1, T2 item2)
    where T1 : struct
    where T2 : struct
{
    using (MemoryMappedFile file = MemoryMappedFile.CreateNew(null, 32))
    using (MemoryMappedViewAccessor accessor = file.CreateViewAccessor())
    {
        accessor.Write<T1>(0L, ref item1);  //  <-- (1)
        accessor.Write<T2>(??, ref item2);  //  <-- (2)
    }
}

static void Main()
{
    Foo foo = new Foo { C = 'α', B = true };
    Bar bar = new Bar { };
    Write(foo, bar);
}

How would I get the number of bytes written in (1) so I can write the next value adjacently in (2)?

Note: The number of bytes in the example is 3(=2+1), not 5(=1+4) as returned by Marshal.SizeOf.

Note 2: sizeof cannot determine the size of generic type parameters.

标签: c# .net-4.0
3条回答
祖国的老花朵
2楼-- · 2020-02-04 06:33

1. One answer on this page proposes using the internal function Marshal.SizeOfType, but this only works for structs which do not contain any managed references. On .NET 4.7 it throws an ArgumentException when passed a reference (class) type, or a struct type which contains embedded references.

2. Another answer here suggests using the IL sizeof opcode. This works correctly for all struct value types--including generics and those with embedded references--but for reference types it always returns IntPtr.Size (i.e., the value 4 or 8), as opposed to the actual layout size of (an instance of) the managed class. This may be what you want, depending on your situation. Note that this sort of result gracefully degrades by conflating the case of a struct containing a single embedded reference (handle) with a single reference (handle) itself.

A simpler way to invoke the sizeof IL instruction is via the System.Runtime.CompilerServices.Un­safe package:

int struct_layout_bytes = Unsafe.Sizeof<T>();

3. If you truly need the actual layout size of (an instance of) a managed class for some reason, then you're probably doing something wrong, but you can obtain it via the following, which only works for reference types, that is, when typeof(T).IsValueType is false.

int class_layout_bytes = Marshal.ReadInt32(typeof(T).TypeHandle.Value, 4)

4. Therefore--and still with the preceding caveat--to get the instance layout size for any reference- or value-type--including those containing embedded references--from its Type handle, combine methods #2 and #3:

int instance_layout_bytes = typeof(T).IsValueType ? 
                                Unsafe.Sizeof<T>() : 
                                Marshal.ReadInt32(typeof(T).TypeHandle.Value, 4);



related: Size of struct with generic type fields

查看更多
再贱就再见
3楼-- · 2020-02-04 06:37

It seems there is no documented/public way to access the internal SizeOfType function used by the MemoryMappedViewAccessor class, so the most practical way of getting the size of those structures would be to use reflection like this:

static readonly Func<Type, uint> SizeOfType = (Func<Type, uint>)Delegate.CreateDelegate(typeof(Func<Type, uint>), typeof(Marshal).GetMethod("SizeOfType", BindingFlags.NonPublic | BindingFlags.Static));

static void Write<T1, T2>(T1 item1, T2 item2)
    where T1 : struct
    where T2 : struct
{
    using (MemoryMappedFile file = MemoryMappedFile.CreateNew(null, 32))
    using (MemoryMappedViewAccessor accessor = file.CreateViewAccessor())
    {
        accessor.Write(0, ref item1);
        accessor.Write(SizeOfType(typeof(T1)), ref item2);
    }
}
查看更多
够拽才男人
4楼-- · 2020-02-04 06:49

You can use Emit to access the Sizeof opcode and bypass compiler's restriction on getting sizeof(T):

var sizeOfMethod = new DynamicMethod(
    "GetManagedSizeImpl"
,   typeof(uint)
,   null
,   true);
var genSizeOf = sizeOfMethod.GetILGenerator();
genSizeOf.Emit(OpCodes.Sizeof, typeof(T));
genSizeOf.Emit(OpCodes.Ret);
var sizeOfFunction = (Func<uint>)sizeOfMethod.CreateDelegate(typeof(Func<uint>));

// ...
int size = checked((int)sizeOfFunction());
查看更多
登录 后发表回答