Ein Silberstreif am Horizont taucht in der jüngsten Zeit in den Newsgruppen auf, die darauf hinweisen, dass C# lernfähig ist und entweder durch Bitoperatoren oder Compiler-Meta-Tags die fehlenden Eigenschaften nachrüsten kann.Bis vor Kurzem beobachtete ich die aufziehenden Wolken aus der Ferne - mein letztes Projekt, das die genannten Eigenschaften benötigte, lag längere Zeit zurück. Dann aber klingelte das Telefon und am Ende des Gesprächs hatte ich den Auftrag angenommen, der mir die Kombination Pocket PC mit .NET Compact Framework und C# vorgab. Auf dieser Plattform sind eigentlich alle Programme systemnah, da das Betriebssystem dieser Smart Devices ihrer jeweiligen Hardware auf den Leib geschneidert wurde. Und in der .NET Compact Framework-Implementierung klaffen große Lücken in Bezug auf die Plattformintegration. So stand ich unvermittelt unter den dunklen Wolken und hoffte auf besseres Wetter.
Ausgangssituation
C und C++ kennen die einfache Vereinbarung von Bitfeldern innerhalb einer Struktur. Der Programmierer legt mit einem Doppelpunkt hinter einem Mitglied gefolgt von einer Ganzzahl fest, wie viele Bits dieses Mitglied innerhalb eines Maschinenwortes einnimmt. Listing 1 zeigt die Struktur eSymbol, die aus einer Terminalemulation stammt und die Speicheraufteilung von 16 Bit vereinbart. Die unteren 4 Bit belegen die Attribute fett, blinken, invers und unterstrichen mit je einem Bit, gefolgt von der Farbe des Zeichens in den darauf folgenden 4 Bit (0-15) und schließlich dem ASCII-Zeichen, das 8 Bit (0-255) beansprucht. Mitglieder dieser Bit-Struktur adressiert der Programmierer genauso wie jeden anderen Zugriff auf Mitglieder einer normalen Struktur. Lediglich bei der Vereinbarung der Struktur sieht der Leser des Quelltexts, dass er eine Bitstruktur vor sich hat, im weiteren Verlauf des Programms muss er an dieses Detail nicht mehr denken - der Compiler übernimmt hier die Kopfarbeit.Listing 1
// structCpp.cpp : Defines the entry point for the console application.//#include "stdafx.h"struct eSymbol {unsigned int fett:1;unsigned int blinken:1;unsigned int invers:1;unsigned int unterstrichen:1;unsigned int farbe:4;unsigned int symbol:8;};union uSymbol {struct eSymbol info;unsigned short wert;};void writeShort(unsigned short arg){printf("argument: %x\n",arg);}unsigned short readShort(void){return 0x46C5;}int _tmain(int argc, _TCHAR* argv[]){union uSymbol lSymbol;char c;lSymbol.wert=0;lSymbol.info.fett=1;lSymbol.info.farbe=12;lSymbol.info.symbol='A';writeShort(lSymbol.wert);lSymbol.wert=readShort();printf("\nSymbol: %x\n",lSymbol.wert);printf("Zeichen: %c\n",(lSymbol.info.symbol));printf("Farbe: %d\n",(lSymbol.info.farbe));if (lSymbol.info.fett)puts("FETT ist gesetzt");if(lSymbol.info.blinken)puts("BLINKEN ist gesetzt");if(lSymbol.info.invers)puts("INVERS ist gesetzt");if(lSymbol.info.unterstrichen)puts("UNTERSTRICHEN ist gesetzt");scanf("%c",&c);return 0;}
Talweg
Um dem Regen zu entkommen, stieg ich hinab ins Tal, kramte in meinem Werkzeugkasten und zog die Bitoperatoren hervor. Genauso behelfen sich die meisten C#-Programmierer, deren maschinennahen Module ich bei meiner Recherche fand. Der Kasten Bitoperatoren fasst die grundlegenden Operatoren im Schnelldurchgang zusammen. Für meine Zwecke benötige ich bitweises und (&), oder(|) und zu guter Letzt das Schieben von Bit nach links (<<) und rechts (>>). Der Kasten Schieberei vermittelt den Umgang mit Bit-Masken, die für die Bearbeitung von Bitmustern in einem Speicherbereich unbedingt erforderlich sind.
|
|
Nachdem ich mit Masken festgelegt habe, auf welchen Bereich des 16 Bit-Werts meine Anweisungen wirken, kann ich mit dem &-Operator diesen Bereich isolieren und anschließend weiterverarbeiten. In Kombination mit dem |-Operator und einer Maske kopiere ich neue Werte in den Zielbereich, ohne die übrigen Bits zu verändern. Da in einer Speicherzelle immer nur ein Wert rechtsbündig in den unteren Bits des Speichers steht, bewegen die Schiebeoperatoren alle anderen Mitglieder in die jeweils richtige Position im Speicher.Listing 2 fasst diese Techniken exemplarisch zusammen und beweist, dass so Programme entstehen, die in ihrer Wirkung Listing 1 entsprechen. Genauso führt Listing 2 aber auch vor Augen, dass diese Bitfummelei mit einer verminderten Lesbarkeit des Programms einher geht. Nicht jeder C-Programmierer kennt die Bitoperatoren oder kann sie zweifels- und fehlerfrei anwenden.
Listing 2
// bitOp.cpp : Defines the entry point for the console application.//#include "stdafx.h"enum masks {FETT = 0x01,PFETT = 0,BLINKEN=0x02,PBLINKEN=1,INVERS=0x04,PINVERS=2,UNTERSTRICHEN=0x08,PUNTERSTRICHEN=3,FARBE=0xF0,PFARBE=4,SYMBOL=0xFF00,PSYMBOL=8};void writeShort(unsigned short arg){printf("argument: %x\n",arg);}unsigned short readShort(void){return 0x46C5;}int _tmain(int argc, _TCHAR* argv[]){unsigned short lSymbol;unsigned short sFarbe;char c;lSymbol=0;sFarbe=12;lSymbol|=FETT;lSymbol|=(sFarbe<<PFARBE)&FARBE;lSymbol|='A'<<PSYMBOL;writeShort(lSymbol);lSymbol=readShort();printf("\nSymbol: %x\n",lSymbol);printf("Zeichen: %c\n",(lSymbol&SYMBOL)>>PSYMBOL);printf("Farbe: %d\n",(lSymbol&FARBE)>>PFARBE);if (lSymbol & FETT)puts("FETT ist gesetzt");if(lSymbol & BLINKEN)puts("BLINKEN ist gesetzt");if(lSymbol & INVERS)puts("INVERS ist gesetzt");if(lSymbol & UNTERSTRICHEN)puts("UNTERSTRICHEN ist gesetzt");scanf("%c",&c);return 0;}
|
Bergtour
Vielleicht konnte ich dem Regen ebenso entkommen, wenn ich aus dem Kessel heraus und über die Wolken stiege, das Problem also auf der Meta-Ebene löste. Hier half mir die Online-Dokumentation des .NET Frameworks weiter, nachdem ich nach union gesucht und die Suche auf C# eingeschränkt hatte. Im Namensraum System.Runtime.InteropServices fand ich das Attribut [StructLayout], dem ich mit dem Argument LayoutKind.Explicit mitteilen kann, dass ich die Position der Mitglieder einer Struktur im verwalteten Code festlege und dies nicht der Laufzeitumgebung überlasse. Mit dem Attribut [FieldOffset(n)] gebe ich den Versatz der Mitglieder im verwalteten Speicher an. Der Versatz im Speicher bezieht sich immer auf den notwendigen Speicherbereich eines Datentyps. So zählt der Versatz für bool-Werte genau ein Bit weiter, während der Versatz für ushort 16 Bit weiterzählt. Hier muss der Programmierer konzentriert bei der Sache sein, da er sich sonst schnell den selbst verwalteten Speicher zerschießt. Die Interop Services unterstützen sogar überlappende Mitglieder innerhalb einer Struktur und die Dokumentation beschreibt diese Lösung als legitimen Nachfolger der unions unter C/C++. Besonders häufig benötigen Programmierer diese Eigenschaften, wenn sie Programme erstellen, die mit bereits vorhandenen Win32- oder COM-Modulen zusammenspielen sollen. Durch diese Meta-Tags, die ihren Weg in die .NET-Assembly finden, weiß die Laufzeitumgebung, wie sie die Mitglieder der Struktur in Reih und Glied (engl. to marshal) bringt, bevor diese an die Altumgebung übergeben werden und ebenso, wie sie die Ergebnisse des Aufrufs für die .NET-Anwendung aufbereiten muss.Listing 3 zeigt die Vereinbarung der selbst verwalteten Struktur, die die gewünschte Anordnung der Struktur-Mitglieder im Speicher sicherstellt. Nachdem ich das Prinzip der Meta-Tags StructLayout und FieldOffset verstanden hatte, erhielt das Programm seine ursprüngliche Schlichtheit zurück. Wie das Mitglied farbe der Struktur zeigt, bewirken die Meta-Tags nicht wirklich eine beliebige Ausrichtung an beliebigen Bitgrenzen. Hier benötige ich trotzdem die Schiebeoperatoren und eine Maske, damit ich die 4 unteren Bits nicht überschreibe.
Listing 3
using System;using System.Runtime.InteropServices;namespace structLayout{/// <summary>/// Summary description for Class1./// </summary>class Class1{/// <summary>/// The main entry point for the application./// </summary>// der Parameter von Offset bezieht sich immer auf die Länge des jeweiligen Datentyps:// - der Offset für bools wird immer um ein Bit weitergezählt// - der Offset für byte wird immer um 8 Bit weitergezählt// - der Offset für ushort wird immer um 16 Bit weitergezählt[StructLayout(LayoutKind.Explicit)]public struct eSymbol{[FieldOffset(0)] public bool fett;[FieldOffset(1)] public bool blinken;[FieldOffset(2)] public bool invers;[FieldOffset(3)] public bool unterstrichen;[FieldOffset(0)] public byte farbe;[FieldOffset(1)] public byte symbol; // oberen 8 Bit[FieldOffset(0)] public ushort wert;};[STAThread]static void Main(string[] args){//// TODO: Add code to start application here//eSymbol lSymbol=new eSymbol();lSymbol.wert=0;lSymbol.fett=true;// hier muss geschoben werden, da keine Werte kleiner 8 Bit vereinbart werden// können, die Farbe aber nur die oberen 4 Bit belegen soll, schließlich sind// die untern 4 Bit bereits durch die Attribute belegt!lSymbol.farbe|=(12<<4);lSymbol.symbol=(byte)'A';Console.WriteLine("wert: {0:x}", lSymbol.wert);Console.ReadLine();}}}
Fernsicht
Regen in den Bergen kann so schnell vorübergehen und hinterlässt oftmals einen guten Fernblick. Vielleicht erkannte ich deshalb das Brett, das ich vor meinem Kopf hatte, indem ich die Lösung in der Sprache und nicht in der Laufzeitumgebung suchte. Die C#-Entwickler hatten gegenüber den Autoren von C und C++ den großen Vorteil, dass sie .NET in einer Art und Weise mitgestalten konnten, wie dies vorher nicht möglich war. Anders Hejlsberg und sein .NET-Team hatten beschlossen, dass die Manipulation von Bitmustern in jeder .NET-Sprache gleichermaßen unterstützt werden und die Unterteilung in maschinennahe und Rapid Prototyping-Programmiersprachen endgültig der Vergangenheit angehören soll. Die Festlegung einer Eigenschaft für genau eine Programmiersprache wäre in diesem Zusammenhang nicht sinnvoll, da die anderen Sprachen diese Eigenschaft dann nicht verstehen und damit die Forderung nach nahtloser Integration von .NET-Modulen verletzt würde.Meine erweiterte Suche nach dem Stichwort Bit ergab mehr als 500 Treffer im Suchraum .NET Framework. Bereits auf Platz 13 stieß ich auf die Struktur(!) BitVector32, die ausdrücklich auch für das Compact Framework vorhanden ist. Das aufgeführte Beispiel entsprach im Wesentlichen meinen Anforderungen, da es den Zugriff auf die einzelnen Bits eines 32 Bit-Speicherbereichs als Boolean und auf Bereiche derselben Bitfolge als kleine Ganzzahlen dokumentiert.BitVector32 ist im Namensraum System.Collections.Specialized angesiedelt. Sie bietet die überlappende Vereinbarung von Abschnitten und macht damit die Forderung nach Unions im Sprachumfang von C# überflüssig. Der Name der Struktur verrät bereits, dass ein Element dieser Struktur exakt 32 Bit belegt. Durch die Vereinbarung als Struktur wird der notwendige Speicherbereich bereits bei der Vereinbarung auf dem Stapel reserviert und durch den Aufruf new initialisiert. Dieses Verhalten, das dem der integrierten Werttypen (z.B. short, byte) entspricht, bringt besonders für den Aufruf von Funktionen oder Methoden der Basisplattform über den P/Invoke-Mechanismus einen großen Vorteil: Der Speicherbereich eines BitVector32 kann ohne Konvertierung an API-Aufrufe übergeben werden und auch die Ergebnisse können nach dem Aufruf ohne Konvertierung wieder aus dem BitVector32 gelesen werden.
Listing 4 zeigt die Vereinbarung der Terminalstruktur über BitVector32 und verdeutlicht den Zugriff auf die Elemente des Vektors über den Index-Operator ([]). Auch hier ist die bit- und bereichsweise Segmentierung des Speicherbereichs an einer Stelle der Anwendung isoliert, der Zugriff auf den Speicher kann ohne Kenntnis dieser Besonderheiten erfolgen.
Listing 4
using System;using System.Collections.Specialized;namespace termEmu{/// <summary>/// Summary description for Class1./// </summary>class Class1{/// <summary>/// The main entry point for the application./// </summary>[STAThread]static void Main(string[] args){//// TODO: Add code to start application here//BitVector32 lSymbol;uint wert;int mFett, mBlinken;// Segment-VereinbarungenBitVector32.Section fett=BitVector32.CreateSection(0x01);BitVector32.Section blinken=BitVector32.CreateSection(0x01,fett);BitVector32.Section invers=BitVector32.CreateSection(0x01,blinken);BitVector32.Section unterstrichen=BitVector32.CreateSection(0x01,invers);BitVector32.Section farbe=BitVector32.CreateSection (0x0f,unterstrichen);BitVector32.Section symbol=BitVector32.CreateSection(0xff,farbe);// cannot be more than 15 Bit cause of short argument!BitVector32.Section wertl=BitVector32.CreateSection(0x7fff);BitVector32.Section werth=BitVector32.CreateSection(0x1);// Masken-VereinbarungmFett=BitVector32.CreateMask();mBlinken=BitVector32.CreateMask(mFett);// Initialisierung des BitVector32 SpeicherslSymbol=new BitVector32(0);// Zugriff über SegmentelSymbol[wertl]=0;lSymbol[werth]=0;lSymbol[fett]=1;lSymbol[farbe]=12;lSymbol[symbol]='A'|0x80;// Zugriff über MaskelSymbol[mBlinken]=true;wert=(ushort)((uint)lSymbol[werth]<<15|(uint)lSymbol[wertl]);Console.WriteLine("Wert: {0:x}",wert);Console.ReadLine();}}}
Zusätzlich skizziert Listing 4 die Vereinbarung von Masken, die den Zugriff auf die einzelnen Bits des Vektors als bool ermöglichen. Analog zu CreateSection benötigt CreateMask den Verweis auf die rechts angrenzende Maske. Eine Länge wird nicht erwartet, da diese konstant 1 Bit beträgt. CreateMask liefert int-Werte zurück, die sich bei näherer Betrachtung als Zweierpotenzen (1,2,4,8,16...) entpuppen und den Bit-Masken aus dem Talweg entsprechen. Die Attribute fett und blinken können durch diese Vereinbarungen entweder als bool (true, false) oder short (1,0) manipuliert werden.Listing 5 (auf der beiliegenden CD) stellt die VB.NET-Variante der neuen Lösung vor, die gleichwertig zur C# Version aus Listing 4 ist.
Gipfelstürmer
Die neu erarbeitete Lösung mit der BitVector32-Struktur bietet einen gleichwertigen Ersatz für die ursprüngliche Kombination von Bitfeldern und unions. So habe ich die vorgestellten Konvertierungsschritte erfolgreich für die Klassen angewendet, die sich im openNETCF-Projekt (www.opennetcf.org) um die serielle Schnittstelle kümmern. Hierdurch ist der Quelltext gestrafft und auch für Neulinge besser verständlich geworden.Ein letzter Schritt ist noch geblieben, den Sie nach Lektüre dieses Artikels selbst durchführen können: Indem Sie die BitVector32-Zugriffe in einer Struktur oder Klasse kapseln, können Sie sogar die eingangs dargestellte Formulierung für den Zugriff auf die Elemente der Bitstruktur wiederherstellen. Vorsicht: Beim Einsatz einer Klasse geht der Vorteil der automatischen Speicherbelegung auf dem Stapel verloren.Jetzt leg ich mich wieder in die Sonne






