ANYSIZE_ARRAY in C#
2024-1-30 02:6:55 Author: rastamouse.me(查看原文) 阅读量:22 收藏

Blog / January 29, 2024 /

There are multiple structures in Windows that contain fixed sized arrays. The instance I came across recently was the KERB_QUERY_TKT_CACHE_RESPONSE struct, which looks like this:

typedef struct _KERB_QUERY_TKT_CACHE_RESPONSE {
  KERB_PROTOCOL_MESSAGE_TYPE MessageType;
  ULONG                      CountOfTickets;
  KERB_TICKET_CACHE_INFO     Tickets[ANYSIZE_ARRAY];
} KERB_QUERY_TKT_CACHE_RESPONSE, *PKERB_QUERY_TKT_CACHE_RESPONSE;

ANYSIZE_ARRAY is defined as 1 in winnt.h, but the reality is that the array will be of size CountOfTickets. This value obviously cannot be known at compile time. Translating these structs to C# can be a bit of a pain because although you can define fixed-sized arrays in an unsafe structure, you can only do so for blittable value types. That means we cannot have a fixed-sized array of another struct.

internal struct KERB_QUERY_TKT_CACHE_RESPONSE
{
    public KERB_PROTOCOL_MESSAGE_TYPE MessageType;
    public uint CountOfTickets;
    public unsafe fixed KERB_TICKET_CACHE_INFO Tickets[1];  // <-- not valid
}

The solution most C# developers go with is to use a pointer type instead, and walk over the memory a little like this:

for (var i = 0; i < cache.CountOfTickets; i++)
{
	// deference ticket info
	var ticket = *(KERB_TICKET_CACHE_INFO*)ticketPtr;
		
	// do something with ticket
			
	// increment pointer by size of ticket struct
	ticketPtr = Unsafe.Add<byte>(ticketPtr, Marshal.SizeOf<KERB_TICKET_CACHE_INFO>());
}

I’m a fan of “unsafe” code, but the problem with pointer mathematics is that it’s quite easy to get wrong. There are two other methods that I know of to handle this type of data. I’m not saying they’re safer or better, just different.

The first is to create a new managed array using the CountOfTickets value, and copy all the ticket data into it in one go.

// create a new array to hold all tickets
var tickets = new KERB_TICKET_CACHE_INFO[cache.CountOfTickets];

// get pointer to 0th position in array
fixed (void* arrayPtr = &tickets[0])
{
	// calculate size of all tickets
	var size = Marshal.SizeOf<KERB_TICKET_CACHE_INFO>() * cache.CountOfTickets;

	// yeet all the data at once
	Buffer.MemoryCopy(ticketPtr, arrayPtr, size, size);
}

The second can be done in a single line, by utilising the relatively new Span feature. Note that this isn’t readily available in .NET Framework projects without installing the System.Memory and System.Buffers NuGet packages.

var tickets = new Span<KERB_TICKET_CACHE_INFO>(ticketPtr, (int)cache.CountOfTickets);

Taking a span is memory efficient because it takes a slice of existing memory, rather than allocating any new memory. However, in scenarios like the above, the ticket buffer has to be freed with LsaFreeReturnBuffer. If we did that, all the data that the span is referencing would be lost. Spans do have a .ToArray() method which creates a copy of the data that would then fall under the scope of the garbage collector.

Happy coding.


文章来源: https://rastamouse.me/anysize-array-csharp/
如有侵权请联系:admin#unsafe.sh