Объявить массив строк 0-длины в VBA - невозможно? - PullRequest
4 голосов
/ 12 марта 2019

Действительно ли невозможно объявить массив 0 длины в VBA?Если я попробую это:

Dim lStringArr(-1) As String

, я получаю ошибку компиляции, говоря, что диапазон не имеет значений.Если я попытаюсь обмануть компилятор и выполнить redim во время выполнения, например, так:

ReDim lStringArr(-1)

Я получаю индекс из-за ошибки диапазона.

Я немного изменил выше, но безнапример,

Dim lStringArr(0 To -1) As String

Вариант использования

Я хочу преобразовать вариантный массив в массив строк.Вариантный массив может быть пустым, поскольку он получен из свойства Keys словаря.Свойство keys возвращает массив вариантов.Я хочу использовать массив строк в своем коде, так как у меня есть некоторые функции для обработки строковых массивов, которые я хотел бы использовать.Вот функция преобразования, которую я использую.Это выдает индекс из-за ошибки диапазона из-за того, что lMaxIndex = -1:

Public Function mVariantArrayToStringArray(pVariants() As Variant) As String()
    Dim lStringArr() As String
    Dim lMaxIndex As Long, lMinIndex As Long
    lMaxIndex = UBound(pVariants)
    lMinIndex = LBound(pVariants)
    ReDim lStringArr(lMaxIndex)
    Dim lVal As Variant
    Dim lIndex As Long
    For lIndex = lMinIndex To lMaxIndex
        lStringArr(lIndex) = pVariants(lIndex)
    Next
    mVariantArrayToStringArray = lStringArr
End Function

Hack

Возвращает одноэлементный массив, содержащий пустую строку.Обратите внимание, это не то, что мы хотим.Нам нужен пустой массив - такой, что зацикливание на нем похоже на бездействие.Но одноэлементный массив, содержащий пустую строку, часто будет работать, например, если позже мы захотим объединить все строки в массив строк.

Public Function mVariantArrayToStringArray(pVariants() As Variant) As String()
    Dim lStringArr() As String
    Dim lMaxIndex As Long, lMinIndex As Long
    lMaxIndex = UBound(pVariants)
    lMinIndex = LBound(pVariants)
    If lMaxIndex < 0 Then
        ReDim lStringArr(1)
        lStringArr(1) = ""
    Else
        ReDim lStringArr(lMaxIndex)
    End If
    Dim lVal As Variant
    Dim lIndex As Long
    For lIndex = lMinIndex To lMaxIndex
        lStringArr(lIndex) = pVariants(lIndex)
    Next
    mVariantArrayToStringArray = lStringArr
End Function

Обновление с ответа

Вот функция Iиспользую для преобразования варианта массива в массив строк.Решение Коминтерна кажется более продвинутым и общим, и я могу перейти к этому однажды, если я все еще застрял в VBA:

Public Function mVariantArrayToStringArray(pVariants() As Variant) As String()
    Dim lStringArr() As String
    Dim lMaxIndex As Long, lMinIndex As Long
    lMaxIndex = UBound(pVariants)
    lMinIndex = LBound(pVariants)
    If lMaxIndex < 0 Then
        mVariantArrayToStringArray = Split(vbNullString)
    Else
        ReDim lStringArr(lMaxIndex)
    End If
    Dim lVal As Variant
    Dim lIndex As Long
    For lIndex = lMinIndex To lMaxIndex
        lStringArr(lIndex) = pVariants(lIndex)
    Next
    mVariantArrayToStringArray = lStringArr
End Function

Примечания

  • Я использую Option Explicit.Это не может измениться, поскольку защищает остальную часть кода в модуле.

Ответы [ 3 ]

5 голосов
/ 12 марта 2019

Как отмечается в комментариях, вы можете сделать это "изначально", вызвав Split для vbNullString, как указано здесь :

выражение - обязательно.Строковое выражение, содержащее подстроки и разделители.Если выражение - строка нулевой длины (""), Split возвращает пустой массив, то есть массив без элементов и без данных.

Если вам нужно более общее решение (т. Е.другие типы данных, вы можете напрямую вызвать функцию SafeArrayRedim в oleaut32.dll и запросить, чтобы она переразмеряла переданный массив до элементов 0. Вам нужно перепрыгнуть через пару обручей, чтобы получить базовый адрес массива (это связано с причудой функции VarPtr.

В разделе объявлений модуля:

'Headers
Private Type SafeBound
    cElements As Long
    lLbound As Long
End Type

Private Const VT_BY_REF = &H4000&
Private Const PVDATA_OFFSET = 8

Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias _
    "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, _
    ByVal length As Long)

Private Declare Sub SafeArrayRedim Lib "oleaut32" (ByVal psa As LongPtr, _
    ByRef rgsabound As SafeBound)

Процедура - передать ей инициализированный массив (любого типа)и он удалит из него все элементы:

Private Sub EmptyArray(ByRef vbArray As Variant)
    Dim vtype As Integer
    CopyMemory vtype, vbArray, LenB(vtype)
    Dim lp As LongPtr
    CopyMemory lp, ByVal VarPtr(vbArray) + PVDATA_OFFSET, LenB(lp)
    If Not (vtype And VT_BY_REF) Then
        CopyMemory lp, ByVal lp, LenB(lp)
        Dim bound As SafeBound
        SafeArrayRedim lp, bound
    End If
End Sub

Пример использования:

Private Sub Testing()
    Dim test() As Long
    ReDim test(0)
    EmptyArray test
    Debug.Print LBound(test)    '0
    Debug.Print UBound(test)    '-1
End Sub
1 голос
/ 12 марта 2019

За Комментарий Коминтерна .

Создайте специальную служебную функцию, которая возвращает результат функции VBA.Strings.Split, отрабатывающей vbNullString, которая фактически является пустым указателем на строку, что делает намерение более явным, чем использование пустого строкового литерала "", который также будет работать:

Public Function EmptyStringArray() As String()
     EmptyStringArray = VBA.Strings.Split(vbNullString)
End Function

Теперь выполните ветвление вашей функции, чтобы проверить наличие ключей, и верните EmptyStringArray, если их нет, в противном случае продолжите изменение размера массива результатов и преобразуйте каждый исходный элемент.

0 голосов
/ 12 марта 2019

Если мы все равно будем использовать WinAPI, мы также можем создать массив с нуля, используя функцию WinAPI SafeArrayCreate, а не изменять его размеры.

Структурные объявления:

Public Type SAFEARRAYBOUND
    cElements As Long
    lLbound As Long
End Type
Public Type tagVariant
    vt As Integer
    wReserved1 As Integer
    wReserved2 As Integer
    wReserved3 As Integer
    pSomething As LongPtr
End Type

Объявления WinAPI:

Public Declare PtrSafe Function SafeArrayCreate Lib "OleAut32.dll" (ByVal vt As Integer, ByVal cDims As Long, ByRef rgsabound As SAFEARRAYBOUND) As LongPtr
Public Declare PtrSafe Sub VariantCopy Lib "OleAut32.dll" (pvargDest As Any, pvargSrc As Any)
Public Declare PtrSafe Sub SafeArrayDestroy Lib "OleAut32.dll"(ByVal psa As LongPtr)

Используйте это:

Public Sub Test()
    Dim bounds As SAFEARRAYBOUND 'Defaults to lower bound 0, 0 items
    Dim NewArrayPointer As LongPtr 'Pointer to hold unmanaged string array
    NewArrayPointer = SafeArrayCreate(vbString, 1, bounds)
    Dim tagVar As tagVariant 'Unmanaged variant we can manually manipulate
    tagVar.vt = vbArray + vbString 'Holds a string array
    tagVar.pSomething = NewArrayPointer 'Make variant point to the new string array
    Dim v As Variant 'Actual variant
    VariantCopy v, ByVal tagVar 'Copy unmanaged variant to managed one
    Dim s() As String 'Managed string array
    s = v 'Copy the array from the variant
    SafeArrayDestroy NewArrayPointer 'Destroy the unmanaged SafeArray, leaving the managed one
    Debug.Print LBound(s); UBound(s) 'Prove the dimensions are 0 and -1    
End Sub
...