Anzahl der Dimensionen eines Arrays ermitteln
Veröffentlicht: 10. Mrz 2000 | Aktualisiert: 08. Nov 2004
Von Mathias Schiffer
Die VB-Funktionen LBound und UBound dienen bekanntlich dazu, die Unter- und Obergrenze einer Array-Dimension zu ermitteln. In der Online-Hilfe zu Visual Basic finden Sie dafür dieses Beispiel:
Dim Untergrenze Dim Feld1(1 To 10, 5 To 15, 10 To 20) ' Datenfeldvariablen ' deklarieren Dim EinFeld(10) Untergrenze = LBound(Feld1, 1) ' Liefert 1. Untergrenze = LBound(Feld1, 3) ' Liefert 10. Untergrenze = LBound(EinFeld) ' Liefert 0 oder 1 (je nach ' Einstellung von Option Base).
Was Visual Basic jedoch nicht anbietet, ist eine direkte Möglichkeit festzustellen, wie viele Dimensionen ein Array überhaupt besitzt. Eine Möglichkeit, diese Information zu erlangen, bietet ein "Trial And Error"-Verfahren: Im zweiten Parameter von LBound oder UBound kann wie oben gezeigt optional die Dimension angegeben werden, deren Dimensionsgrenzen gesucht werden. Existiert dabei die entsprechende Dimension nicht, wird ein abfangbarer Fehler ausgelöst. So kann schrittweise von 1 bis 61 (die maximal zulässige Anzahl an Dimensionen beträgt 60) eine Grenze jeder Dimension abgefragt werden – tritt dabei ein Fehler auf, kennzeichnet dieser die erste nicht existierende Dimension:
Private Function ArrayDimensionCountVB(MeinFeld As Variant) As Integer Dim i As Long, LB As Long If Not IsArray(MeinFeld) Then Exit Function On Error Resume Next ' Es wird bis 61 gegangen, um in jedem Fall einen ' Fehler zu provozieren, so dass keine weitere ' Fallunterscheidung notwendig ist, falls das Array ' 60 Dimensionen haben sollte: For ArrayDimensionCountVB = 1 To 61 LB = LBound(MeinFeld, ArrayDimensionCountVB) If Err.Number <> 0 Then Exit For Next 'ArrayDimensionCountVB ArrayDimensionCountVB = ArrayDimensionCountVB - 1 End Function
Für das Array Feld1 aus dem Beispiel der Hilfedatei ergibt sich als Rückgabewert wie erwartet, dass dieses Array drei Dimensionen besitzt:
Private Sub Test() Dim Feld1(1 To 10, 5 To 15, 10 To 20) ' Datenfeldvariablen MsgBox "Feld1 hat " & CStr(ArrayDimensionCountVB(Feld1)) & " Dimensionen" End Sub
Eine weitaus schnellere Möglichkeit zur Bestimmung dieses Wertes ergibt sich jedoch durch die Nutzung des Windows API. Visual Basic speichert Arrays im OLE-Typ SAFEARRAY, der für Win32 die folgende Struktur aufweist:
typedef struct FARSTRUCT tagSAFEARRAY { unsigned short cDims; // Anzahl der Dimensionen des Arrays unsigned short fFeatures; // Beschreibt einige Details des Arrays unsigned long cbElements; // Größ;e eines Array-Elements // (bei Pointern nur die Größ;e des Pointers) unsigned long cLocks; // Lock-Zähler void HUGEP* pvData; // Zeiger auf Array-Inhalt SAFEARRAYBOUND rgsabound[n]; // Grenzen einer Dimension } SAFEARRAY;
mit
struct SAFEARRAYBOUND { DWORD cElements; // Anzahl der Elemente LONG lLbound; // Nr. des ersten Elements };
Genauer müssen wir uns diese Struktur für unseren Zweck gar nicht ansehen: Die uns interessierende Information findet sich offensichtlich gleich in ihren ersten zwei Bytes; cDims enthält die Anzahl der Dimensionen des Arrays. Mehr als die API-Prozedur RtlMoveMemory (besser bekannt unter dem Alias-Namen "CopyMemory") und einen Zeiger auf diese Struktur für eine Array-Variable wird also nicht benötigt, um die Dimensionstiefe des Arrays zu ermitteln.
Im MSDN Quickie "Undokumentierte Variablenzeiger" haben Sie erfahren, dass Sie über die undokumentierte Funktion VarPtr einen Zeiger auf die Adresse einer Variablen ermitteln können. Acht Bytes über dieser Adresse findet sich für ein Array ein Deskriptor, über den der Zugriff auf die zugehörige SafeArray-Struktur möglich ist. Schliesslich werden die ersten beiden Bytes dieser Struktur als die gesuchte Dimensionstiefe des Arrays kopiert:
Private Declare Sub CopyMemory Lib "kernel32" _ Alias "RtlMoveMemory" (dest As Any, _ source As Any, ByVal bytes As Long) Private Function ArrayDimensionCount(MeinFeld As Variant) As Integer Dim lpSADescriptor As Long, lpSafeArray As Long If Not IsArray(MeinFeld) Then ArrayDimensionCount = -1 Exit Function End If ' Adresse des SAFEARRAY-Deskriptors holen CopyMemory lpSADescriptor, ByVal VarPtr(MeinFeld) + 8, 4 ' Adresse der SAFEARRAY-Struktur holen CopyMemory lpSafeArray, ByVal lpSADescriptor, 4 If lpSafeArray <> 0 Then ' Die ersten zwei Bytes enthalten die Anzahl der Dimensionen: CopyMemory ArrayDimensionCount, ByVal lpSafeArray, 2 End If End Function Private Sub Test() Dim Feld1(1 To 10, 5 To 15, 10 To 20) ' Datenfeldvariablen MsgBox "Feld1 hat " & CStr(ArrayDimensionCount(Feld1)) & " Dimensionen" End Sub