在VB6中,如果向未定义维数的数组传递到Ubound函数会导致错误,因此在尝试检查其上限之前,我希望检查它是否已被定义维度。如何实现这一点?
注意:代码已更新,原版可在修订历史记录中找到(并不是很有用)。更新后的代码不依赖于未记录的
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),但以我认为是编译器错误的方式完成:)
我刚想到这个方法。很简单,不需要调用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) 将导致下标超出范围的错误。
这是我采用的方案。与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
如果ArrayIsInitialized Then ArrayIsInitialized = Ubound(arr) >= Lbound(arr)
。 - GSerg我找到这个:
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)。
GSerg和Raven两种方法都是未经记录的黑客技巧,但由于Visual Basic 6不再开发,因此这不是一个问题。然而,Raven的例子并不适用于所有机器。您需要像这样进行测试。
如果(Not someArray)= -1,则
在某些机器上,它将返回零,在其他机器上则返回一些大的负数。
在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
既然想在这里发表评论,我就来回答一下。
正确的答案似乎是来自@raven:
Dim someArray() As Integer
If ((Not someArray) = -1) Then
Debug.Print "this array is NOT initialized"
End If
Dim x As Integer
x = 3 And 5 'x=1
因此,上述And也被视为按位运算符。
此外,值得检查的是,即使与此直接相关的内容并非如此,
Not运算符可以被重载,这意味着类或结构可以在其操作数具有该类或结构的类型时重新定义其行为。 重载
因此,Not将数组解释为其按位表示,并且它以带符号数字的形式区分数组是否为空。因此,可以认为这不是一种黑客技术,而只是对数组按位表示的未记录说明,Not在此处利用了它。
Not需要一个操作数并反转所有位,包括符号位,并将该值赋给结果。这意味着对于有符号正数,Not始终返回负值,而对于负数,Not始终返回正数或零值。 逻辑按位
决定发布此内容,因为它提供了一种新的方法,欢迎任何人扩展、完善或调整它们对数组在其结构中的表示方式具有访问权限。因此,如果有人提供证据表明数组实际上并不打算通过Not按位处理,我们应该将其视为最佳干净答案,无论他们是否支持这个理论,如果它是对这一点的建设性评论,则当然欢迎。
这是对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
如果使用拆分函数,这个也应该有效。限制在于你需要定义数组类型(例如,在这个示例中是字符串)。
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
当你初始化数组时,使用一个带有标志=1的整数或布尔值。需要时查询此标志。