如何确定在VB6中数组是否已初始化?

63

在VB6中,如果向未定义维数的数组传递到Ubound函数会导致错误,因此在尝试检查其上限之前,我希望检查它是否已被定义维度。如何实现这一点?

24个回答

25

注意:代码已更新,原版可在修订历史记录中找到(并不是很有用)。更新后的代码不依赖于未记录的GetMem4函数,并且正确处理所有类型的数组。

VBA用户请注意:此代码适用于VB6,该版本从未得到x64更新。如果您想在VBA中使用此代码,请参见https://dev59.com/RI7ea4cB1Zd3GeqPH_N3#32539884获取VBA版本。您只需要复制CopyMemory声明和pArrPtr函数,其余部分则无需复制。

我使用这个:

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

Private Const VT_BYREF As Long = &H4000&

' When declared in this way, the passed array is wrapped in a Variant/ByRef. It is not copied.
' Returns *SAFEARRAY, not **SAFEARRAY
Public Function pArrPtr(ByRef arr As Variant) As Long
  'VarType lies to you, hiding important differences. Manual VarType here.
  Dim vt As Integer
  CopyMemory ByVal VarPtr(vt), ByVal VarPtr(arr), Len(vt)

  If (vt And vbArray) <> vbArray Then
    Err.Raise 5, , "Variant must contain an array"
  End If

  'see https://msdn.microsoft.com/en-us/library/windows/desktop/ms221627%28v=vs.85%29.aspx
  If (vt And VT_BYREF) = VT_BYREF Then
    'By-ref variant array. Contains **pparray at offset 8
    CopyMemory ByVal VarPtr(pArrPtr), ByVal VarPtr(arr) + 8, Len(pArrPtr)  'pArrPtr = arr->pparray;
    CopyMemory ByVal VarPtr(pArrPtr), ByVal pArrPtr, Len(pArrPtr)          'pArrPtr = *pArrPtr;
  Else
    'Non-by-ref variant array. Contains *parray at offset 8
    CopyMemory ByVal VarPtr(pArrPtr), ByVal VarPtr(arr) + 8, Len(pArrPtr)  'pArrPtr = arr->parray;
  End If
End Function

Public Function ArrayExists(ByRef arr As Variant) As Boolean
  ArrayExists = pArrPtr(arr) <> 0
End Function

使用方法:

? ArrayExists(someArray)

你的代码似乎在做同样的事情(测试SAFEARRAY**是否为NULL),但以我认为是编译器错误的方式完成:)


2
很抱歉,这个答案没有被选中,尽管它是最优雅和灵活的解决方案。我会将其储存起来以备将来使用。谢谢。 - Praesagus

23

我刚想到这个方法。很简单,不需要调用API。这样做有什么问题吗?

Public Function IsArrayInitialized(arr) As Boolean

  Dim rv As Long

  On Error Resume Next

  rv = UBound(arr)
  IsArrayInitialized = (Err.Number = 0)

End Function

编辑:我发现了一个问题,与Split函数的行为有关(实际上我认为这是Split函数中的一个缺陷)。看这个例子:

Edit: 我发现了一个问题,与Split函数的行为有关(实际上我认为这是Split函数中的一个缺陷)。看这个例子:

Dim arr() As String

arr = Split(vbNullString, ",")
Debug.Print UBound(arr)

此时 Ubound(arr) 的值是多少?它是-1!所以,将这个数组传递给 IsArrayInitialized 函数将返回 true,但尝试访问 arr(0) 将导致下标超出范围的错误。


1
你真是比我快;-)这通常是我的做法。小小的批评一下:上面的代码忽略了可能发生的任何错误,这并不理想。例如,如果有人不小心传递了一个普通的整数,你希望函数将“类型不匹配”错误返回给调用者,而不是忽略它。 - Mike Spross
3
续上次评论,错误9(“下标超出范围”)是当您将未初始化的数组传递给LBound和UBound函数时会发生的具体错误,因此如果Err.Number = 9,则可以假定该数组为空。如果发生其他错误,则重新抛出。 - Mike Spross
3
在编译的.exe文件中,我的检查在执行一百万次时只需0.06秒,而这个需要8秒(是的,在IDE中少得多,但那不相关)。异常很耗费资源,你知道的... 有问题吗? - GSerg
3
异常处理是比较耗费资源的,但我不认为我很快就需要一百万次检查数组是否存在,即使需要,8秒也可以接受。这段代码简洁明了,不需要理解Win32 API。你的回答也很有价值,但这个更好。+1 - João Mendes
是的,这对我也起作用了。但是我收到了一个错误消息:“只有在公共对象模块中定义的公共用户定义类型才能用作类模块的公共过程的参数或返回类型,或者用作公共用户定义类型的字段”。我不得不在函数调用中指定数组的确切类型。那是结构体数组。 - Ivan P.

15

这是我采用的方案。与GSerg的答案类似,但使用更好文档化的CopyMemory API函数,完全自包含(您可以直接将数组传递给该函数,而不是ArrPtr(array))。它确实使用了VarPtr函数,但Microsoft警告不要使用它,但这是一个仅支持XP应用程序,并且它可行,所以我不担心。

是的,我知道这个函数将接受任何你传递给它的东西,但我会把错误检查留给读者来练习。

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
  (pDst As Any, pSrc As Any, ByVal ByteLen As Long)

Public Function ArrayIsInitialized(arr) As Boolean

  Dim memVal As Long

  CopyMemory memVal, ByVal VarPtr(arr) + 8, ByVal 4 'get pointer to array
  CopyMemory memVal, ByVal memVal, ByVal 4  'see if it points to an address...  
  ArrayIsInitialized = (memVal <> 0)        '...if it does, array is intialized

End Function

RtlMoveMemory 比本地的 GetMem4 慢。GetMem4 的速度超过两倍: http://www.vbforums.com/archive/index.php/t-360672.html - Motomotes
与得到最高票答案不同,这个确实可以用于字符串数组。 - Gavin
1
从另一条评论链接的讨论中,Karl Peterson提供了一个类似的函数(https://groups.google.com/d/msg/microsoft.public.vb.general.discussion/3CBPw3nMX2s/zCcaO-hiCI0J),其中包括一些额外的验证。 - Holistic Developer
刚注意到这个不适用于类型为“Object”(或类型为“SomeClass”,其中“SomeClass”是一个类而不是UDT)的数组。始终返回“True”。而在这种情况下,“ArrPtr”可以使用。 - GSerg
1
可以通过向函数添加最后一行来修复:如果ArrayIsInitialized Then ArrayIsInitialized = Ubound(arr) >= Lbound(arr) - GSerg

14

我找到这个:

Dim someArray() As Integer

If ((Not someArray) = -1) Then
  Debug.Print "this array is NOT initialized"
End If

编辑: RS Conley在他的答案中指出,(Not someArray)有时会返回0,因此您必须使用((Not someArray) = -1)。


10
不建议使用“Not” hack,因为它实际上不是一种语言特性,而是编译器中的错误导致的结果,并且其行为可能会产生意想不到的后果。相反,请使用GSerg的方法。 - Konrad Rudolph
1
@Konrad,很有趣。你知道有没有更多描述这个 bug 的来源吗? - jtolle
@jtolle:很遗憾,没有。据我所知,它从未在MSDN中得到确认,但VB6社区已经知道了多年。 - Konrad Rudolph
1
这里有一个很好的德语错误描述,也许Google翻译可以帮助那些对此感兴趣但不懂德语的人:http://classic.vb-faq.de/wie-kann-ich-feststellen-ob-ein-dynamisches-array-leer-ist/ - Felix Dombek
使用这种方式可能会在程序中引起一些危险问题,比如在使用浮点数时出现错误。 - mohsen amiri

9

GSerg和Raven两种方法都是未经记录的黑客技巧,但由于Visual Basic 6不再开发,因此这不是一个问题。然而,Raven的例子并不适用于所有机器。您需要像这样进行测试。

如果(Not someArray)= -1,则

在某些机器上,它将返回零,在其他机器上则返回一些大的负数。


你知道吗,这个例子最初就有那个语法,但我编辑了它,因为我认为它是无意义的。现在我更明白了。 - raven
这是我使用的。但它阅读起来真的很混乱,所以我编写了一个函数。 - Matthew Cole
2
Karl Peterson说这可能会引起问题。在我看来,这已经足够好的理由去避免它了。http://groups.google.co.uk/group/microsoft.public.vb.general.discussion/browse_thread/thread/dc204fc379cc5f6b?hl=en# - MarkJ
尝试从IDE运行此代码。我在最后一个MsgBox上得到了一个错误16表达式太复杂。私有子表单_Load() Dim X()作为长 如果不是X,则不要MsgBox“耶” Debug.Assert App.hInstance MsgBox CLng(0.1@) 如果不是X,则不要MsgBox“耶” MsgBox CLng(0.1@) 结束子 - MarkJ

5

在VB6中,存在一个名为“IsArray”的函数,但它不会检查数组是否已初始化。如果您尝试在未初始化的数组上使用UBound,则会收到错误9 - 下标超出范围的消息。我的方法与S J的非常相似,只是它适用于所有变量类型并具有错误处理功能。如果检查非数组变量,则会收到错误13 - 类型不匹配的消息。

Private Function IsArray(vTemp As Variant) As Boolean
    On Error GoTo ProcError
    Dim lTmp As Long

    lTmp = UBound(vTemp) ' Error would occur here

    IsArray = True: Exit Function
ProcError:
    'If error is something other than "Subscript
    'out of range", then display the error
    If Not Err.Number = 9 Then Err.Raise (Err.Number)
End Function

4

既然想在这里发表评论,我就来回答一下。

正确的答案似乎是来自@raven:

Dim someArray() As Integer

If ((Not someArray) = -1) Then
  Debug.Print "this array is NOT initialized"
End If

当文档或谷歌无法立即返回解释时,人们往往称其为hack。虽然看起来的解释是Not不仅是逻辑运算符,它还是一个位运算符,因此它处理结构的位表示,而不仅仅是布尔值。
这里有另一种位运算的例子:
Dim x As Integer
x = 3 And 5 'x=1

因此,上述And也被视为按位运算符。

此外,值得检查的是,即使与此直接相关的内容并非如此,

Not运算符可以被重载,这意味着类或结构可以在其操作数具有该类或结构的类型时重新定义其行为。 重载

因此,Not将数组解释为其按位表示,并且它以带符号数字的形式区分数组是否为空。因此,可以认为这不是一种黑客技术,而只是对数组按位表示的未记录说明,Not在此处利用了它。

Not需要一个操作数并反转所有位,包括符号位,并将该值赋给结果。这意味着对于有符号正数,Not始终返回负值,而对于负数,Not始终返回正数或零值。 逻辑按位

决定发布此内容,因为它提供了一种新的方法,欢迎任何人扩展、完善或调整它们对数组在其结构中的表示方式具有访问权限。因此,如果有人提供证据表明数组实际上并不打算通过Not按位处理,我们应该将其视为最佳干净答案,无论他们是否支持这个理论,如果它是对这一点的建设性评论,则当然欢迎。


3

这是对raven的答案的修改,不需要使用API。

没有使用API的方式。
Public Function IsArrayInitalized(ByRef arr() As String) As Boolean
'Return True if array is initalized
On Error GoTo errHandler 'Raise error if directory doesnot exist

  Dim temp As Long
  temp = UBound(arr)

  'Reach this point only if arr is initalized i.e. no error occured
  If temp > -1 Then IsArrayInitalized = True 'UBound is greater then -1

Exit Function
errHandler:
  'if an error occurs, this function returns False. i.e. array not initialized
End Function

如果使用拆分函数,这个也应该有效。限制在于你需要定义数组类型(例如,在这个示例中是字符串)。


2
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Declare Function ArrPtr Lib "msvbvm60" Alias "VarPtr" (arr() As Any) As Long

Private Type SafeArray
    cDims As Integer
    fFeatures As Integer
    cbElements As Long
    cLocks As Long
    pvData As Long
End Type

Private Function ArrayInitialized(ByVal arrayPointer As Long) As Boolean
    Dim pSafeArray As Long

    CopyMemory pSafeArray, ByVal arrayPointer, 4

    Dim tArrayDescriptor As SafeArray

    If pSafeArray Then
        CopyMemory tArrayDescriptor, ByVal pSafeArray, LenB(tArrayDescriptor)

        If tArrayDescriptor.cDims > 0 Then ArrayInitialized = True
    End If

End Function

使用方法:

Private Type tUDT
    t As Long
End Type

Private Sub Form_Load()
    Dim longArrayNotDimmed() As Long
    Dim longArrayDimmed(1) As Long

    Dim stringArrayNotDimmed() As String
    Dim stringArrayDimmed(1) As String

    Dim udtArrayNotDimmed() As tUDT
    Dim udtArrayDimmed(1) As tUDT

    Dim objArrayNotDimmed() As Collection
    Dim objArrayDimmed(1) As Collection


    Debug.Print "longArrayNotDimmed " & ArrayInitialized(ArrPtr(longArrayNotDimmed))
    Debug.Print "longArrayDimmed " & ArrayInitialized(ArrPtr(longArrayDimmed))

    Debug.Print "stringArrayNotDimmed " & ArrayInitialized(ArrPtr(stringArrayNotDimmed))
    Debug.Print "stringArrayDimmed " & ArrayInitialized(ArrPtr(stringArrayDimmed))

    Debug.Print "udtArrayNotDimmed " & ArrayInitialized(ArrPtr(udtArrayNotDimmed))
    Debug.Print "udtArrayDimmed " & ArrayInitialized(ArrPtr(udtArrayDimmed))

    Debug.Print "objArrayNotDimmed " & ArrayInitialized(ArrPtr(objArrayNotDimmed))
    Debug.Print "objArrayDimmed " & ArrayInitialized(ArrPtr(objArrayDimmed))

    Unload Me
End Sub

请根据S.O.格式化更正您的代码格式。 - josegomezr
看起来“Celeo”已经为我纠正了它。谢谢,我是第一次在这里发布。 - Frodo
@Frodo 对于像这样的代码块,请在每行代码前面放置4个空格,除了代码本身的间距/缩进。您可以粘贴一个代码块,并在其上选择“代码示例”按钮或ctrl+k,它将为您添加空格。记住,在发布之前始终检查问题预览。 - djv

2

当你初始化数组时,使用一个带有标志=1的整数或布尔值。需要时查询此标志。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接