Распределение записей с битовыми полями в C#
Возможно ли маршалировать структуру в стиле C, содержащую битовые поля, в структуру C#, или вам придется маршалировать ее в базовый тип, а затем делать битовые маски?
Например, я хотел бы выделить из структуры стиля C, как это:
struct rgb16 {
unsigned int R : 4;
unsigned int G : 5;
unsigned int B : 4;
}
И направить это на что-то вроде этого:
[StructLayout(LayoutKind.Sequential)]
public struct Rgb16 {
public byte R;
public byte G;
public byte B;
}
4 ответа
В C# нет битовых полей. Таким образом, я бы пошел со свойствами, которые инкапсулируют биты:
[StructLayout(LayoutKind.Sequential)]
public struct Rgb16 {
private readonly UInt16 raw;
public byte R{get{return (byte)((raw>>0)&0x1F);}}
public byte G{get{return (byte)((raw>>5)&0x3F);}}
public byte B{get{return (byte)((raw>>11)&0x1F);}}
public Rgb16(byte r, byte g, byte b)
{
Contract.Requires(r<0x20);
Contract.Requires(g<0x40);
Contract.Requires(b<0x20);
raw=r|g<<5|b<<11;
}
}
Я избегал добавления сеттеров, потому что мне нравятся неизменяемые структуры, но в принципе вы тоже можете их добавлять.
Это мой "безопасный C#" порт структуры rgb16.
[StructLayout(LayoutKind.Explicit, Size = 2, Pack = 1)]
public class Color16
{
// Btifield: 5
[FieldOffset(0)]
private ushort b_i;
public ushort b
{
get { return (ushort)((b_i >> 11) & 0x1F); }
set { b_i = (ushort)((b_i & ~(0x1F << 11)) | (value & 0x3F) << 11); }
}
// Bitfield: 6
[FieldOffset(0)]
private ushort g_i;
public ushort g
{
get { return (ushort)((g_i >> 5) & 0x7F); }
set { g_i = (ushort)((g_i & ~(0x7F << 5)) | (value & 0x7F) << 5); }
}
// Bitfield: 5
[FieldOffset(0)]
private ushort r_i;
public ushort r
{
get { return (ushort) (r_i & 0x1F); }
set { r_i = (ushort) ((r_i & ~0x1F) | (value & 0x1F)); }
}
[FieldOffset(0)]
public ushort u;
public Color16() { }
public Color16(Color16 c) { u = c.u; }
public Color16(ushort U) { u = U; }
}
Большую часть вчерашнего дня я потратил, пытаясь решить эту проблему, как большую часть проблемы "Представления битовых полей объединения с использованием StrucLayout и FieldOffset в C#", которая не только ответит на ваш вопрос (см. Выше), но и может быть найдена здесь:
Представление битовых полей объединения с использованием StrucLayout и FieldOffset в C#
По сути, вам необходимо определить структуру (тип значения) и использовать объект BitVector32, чтобы определить секцию битового поля для каждого битового поля, которое вы хотите представить. Вы можете пропустить часть о союзе, так как это не относится к вашему вопросу, но большая часть поста по-прежнему относится к вашему вопросу.
Просто для удовольствия, я подумал, что я хотел бы создать структуру C# для вашего примера RGB16:
Примечание: объект BitVector32 имеет длину 32 бита, поэтому "16" на прозвище вводит в заблуждение... обратите внимание на это
[StructLayout(LayoutKind.Explicit, Size = 1, CharSet = CharSet.Ansi)]
public struct Rgb16
{
#region Lifetime
/// <summary>
/// Ctor
/// </summary>
/// <param name="foo"></param>
public Rgb16(int foo)
{
// allocate the bitfield
buffer = new BitVector32(0);
// initialize bitfield sections
r = BitVector32.CreateSection(0x0f); // 4
g = BitVector32.CreateSection(0x1f, r); // 5
b = BitVector32.CreateSection(0x0f, g); // 4
}
#endregion
#region Bifield
// Creates and initializes a BitVector32.
[FieldOffset(0)]
private BitVector32 buffer;
#endregion
#region Bitfield sections
/// <summary>
/// Section - Red
/// </summary>
private static BitVector32.Section r;
/// <summary>
/// Section - Green
/// </summary>
private static BitVector32.Section g;
/// <summary>
/// Section - Blue
/// </summary>
private static BitVector32.Section b;
#endregion
#region Properties
/// <summary>
/// Flag 1
/// </summary>
public byte R
{
get { return (byte)buffer[r]; }
set { buffer[r] = value; }
}
/// <summary>
/// Flag 2
/// </summary>
public byte G
{
get { return (byte)buffer[g]; }
set { buffer[g] = value; }
}
/// <summary>
/// Flag 1
/// </summary>
public byte B
{
get { return (byte)buffer[b]; }
set { buffer[b] = value; }
}
#endregion
#region ToString
/// <summary>
/// Allows us to represent this in human readable form
/// </summary>
/// <returns></returns>
public override string ToString()
{
return $"Name: {nameof(Rgb16)}{Environment.NewLine}Red: {R}: Green: {G} Blue: {B} {Environment.NewLine}BitVector32: {buffer}{Environment.NewLine}";
}
#endregion
}
Чтобы использовать это, вы должны выделить следующее:
internal static class Program
{
/// <summary>
/// Main entry point
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
var rgb16 = new Rgb16(0)
{
R = 24,
G = 16,
B = 42
};
Также обратите внимание, что есть ссылка на это:
Здесь есть много других ответов, но у них есть много подводных камней, о которых нужно знать. Возможно, лучшее, что я могу сделать здесь, это просто перечислить то, что вы можете искать:
- Обязательно упакуйте свои данные на границе байта
- Убедитесь, что вы указали размер ваших типов данных, т.е. int меняет размер в зависимости от аппаратного обеспечения, System.Int32 - нет.
- Убедитесь, что вы соблюдаете "порядковый номер" ваших целочисленных типов данных
- Избегайте, если это вообще возможно, каких-либо связей с базовым языком, т.е. избегайте менеджера языковой памяти - придерживайтесь "простых старых типов данных". Это значительно упростит маршалинг данных по сети.
Я упорядочил битовые поля, такие как:
public struct Rgb16 {
public ushort Value; // two byte value that internally contain structure R(4):G(5):B(4)
public Rgb16BitField GetBitField
{
get; set;
}
}
где свойство создает новую структуру, как вы упомянули, путем деления значения на биты.
Это не лучший способ сделать это, но я не нашел ничего другого, что сработало бы для меня. Я могу предоставить код для GetBitField, если вы хотите (он не очень компактен)
Upd: Ссылка, предоставленная Тони в комментариях к вашему вопросу, использует ту же идею, но кажется более точной, чем моя, поэтому используйте его решение, если вы не можете найти лучшего