Fixed size buffer cannot be directly used from "this" object
Asked Answered
H

2

8

I am used a structure to represent pure data. One of the fields is a fixed-size buffer, as shown below.

[StructLayout(LayoutKind.Sequential, Pack=2)]
unsafe struct ImageDosHeader
{
    ...
    private fixed ushort _e_res[4];
    ...

    [Description("Reserved")]
    [DisplayName("e_res[0]")]
    public ushort e_res_0 { get { ... } set { ... } }

    ...
}

Within the get/set functions I tried to do the following but I get "Compiler Error CS1666: You cannot use fixed size buffers contained in unfixed expressions. Try using the fixed statement."

return this._e_res[0];

However, the following work:

fixed (ImageDosHeader* p = &this)
    return p->_e_res[0];

ImageDosHeader local = this;
return local._e_res[0];

I can easily use the workarounds, however, I am wondering why directly accessing the fixed-size buffer from this is illegal. Or is this a bug that I should report?

I am using .NET 2.0.

Hybridism answered 15/5, 2011 at 23:31 Comment(0)
G
12

It's because of the underlying IL instructions.

The program does this sequence of instructions to get the element you want:

  1. Load the address onto the stack.

  2. Load the offset onto the stack.

  3. Add them.

  4. Read the value at that memory address.

If the object is in the heap and then moves because of garbage collection before step 4, then the address loaded from step 1 will no longer be valid. To protect against this, you need to pin the object into memory first.

(The fact that you're accessing the structure through the this pointer means that you have no idea if the structure is on the heap or on the stack, so you have to pin it just in case it's on the heap.)

The second example works because it copies the structure to the stack, and so the copy can never move around, so the address will always be valid.

Why doesn't the same issue happen with other kinds of fields? Because their offset is known at compile-time, whereas the array index is known at run-time, so the JIT can generate code that will always access the fields correctly.

Grattan answered 15/5, 2011 at 23:37 Comment(1)
Thanks! Due to your comments about method #2 (copying to a local variable) I realized that that method would not work at all for the "set" method.Hybridism
I
6

The perspective from which you look at the fixed keyword changes its semantics, which is rather confusing. The fixed's statement original purpose has been to pin a piece of blittable memory in place, in C# 2.0 it is used along with a field declaration to denote that 'the array is exactly N elements long', thus, of fixed size, not fixed in memory.

I'd get rid of the fixed keyword in field declaration and just use:

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] private ushort[] _e_res;

This way, the struct is still blittable and not a pain in the butt to work with.

Improvisation answered 16/5, 2011 at 0:40 Comment(13)
Just don't try taking the address of the structure. ;)Grattan
The trick is to not take the address of the structure in the first place, since it is not needed. This code allows OP to call: return this._e_res[0]; while retaining blittable semantics. If you explicitly need the address, just use what GCHandle has been made for.Improvisation
@arul: Yeah, you don't need the address if you're working with a single structure. If you're working with an array of structures, though, you might want to think twice before using .NET arrays. :) (Also, GCHandle isn't a great idea here -- it performs boxing.)Grattan
Well, what problem using array of structures make? About the boxing, I'm assuming that you rarely need 'the raw pointer' to the object, so it's not much of a concern. :)Improvisation
The problem with an array is that it would then require copying the structure into new memory, calling the code, then copying it back, because the structure would not be blittable. And that's pretty slow. (That's an error I just noticed, by the way. "Blittable" means having the same managed and unmanaged representation, and having an embedded reference type makes the structure unblittable.)Grattan
You are mistaken here. ushort is a blittable type, and thus, any one-dimensional array of that type is also implicitly blittable. Also, there's no copying, if the type is blittable, then it's pinned rather then copied during marshalling.Improvisation
@arul: The array is indeed blittable by itself, but the struct isn't.Grattan
"As an optimization, arrays of blittable types and classes that contain only blittable members are pinned instead of copied during marshaling ... ". msdn.microsoft.com/en-us/library/75dwhxf7.aspx So there's no hidden copy, guess we strayed too off of the topic here ... :)Improvisation
@arul: Which array are you referring to? A T[] is blittable if and only if T is blittable. So a ushort[] is blittable. But no structure containing any array is blittable! Don't mix these up.Grattan
@arul: "Object references are not blittable. This includes an array of references to objects that are blittable by themselves. For example, you can define a structure that is blittable, but you cannot define a blittable type that contains an array of references to those structures."Grattan
If the structure is build only from blittable types, then it's blittable, as stated in the link I posted above.Improvisation
Read the documentation thoroughly: The following complex types are also blittable types: ... Formatted value types that contain only blittable types (and classes if they are marshaled as formatted types). ...Improvisation
@arul: The array contents are blittable and can be pinned. But the structure contains a reference to the array, which is not blittable. If you're still not convinced, try running GCHandle.Alloc(new ImageDosHeader()).AddrOfPinnedObject() with the OP's struct using your own array, and seeing what happens.Grattan

© 2022 - 2024 — McMap. All rights reserved.