arrays - resueltos - ¿Cómo puedo determinar si una matriz se inicializa en VB6?
vb.net array dinamico (19)
Pasar una matriz no dimensionada a la función Ubound del VB6 causará un error, por lo que quiero verificar si se ha dimensionado aún antes de intentar verificar su límite superior. ¿Cómo hago esto?
Ambos métodos de GSerg y Raven son piratas no documentados, pero dado que Visual Basic 6 ya no se está desarrollando, no es un problema. Sin embargo, el ejemplo de Raven no funciona en todas las máquinas. Tienes que probar así.
If (No someArray) = -1 Entonces
En algunas máquinas devolverá un cero en los demás un gran número negativo.
Cuando inicialice la matriz ponga un entero o booleano con una bandera = 1. y consulte esta bandera cuando lo necesite.
El título de la pregunta pregunta cómo determinar si una matriz se ha inicializado, pero, después de leer la pregunta, parece que el problema real es cómo obtener el UBound
de una matriz que no se ha inicializado.
Aquí está mi solución (al problema real, no al título):
Function UBound2(Arr) As Integer
On Error Resume Next
UBound2 = UBound(Arr)
If Err.Number = 9 Then UBound2 = -1
On Error GoTo 0
End Function
Esta función funciona en los siguientes cuatro escenarios, los primeros tres que he encontrado cuando Arr
es creado por un dll externo COM y el cuarto cuando el Arr
no es ReDim
-ed (el sujeto de esta pregunta):
-
UBound(Arr)
funciona, por lo que llamar aUBound2(Arr)
agrega un poco de sobrecarga, pero no duele demasiado -
UBound(Arr)
falla en la función que defineArr
, pero tiene éxito dentro deUBound2()
-
UBound(Arr)
falla tanto en la función que defineArr
como enUBound2()
, por lo que el manejo del error hace el trabajo - Después de
Dim Arr() As Whatever
, antes deReDim Arr(X)
En VB6 hay una función llamada "IsArray", pero no comprueba si la matriz se ha inicializado. Recibirá un error 9 - Subíndice fuera de rango si intenta usar UBound en una matriz no inicializada. Mi método es muy similar a S J, excepto que funciona con todos los tipos de variables y tiene manejo de errores. Si se marca una variable que no es de matriz, recibirá el Error 13 - No coinciden los tipos.
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
En base a toda la información que leí en esta publicación existente, esto funciona mejor para mí cuando se trata de una matriz tipada que comienza como no inicializada.
Mantiene el código de prueba consistente con el uso de UBOUND y no requiere el uso de manejo de errores para las pruebas.
ES DEPENDIENTE de las matrices basadas en cero (que es el caso en la mayoría del desarrollo).
No debe usar "Erase" para borrar la matriz. utilice la alternativa que se detalla a continuación.
Dim data() as string '' creates the untestable holder.
data = Split(vbNullString, ",") '' causes array to return ubound(data) = -1
If Ubound(data)=-1 then '' has no contents
'' do something
End If
redim preserve data(Ubound(data)+1) '' works to increase array size regardless of it being empty or not.
data = Split(vbNullString, ",") '' MUST use this to clear the array again.
Encontré esto:
Dim someArray() As Integer
If ((Not someArray) = -1) Then
Debug.Print "this array is NOT initialized"
End If
Editar : RS Conley señaló en su answer que (No someArray) a veces devolverá 0, por lo que debe usar ((No someArray) = -1).
Esta es la modificación de la answer de cuervo. Sin usar API''s
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
Este también debería estar funcionando en caso de función dividida. La limitación es que necesitaría definir el tipo de matriz (cadena en este ejemplo).
Esto es lo que hice. Esto es similar a la answer de GSerg, pero utiliza la función mejor documentada de CopyMemory API y es totalmente autónoma (puede pasar la matriz en lugar de ArrPtr (matriz) a esta función). Utiliza la función VarPtr, support.microsoft.com/kb/199824 cual Microsoft support.microsoft.com/kb/199824 , pero esta es una aplicación solo para XP, y funciona, así que no estoy preocupado.
Sí, sé que esta función aceptará cualquier cosa que le arroje, pero dejaré la comprobación de errores como un ejercicio para el lector.
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
Hay dos escenarios ligeramente diferentes para probar:
- La matriz se inicializa (efectivamente no es un puntero nulo)
- La matriz se inicializa y tiene al menos un elemento
El caso 2 es necesario para casos como Split(vbNullString, ",")
que devuelve una matriz String
con LBound=0
y UBound=-1
. Estos son los ejemplos más simples de fragmentos de código que puedo producir para cada prueba:
Public Function IsInitialised(arr() As String) As Boolean
On Error Resume Next
IsInitialised = UBound(arr) <> 0.5
End Function
Public Function IsInitialisedAndHasElements(arr() As String) As Boolean
On Error Resume Next
IsInitialisedAndHasElements = UBound(arr) >= LBound(arr)
End Function
La forma más fácil de manejar esto es asegurarse de que la matriz se inicialice por adelantado, antes de que necesite verificar el Ubound. Necesitaba una matriz que se declaró en el área (General) del código del formulario. es decir
Dim arySomeArray() As sometype
Luego, en la rutina de carga de formulario redibujo la matriz:
Private Sub Form_Load()
ReDim arySomeArray(1) As sometype ''insure that the array is initialized
End Sub
Esto permitirá que la matriz sea redefinida en cualquier momento posterior del programa. Cuando descubras qué tan grande debe ser la matriz, solo redimíntala.
ReDim arySomeArray(i) As sometype ''i is the size needed to hold the new data
Mi único problema con las llamadas a la API es pasar de los sistemas operativos de 32 bits a los de 64 bits.
Esto funciona con objetos, cadenas, etc.
Public Function ArrayIsInitialized(ByRef arr As Variant) As Boolean
On Error Resume Next
ArrayIsInitialized = False
If UBound(arr) >= 0 Then If Err.Number = 0 Then ArrayIsInitialized = True
End Function
Puede resolver el problema con la función Ubound()
, comprobar si la matriz está vacía recuperando el recuento total de elementos utilizando el objeto VBArray()
JScript (funciona con matrices de tipo variante, individual o multidimensional):
Sub Test()
Dim a() As Variant
Dim b As Variant
Dim c As Long
'' Uninitialized array of variant
'' MsgBox UBound(a) '' gives ''Subscript out of range'' error
MsgBox GetElementsCount(a) '' 0
'' Variant containing an empty array
b = Array()
MsgBox GetElementsCount(b) '' 0
'' Any other types, eg Long or not Variant type arrays
MsgBox GetElementsCount(c) '' -1
End Sub
Function GetElementsCount(aSample) As Long
Static oHtmlfile As Object '' instantiate once
If oHtmlfile Is Nothing Then
Set oHtmlfile = CreateObject("htmlfile")
oHtmlfile.parentWindow.execScript ("function arrlength(arr) {try {return (new VBArray(arr)).toArray().length} catch(e) {return -1}}"), "jscript"
End If
GetElementsCount = oHtmlfile.parentWindow.arrlength(aSample)
End Function
Para mí, se necesitan aproximadamente 0,4 mksec por cada elemento + 100 mseg de inicialización, y se compila con VB 6.0.9782, por lo que la matriz de elementos de 10M tarda aproximadamente 4,1 segundos. La misma funcionalidad podría implementarse mediante ScriptControl
ActiveX.
Solo pensé en esto. Bastante simple, no necesita llamadas API. ¿Algún problema con eso?
Public Function IsArrayInitialized(arr) As Boolean
Dim rv As Long
On Error Resume Next
rv = UBound(arr)
IsArrayInitialized = (Err.Number = 0)
End Function
Editar : Descubrí un error relacionado con el funcionamiento de la función Dividir (en realidad lo llamaría un defecto en la función Dividir). Toma este ejemplo:
Dim arr() As String
arr = Split(vbNullString, ",")
Debug.Print UBound(arr)
¿Cuál es el valor de Ubound (arr) en este punto? ¡Es -1! Por lo tanto, pasar esta matriz a esta función IsArrayInitialized devolvería true, pero intentar acceder a arr (0) provocaría un error de subíndice fuera de rango.
Yo uso esto:
Public Declare Function GetMem4 Lib "msvbvm60" (ByVal pSrc As Long, ByVal pDst As Long) As Long
Public Declare Function ArrPtr Lib "msvbvm60" Alias "VarPtr" (arr() As Any) As Long
Public Function StrArrPtr(arr() As String, Optional ByVal IgnoreMe As Long = 0) As Long
GetMem4 VarPtr(IgnoreMe) - 4, VarPtr(StrArrPtr)
End Function
Public Function UDTArrPtr(ByRef arr As Variant) As Long
If VarType(arr) Or vbArray Then
GetMem4 VarPtr(arr) + 8, VarPtr(UDTArrPtr)
Else
Err.Raise 5, , "Variant must contain array of user defined type"
End If
End Function
Public Function ArrayExists(ByVal ppArray As Long) As Long
GetMem4 ppArray, VarPtr(ArrayExists)
End Function
Uso:
? ArrayExists(ArrPtr(someArray))
? ArrayExists(StrArrPtr(someArrayOfStrings))
? ArrayExists(UDTArrPtr(someArrayOfUDTs))
Tu código parece hacer lo mismo (probando que SAFEARRAY ** sea NULL), pero de una manera que consideraría un error del compilador :)
Si la matriz es una matriz de cadenas, puede usar el método Join () como una prueba:
Private Sub Test()
Dim ArrayToTest() As String
MsgBox StringArrayCheck(ArrayToTest) '' returns "false"
ReDim ArrayToTest(1 To 10)
MsgBox StringArrayCheck(ArrayToTest) '' returns "true"
ReDim ArrayToTest(0 To 0)
MsgBox StringArrayCheck(ArrayToTest) '' returns "false"
End Sub
Function StringArrayCheck(o As Variant) As Boolean
Dim x As String
x = Join(o)
StringArrayCheck = (Len(x) <> 0)
End Function
Dim someArray() as Integer
If someArray Is Nothing Then
Debug.print "this array is not initialised"
End If
If ChkArray(MyArray)=True then
....
End If
Public Function ChkArray(ByRef b) As Boolean
On Error goto 1
If UBound(b) > 0 Then ChkArray = True
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
Uso:
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