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