C# interop: bad interaction between fixed and MarshalAs - c#

I need to marshal some nested structures in C# 4.0 into binary blobs to pass to a C++ framework.
I have so far had a lot of success using unsafe/fixed to handle fixed length arrays of primitive types. Now I need to handle a structure that contains nested fixed length arrays of other structures.
I was using complicated workarounds flattening the structures but then I came across an example of the MarshalAs attribute which looked like it could save me a great deal of problems.
Unfortunately whilst it gives me the correct amount of data it seems to also stop the fixed arrays from being marshalled properly, as the output of this program demonstrates. You can confirm the failure by putting a breakpoint on the last line and examining the memory at each pointer.
using System;
using System.Threading;
using System.Runtime.InteropServices;
namespace MarshalNested
{
public unsafe struct a_struct_test1
{
public fixed sbyte a_string[3];
public fixed sbyte some_data[12];
}
public struct a_struct_test2
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public sbyte[] a_string;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public a_nested[] some_data;
}
public unsafe struct a_struct_test3
{
public fixed sbyte a_string[3];
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public a_nested[] some_data;
}
public unsafe struct a_nested
{
public fixed sbyte a_notherstring[3];
}
class Program
{
static unsafe void Main(string[] args)
{
a_struct_test1 lStruct1 = new a_struct_test1();
lStruct1.a_string[0] = (sbyte)'a';
lStruct1.a_string[1] = (sbyte)'b';
lStruct1.a_string[2] = (sbyte)'c';
a_struct_test2 lStruct2 = new a_struct_test2();
lStruct2.a_string = new sbyte[3];
lStruct2.a_string[0] = (sbyte)'a';
lStruct2.a_string[1] = (sbyte)'b';
lStruct2.a_string[2] = (sbyte)'c';
a_struct_test3 lStruct3 = new a_struct_test3();
lStruct3.a_string[0] = (sbyte)'a';
lStruct3.a_string[1] = (sbyte)'b';
lStruct3.a_string[2] = (sbyte)'c';
IntPtr lPtr1 = Marshal.AllocHGlobal(15);
Marshal.StructureToPtr(lStruct1, lPtr1, false);
IntPtr lPtr2 = Marshal.AllocHGlobal(15);
Marshal.StructureToPtr(lStruct2, lPtr2, false);
IntPtr lPtr3 = Marshal.AllocHGlobal(15);
Marshal.StructureToPtr(lStruct3, lPtr3, false);
string s1 = "";
string s2 = "";
string s3 = "";
for (int x = 0; x < 3; x++)
{
s1 += (char) Marshal.ReadByte(lPtr1+x);
s2 += (char) Marshal.ReadByte(lPtr2+x);
s3 += (char) Marshal.ReadByte(lPtr3+x);
}
Console.WriteLine("Ptr1 (size " + Marshal.SizeOf(lStruct1) + ") says " + s1);
Console.WriteLine("Ptr2 (size " + Marshal.SizeOf(lStruct2) + ") says " + s2);
Console.WriteLine("Ptr3 (size " + Marshal.SizeOf(lStruct3) + ") says " + s3);
Thread.Sleep(10000);
}
}
}
Output:
Ptr1 (size 15) says abc
Ptr2 (size 15) says abc
Ptr3 (size 15) says a
So for some reason it is only marshalling the first character of my fixed ANSI strings. Is there any way around this, or have I done something stupid unrelated to the marshalling?

This is a case of a missing diagnostic. Somebody should have spoken up and tell you that your declaration is not supported. Where that somebody is either the C# compiler, producing a compile error, or the CLR field marshaller, producing a runtime exception.
It's not like you can't get a diagnostic. You'll certainly get one when you actually start using the struct as intended:
a_struct_test3 lStruct3 = new a_struct_test3();
lStruct3.some_data = new a_nested[4];
lStruct3.some_data[0] = new a_nested();
lStruct3.some_data[0].a_notherstring[0] = (sbyte)'a'; // Eek!
Which elicits CS1666, "You cannot use fixed size buffers contained in unfixed expressions. Try using the fixed statement". Not that "try this" advice is all that helpful:
fixed (sbyte* p = &lStruct3.some_data[0].a_notherstring[0]) // Eek!
{
*p = (sbyte)'a';
}
Exact same CS1666 error. Next thing you'd try is put an attribute on the fixed buffer:
public unsafe struct a_struct_test3 {
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public fixed sbyte a_string[3];
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public a_nested[] some_data;
}
//...
a_struct_test3 lStruct3 = new a_struct_test3();
lStruct3.some_data = new a_nested[4];
IntPtr lPtr3 = Marshal.AllocHGlobal(15);
Marshal.StructureToPtr(lStruct3, lPtr3, false); // Eek!
Keeps the C# compiler happy but now the CLR speaks up and you get a TypeLoadException at runtime: "Additional information: Cannot marshal field 'a_string' of type 'MarshalNested.a_struct_test3': Invalid managed/unmanaged type combination (this value type must be paired with Struct)."
So, in a nutshell you should have gotten either CS1666 or TypeLoadException on your original attempt as well. That did not happen because the C# compiler was not forced to look at the bad part, it only generates CS1666 on a statement that accesses the array. And it did not happen at runtime because the field marshaller in the CLR did not attempt to marshal the array because it is null. You can file a bug feedback report at connect.microsoft.com but I'd be greatly surprised if they won't close it with "by design".
In general, an obscure detail matters a great deal to the field marshaller in the CLR, the chunk of code that converts struct values and class objects from their managed layout to their unmanaged layout. It is poorly documented, Microsoft does not want to nail down the exact implementation details. Mostly because they depend too much on the target architecture.
What matters a great deal is whether or not a value or object is blittable. It is blittable when the managed and unmanaged layout is identical. Which only happens when every member of the type has the exact same size and alignment in both layouts. That normally only happens when the fields are of a very simple value type (like byte or int) or a struct that itself is blittable. Notoriously not when it is bool, too many conflicting unmanaged bool types. A field of an array type is never blittable, managed arrays don't look anything like C arrays since they have an object header and a Length member.
Having a blittable value or object is highly desirable, it avoids the field marshaller from having to create a copy. The native code gets a simple pointer to managed memory and all that is needed is to pin the memory. Very fast. It is also very dangerous, if the declaration does not match then the native code can easily color outside the lines and corrupt the GC heap or stack frame. A very common reason for a program that use pinvoke to bomb randomly with ExecutionEngineException, excessively difficult to diagnose. Such a declaration really deserves the unsafe keyword but the C# compiler does not insist on it. Nor can it, compilers are not allowed to make any assumptions about managed object layout. You keep it safe by using Debug.Assert() on the return value of Marshal.SizeOf<T>, it must be an exact match with the value of sizeof(T) in a C program.
As noted, arrays are an obstacle to getting a blittable value or object. The fixed keyword is intended as a workaround for this. The CLR treats it like an opaque value type with no members, just a blob of bytes. No object header and no Length member, as close as you could get to a C array. And used in C# code like you'd use an array in a C program, you must use a pointer to address the array elements and check three times that you don't color outside of the lines. Sometimes you must use a fixed array, happens when you declare a union (overlapping fields) and you overlap an array with a value. Poison to the garbage collector, it can no longer figure out if the field stores an object root. Not detected by the C# compiler but reliably trips a TypeLoadException at runtime.
Long story short, use fixed only for a blittable type. Mixing fields of a fixed size buffer type with fields that must be marshaled cannot work. And isn't useful, the object or value gets copied anyway so you might as well use the friendly array type.

Related

How do I maintain the value of members referred to by a pointer in C#?

I couldn't think of a better way to word the title of this question, so I can change that if recommended.
I'm writing a Space Invaders emulator as a learning project and I have encountered an issue when trying to test one of the op codes I implemented. Here is the test code:
public unsafe void InrBTest()
{
Processor8080 processor = new Processor8080();
Processor8080* processorPtr = &processor;
byte testMemory = 0x03;
processorPtr->pc = 0x04;
processorPtr->b = 0x38;
//processorPtr->c = 0xFE;
processorPtr->memory = &testMemory;
processorPtr->memory[processorPtr->pc] = 0x04;
emulator.Emulate8080OpCode(processorPtr);
}
As you can see, there is nothing crazy going on here. All I'm doing is setting up a Processor8080 object, getting a pointer to it, and then setting some values to test.
The problem is the second-to-last line: processorPtr->memory[processorPtr->pc] = 0x04;. After this line executes, all of the values that I had previously set get completely changed and I get crazy values for the fields in the Processor8080 object -- even values that hadn't previously been set.
Also, the memory field in the Processor8080 struct gets set to null. I have several other tests that are set up this exact way, but none of them encounter this issue; they all work fine. I'll put the code for the Processor8080 struct below for reference, but I don't think the issue is there.
I'm fairly new to this kind of programming, so it wouldn't surprise me if I've just missed something somewhere. The most confusing thing to me is that other tests I've written don't do this at all.
I'll post one of the others below to show you an example. Thanks for any help.
Processor8080 struct:
public struct ConditionCodes
{
[BitFieldLength(1)]
public byte z;
[BitFieldLength(1)]
public byte p;
[BitFieldLength(1)]
public byte s;
[BitFieldLength(1)]
public byte cy;
[BitFieldLength(1)]
public byte ac;
[BitFieldLength(3)]
public byte pad;
}
public struct Processor8080
{
public byte a, b, c, d, e, h, l; //registers
public ushort sp, pc; //stack pointer, program counter
public unsafe byte* memory; //RAM
public byte intEnable;
public ConditionCodes cc;
}
Example of a test that is working:
public void InxBTest()
{
//Tests the INX B (0x03) instruction
unsafe
{
Processor8080 processor = new Processor8080();
Processor8080* processorPtr = &processor;
byte testMemory = 0x03;
processorPtr->pc = 0x03;
processorPtr->b = 0x38;
processorPtr->c = 0xFE;
processorPtr->memory = &testMemory;
processorPtr->memory[processorPtr->pc] = 0x03;
emulator.Emulate8080OpCode(processorPtr);
}
}
EDIT:
I have discovered that, as long as I don't set processorPtr->pc to a value greater that 0x03, there is no issue. I can't think of why that is, but if someone can shed some light, I'd greatly appreciate it.
It's a good old fashioned buffer overflow error.
You're assigning the address of a single byte to pc->memory:
processorPtr->memory = &testMemory;
Then, you're accessing that memory like an array:
processorPtr->memory[processorPtr->pc] = 0x03;
Problem is, here, processorPtr->pc is equal to 4, but the memory is only one byte long. Uh oh. Since it's memory on the stack, my guess is you're actually trampling over other values on the stack, and since your Processor8080 type is a struct, it lives on the stack, so you end up messing around with it.
If it appears to be working with values smaller than 4, it's probably because of padding or alignment. If your stack is 4 byte aligned, then you have to go at least 4 bytes to start touching other objects on it. Those are all suppositions, of course: you're dealing with undefined behavior here.
So, you're essentially accessing things outside the bounds of an array, but since it's unsafe code, you don't get a nice clean exception when doing it, instead you get weird side effects.
But anyway, working with pointers is a pain, and that's one example of it. I'd recommend you stick to managed memory.

Why does it appear that this string is stored inline by value in an explicit layout class or struct?

I have been doing some extremely unsafe and slightly useless messing with the System.Runtime.CompilerServices.Unsafe MSIL package that allows you to do a lot of things with pointers you can't in C#. I created an extension method that returns a ref byte, with that byte being the start of the Method Table pointer at the start of the object, which allows you to use any object in a fixed statement, taking a byte pointer to the start of the object:
public static unsafe ref byte GetPinnableReference(this object obj)
{
return ref *(byte*)*(void**)Unsafe.AsPointer(ref obj);
}
I then decided to test it, using this code:
[StructLayout(LayoutKind.Explicit, Pack = 0)]
public class Foo
{
[FieldOffset(0)]
public string Name = "THIS IS A STRING";
}
[StructLayout(LayoutKind.Explicit, Pack = 0)]
public struct Bar
{
[FieldOffset(0)]
public string Name;
}
And then in the method
var foo = new Foo();
//var foo = new Bar { Name = "THIS IS A STRING" };
fixed (byte* objPtr = foo)
{
char* stringPtr = (char*)(objPtr + (foo is Foo ? : 12));
for (var i = 0; i < foo.Name.Length; i++)
{
Console.Write(*(stringPtr + i /* Char offset */));
}
Console.WriteLine();
}
Console.ReadKey();
The really weird thing about this is that this successfully prints "THIS IS A STRING"? The code works like this:
Get a byte pointer, objPtr, to the very start of the object
Add 16 to get to the actual data
Add another 16 to get past the string header to the string's actual data
Add 4 to skip the first 4 bytes of the string, which are the int _stringLength (exposed to us as Length property)
Interpret the result as a char pointer
EDIT: Important point - when switching foo to type Bar, I only add 12 rather than 36 bytes on (36 = 16 + 16 + 4). Why does it only have 8 bytes of header in the struct rather than 32 in the class? It would make sense that the struct has a smaller header (no syncblk i believe), but then why doesn't the string still have a 16 byte head? I would expect the offset to be 8 + 16 + 4 (28) rather than just 8 + 4 (12)
However, this assumption makes a big flaw. It assumes the string is stored inline inside the class/struct. However, strings are reference types and only a reference to them is stored inside the object from my knowledge. Particularly, I thought reference types can only be put on the heap - and as this struct is a local variable I thought it was on the stack. If it wasn't, the code would surely look something more like this to get the stringPtr
byte** stringRefptr = objPtr + 16;
char* stringPtr = (char*)(*stringRefPtr + 20);
where you take the string reference as a byte** and then use it to get to the chars. And this still wouldn't make sense if the string internally was a char[] (I'm not sure if it is)
So why does this work, and print the string, even though it mistakenly assumes string is stored inline, when string is a reference type?
NOTE: Requires .NET Core 2.0+ with System.Runtime.CompilerServices.Unsafe nuGet package, and C# 7.3+.
Because strings are indeed stored inline. The problem with your assumption is that strings are not normal objects but handled as a special case by the CLR (probably for performance reasons).
And as for the objects, since the string is the only member this would naturally be the most efficient way to allocate the memory. Try adding more members after your string member and your code would break.
Here’s a few references in how strings are stored in the CLR
https://mattwarren.org/2016/05/31/Strings-and-the-CLR-a-Special-Relationship/
https://codeblog.jonskeet.uk/2011/04/05/of-memory-and-strings/
Edit: I didn’t check, but I believe your reasoning behind the offsets is off. 36 = 24 (size of object) + 8 (string header?) + 4 (size of int) while for the struct the24 bytes becomes 0 as it has no header.

Marshalling Array of Struct from IntPtr - Only First Value Marshals Correctly

I'm passing an address to an array of structures into a C# DLL from an external program.
I thought that I would first make a simple test, in order to see if the approach would work, by trying to marshal a pointer into an array of structs on the C# side.
Given the following struct:
[StructLayout(LayoutKind.Sequential)]
struct TestStruct
{
public int id;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string someString;
}
and the following code which attempts to read the struct array (the section up until the for loop is just to simulate the pointer being passed from the other program):
TestStruct[] testStructs = new TestStruct[3];
testStructs[0].id = 1;
testStructs[0].someString = "Value 1";
testStructs[1].id = 2;
testStructs[1].someString = "Value 2";
testStructs[2].id = 3;
testStructs[2].someString = "Value 3";
int size = Marshal.SizeOf(testStructs[0]);
IntPtr ptrFirst = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(testStructs[0], ptrFirst, true);
long ptrAddrLong = ptrFirst.ToInt64();
for (int i = 0; i < testStructs.Length; i++) {
IntPtr thisPtr = new IntPtr(ptrAddrLong);
TestStruct testStruct = Marshal.PtrToStructure<TestStruct>(thisPtr);
ptrAddrLong += size;
}
can anyone shed any light why, when I debug through the for loop, only the first testStruct item is marshalled correctly from the pointer? All subsequent items contain garbage in the fields, as it appears the pointer address is incorrect after the first iteration.
First iteration:
Second iteration:
Size is reported as 36, which seems to be correct.
I've tried using explicit layout, but this didn't make any difference.
Thanks
Why do you think that Marshal.StructureToPtr marshals anything more than you have told it to marshal?
Marshal.StructureToPtr(testStructs[0], ptrFirst, true); marshals a single TestStruct to the memory at ptrFirst.
It is basically just a smart memory copy that takes into account all those marshaling attributes.
P.S.: And take into account that memory at ptrFirst can hold at most Marshal.SizeOf(testStructs[0]). So you can't (at least without risking some nasty memory access problem) read a memory behind ptrFirst + size.

interop with nim return Struct Array containing a string /char* member

interoping nim dll from c# i could call and execute the code below
if i will add another function (proc) that Calls GetPacks() and try to echo on each element's buffer i could see the output in the C# console correctly
but i could not transfer the data as it is, i tried everything but i could not accomplish the task
proc GetPacksPtrNim(parSze: int, PackArrINOUT: var DataPackArr){.stdcall,exportc,dynlib.} =
PackArrINOUT.newSeq(parSze)
var dummyStr = "abcdefghij"
for i, curDataPack in PackArrINOUT.mpairs:
dummyStr[9] = char(i + int8'0')
curDataPack = DataPack(buffer:dummyStr, intVal: uint32 i)
type
DataPackArr = seq[DataPack]
DataPack = object
buffer: string
intVal: uint32
when i do same in c/c++ the type i am using is either an IntPtr or char*
that is happy to contain returned buffer member
EXPORT_API void __cdecl c_returnDataPack(unsigned int size, dataPack** DpArr)
{
unsigned int dumln, Index;dataPack* CurDp = {NULL};
char dummy[STRMAX];
*DpArr = (dataPack*)malloc( size * sizeof( dataPack ));
CurDp = *DpArr;
strncpy(dummy, "abcdefgHij", STRMAX);
dumln = sizeof(dummy);
for ( Index = 0; Index < size; Index++,CurDp++)
{
CurDp->IVal = Index;
dummy[dumln-1] = '0' + Index % (126 - '0');
CurDp->Sval = (char*) calloc (dumln,sizeof(dummy));
strcpy(CurDp->Sval, dummy);
}
}
c# signature for c code above
[DllImport(#"cdllI.dll", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
private static extern uint c_returnDataPack(uint x, DataPackg.TestC** tcdparr);
C# Struct
public unsafe static class DataPackg
{
[StructLayout(LayoutKind.Sequential)]
public struct TestC
{
public uint Id;
public IntPtr StrVal;
}
}
finally calling the function like so:
public static unsafe List<DataPackg.TestC> PopulateLstPackC(int ArrL)
{
DataPackg.TestC* PackUArrOut;
List<DataPackg.TestC> RtLstPackU = new List<DataPackg.TestC>(ArrL);
c_returnDataPack((uint)ArrL, &PackUArrOut);
DataPackg.TestC* CurrentPack = PackUArrOut;
for (int i = 0; i < ArrL; i++, CurrentPack++)
{
RtLstPackU.Add(new DataPackg.TestC() { StrVal = CurrentPack->StrVal, Id = CurrentPack->Id });
}
//Console.WriteLine("Res={0}", Marshal.PtrToStringAnsi((IntPtr)RtLstPackU[1].StrVal));//new string(RtLstPackU[0].StrVal));
return RtLstPackU;
}
how could i produce similar c code as above from Nim ?
it doesn't have to be same code, but same effect, that in c# i would be able to read the content of the string. for now, the int is readable but the string is not
Edit:
this is what i tried to make things simple
struct array of int members
Update:
it seem that the problem is to do with my settings of nim in my windows OS.
i will be updating as soon as i discover what exactly is wrong.
The string type in Nim is not equivalent to the C's const char* type. Strings in Nim are represented as pointers, pointing into a heap-allocated chunk of memory, which has the following layout:
NI length; # the length of the stored string
NI capacity; # how much room do we have for growth
NIM_CHAR data[capacity]; # the actual string, zero-terminated
Please beware that these types are architecture specific and they are really an implementation detail of the compiler that can be changed in the future. NI is the architecture-default interger type and NIM_CHAR is usually equivalent to a 8-bit char, since Nim is leaning towards the use of UTF8.
With this in mind, you have several options:
1) You can teach C# about this layout and access the string buffers at their correct location (the above caveats apply). An example implementation of this approach can be found here:
https://gist.github.com/zah/fe8f5956684abee6bec9
2) You can use a different type for the buffer field in your Nim code. Possible candidates are ptr char or the fixed size array[char]. The first one will require you to give up the automatic garbage collection and maintain a little bit of code for manual memory management. The second one will give up a little bit of space efficiency and it will put hard-limits on the size of these buffers.
EDIT:
Using cstring may also look tempting, but it's ultimately dangerous. When you assign a regular string to a cstring, the result will be a normal char * value, pointing to the data buffer of the Nim string described above. Since the Nim garbage collector handles properly interior pointers to allocated values, this will be safe as long as the cstring value is placed in a traced location like the stack. But when you place it inside an object, the cstring won't be traced and nothing prevents the GC from releasing the memory, which may create a dangling pointer in your C# code.
Try to change your struct to:
public unsafe static class DataPackg
{
[StructLayout(LayoutKind.Sequential)]
public struct TestC
{
public uint Id;
[MarshalAs(UnmanagedType.LPStr)]
public String StrVal;
}
}

Using reflection to determine how a .Net type is layed out in memory

I'm experimenting with optimizing parser combinators in C#. One possible optimization, when the serialized format matches the in-memory format, is to just do an (unsafe) memcpy of the data to be parsed over an instance or even many instances of the type.
I want to write code that determines if the in-memory format matches the serialized format, in order to dynamically determine if the optimization can be applied. (Obviously this is an unsafe optimization and might not work for a whole bunch of subtle reasons. I'm just experimenting, not planning to use this in production code.)
I use the attribute [StructLayout(LayoutKind.Sequential, Pack = 1)] to force no padding and to force the in-memory order to match declaration order. I check for that attribute with reflection, but really all this confirms is "no padding". I also need the order of the fields. (I would strongly prefer to not have to manually specified FieldOffset attributes for every field, since that would be very error prone.)
I assumed I could use the order of fields returned by GetFields, but the documentation explicitly calls out that the order is unspecified.
Given that I am forcing the order of fields with the StructLayout attribute, is there a way to reflect on that ordering?
edit I'm fine with the restriction that all of the fields must be blittable.
This is unnecessary if using LayoutKind.Sequential with blittable types
You don't need to use reflection or any other mechanism to find out the order of struct fields in memory, as long as all the fields are blittable.
The blittable fields for a struct declared with LayoutKind.Sequential will be in memory in the order in which the fields are declared. That's what LayoutKind.Sequential means!
From this documentation:
For blittable types, LayoutKind.Sequential controls both the layout in managed memory and the layout in unmanaged memory. For non-blittable types, it controls the layout when the class or structure is marshaled to unmanaged code, but does not control the layout in managed memory.
Note that this doesn't tell you how much padding each field is using. To find that out, see below.
To determine the field order when using LayoutKind.Auto, or the field offsets when using any layout
It's fairly easy to find the struct field offsets if you're happy to use unsafe code, and to not use reflection.
You just need to take the address of each field of the struct and calculate its offset from the start of the struct. Knowing the offsets of each field, you can calculate their order (and any padding bytes between them). To calculate the padding bytes used for the last field (if any) you will also need to get the total size of the struct using sizeof(StructType).
The following example works for 32-bit and 64-bit. Note that you don't need to use fixed keyword because the struct is already fixed due to it being on the stack (you'll get a compile error if you try to use fixed with it):
using System;
using System.Runtime.InteropServices;
namespace Demo
{
[StructLayout(LayoutKind.Auto, Pack = 1)]
public struct TestStruct
{
public int I;
public double D;
public short S;
public byte B;
public long L;
}
class Program
{
void run()
{
var t = new TestStruct();
unsafe
{
IntPtr p = new IntPtr(&t);
IntPtr pI = new IntPtr(&t.I);
IntPtr pD = new IntPtr(&t.D);
IntPtr pS = new IntPtr(&t.S);
IntPtr pB = new IntPtr(&t.B);
IntPtr pL = new IntPtr(&t.L);
Console.WriteLine("I offset = " + ptrDiff(p, pI));
Console.WriteLine("D offset = " + ptrDiff(p, pD));
Console.WriteLine("S offset = " + ptrDiff(p, pS));
Console.WriteLine("B offset = " + ptrDiff(p, pB));
Console.WriteLine("L offset = " + ptrDiff(p, pL));
Console.WriteLine("Total struct size = " + sizeof(TestStruct));
}
}
long ptrDiff(IntPtr p1, IntPtr p2)
{
return p2.ToInt64() - p1.ToInt64();
}
static void Main()
{
new Program().run();
}
}
}
To determine the field offsets when using LayoutKind.Sequential
If your struct uses LayoutKind.Sequential then you can use Marshal.OffsetOf() to get the offset directly, but this does not work with LayoutKind.Auto:
foreach (var field in typeof(TestStruct).GetFields())
{
var offset = Marshal.OffsetOf(typeof (TestStruct), field.Name);
Console.WriteLine("Offset of " + field.Name + " = " + offset);
}
This is clearly a better way to do it if you are using LayoutKind.Sequential since it doesn't require unsafe code, and it's much shorter - and you don't need to know the names of the fields in advance. As I said above, it is not needed to determine the order of the fields in memory - but this might be useful if you need to find out about how much padding is used.
As a reference for those who want to know the order and the kind of layout. For example if a type contains non-blittable types.
var fields = typeof(T).GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
fields.SortByFieldOffset();
var isExplicit = typeof(T).IsExplicitLayout;
var isSequential = typeof(T).IsLayoutSequential;
It uses an extension method that I wrote:
public static void SortByFieldOffset(this FieldInfo[] fields) {
Array.Sort(fields, (a, b) => OffsetOf(a).CompareTo(OffsetOf(b)) );
}
private static int OffsetOf(FieldInfo field) {
return Marshal.OffsetOf(field.DeclaringType, field.Name).ToInt32();
}
MSDN contains useful info on IsLayoutSequential.

Categories