Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BinToss/3c7c385191031ea1743d463bd96c287e to your computer and use it in GitHub Desktop.
Save BinToss/3c7c385191031ea1743d463bd96c287e to your computer and use it in GitHub Desktop.
C# - Equivalents to C and C++ Unions and Bitfields
#!meta
{"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":[],"name":"csharp"}]}}
#!csharp
/** Q: How do I make C# equivalents to C/C++ unions and bitfields?
A: Short answer: it's possible, but can be difficult to read. */
/** references:
* Unionize Your Variables - An Introduction To Advanced Data Types In C | Hackaday
* https://hackaday.com/2018/03/02/unionize-your-variables-an-introduction-to-advanced-data-types-in-c/
* System Informer's definition of Windows' RTL_BALANCED_NODE
* https://sourcegraph.com/github.com/winsiderss/systeminformer/-/blob/phnt/include/phnt_ntdef.h?L150-171
* How to create a C/C++ union by using attributes (C#) | Microsoft Learn
* https://learn.microsoft.com/en-us/dotnet/csharp/advanced-topics/reflection-and-attributes/how-to-create-a-c-cpp-union-by-using-attributes
*/
uint tmp = 31;
// assign a given value to a bitrange, presevering unset or unrelated bits
Convert.ToString((((4 & 0b1) << 1 ) | (31 &~(0b1 << 1))), toBase: 2).Display(); // 0b0b11101
// Bitwise OR (set bits)
Convert.ToString((tmp |= ((4 & 0b1) << 1)), 2).Display(); // 0b11111
Convert.ToString((byte)((UInt32.MaxValue >> 1) & 0b11), toBase: 2).Display(); // 0b11
// Unions can be implemented in a few ways and are sometimes used in conjunction with bitfields.
// 1. Explicit layout with FieldOffset. Difficult to adjust and limited in use compared to...
// 2. Properties referring to a single field, bitmasking when only a subset of the field is used. If all significant bitfields occupy the name number of bits, use a Flag enum. Otherwise, you can a constant value for the mask.
/** readonly union with Flag-based bitmasks */
/** Example 1: Windows' PROCESS_EXTENDED_BASIC_INFORMATION struct
* // "Flags" is the shared field while the anonoymous struct's bitfields each occupy one bit following the other. Except SpareBits. Where the other fields can use bitwise ops with Flags, SpareBits will need a bitmask convering...everything except the flags.
* typedef struct _PROCESS_EXTENDED_BASIC_INFORMATION
* {
* SIZE_T Size; // set to sizeof structure on input
* PROCESS_BASIC_INFORMATION BasicInfo;
* union
* {
* ULONG Flags;
* struct
* {
* ULONG IsProtectedProcess : 1;
* ULONG IsWow64Process : 1;
* ULONG IsProcessDeleting : 1;
* ULONG IsCrossSessionCreate : 1;
* ULONG IsFrozen : 1;
* ULONG IsBackground : 1;
* ULONG IsStronglyNamed : 1;
* ULONG IsSecureProcess : 1;
* ULONG IsSubsystemProcess : 1;
* ULONG SpareBits : 23;
* };
* };
* } PROCESS_EXTENDED_BASIC_INFORMATION, *PPROCESS_EXTENDED_BASIC_INFORMATION;
*/
public struct PEBI
{
public /*SIZE_T*/ IntPtr Size; // SIZE_T is a value type that spans 32 or 64 bits depending on pointer length. It is used to specify the length, in bytes, of data or types aligned in memory. This includes padding within a type.
public /*PROCESS_BASIC_INFORMATION*/ byte BasicInfo; // let's pretend this struct is defined. I'm too lazy to write it again.
public uint Flags; // values are <1 Byte. Use bitwise flags.
// Because each bitfield is less than one byte, we have to use bitmasks via properties.
// WARNING: not thread safe! Wrap in locks or monitors to prevent race conditions!
public bool IsProtectedProcess => (Flags & (uint)PEBI_Flags.IsProtectedProcess) is not 0;
public bool IsWow64Process => (Flags & (uint)PEBI_Flags.IsWow64Process) is not 0;
/// et cetera
[Flags]
public enum PEBI_Flags : uint
{
IsProtectedProcess = 1,
IsWow64Process = 1 << 1,
IsProcessDeleting = 1 << 2,
IsCrossSessionCreate = 1 << 3,
IsFrozen = 1 << 4,
IsBackground = 1 << 5,
IsStronglyNamed = 1 << 6,
IsSecureProcess = 1 << 7,
IsSubsystemProcess = 1 << 8, // i.e. linux/android subsystem.
}
}
/** Unions and bitfields have different applications.
* Bitfields store smaller-than-byte data.
* Unions store larger-than-byte data.
*/
/** properties exposing subset of field data
* Example of union: (Children, Left, Right)
* Example of bitfields: (_, Red, Balance)
*
* RTL_BALANCED_NODE's Children-Left-Right union and Red-Balance union bitfields occupy different amounts of bits. Enum Flags could work, but it's just more work and added complexity.
*/
public unsafe struct RTL_BALANCED_NODE
{
public const uint RTL_BALANCED_NODE_RESERVED_PARENT_MASK = 3u;
public readonly RTL_BALANCED_NODE** Children;
public RTL_BALANCED_NODE* Left { get => Children[0]; set => Children[0] = value; }
public RTL_BALANCED_NODE* Right { get => Children[1]; set => Children[1] = value; }
public nuint parentValue;
public byte Red
{
get => (byte)((parentValue >> 0) & 0b1);
set => parentValue = (byte)((((nuint)value & 0b1) << 0) | (parentValue & ~((nuint)0b1 << 0)));
}//: 1;
public byte Balance
{
get => (byte)((parentValue >> 1) & 0b11);
set => parentValue = (byte)(((nuint)(value & 0b11) << 1) | (parentValue & ~((nuint)0b11 << 1)));
}//: 2;
public RTL_BALANCED_NODE* ParentValue => GetParentValue();
public unsafe RTL_BALANCED_NODE* GetParentValue() => (RTL_BALANCED_NODE*)(parentValue & ~RTL_BALANCED_NODE_RESERVED_PARENT_MASK);
/* C defs
#define RTL_BALANCED_NODE_RESERVED_PARENT_MASK 3
#define RTL_BALANCED_NODE_GET_PARENT_POINTER(Node) \
((PRTL_BALANCED_NODE)((Node)->ParentValue & ~RTL_BALANCED_NODE_RESERVED_PARENT_MASK))
*/
}
public struct ReadWriteUnionBits
{
public uint Field;
// first two bits. Right-most.
public byte BitSet0
{
get => (byte)(Field & 0b11);
set => Field = ((uint)(value & 0b11) | (Field & ~(0b11u)));
}
// next four bits
public byte BitSet1
{
get => (byte)((Field >> 2) & 0b1111);
set => Field = (((uint)(value & 0b1111) << 2) | (Field & ~(0b1111u << 2)));
}
}
/** Example of flags for bit masking a field, exposing the values as readonly properties
* Excerpt from partial PEB definition which adds undocumented fields as properties to CsWin32's PEB definition.
*
* #region bit field
* public bool ImageUsesLargePages => (((R2_bits)Reserved2[0]) & R2_bits.ImageUsesLargePages) != 0;
* public bool IsProtectedProcess => (((R2_bits)Reserved2[0]) & R2_bits.IsProtectedProcess) != 0;
* public bool IsImageDynamicallyRelocated => (((R2_bits)Reserved2[0]) & R2_bits.IsImageDynamicallyRelocated) == R2_bits.IsImageDynamicallyRelocated;
* public bool SkipPatchingUser32Forwarders => (((R2_bits)Reserved2[0]) & R2_bits.SkipPatchingUser32Forwarders) != 0;
* public bool IsPackagedProcess => (((R2_bits)Reserved2[0]) & R2_bits.IsPackagedProcess) == R2_bits.IsPackagedProcess;
* public bool IsAppContainer => (((R2_bits)Reserved2[0]) & R2_bits.IsAppContainer) == R2_bits.IsAppContainer;
* public bool IsProtectedProcessLight => (((R2_bits)Reserved2[0]) & R2_bits.IsProtectedProcessLight) == R2_bits.IsProtectedProcessLight;
* public bool IsLongPathAwareProcess => (((R2_bits)Reserved2[0]) & R2_bits.IsLongPathAwareProcess) != 0;
*
* [Flags]
* private enum R2_bits : byte
* {
* None = 0,
* ImageUsesLargePages,
* IsProtectedProcess,
* IsImageDynamicallyRelocated,
* SkipPatchingUser32Forwarders,
* IsPackagedProcess,
* IsAppContainer,
* IsProtectedProcessLight,
* IsLongPathAwareProcess
* }
* #endregion bit field
*/
/////////// END OF EXAMPLES ///////////
#region miscDefs
public readonly struct PROCESS_BASIC_INFORMATION // size in memory: 384 bits (320 + alignment padding)
{
public readonly NTSTATUS ExitStatus;
/* 32 bits of padding for alignment to size of largest base type (64-bit pointer) */
/// <summary>
/// The address of the PEB relative to its process's memory. Read object via <see cref="Peb"/>
/// </summary>
public readonly unsafe void* PebBaseAddress; // PEB*
public readonly UIntPtr AffinityMask;
public readonly KPRIORITY BasePriority;
/* 32 bits of padding for alignment to size of largest base type (64-bit pointer) */
public readonly nuint uniqueProcessId;
public readonly nuint inheritedFromUniqueProcessId;
}
// simplfied def. See dotnet/PInvoke.git, Microsoft/CsWin32.git
public struct NTSTATUS
{
public uint Value;
public static implicit operator NTSTATUS(uint v) => new(){ Value = v };
public static implicit operator uint(NTSTATUS v) => v.Value;
}
public struct KPRIORITY
{
public KPRIORITY(int value) : this() => Value = value;
public int Value;
public static implicit operator KPRIORITY(int v) => new(v);
public static explicit operator int(KPRIORITY v) => v.Value;
}
#endregion miscDefs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment