vb@rchiv
VB Classic
VB.NET
ADO.NET
VBA
C#

https://www.vbarchiv.net
Rubrik: .NET   |   VB-Versionen: VB2005, VB200815.04.08
Übersicht über die Hilfsmittel für Operationen auf Bit-Ebene (VB2005/2008)

VB stellt einige Mittel zur Verfügung, um Operationen auch auf der Bit-Ebene durchführen zu können, die in diesem Workshop ausführlich und anschaulich beschrieben werden.

Autor:  Manfred BohnBewertung:  Views:  11.240 

1. Die Übertragung von Variablenwerten in ein 'BitArray'
Um die Folge der Bits aus einer Variable eines integrierten Datentyps zu erhalten, ist ein zwei-stufiges Verfahren nötig.

Zuerst wird über die GetBytes-Methode der BitConverter-Klasse ein Byte-Array mit der Bitfolge gefüllt, die in einer Variable enthalten ist. Diese Bits können dann in eine Instanz der BitArray-Klasse eingetragen werden - und zwar über deren Konstruktor.

Zusammengefasst in einer Code-Zeile sieht das so aus:

Dim Lng as Long = Long.MaxValue
Dim ba As New BitArray(BitConverter.GetBytes(Lng))

Diese Zuweisung kann man mit allen integrierten numerischen Datentypen durchführen Ausnahme: der Datentyp Decimal. Der verfügt über eine integrierte Methode:

Dim Dec as Decimal = Decimal.MaxValue
ba = New BitArray(Decimal.GetBits(Dec))

Die Länge des auf diese Weise entstehenden Bitarrays richtet sich nach dem Datentyp der Variable, die an die GetBytes-Methode des Bitkonverters übergeben worden ist (null-basierter Index). Im Fall einer Long-Variable handelt es sich deshalb um eine Länge von 64 (Bit). Die in den Decimal-Datentyp integrierte Methode GetBits liefert (indirekt!) die 128 Bits, aus denen sich eine Decimal-Variable zusammensetzt.

Allerdings ist zu beachten, dass Variable der Datentypen Decimal, Single und Double im Speicher 'codiert' vorliegen. Das bedeutet, durch Bit-Manipulationen erreicht man normalerweise nicht das gewünschte Ergebnis, weil die Bitfolge, als Numeric-Code ausgewertet (nach der IEEE bzw. der DECIMAL-Datentyp-Konvention) wird.

Dazu noch eine Anmerkung:
Der Bitkonverter enthält Methoden, die es erlauben, die Bitmuster (64 Bit) zwischen einer Variable des Gleitkomma-Datentyps Double und des Ganzzahl-Datentyps Long zu übertragen (DoubleToInt64Bits / Int64BitsToDouble). Anders als die VB-Dokumentation dazu missverständlich anmerkt, entstehen dabei KEINE äquivalenten Werte. Der numerische Wert eines Double-Bitmusters besitzt keine Entsprechung zu dem numerischen Wert eines identischen Long-Bitmusters, weil die Bitfolgen bei diesen Datentypen unterschiedlich 'interpretiert' werden.

Es handelt sich bei den Bitfolgen, von denen hier die Rede ist, um die Datentyp-spezifische Repräsentation im Speicher, nicht um eine Konvertierung von Zahlenwerten vom Zehner- ins Binärsystem.

Auch bei den vorzeichenbehafteten Ganzzahl-Datentypen Short, Integer oder Long ist die direkte Korrespondenz zwischen dem Wert der Variable im Zehnersystem und der Bedeutung der Bitfolge im Dualsystem nur gegeben, wenn das letzte Bit ( = die Kennung für eine negativ zu interpretierende Zahl) im Bit-Array nicht gesetzt ist.

Anders bei den Unsigned-Varianten der Ganzzahltypen UShort, UInteger und ULong. Die Bits werden bei der Decodierung von links nach rechts ausgewertet:
(nur ba(0) = true ---> 1; nur ba(1) = true --> 2; nur ba(2) = true --> 4 usw.)

Den Inhalt von Stringvariablen muss man auf Bit-Ebene durch den Zugriff auf deren Chars-Auflistung sukzessive in einer Schleife auslesen. Die GetBytes-Methode der BitConverter-Klasse ist nämlich nur für den Datentyp ‚Character’ überladen (Für jedes übergebene Zeichen liefert sie in Form von 2 Bytes den zugeordneten UNICODE).

2. Die Übertragung von Bitmustern in integrierte Datentypen
Wie sieht der Rückweg aus? Wie kopiert man die Bits aus dem Bitarray wieder in einen integrierten Datentyp?
Direktes Kopieren der Bits in ein Byte-Array klappt NICHT:

Dim byt as Byte = ba.Clone
Dim byt as Byte = CType(ba.clone, Byte())

In VB fehlt offenbar eine elementare Konvertierungsfunktion (von Bits zu Bytes). Die Klasse 'BitArray' ist nicht im Namespace System.Array, sondern im Namespace System.Collection angesiedelt. Vermutlich liegt darin der Grund, weshalb es mit dem Clonen nicht klappt.

Die CopyTo-Methode der BitArray-Klasse dient dazu, die Daten in ein Byte-Array zu übertragen. (Diese Methode ermöglicht auch Ziel-Arrays der Typen Boolean und Integer. Sie benutzt - laut Doku - intern die Array.Copy-Methode).

Da die CopyTo-Methode mit einem Startindex arbeitet, kann sie die erforderliche Länge des Zielarray nicht selbst einrichten. Man muss deshalb zunächst eine Dimensionierung durchführen. Die erforderliche Mindestlänge des null-basierten Zielarray vom Typ Byte bei Startindex = 0 ist gegeben durch:

Dim EBytes(ba.length \ 8 - 1) as Byte

Der Kopiervorgang läuft dann über folgendes Kommando:

Ba.CopyTo(EBytes, 0)

Den Inhalt dieses Byte-Arrays kann der BitConverter weiterverarbeiten. Dessen Methode 'ToInt64' 'formt' aus 8 Byte ab der gegebenen Startposition einen vorzeichenbehafteten System.Int64 (= Long).

Dim lng2 As Long = BitConverter.ToInt64(EBytes, 0)

Der BitConverter enthält spezifische To...-Methoden für alle numerischen Datentypen - mit Ausnahme des Datentyps Decimal (dazu vgl. unten: SetBits2Decimal).

Um die in ein Bytearray kopierten Bits statt in einen numerischen Datentyp wieder in eine Stringvariable zurück zu transportieren, ist eine Schleife erforderlich, die per Step 2 immer zwei Bytes aus UNICODE in ein Zeichen zurückverwandelt.

3. Details zur Klasse 'BitArray': Bit-Operationen
Das BitArray ist etwas gewöhnungsbedürftig konstruiert. Seine einzelnen Elemente können wie eine BOOLEAN-Variable behandelt werden. Obwohl als 'Array' bezeichnet, ist es eine Collection und deshalb kann z.B. die ReDim-Methode nicht angewendet werden. Die Länge einer BitArray-Instanz verändert man durch Zuweisung eines Wertes auf deren Length-Eigenschaft. Dieser Wert muss >= 0 sein. Eine Verkleinerung der Länge löscht einen Teil des Inhalts des BitArray. Bei einer Vergrößerung der Länge werden 'false'-Bits (0) angehängt.

Das IList-Interface ist implementiert (-> indizierter Zugriff auf einzelne Elemente), ebenso IEnumerable (-> For Each .. Next-Schleife) und ICloneable (Clone-Methode).

Das BitArray steht nicht in einem Zusammenhang mit der Collection-Klasse (Microsoft.VisualBasic), und verfügt deshalb nicht über Methoden wie Add, Remove oder Clear. Ein BitArray darf man auch nicht verwechseln mit einem Array dessen Elemente aus dem Datentyp 'Boolean' bestehen. Boolean-Variable sind ein Byte lang. Im Zustand 'true' ist bei einem Boolean das erste Bit gesetzt, sonst nicht. Die anderen 7 Bits sind funktionslos ('false').

Anmerkung zur numerischen Repräsentation von 'True':
In frühen MS-Basic-Versionen gab es keine 'boolsche Variable'. In BASIC ist es deshalb üblich, den Wert 0 als 'false' zu interpretieren und alle anderen numerischen Werte als 'true'. Die Konvertierung durch VB-CBool wandelt alle Nicht-0-Werte in 'true'. Für 'true' wird bei der Rückkonvertierung in einen numerischen Wert (z.B. durch CInt) -1 gesetzt. Die Convert.ToBoolean-Methode wandelt ebenfalls jeden Nicht-0-Wert in 'true'. ABER: Die Convert-Methoden liefern bei der Rücktransformation von 'true' in eine numerische Variable den Wert +1.

Dim i, k As Integer
i = CInt(true) 
k = Convert.ToInt32(true)

Die beiden Integer enthalten keine identischen Werte. (i = -1; k = +1) Selbst bei einem ganz einfachen Bit hat das Framework mit VB so seine 'speziellen' Probleme.

Mit den integrierten Ganzzahl-Datentypen können die OPERATOREN And, Or, Xor, Not auch direkt bitweise Operationen durchführen. Die Bitfolge muss dazu nicht in ein BitArray übertragen werden. Die VB-Dokumentation weist darauf hin, dass diese Operatoren in der Operator-Rangstufe einen sehr niedrigen Stellenwert haben und empfiehlt, sie ggf. geeignet in Klammern zu setzen. (Sie werden sonst erst nach den arithmetischen und den Vergleichs-Operatoren ausgewertet).

Für die Manipulation eines BitArray stehen die METHODEN And, Or, Xor und Not zur Verfügung. Sie verknüpfen durch EINEN Aufruf ALLE Bits aus zwei korrespondierenden BitArrays auf jeweils spezifische Weise miteinander. Die beiden Arrays müssen allerdings die gleiche Länge aufweisen. Das (optionale!) Zielarray wird automatisch auf die entsprechende Länge eingerichtet.

Zunächst werden zwei BitArrays (ba1, ba2) erstellt:

Dim ba1 As New BitArray(BitConverter.GetBytes(lng1))
Dim ba2 As New BitArray(BitConverter.GetBytes(lng2))
 
Dim ba3 As BitArray = ba1.And(ba2)

Folgendes geht übrigens nicht:

ba3 = ba1 And ba2

AND (ebenso wie OR, XOR) ist in diesem Fall nämlich die Bezeichnung für eine (Bitkombinations-) Methode und ist nicht als Operator implementiert.

Noch einmal zur Klarstellung:
Die Anweisung ba3 = ba1.And(ba2) trägt in das Bitarray ba3 die AND-verknüpften Bits ein, ABER: Auch in ba1 sind die Bits mit AND gemäß ba2 verknüpft worden. Man kann das Zielarray deshalb auch einfach weglassen: ba1.and(ba2). Das Ergebnis steht bereits in ba1!!

Zur Erinnerung: - die Arbeitsweise der Bit-Operatoren:

Dim bb1 As New BitArray(4)
Dim bb2 As New BitArray(4)
 
bb1(0) = True : bb1(1) = True : bb1(2) = False : bb1(3) = False
bb2(0) = True : bb2(1) = False : bb2(2) = True : bb2(3) = False
 
bb1.And(bb2)

---> bb1 enthält: true, false, false, false (nur die Kombination true/true bleibt true)

bb1 wird wieder auf den obigen Ausgangswert zurückgesetzt!

bb1.Or(bb2)

---> bb1 enthält: true, true, true , false (nur die Kombination false/false bleibt false)

bb1 wird wieder auf den obigen Ausgangswert zurückgesetzt!

bb1.XOr(bb2)

---> bb1 enthält: false, true, true , false (nur sich unterscheidende Kombis werden true) ('exklusives oder')

Nicht alle VB(6)-üblichen logischen Operatoren werden von den BitArray-Methoden direkt unterstützt. (Vgl. dazu ergänzend in der VB-Doku den Abschnitt 'Boolean Operator für Visual Basic 6 Users')

Es fehlen die Imp- und die Eqv-Methode (Implikation und Äquivalenz).

Die Imp-Verknüpfung bekommt man durch eine Kombination von 'Not' und 'Or':
(bb1 enthält danach die IMP-Bits):

bb1.Not.Or(bb2)

Auch die EQV-Verknüpfung ist durch eine geeignete Kombination zu erhalten (bb1 enthält danach die EQV-Bits):

bb1.Xor(bb2).Not()

Die Not-Methode invertiert alle Bits in einem Bitarray. Auch hier gilt. Falls ein Ziel-Array angegeben wird, erfolgt eine Übertragung der Ergebnis-Bits, aber: im Quell-Array sind die Bits dennoch ebenfalls umgesetzt worden. (Für den Aufruf der Not-Methode existiert eine undokumentierte Überladung, die einen Index übernimmt und einen Boolean zurückgibt. Trotz des Index-Parameters wird aber das gesamte Array invertiert. ???)

Durch die SetAll-Methode können alle Bits im Bitarray auf den gleichen vorgegebenen Wert gesetzt werden. Diese Methode akzeptiert keine Angabe eines Ziel-Arrays. Man kann sie als Bit-Clear-Verfahren verwenden: SetAll(false)

Einzelne Bit-Werte können - wie bei einem Array - direkt per nullbasiertem Index abgefragt oder geändert werden. Die explizite Verwendung der Set- und Get-Methoden ist nicht erforderlich.

In VB gibt es zwei (nicht-zirkuläre) 'arithmetische' Bit-Shift-Operatoren, die für alle Ganzzahl-Datentypen zur Verfügung stehen ( <<, <<=, >>. =>>).

Durch Übertragung der Bitmuster von Variablen in BitArrays lassen sich auf einfache Weise eigene (z.B. zirkuläre) Shift-Methoden programmieren. (Durch Verwendung des StartIndex-Parameters der CopyTo-Methode können die Bits 'nach links geshiftet' in ein Byte-Array kopiert und von dort weitergegeben werden.)

Im Namespace Collections.Specialized befindet sich die Struktur 'BitVector32'. Dieser Struktur kann eine Integer-Variable zugeordnet werden. Sie erlaubt die Abfrage von Bits über frei definierbare Bitmasken und Abschnitte. Ihre Bedeutung liegt - laut VB-Dokumentation - in der relativ hohen Effizienz (Werttyp) mit der Bit-Operationen (z.B. im Sinne von 'Flag'-Bearbeitung) ausgeführt werden können.

Hinweis:
Die Übergabe einer Instanz der Klasse 'BitArray' an eine Funktion erfolgt stets als Referenz 'ByRef' - auch wenn der entsprechende Parameter in der Funktions-Deklaration mit 'ByVal' angegeben worden ist!

4. Bit-Operationen bei der Bildbearbeitung
Von Interesse sind die schnellen Bit-Operationen z.B. bei der Bildbearbeitung.

(Falls Ihnen der folgende Code unverständlich ist, sollten Sie einen Blick in den Vb@rchiv Workshop  Direkte Manipulation von Bilddaten werfen.)

Es wird eine Bilddatei benötigt (24-Bit-Bitmap oder JPG)

Dim bmp As Bitmap = _
  New Bitmap("Name_einer_Bilddatei_mit_24_Bit_Bitmap")
 
' Der folgende Code kopiert die Bilddaten in ein Byte-Array
Dim bmp_rect As New Drawing.Rectangle(0, 0, bmp.Width, bmp.Height)
 
Dim bmp_data As Drawing.Imaging.BitmapData = _
  bmp.LockBits(bmp_rect, Drawing.Imaging.ImageLockMode.ReadWrite, _
  bmp.PixelFormat)
 
Dim bmp_ptr As IntPtr = bmp_data.Scan0
Dim bmp_bytes As Integer = bmp.Width * bmp.Height * 3
 
Dim bmp_array(0 To bmp_bytes - 1) As Byte
Runtime.InteropServices.Marshal.Copy(bmp_ptr, bmp_array, 0, _ bmp_bytes)

Jetzt bekommen wir das Bild als eine Folge von Bits

Dim BmpBits As New BitArray(bmp_array)

Ab hier kann jetzt bitweise manipuliert werden, z.B. die Kombination mit den Bits eines anderen Bildes, das entsprechend geladen worden ist.

Durch den XOR-Operator würde das Ergebnisbild schwarz, weil es keine Unterschiede gibt

BmpBits.Xor(BmpBits)

Durch den EQV-Operator würde das Ergebnis-Bild weiss, weil alle Bits korrespondieren

BmpBits.Xor(BmpBits).Not()

Durch den NOT-Operator würde das Ergebnisbild invertiert

BmpBits.Not()
' Hier werden die manipulierten Bits zurückkopiert 
BmpBits.CopyTo(bmp_array, 0)
Runtime.InteropServices.Marshal.Copy( _
bmp_array, 0, bmp_ptr, bmp_bytes)
bmp.UnlockBits(bmp_data)
' Und das Bild wird gespeichert
bmp.Save("Dateiname_des_Zielbildes.bmp")

5. Beispiele zum 'BitArray'
Zum Abschluss noch einige Routinen, die verschiedenartige Varianten demonstrieren, wie die Bits zwischen einem BitArray und den integrierten Datentypen (z.B. Arrays) übertragen werden können.

Public Function SetBits2Decimal(ByVal b As BitArray) As Decimal
 
  ' Übertragung von 128 Bits in eine Variable des
  ' Typs 'Decimal'
 
  ' Parameter prüfen
  If b.Length <> 128 Then Return CDec(0)
 
  ' Lokale Variable vereinbaren
  Dim i As Integer 'Loop
  Dim ByteArray(15) As Byte   ' 16 Byte
  Dim IntArray(3) As Integer  ' 4 Integer
 
  ' BitArray --> ByteArray
  b.CopyTo(ByteArray, 0)
 
  ' ByteArray --> IntegerArray
  For i = 0 To 3
    IntArray(i) = BitConverter.ToInt32(ByteArray, i * 4)
  Next i
 
  ' IntegerArray --> Decimal
  Return New Decimal(IntArray)
 
End Function
Public Function SetBitArray2Integer( _
  ByVal ba As BitArray) As Integer()
 
  ' Den Inhalt des BitArray in ein 
  ' Integer-Array eintragen
 
  Dim intarr(ba.Length \ 32 - 1) As Integer
  ba.CopyTo(intarr, 0)
  Return intarr
 
End Function
Public Function SetUIntegerArray2BitArray( _
  ByVal uint() As UInteger) As BitArray
 
  ' Den Inhalt des UInteger-Array in ein 
  ' BitArray eintragen
 
  Dim i As Integer
  Dim byt1() As Byte
 
  ' Array für alle Bytes des UINT-Array 
  Dim BytesTot(uint.Length * 4 - 1) As Byte
  For i = 0 To UBound(uint)
    ' Bytes eines Arrayelements besorgen ...
    byt1 = BitConverter.GetBytes(uint(i))
    ' ... und in Gesamt-Bytearray kopieren
    Array.Copy(byt1, 0, BytesTot, i * 4, 4)
  Next i
 
  ' Alle Bytes als BitArray zurückgeben
  Return New BitArray(BytesTot)
 
End Function
Public Function SetBitArrayToUIntegerArray( _
  ByVal ba As BitArray) As UInteger()
 
  ' Bits in einem BitArray in ein Array '
  ' des Typs UInteger übertragen
 
  Dim Uint(0) As UInteger
 
  ' Parameter prüfen
  If ba.Length Mod 32 <> 0 Then Return Uint
 
  Dim i As Integer
 
  ' Korrespondierende Zahl der UInteger
  Dim ints As Integer = ba.Length \ 32
  ReDim Uint(ints - 1)
  ' Byte-Array deklarieren
  Dim Bytestot(ints * 4 - 1) As Byte
 
  ' Bits --> Bytes
  ba.CopyTo(Bytestot, 0)
 
  ' Bytes --> UInteger
  For i = 0 To ints - 1
    Uint(i) = _
    BitConverter.ToUInt32(Bytestot, i * 4)
  Next i
 
  ' Rückgabe UInteger
  Return Uint
 
End Function
Public Function BitArray2DecimalValue( _
  ByVal ba As BitArray) As Decimal
 
  ' Der Inhalt eines Bitarray bis max. 96 Bits wird als
  ' Wert im Zehnersystem dargestellt 
  ' (Umwandlung von von links nach rechts)
 
  Dim i As Integer, dec As Decimal
 
  ' Eingabeparameter prüfen
  If ba.Length > 96 Then Return CDec(-1)
    For i = 0 To ba.Length - 1
      If ba(i) Then dec += CDec(2 ^ i)
    Next i
  Return dec
End Function
Public Function BitArray2ULongValue( _
  ByVal ba As BitArray) As ULong
 
  ' Der Inhalt eines Bitarray bis max. 64 Bits wird als
  ' Wert im Zehnersystem dargestellt (ULONG) 
  ' (Umwandlung von von links nach rechts)
 
  Dim i As Integer, ulng As ULong
 
  ' Eingabeparameter prüfen
  If ba.Length > 64 Then Return CULng(0)
  For i = 0 To ba.Length - 1
    If ba(i) Then ulng += CULng(2 ^ i)
  Next i
  Return ulng
End Function
Public Function SetLongArray2BitArray( _
  ByVal lngarr() As Long) As BitArray
 
  ' Long-Array in Bitarray eintragen (Interop.Marshal)
 
  Dim bytes As Integer = lngarr.Length * 8
  ' ByteArray deklarieren
  Dim bytarr(bytes - 1) As Byte
  ' Platz im Freispeicher reservieren/sperren
  Dim ptr As IntPtr = _
    Runtime.InteropServices.Marshal.AllocCoTaskMem(bytes)
  ' Long-Array in den Freispeicher kopieren
  Runtime.InteropServices.Marshal.Copy(lngarr, 0, ptr, lngarr.Length)
  ' Byte-Array aus dem Freispeicher füllen
   Runtime.InteropServices.Marshal.Copy(ptr, bytarr, 0, bytarr.Length)
  ' Frei-Speicher wieder freigeben
  Runtime.InteropServices.Marshal.FreeCoTaskMem(ptr)
  ' ByteArray in BitArray umwandeln und zurückgeben
  Return New BitArray(bytarr)
 
End Function
Public Function SetBitArray2LongArray( _
  ByVal ba As BitArray) As Long()
 
  ' Bitarray in Long-Array eintragen (Interop.Marshal)
 
  Dim lngarr(0) As Long
 
  ' Parameter prüfen
  If ba.Length Mod 64 <> 0 Then Return lngarr
 
  Dim bytes As Integer = ba.Length \ 8
  ' Bytearray deklarieren
  Dim bytarr(bytes - 1) As Byte
  ' Bits in das Bytearray kopieren
  ba.CopyTo(bytarr, 0)
  ' LongArraqy deklarieren
  ReDim lngarr(bytes \ 8 - 1)
  ' Freispeicher reservieren / sperren
  Dim ptr As IntPtr = _
    Runtime.InteropServices.Marshal.AllocCoTaskMem(bytes)
  ' Byte-Array in Freispeicher kopieren
  Runtime.InteropServices.Marshal.Copy(bytarr, 0, ptr, bytarr.Length)
  ' LongArray aus dem Freispeicher füllen
  Runtime.InteropServices.Marshal.Copy(ptr, lngarr, 0, lngarr.Length)
  ' Freispeichert freigeben
  Runtime.InteropServices.Marshal.FreeCoTaskMem(ptr)
  ' Long-Array zurückgeben
  Return lngarr
 
End Function



Anzeige

Kauftipp Unser Dauerbrenner!Diesen und auch alle anderen Workshops finden Sie auch auf unserer aktuellen vb@rchiv  Vol.6

Ein absolutes Muss - Geballtes Wissen aus mehr als 8 Jahren vb@rchiv!
- nahezu alle Tipps & Tricks und Workshops mit Beispielprojekten
- Symbol-Galerie mit mehr als 3.200 Icons im modernen Look
Weitere Infos - 4 Entwickler-Vollversionen (u.a. sevFTP für .NET), Online-Update-Funktion u.v.m.
 
 
Copyright ©2000-2024 vb@rchiv Dieter OtterAlle Rechte vorbehalten.


Microsoft, Windows und Visual Basic sind entweder eingetragene Marken oder Marken der Microsoft Corporation in den USA und/oder anderen Ländern. Weitere auf dieser Homepage aufgeführten Produkt- und Firmennamen können geschützte Marken ihrer jeweiligen Inhaber sein.