-
Feb 11th, 2017, 07:29 PM
#1
[RESOLVED] How to create (0 to -1) bounds byte array?
Hi, @all!
Code:
'some examples of (0 to -1)
Private Sub Form_Load()
Dim arr$(), v
arr = Split("") 'String(0 to -1)
v = Array() 'Variant(0 to -1)
'??? 'Byte(0 to -1)
End Sub
In some cases I would like my function will return Byte(0 to -1), like 'vb Split' do, so array will be always initialized and further I can safely use Ubound() for checking or directly in 'for each' e.t.c., no matter, but I don't know how correctly create such "empty" byte array in a way to be sure not to get crash surprises when runtime decides to free memory of such "empty" array.
Thanks for help.
Alex.
-
Feb 11th, 2017, 08:13 PM
#2
Re: How to create (0 to -1) bounds byte array?
For a ByteArray it would simply be:
Code:
Dim B() As Byte
B = "" 'or B = vbNullString
Olaf
-
Feb 11th, 2017, 09:05 PM
#3
Re: How to create (0 to -1) bounds byte array?
Thank you.
-
Feb 12th, 2017, 12:47 PM
#4
Re: How to create (0 to -1) bounds byte array?
Hmmm, I never use these things, but this thread got me to thinking. It's interesting that you could do something like ...
Code:
Dim bb() As Byte
bb = vbNullString
Dim i As Long
For i = LBound(bb) To UBound(bb)
' do something
Next i
... and you wouldn't get into trouble.
That got me wondering about exactly what's going on with the SafeArray, so I wrote this code:
Code:
Option Explicit
'
Private Type SAFEARRAY
cDims As Integer
fFeatures As Integer
cbElements As Long
cLocks As Long
pvData As Long
End Type
'
Private Type SAFEARRAYBOUND
cElements As Long
lLbound As Long
End Type
'
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Sub Form_Load()
Dim bb() As Byte
'
Dim sa As SAFEARRAY
Dim sab() As SAFEARRAYBOUND
'
bb = ""
GetSafeArrayInfo bb, sa, sab()
Debug.Print sab(0).cElements
Debug.Print sab(0).lLbound
Debug.Print "done"
Unload Me
End Sub
Private Sub GetSafeArrayInfo(v As Variant, sa As SAFEARRAY, sab() As SAFEARRAYBOUND)
Dim ppSA As Long
Dim pSA As Long
'
CopyMemory ppSA, ByVal VarPtr(v) + 8, 4
CopyMemory pSA, ByVal ppSA, 4 ' Dereference.
'
If pSA = 0 Then Exit Sub ' No array.
'
CopyMemory sa, ByVal pSA, LenB(sa)
If sa.cDims = 0 Then Exit Sub ' No dimensions, not dimensioned.
'
ReDim sab(0 To sa.cDims - 1)
'
' Now we can get the remainder of the SAFEARRAY structure.
CopyMemory sab(0), ByVal (pSA + LenB(sa)), (sa.cDims * 8)
End Sub
It turns out that it is "sort of" dimensioned. In other words, SAFEARRAY.cDims has a non-zero value. However, when we look at the SAFEARRAYBOUND values, we see that the lLBound = 0 but the cElements also = 0. Obviously, the VB6 UBound function just does something like: UBound = sab.lLBound + (sab.cElements - 1)
And that all makes perfect sense to me.
And then, that got me thinking some more. We can certainly place any type of an array in a Variant. So why can't we place one of these funny "sort of" dimensioned array into a Variant and then typecast it? Along these lines, I wrote this code:
Code:
Option Explicit
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Sub Form_Load()
Dim c() As Currency
c = MakeZeroToNegOneArray(vbCurrency)
MsgBox TypeName(c)
MsgBox LBound(c) & " " & UBound(c)
Debug.Print "done"
Unload Me
End Sub
Private Function MakeZeroToNegOneArray(iType As VBA.VbVarType) As Variant
Dim bb() As Byte
Const VT_ARRAY = &H2000
'
bb = vbNullString ' We'll make a Byte type and then force-typecast it.
MakeZeroToNegOneArray = bb ' Put empty array into variant.
CopyMemory MakeZeroToNegOneArray, iType Or VT_ARRAY, 2 ' Typecast, making sure it's still an array.
End Function
Given that no actual memory is allocated for the array data, I see no foul in doing this typecasting.
It would appear that this is a workable method for creating one of these "funny" arrays for any type. It could probably be improved a bit with GetMem2, but the above certainly seems to work.
Regards,
Elroy
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
-
Feb 12th, 2017, 03:55 PM
#5
Re: How to create (0 to -1) bounds byte array?
Originally Posted by Elroy
Code:
Private Function MakeZeroToNegOneArray(iType As VBA.VbVarType) As Variant
Dim bb() As Byte
Const VT_ARRAY = &H2000
'
bb = vbNullString ' We'll make a Byte type and then force-typecast it.
MakeZeroToNegOneArray = bb ' Put empty array into variant.
CopyMemory MakeZeroToNegOneArray, iType Or VT_ARRAY, 2 ' Typecast, making sure it's still an array.
End Function
Given that no actual memory is allocated for the array data, I see no foul in doing this typecasting.
It would appear that this is a workable method for creating one of these "funny" arrays for any type. It could probably be improved a bit with GetMem2, but the above certainly seems to work.
Nope, that's not going to fly, since you neglected to change the .cbElements Member
of the (underlying) ByteArray you've choosen as a "template".
One can remedy (and add/overwrite) that too, of course - but then the code would become longer -
so why not use the OleAut-function which was desigend for that purpose instead (to save some lines of code)...
Code:
Option Explicit
Private Declare Sub MemCopy Lib "kernel32" Alias "RtlMoveMemory" (pDst As Any, pSrc As Any, ByVal CB&)
Private Declare Function SafeArrayCreateEx& Lib "oleaut32" (ByVal VarType&, ByVal Dims&, SAUB As Any, ByVal pvExtra&)
Private Sub Form_Load()
Dim C() As Currency
C = CreateEmptyArray(vbCurrency)
Debug.Print LBound(C), UBound(C), TypeName(C)
End Sub
Private Function CreateEmptyArray(ByVal ArrType As VbVarType, Optional ByVal Dims As Long = 1)
Static SAUB(0 To 29) As Long 'enough statically allocated "zero-space" for max. 15 Dimensions
MemCopy ByVal VarPtr(CreateEmptyArray) + 8, SafeArrayCreateEx(ArrType And Not vbArray, Dims, SAUB(0), 0), 4
MemCopy ByVal VarPtr(CreateEmptyArray), ArrType Or vbArray, 2
End Function
Olaf
-
Feb 12th, 2017, 04:32 PM
#6
Re: How to create (0 to -1) bounds byte array?
Originally Posted by Schmidt
Nope, that's not going to fly, since you neglected to change the .cbElements Member
of the (underlying) ByteArray you've choosen as a "template".
You're right, but I'm not sure it matters. In fact, using the above code, I executed the following ...
Code:
Private Sub Form_Load()
Dim c() As Currency
c = MakeZeroToNegOneArray(vbCurrency)
ReDim c(1 To 1000)
Dim sa As SAFEARRAY
Dim sab() As SAFEARRAYBOUND
GetSafeArrayInfo c(), sa, sab()
MsgBox sa.cbElements
End Sub
.. and cbElements reports 8. It's obviously fixed on the Redim.
Regards,
Elroy
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
-
Feb 12th, 2017, 05:00 PM
#7
Re: How to create (0 to -1) bounds byte array?
Originally Posted by Elroy
You're right, but I'm not sure it matters.
Well, I'm sure it matters (otherwise wouldn't have bothered with posting a warning).
Originally Posted by Elroy
In fact, using the above code, I executed the following ...
...
.. and cbElements reports 8. It's obviously fixed on the Redim.
A Redim (without Preserve) is a completely new allocation - that's why it worked for you.
But check out the following:
Code:
Option Explicit
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Sub Form_Load()
Dim c() As Currency
c = MakeZeroToNegOneArray(vbCurrency)
ReDim Preserve c(1)
Debug.Print c(1) 'should print 0, but on my machine gives: -807044173589455.4976
End Sub
Private Function MakeZeroToNegOneArray(iType As VBA.VbVarType) As Variant
Dim bb() As Byte
Const VT_ARRAY = &H2000
'
bb = vbNullString ' We'll make a Byte type and then force-typecast it.
MakeZeroToNegOneArray = bb ' Put empty array into variant.
CopyMemory MakeZeroToNegOneArray, iType Or VT_ARRAY, 2 ' Typecast, making sure it's still an array.
End Function
BTW, vbArray has the same value as your VT_ARRAY-def.
Olaf
-
Feb 12th, 2017, 05:22 PM
#8
Re: How to create (0 to -1) bounds byte array?
And just for grins, I went ahead and patched up cbElements, although it probably truly can't matter. And yeah, it did take a few more lines of code.
Code:
Option Explicit
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Type SAFEARRAY
cDims As Integer
fFeatures As Integer
cbElements As Long
cLocks As Long
pvData As Long
End Type
'
Private Type SAFEARRAYBOUND
cElements As Long
lLbound As Long
End Type
Private Function MakeZeroToNegOneArray(iType As VBA.VbVarType) As Variant
' Allowed: Boolean, Byte, Currency, Date, Double, Integer, Long, Single, String, Variant, Object
If Not ((iType >= 2 And iType <= 9) Or iType = 11 Or iType = 12 Or iType = 17) Then Exit Function
'
Dim bb() As Byte
Dim ppSA As Long
Dim iBytes As Long
'
bb = vbNullString ' We'll make a Byte type and then force-typecast it.
MakeZeroToNegOneArray = bb ' Put empty array into variant.
CopyMemory MakeZeroToNegOneArray, iType Or vbArray, 2& ' Typecast, making sure it's still an array.
'
' And now fix cbElements.
CopyMemory ppSA, ByVal VarPtr(MakeZeroToNegOneArray) + 8&, 4&
ppSA = ppSA + 4& ' Offset pointer to cbElements.
Select Case iType
Case vbByte: iBytes = 1&
Case vbInteger, vbBoolean: iBytes = 2&
Case vbString, vbSingle, vbLong, vbObject: iBytes = 4&
Case vbCurrency, vbDate, vbDouble: iBytes = 8&
Case vbVariant: iBytes = 16&
End Select
CopyMemory ByVal ppSA, iBytes, 4&
End Function
Private Sub Form_Load() '11 17 12
Dim l() As Long
l = MakeZeroToNegOneArray(vbLong)
Dim sa As SAFEARRAY
Dim sab() As SAFEARRAYBOUND
GetSafeArrayInfo l(), sa, sab()
MsgBox sa.cbElements
End Sub
Private Sub GetSafeArrayInfo(v As Variant, sa As SAFEARRAY, sab() As SAFEARRAYBOUND)
Dim ppSA As Long
Dim pSA As Long
'
CopyMemory ppSA, ByVal VarPtr(v) + 8, 4
CopyMemory pSA, ByVal ppSA, 4 ' Dereference.
'
If pSA = 0 Then Exit Sub ' No array.
'
CopyMemory sa, ByVal pSA, LenB(sa)
If sa.cDims = 0 Then Exit Sub ' No dimensions, not dimensioned.
'
ReDim sab(0 To sa.cDims - 1)
'
' Now we can get the remainder of the SAFEARRAY structure.
CopyMemory sab(0), ByVal (pSA + LenB(sa)), (sa.cDims * 8)
End Sub
Regards,
Elroy
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
-
Feb 12th, 2017, 05:28 PM
#9
Re: How to create (0 to -1) bounds byte array?
Ahhh, I didn't see your post. Ok, it looks like it does matter. I'll admit that I was a bit nervous about it. But it's all fixed now, either way.
EDIT1: Truth be told, I'm a bit surprised that a Redim Preserve works on these things, but I suppose that does make sense.
EDIT2: Yeah, I went ahead and used vbArray.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
-
May 14th, 2017, 11:04 AM
#10
Re: [RESOLVED] How to create (0 to -1) bounds byte array?
Are there a minimalistic code for Long() ? (like in 2-nd post)
I need Long(0 to -1).
-
May 14th, 2017, 12:07 PM
#11
Re: [RESOLVED] How to create (0 to -1) bounds byte array?
You can use SafeArrayCreateVector like in this thread to create empty arrays of any type, including private UDTs or classes like this
Code:
Private Declare Function EmptyBiff12PartArray Lib "oleaut32" Alias "SafeArrayCreateVector" (Optional ByVal VarType As VbVarType = vbObject, Optional ByVal Low As Long = 0, Optional ByVal Count As Long = 0) As cBiff12Part()
...
Dim aParts() As cBiff12Part
aParts = EmptyBiff12PartArray
cheers,
</wqw>
-
May 14th, 2017, 05:22 PM
#12
Re: [RESOLVED] How to create (0 to -1) bounds byte array?
-
May 14th, 2017, 07:57 PM
#13
Re: [RESOLVED] How to create (0 to -1) bounds byte array?
Sorry, wqweto. This is not a solution.
According to MSDN, SafeArrayCreateVector creates fixed array (FADF_FIXEDSIZE). Such array (0 to -1) is useless in this case.
After trying to 'redim' and save value, the program will crash.
-
May 15th, 2017, 01:35 AM
#14
Re: [RESOLVED] How to create (0 to -1) bounds byte array?
Why not use the code I've posted at the end of #5?
Olaf
-
May 15th, 2017, 02:49 AM
#15
Re: [RESOLVED] How to create (0 to -1) bounds byte array?
Originally Posted by Dragokas
Sorry, wqweto. This is not a solution.
According to MSDN, SafeArrayCreateVector creates fixed array (FADF_FIXEDSIZE). Such array (0 to -1) is useless in this case.
After trying to 'redim' and save value, the program will crash.
I just test this snippet
Code:
Option Explicit
Private Declare Function EmptyLongArray Lib "oleaut32" Alias "SafeArrayCreateVector" (Optional ByVal vt As VbVarType = vbLong, Optional ByVal lLow As Long = 0, Optional ByVal lCount As Long = 0) As Long()
Private Sub Form_Load()
Dim aTemp() As Long
aTemp = EmptyLongArray
ReDim Preserve aTemp(0 To 10) As Long
MsgBox aTemp(0)
End Sub
and it's working in the IDE and compiled.
Is this what you mean by 'redim' (with Preserve, like in 'resize')?
cheers,
</wqw>
-
May 15th, 2017, 09:49 AM
#16
Re: [RESOLVED] How to create (0 to -1) bounds byte array?
Your example with 'Long' doesn't cause crash (but I suspect that it can be unstable).
UDT - crash:
Code:
Option Explicit
Private Type cBiff12Part
l As Long
End Type
Private Declare Function EmptyBiff12PartArray Lib "oleaut32" Alias "SafeArrayCreateVector" (Optional ByVal VarType As VbVarType = vbObject, Optional ByVal Low As Long = 0, Optional ByVal Count As Long = 0) As cBiff12Part()
Private Sub Form_Load()
Dim aParts() As cBiff12Part
aParts = EmptyBiff12PartArray
ReDim Preserve aParts(1) As cBiff12Part
aParts(1).l = 1
End Sub
Originally Posted by Schmidt
Why not use the code I've posted at the end of #5?
Sorry, my inattentive, I forgot about it.
-
May 15th, 2017, 10:31 AM
#17
Re: [RESOLVED] How to create (0 to -1) bounds byte array?
@Dragokas: My sample `EmptyBiff12PartArray` was intended for `cBiff12Part` being a VB6 class. For an empty UDTs array as in your sample code, you have to tweak the signature to use `Optional ByVal VarType As VbVarType = vbUserDefinedType` to prevent crashes. Yes, mismatch on `VarType` param default value and API declare's retval type can certainly lead to catastrophic failures.
The funny thing in your mismatched snippet is that `ReDim` does not AV but calling `Erase` on this franken-array actually does.
cheers,
</wqw>
Tags for this Thread
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|