Proposal: Fixed-size buffers enhancements
See original GitHub issueBackground
Interop with code implemented with C and C++ often involves fixed-size buffers. For multiple releases C# has supported the creation of fixed-size arrays on the stack, using the ‘stackalloc’ keyword:
static unsafe void SomeMethod()
{
int* block = stackalloc int[100]; // array of 100 integers on the stack
…
}
It’s also supported the creation of fixed-size arrays embedded within structures, using the ‘fixed’ keyword:
internal unsafe struct MyBuffer
{
public fixed char FixedBuffer[128]; // array of 128 chars in the struct
}
Problem
In both of these cases, however, the creation of the fixed-size array may only happen in an unsafe context, hence the usage of the ‘unsafe’ keyword on the method and struct, respectively, in each of these examples. Further, in the case of fixed-size buffers in structs, the type can only be of a limited set: bool, byte, short, int, long, char, sbyte, ushort, uint, ulong, float or double.
As C#'s reach is extended and interop becomes even more common-place, it’s desirable to be able to work with fixed-size arrays, in particular embedded in structures, while still getting the benefits of safe code, such as bounds checking.
Solution
If the language supports ref returns (#118), C# can support this without further syntax, with simply relaxing the existing constraints on fixed-size buffers and changing the code generated for these types. For example, consider the following type:
internal struct SYSTEM_POWER_LEVEL
{
public byte Enable;
public fixed byte Spare[3]; // inline array, as in C
public uint BatteryLevel;
public POWER_ACTION_POLICY PowerPolicy;
public SYSTEM_POWER_STATE MinSystemState;
}
Exactly how this is implemented is up to the compiler, but logically the support could be thought of as being implemented as an anonymous struct with a field for every element and an indexer for accessing them:
internal struct SYSTEM_POWER_LEVEL
{
public byte Enable;
public <>s__ArrayStruct1 Spare;
public uint BatteryLevel;
public POWER_ACTION_POLICY PowerPolicy;
public SYSTEM_POWER_STATE MinSystemState;
}
internal struct <>s__ArrayStruct1
{
public byte Item0, Item1, Item2;
public ref byte this[int index]
{
get {
switch(index) {
case 0: return ref Item0;
case 1: return ref Item1;
case 2: return ref Item2;
default: Environment.FailFast();
}
}
}
}
In the past, developers have resorted to hand-crafting such a solution, implementing helper types like this manually. Instead, the compiler would be capable of implementing all of that boilerplate for the developer in as efficient a manner as possible. (If there were concerns around the efficiency of the new code generation, it could potentially only be used when using a type that was previously unsupported or when using a fixed-size buffer in a safe context. Or alternatively new syntax for expressing this new form could be devised.)
Issue Analytics
- State:
- Created 9 years ago
- Reactions:15
- Comments:21 (10 by maintainers)
Top GitHub Comments
At some level most code one writes in C# is doing interop; the question is how far down you can push and isolate the unsafeness. Yes, with this proposal I can still mess up my DllImport signature and cause problems, but if I get the DllImport correct, then the resulting struct can be used in safe code, with bounds-checked accesses to its contained fixed-size arrays, etc. In contrast, today even if I get the DllImport correct, I’m still required to use unsafe code to access the resulting struct, and I’m still able to cause serious problems when I use the resulting struct because I really am just indexing into whatever memory I want, without bounds checks, etc.
This feels like something that should be done as a CLR change. The code gen proposed is worrisome in its complexity.
Instead the CLR could allow fixed buffers with bounds checking, and the compiler could then target that.