Bit-Fields in Structs + Code-Analysis

  • C#

Es gibt 7 Antworten in diesem Thema. Der letzte Beitrag () ist von Facebamm.

    Bit-Fields in Structs + Code-Analysis

    Hallo :D

    Ich bin gerade dabei mal wieder etwas aus C++ in C# zu Binden und bin auf das Problem gestoßen des Bit-Fields in C#

    Normal kann ich via masken und shiften mein Bits an die Stelle setzten wo ich will.
    Nun bin ich kein Fan von hin und herschieben magischer werten die am Ende irendwo sitzen, sonden würde dem Ganzen gern einen Namen geben wie in C++

    C-Quellcode

    1. typedef struct tTest {
    2. uint32_t Name1: 1;
    3. uint32_t Name2: 1;
    4. uint32_t Name3: 1;
    5. } Test;



    Mein erster gedanke war: ich bauche ein "struct".

    C#-Quellcode

    1. [StructLayout(LayoutKind.Explicit, Size = sizeof(int), Pack = 1)]
    2. struct Example
    3. {
    4. [FieldOffset(0)]
    5. public int BitOne;
    6. [FieldOffset(0)]
    7. public int BitTwo;
    8. [FieldOffset(0)]
    9. public int BitThree;
    10. }


    Nun bin ich kein Fan von Properties in Structs.
    So googelte ich etwas und fand (github.com/dotnet/csharplang/discussions/465) eine Feature request zu den ganzen Thema.
    Enttäuschent musste ich festellen, das dieses nicht existiert :/

    Also Kam ich zu diesen projekt codeproject.com/Articles/10955…ld-in-Csharp-using-struct
    Was eig cool ist aber, ich kann nicht vom endnutzer verlangen das er Konvertierungsmethoden aufrufen soll.

    Zum eig.!!

    Ich hab das ganze Projekt etwas kleiner gemacht (im ersten sinne)(Sieh unten) und würde/bin dabei ein Code-Analysis mit Code-Fix zu schreiben.

    Heißt: Ich durch suche mein QuellCode nach Structs welche Member mit dem Attributs "FieldOffset" und "BitFieldInfo" haben.
    Wenn das fertig ist, starte ich ein Tracking der Structs.
    Schau wo die Structs deklariet werden und ermittel den Scope.
    Schau im Scope nach weiteren aufrufen und nach Inits.
    Sollte ich ein Init finden würde ich per Code-Fix die Anweisung umschreiben in e.g.:

    Quellcode

    1. input = 1;
    2. Example1.BitTwo = input wird zu Example1.BitTwo = (Example1.BitTwo & ~(0b11 << 1)) | ((input & 0b11) << 1);
    3. Bit-Scope: (Example1.BitTwo & ~(0b11 << 1)) => 0b00000]XX[0


    Was haltet ihr davon oder würdet ihr was anders machen?

    Spoiler anzeigen

    C#-Quellcode

    1. /// <summary>
    2. /// Specifies the length of each bit field
    3. /// </summary>
    4. [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
    5. public sealed class BitFieldInfoAttribute : Attribute
    6. {
    7. /// <summary>
    8. /// Initializes new instance of BitFieldInfoAttribute with the specified field offset and length
    9. /// </summary>
    10. /// <param name="offset">The offset of the bit field</param>
    11. /// <param name="length">The number of bits the bit field occupies</param>
    12. public BitFieldInfoAttribute(byte offset, byte length){}
    13. }
    14. [StructLayout(LayoutKind.Explicit, Size = sizeof(int), Pack = 1)]
    15. struct Example
    16. {
    17. [FieldOffset(0)]
    18. [BitFieldInfo(0, 1)]
    19. public int BitOne;
    20. [FieldOffset(0)]
    21. [BitFieldInfo(1, 2)]
    22. public int BitTwo;
    23. [FieldOffset(0)]
    24. [BitFieldInfo(3, 1)]
    25. public int BitThree;
    26. }
    Schwieriges Thema. Ich glaube wenn es um .Net 5 geht, dann würde ich mir mit Code-Generatoren Properties in den Structs generieren lassen. Aber da du schon geschrieben hast:

    Facebamm schrieb:

    Nun bin ich kein Fan von Properties in Structs.
    fallen glaube ich schon viele optionen weg?
    so, da kommt jetzt mal nen update :D

    problem besteht:
    a) ich support noch nicht alle fälle
    b) ich weiß nicht wie ich meinen neuen SyntaxTree zum compiler schicke, bzw. ob ich das richtige module nutz

    Beispiel:
    Spoiler anzeigen

    C#-Quellcode

    1. using System;
    2. using System.Runtime.InteropServices;
    3. /// <summary>
    4. /// Specifies the length of each bit field
    5. /// </summary>
    6. [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
    7. public sealed class BitFieldInfoAttribute : Attribute
    8. {
    9. /// <summary>
    10. /// Initializes new instance of BitFieldInfoAttribute with the specified field offset and length
    11. /// </summary>
    12. /// <param name=""offset"">The offset of the bit field</param>
    13. /// <param name=""length"">The number of bits the bit field occupies</param>
    14. public BitFieldInfoAttribute(byte offset, byte length)
    15. {
    16. Offset = offset;
    17. Length = length;
    18. }
    19. /// <summary>
    20. /// The offset of the bit field
    21. /// </summary>
    22. public byte Offset { get; private set; }
    23. /// <summary>
    24. /// The number of bits the bit field occupies
    25. /// </summary>
    26. public byte Length { get; private set; }
    27. }
    28. [StructLayout(LayoutKind.Explicit, Size = sizeof(int), Pack = 1)]
    29. struct StructName
    30. {
    31. [FieldOffset(0)]
    32. public int One;
    33. [FieldOffset(0)]
    34. [BitFieldInfo(2, 2)]
    35. public int Two;
    36. [FieldOffset(0)]
    37. [BitFieldInfo(4, 1)]
    38. public int Three;
    39. }
    40. class Program
    41. {
    42. static void Main(string[] args)
    43. {
    44. StructName stName;
    45. stName.One = 1;
    46. stName.Two = 2 << 1;
    47. stName.Three = 1;
    48. }
    49. }


    DiagnosticAnalyzer:
    Spoiler anzeigen

    C#-Quellcode

    1. [DiagnosticAnalyzer(LanguageNames.CSharp)]
    2. public class BitFieldFixAnalyzer : DiagnosticAnalyzer
    3. {
    4. public const string DiagnosticId = "BitFieldFix";
    5. private const string Category = "Category";
    6. //TODO: Later
    7. private static readonly LocalizableString Title = "Title";
    8. private static readonly LocalizableString MessageFormat = "MessageFormat";
    9. private static readonly LocalizableString Description = "Description";
    10. private static readonly DiagnosticDescriptor Rule
    11. = new DiagnosticDescriptor(
    12. DiagnosticId,
    13. Title,
    14. MessageFormat,
    15. Category,
    16. DiagnosticSeverity.Warning,
    17. true,
    18. Description);
    19. public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
    20. {
    21. get { return ImmutableArray.Create(Rule); }
    22. }
    23. public override void Initialize(AnalysisContext context)
    24. {
    25. context.RegisterCompilationStartAction(AnalyzeNodeCompile);
    26. }
    27. private void AnalyzeNodeCompile(CompilationStartAnalysisContext context)
    28. {
    29. IEnumerable<SyntaxTree> syntaxTree = context.Compilation.SyntaxTrees;
    30. var compilation = CSharpCompilation.Create(context.Compilation.AssemblyName)
    31. .AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location))
    32. .AddSyntaxTrees(syntaxTree);
    33. var syntaxTrees = context.Compilation.SyntaxTrees.ToArray();
    34. foreach (SyntaxTree tree in syntaxTrees)
    35. {
    36. var root = tree.GetRoot();
    37. var semantic = compilation.GetSemanticModel(tree);
    38. var bitFieldMemberWalker = new BitFieldMemberWalker(semantic);
    39. bitFieldMemberWalker.Visit(root);
    40. ;
    41. var rewriter = new SyntaxRewriter(semantic, bitFieldMemberWalker);
    42. var newRoot = rewriter.Visit(root);
    43. var newTree = SyntaxFactory.SyntaxTree(newRoot);
    44. context.Compilation.ReplaceSyntaxTree(tree, newTree);
    45. }
    46. }
    47. }
    48. internal class BitFieldMemberWalker : CSharpSyntaxWalker
    49. {
    50. private const string ATTRIBUTE_BITFIELDINFO = "BitFieldInfo";
    51. private readonly SemanticModel _semantic;
    52. public List<BitFieldDescription> BitFieldDescriptions { get; }
    53. public bool HasBitFields { get => BitFieldDescriptions.Any(); }
    54. public BitFieldMemberWalker(SemanticModel semantic)
    55. : base(SyntaxWalkerDepth.Node)
    56. {
    57. _semantic = semantic;
    58. BitFieldDescriptions = new List<BitFieldDescription>();
    59. }
    60. public override void VisitFieldDeclaration(FieldDeclarationSyntax node)
    61. {
    62. //pre compile
    63. var attributes = node.AttributeLists.SelectMany(attrs => attrs.Attributes).ToArray();
    64. var bitFieldInfo = attributes.FirstOrDefault(x => x.Name.GetText().ToString().Equals(ATTRIBUTE_BITFIELDINFO));
    65. if (bitFieldInfo != null)
    66. {
    67. var args = bitFieldInfo.ArgumentList.Arguments;
    68. foreach (VariableDeclaratorSyntax variable in node.Declaration.Variables)
    69. {
    70. BitFieldDescription bitFieldDescription = new BitFieldDescription
    71. {
    72. DeclaredSymbol = _semantic.GetDeclaredSymbol(variable),
    73. MemberName = variable.ToString(),
    74. BitOffset = int.Parse(args[0].ToString()),
    75. BitCount = int.Parse(args[1].ToString())
    76. };
    77. BitFieldDescriptions.Add(bitFieldDescription);
    78. }
    79. }
    80. base.VisitFieldDeclaration(node);
    81. }
    82. }
    83. internal class SyntaxRewriter : CSharpSyntaxRewriter
    84. {
    85. private const string ATTRIBUTE_BITFIELDINFO = "BitFieldInfoAttribute";
    86. private readonly SemanticModel _semanticModel;
    87. private readonly BitFieldMemberWalker _bitFieldMemberWalker;
    88. public SyntaxRewriter(SemanticModel semanticModel, BitFieldMemberWalker bitFieldMemberWalker)
    89. {
    90. _semanticModel = semanticModel;
    91. _bitFieldMemberWalker = bitFieldMemberWalker;
    92. }
    93. public override SyntaxNode VisitAssignmentExpression(AssignmentExpressionSyntax node)
    94. {
    95. var left = node.Left;
    96. var right = node.Right;
    97. if (left is MemberAccessExpressionSyntax memberAccess)
    98. {
    99. var member = memberAccess.DescendantNodes().OfType<IdentifierNameSyntax>().Last();
    100. var leftType = _semanticModel.GetSymbolInfo(member).Symbol;
    101. var bitFieldDescription = _bitFieldMemberWalker.BitFieldDescriptions.FirstOrDefault(x => x.DeclaredSymbol.Equals(leftType));
    102. if (bitFieldDescription != null)
    103. {
    104. return SyntaxFactory.AssignmentExpression(
    105. node.Kind(),
    106. left,
    107. SyntaxFactory.BinaryExpression(
    108. SyntaxKind.LeftShiftExpression,
    109. SyntaxFactory.ParenthesizedExpression(
    110. SyntaxFactory.BinaryExpression(
    111. SyntaxKind.BitwiseAndExpression,
    112. SyntaxFactory.ParenthesizedExpression(
    113. right),
    114. SyntaxFactory.LiteralExpression(
    115. SyntaxKind.NumericLiteralExpression,
    116. SyntaxFactory.ParseToken(bitFieldDescription.GetMaskValue.ToString())))),
    117. SyntaxFactory.LiteralExpression(
    118. SyntaxKind.NumericLiteralExpression,
    119. SyntaxFactory.ParseToken(bitFieldDescription.BitOffset.ToString())))
    120. ).NormalizeWhitespace();
    121. }
    122. }
    123. return base.VisitAssignmentExpression(node);
    124. }
    125. }
    126. internal class BitFieldDescription
    127. {
    128. public ISymbol DeclaredSymbol;
    129. public string MemberName;
    130. public int BitOffset;
    131. public int BitCount;
    132. public int GetMaskValue => F(BitCount);
    133. private int F(int x)
    134. {
    135. if (x > 1)
    136. {
    137. return (F(x - 1) << 1) + 1;
    138. }
    139. return 1;
    140. }
    141. }
    @Facebamm Ich werfe mal [Flag] Enum in den Raum.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Ja, natürlich kann ich via Flags das ganze aufbauen aber dann muss ich mir immer alles selber zusammen bauen und auslesen,
    so kann ich meinem BitFeld gleich nen namen geben und die einzelnen bits auslesen (beim auslesen bin ich aber noch nicht)

    Also es ist leichter zu hand haben

    Facebamm schrieb:

    Also es ist leichter zu hand haben
    Mit .HasFlag(...) kannst Du einzelne Bits und Gruppen von Bits auslesen ...
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    ja schon richtig!
    Aber ist dock kacke.

    Wenn ich ein Byte beschreiben will für den End-User, dann muss er dämlich Enum-"Items" verodern ...
    oder er bekommt eine Zahl (int) und soll da die Bits auslesen.
    Ist doch kacke!
    Erst recht wenn wir von API's reden und die ins Binary umwandeln wollen.