-
Re: CopyMemory Function
Well remember what we discussed previously. Arrays of objects and strings are pointers to pointers. So, for these, the VarPtr(array(n)) contains the location in memory where the StrPtr can be found:
CopyMemory lStrPtr, ByVal VarPtr(array(n)), 4&
lStrPtr then is equal to StrPtr(array(n))
So the array elements are the string pointers. The acutal location of the strings will almost never, but most likely always never, follow each other in memory. The actual string data will be sprinkled all about. The only thing that will follow each other in memory are the individual characters of the string, not the strings in the array.
-
Re: CopyMemory Function
Right, but if you could get different characters of a string to point to different pointers, you could still get that to work couldn't you?
-
Re: CopyMemory Function
Regarding your last statement. Characters never point to anything. A string is basically an array of bytes (2 bytes per character) that is prefixed by a Long identifying the string's length in bytes and suffixed by a pair of null terminating characters (vbNullChar). To combine characters from other strings, you'd need to create a new string of the desired length or use an existing string => the desired length, then copy characters from one string to the new string.
I think I misunderstood your original question. You don't want to join whole words together using some delimiter, you want to join characters or ranges of characters to a new string?
If so, I wouldn't suggest using copymemory at all. Rather use VB's built-in string functions of LenB/Len and Mid$(). For example
1. Build new string of desired length
2. Append the characters to the new string, the len in both must be identical
Mid$(newString, x, len) = Mid$(sourceString, y, len)
The Mid$() function is very fast.
-
Re: CopyMemory Function
No, you understood my original question correctly: it was just a dumb question. I see what you're saying though. A string just is the array of bytes, so talking about getting its different characters to point to different locations doesn't really any sense.
-
Re: CopyMemory Function
I'm sure you've got better things to be doing with your life than answering all my questions. But in case you have any thoughts and/or time on your hands: I raised the below a while back and didn't get any take up:
http://www.vbforums.com/showthread.php?t=575931
P.S. Is LenB fairly safe to use? I read somewhere that it "wasn't supported any more", whatever that means. Also, why would it be quicker? Whether you use Len or LenB, isn't it just a case of VB reading the field at the start of the string?
-
Re: CopyMemory Function
James, regarding your other post: never messed with Assistant balloons, so you know more about it than I do.
Regarding LenB, I read somewhere (maybe on these forums) that it wasn't supported in .Net, within a VB app (and I'd think VBA), it is good to go.
Faster? Sure, but it is minimal. The reason is simply this. To return the length of a string, VB goes to the StrPtr, then gets the 4 bytes prior to that pointer which is the length of the string in bytes (2 bytes per character): "LaVolpe" woud have 14 as those 4 bytes. So the return from the Len function must then divide by 2 before returning. One more calculation = a little more time. We are not talking earthshattering optimization here, just a matter of total cpu instructions performed.
-
Re: CopyMemory Function
Oh I see. It's probably not going to make a world of difference to my run times then! Still, I guess
Code:
If LenB(rCell.value) <> 0 Then
or perhaps just
Code:
If Not LenB(rCell.value) Then
will be quicker than, say,
Code:
If Len(r.Value) > 0 Then
or
Code:
If r.Value <> "" Then
if it's used a lot of times in a loop (which is often the case).
Regarding the balloon stuff, fair enough. Though I'm not sure the balloon aspect of it is that crucial to it. That is, suppose every time I created a shape, I wanted to colour it red and make it a particular size. It seems to me that I should be able to define a class called, say, cShape and then get VBA to do the colouring and sizing for me every time I create a new instance of it. But I'm not sure quite how. Am I barking up the right tree, or is this not really what classes are for?
-
Re: CopyMemory Function
I'd really appreciate some help/explanation on this one please. The story is as follows.
I wrote a procedure that would invert arrays for me. It accepts variant arrays and inverts them by swapping their 16-byte elements. I then (while doing something else) realised that a procedure declared to take variant input will happily accept an array of strings, and that I can determine whether such a thing has happened by looking at its VarType. If, for instance, I send a variant-taking proc an array of longs, its VarType is 8195 (i.e. 8192 + 3) as opposed to the 'normal' 8203 (8192 + 11). With this in mind, I thought I'd try to generalise my inversion proc to handle all sorts of different arrays as follows.
Code:
Public pLB1 as long, pUB1 as long
Public pLB2 as long, pUB2 as long
Public Sub InvArr(ByRef v As Variant, Optional L As Long = -1, _
Optional R As Long = -1)
Dim i As Long, j As Long
Dim nTmp(1 To 4) As Long
'Determine the number of bytes that need to be moved around
'by looking at the VarType of the parsed array
Dim nByt As Long
Select Case VarType(v)
Case 8195 To 8196, 8200 'i.e. long, single, or string
nByt = 4
Case 8197 To 8199 'i.e. double, currency, or date
nByt = 8
Case 8204
nByt = 16
End Select
'If the array is anything other than the above, exit the proc
If nByt = 0 Then Exit Sub
'Now do the inversion
SetBndVars v '<--See below.
If pLB2 = -1 Then 'v is a truly 1d array
If L = -1 Then L = pLB1
If R = -1 Then R = pUB1
For i = L To (L + R - 1) \ 2
CopyMemory nTmp(1), ByVal VarPtr(v(i)), nByt
CopyMemory ByVal VarPtr(v(i)), ByVal VarPtr(v(L + R - i)), nByt
CopyMemory ByVal VarPtr(v(L + R - i)), nTmp(1), nByt
Next i
ElseIf pLB1 <> pUB1 Then 'v is a 2d row
If L = 0 Then L = pLB2
If R = 0 Then R = pUB2
For i = L To (L + R - 1) \ 2
CopyMemory nTmp(1), ByVal VarPtr(v(1, i)), nByt
CopyMemory ByVal VarPtr(v(1, i)), _
ByVal VarPtr(v(1, L + R - i)), nByt
CopyMemory ByVal VarPtr(v(1, L + R - i)), nTmp(1), nByt
Next i
Else 'v is a 2d column or a truly 2d array
For j = pLB2 To pUB2
For i = L To (L + R - 1) \ 2
CopyMemory nTmp(1), ByVal VarPtr(v(i, j)), nByt
CopyMemory ByVal VarPtr(v(i, j)), _
ByVal VarPtr(v(L + R - i, j)), nByt
CopyMemory ByVal VarPtr(v(L + R - i, j)), nTmp(1), nByt
Next i
Next j
End If
End Sub
Sub SetBndVars(v As Variant)
pLB2 = -1: pUB2 = -1
pLB1 = LBound(v, 1): pUB1 = UBound(v, 1)
On Error Resume Next
pLB2 = LBound(v, 2): pUB2 = UBound(v, 2)
End Sub
The result? Weirdness. Or at least weirdness to someone who doesn't understand what's going on. When I pass it an array of variants, all is well, e.g.
Code:
Sub Trial()
Dim v() As Variant, i As Long
ReDim v(1 To 10): For i = 1 To 10: v(i) = i: Next i
InvArr v
End Sub
When I pass it an array of longs, however, the proc identifies it as such but then whatever it does with it makes no difference to what it's been passed. Take, for instance, the following:
Code:
Sub Trial()
Dim l() As Long, i As Long
ReDim l(1 To 10): For i = 1 To 10: l(i) = i: Next i
InvArr l
End Sub
In this case, the InvArr proc passes a value of 16387 to nTmp(1) for each and every element of the array and then, when it swaps it, it doesn't make any difference. I was expecting
Code:
CopyMemory nTmp(1), ByVal VarPtr(v(i)), nByt
to pass nTmp a long which was equal to the value of v(i), i.e. 1. What's going on here?
-
Re: CopyMemory Function
Not a player in your issue, but just FYI. So you know what the vartype is returning.
Code:
If (VarType(v) And vbArray) Then ' v contains an array
Select Case (VarType(v) And Not vbArray)
Case vbVariant ' array of variants (16 bytes)
Case vbString ' array of dynamic strings (4 bytes)
Case vbLong, vbSingle ' (4 bytes)
Case vbInteger, vbBoolean ' (2 bytes)
Case vbDate, vbDouble, vbCurrency ' (8 bytes)
Case vbByte ' (1 byte)
' etc
End Select
End If
Now here is why you are getting bad results. Change variable t below from String, to Long, to Variant and see what the results are, you will be initially confused:
Code:
Option Explicit
Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)
' UDT is for FYI only, not used in examples
Private Type SAFEARRAYBOUND
cElements As Long
lLbound As Long
End Type
Private Type SafeArray ' FYI only, http://msdn.microsoft.com/en-us/library/ms221482.aspx
cDims As Integer ' array ptr +0: nr of dimensions
fFeatures As Integer ' +2: see link
cbElements As Long ' +4: how many bytes per array item
cLocks As Long ' +8: whether array is locked or not
pvData As Long ' +12: pointer to first element in the array
rgSABound() As SAFEARRAYBOUND ' +16: pointer to last dimension's count & LBound structure
End Type ' stored in right to left order shown in VB's ReDim statement
Private Sub Command1_Click()
Dim t() As String, X As Long
ReDim t(0 To 3)
Debug.Print "Real ptrs: ";
For X = LBound(t) To UBound(t)
Debug.Print VarPtr(t(X));
Next: Debug.Print
TestPointers t
GetRealPointer t
End Sub
Private Sub TestPointers(v As Variant)
Dim X As Long
Debug.Print "Refd ptrs: ";
For X = LBound(v) To UBound(v)
Debug.Print VarPtr(v(X));
Next: Debug.Print
End Sub
Private Sub GetRealPointer(v As Variant)
Dim X As Long, vType As Integer, ptr As Long
Const VT_BYREF As Long = &H4000&
If (VarType(v) And vbArray) = 0 Then Exit Sub ' just testing arrays here
' fyi, the 1st 2 bytes of the variant identifies its type
CopyMemory vType, ByVal VarPtr(v), 2
Debug.Print "Variant contains VarType of "; (vType And Not VT_BYREF); VarType(v)
' the next 8 bytes after the varptr is a pointer to the array structure pointer
' unless the vType above contains VT_ByRef, in which case those next
' 8 bytes are a pointer to a pointer....
' FYI: For non-array contents, those 8 bytes are...
' :: Strings - the StrPtr to the string
' :: Objects - the ObjPtr to the object
' :: Numerical values - the actual value
CopyMemory ptr, ByVal VarPtr(v) + 8&, 4& ' get pointer in the variant
If (vType And VT_BYREF) Then CopyMemory ptr, ByVal ptr, 4& ' get pointer to array structure
If ptr Then ' safety check in case null array was passed
CopyMemory X, ByVal ptr + 12&, 4& ' get pointer to first item in array
Debug.Print "Actual VarPtr to 1st array item is "; X
Else
Debug.Print "Passed a null array, don't do that"
End If
End Sub
You will see that when v contains non-variants, VarPtr(v(x)) always returns the same pointer -- the cause of your results. But for variants, VarPtr returns what is expected. Don't ask me why an array of variants works in your example, but an array of anything else does not. I don't know, but my guess is it has something to do with VB converting from other data types to Variant, storing to a temporary variable the value of that array item, and passing you the VarPtr of that temp variable vs the pointer to the actual array item. Just guesses.
So, how does the above work for you? You'd have to modify your routine to not use VarPtr obviously, but extract the actual pointer to the 1st array item, then use addition to move the pointer along, 1, 2, 4, 8, 16 bytes at a time as needed. If you are always passing 1D arrays, you can still use UBound/LBound to calc the size of the array.
Edited: Curiosity got me. My guess was close... The VarPtr returned via VarPtr(v(x)) for non-variant arrays, is a pointer to another variant variable (kept by VB). That variant variable has a vType of: VT_ByRef & VarType(v(x)). The long value at the 8th byte is the actual array's item VarPtr.
-
Re: CopyMemory Function
So I've stepped through your code about 10 or 15 times and I think I'm getting there. But there are still huge gaps in my background knowledge, so please bear with me.
Basically, when you pass an array of, say, longs to a procedure that accepts variants, it gets accepted, not as an array of longs nor as an array of variants, but as an array of longs with some stuff at the start and the end, right?
This stuff at the start tells VBA what the array consists of, which is what your &H4000& refers to, yes? But &H4000& means nothing to me. Is this a number of some kind?
P.S. I worked out that 16387 is 2^14 + 3, 3 being the VarType of a long. But why this would be the case, and why everything seems to be connected to powers of 2 when it comes to computers, is beyond me.
-
Re: CopyMemory Function
The variant is 16 bytes, I don't recall how each byte is used -- google if interested.
But the 1st 2 bytes is the VarType of the variant. The VT_BYREF flag added onto that byte indicates that variant doesn't hold the value, but holds a pointer to the value/object.
The 8th-16th byte (8 bytes total) are used for the value within the variant. If the variant is VT_BYREF or contains a string/object, then those bytes will be a pointer. That is the jist of a variant.
An array is encompassed by a SafeArray structure in VB. That structure is used to define the array and a pointer in memory where the first array element can be found. That pointer is at the 12th byte in the structure.
So, when passing a Long array to your routine that has a Variant parameter ByRef, the variant contains a pointer to a pointer to the SafeArray structure. The VarType of that variant will be vbArray + vbLong + VT_BYREF. If the Variant was passed ByVal then the VT_BYREF would not be included, but that is a slightly different scenario.
Now VB handles Variants its own way as we discovered. VarPtr(v(x)) does not return the same value as VarPtr(PassedArray(x)) if the array is non-variant. So, knowing the structure of a Variant, the fact it "contains" an array, we can eventually get the pointer to the safearray structure, then get the pointer to the array's first item from the 12th byte of that structure and the gaps between pointers from the 4th byte. With that information, we can navigate the array.
The variant is simply a container that describes the passed array.
Regarding powers of 2. You might want to read up on bit shifting and CPUs/math processors. Bit shifting allows a single value to hold several "flags", each a power of 2 or a combination of them, that can be extracted/examined very quickly.
P.S. 16387 = 16384 (VT_BYREF) + 3 (vbLong) indicates variant holds a pointer to a Long value.
24579 = 16384 (VT_BYREF) + 8192 (vbArray) + 3 (vbLong) indicates variant holds a pointer to an array structure and the array will be Longs.
FYI: vbArray = 8192 = 2^13 = &H2000, VT_BYREF = 16387 = 2^14 = &H4000
Also, Remember the VT_BYREF won't always be in the variant.
-
Re: CopyMemory Function
Thanks. Where am I going wrong on the below then? (I'm trying to follow through your example). I'll include my thinking in comments.
Code:
Sub Test()
'Define some array of longs
Dim Lng(1 To 3) As Long
Lng(1) = 10
Lng(2) = 20
Lng(3) = 30
'and parse it to a variant-taking procedure
Parse Lng
End Sub
Sub Parse(ByRef v As Variant)
Dim n1 As Long, n2 As Long, n3 As Long
'Let n1 be the location of the pointer in the variant:
CopyMemory n1, ByVal VarPtr(v) + 8&, 4&
'Now find out where that pointer is pointing to:
CopyMemory n1, ByVal n1, 4&
'It's pointing to an array, the 12th byte of which is where my longs start,
'So let n2 be this value
CopyMemory n2, ByVal n1 + 12&, 4&
'Now copy the value that n2 is pointing to to n3
CopyMemory ByVal VarPtr(n3), n2, 4&
End Sub
-
Re: CopyMemory Function
n2 is a memory location. Use ByVal n2 in your final CopyMemory call.
The next array item would be n2+4, or n2=n2+4 whichever your prefer
Edited: Actually, a little tweak: CopyMemory n3, ByVal n2, 4&
Using CopyMemory ByVal VarPtr(n3), ... is not incorrect, just a bit more than needed.
-
Re: CopyMemory Function
Magic. Thank you. Though doubtless I'll have other questions soon!
-
Re: CopyMemory Function
It'd be useful for me (it would reduce the number of cases I have to consider) if I could treat a double as two longs. Am I going about it wrong, or is it just a no-no?
(The below, by the way, will crash Excel so probably best not to run it!)
Code:
Sub Dbls()
Dim Dbl As Double, Lng(1 To 2) As Long
Dbl = 4.66667
CopyMemory Lng(1), ByVal VarPtr(Dbl), 8&
ZeroMemory ByVal VarPtr(Dbl), 8&
CopyMemory Dbl, ByVal Lng(1), 8&
End Sub
-
Re: CopyMemory Function
The below isn't quite as handy (but still better than nothing). Is this a crash waiting to happen and I've been lucky so far, or is it legitimate?
Code:
Sub Curs()
Dim Cur As Currency, Lng(1 To 2) As Long
Lng(1) = 4
Lng(2) = 8
CopyMemory Cur, ByVal VarPtr(Lng(1)), 8&
ZeroMemory ByVal VarPtr(Lng(1)), 8&
CopyMemory ByVal VarPtr(Lng(1)), Cur, 8&
End Sub
-
Re: CopyMemory Function
In post 55 above, the crash is because of the middle parameter.
You are using ByVal Lng(1); but you should not be using ByVal.
Assuming copymemory 1st 2 parameters are As Any....
Remember: Use ByVal when the variable references a memory address or pointer to memory other than that of the variable
Remember: Don't use ByVal when the variable's actual content/memory location is to be used
So, CopyMemory Dbl, ByVal Lng(1), 8& is incorrect since you want the actual memory location of Lng(1). The way you have it, ByVal will go to the memory address indicated in Lng(1)'s value which is why you are crashing -- that memory address doesn't exist, is uninitialized, or is protected.
Either: CopyMemory Dbl, Lng(1), 8& or
CopyMemory Dbl, ByVal VarPtr(Lng(1)), 8&
P.S. Instead of an array of longs, you can use byte array which can be sized to whatever you need
Code:
Dim tBytes() As Byte
ReDim tBytes(0 to 7) ' doubles, currency, dates
ReDim tBytes(0 to 3) ' longs, singles, pointers
etc
Regarding Post #56, I don't see anything dangerous, though you don't need the ZeroMemory. Simply: Erase Lng()
Edited: Reworded the "Remember" statements above. Hope it is a bit more clear.
-
Re: CopyMemory Function
I'm with you. A quick thought though: Given what you said earlier (and linked to) concerning the various flags in a safe array, it seems like transposing a 'vertical' into a 'horizontal' array should be very easy, that it should just require us to tweak a few setting if we know how. Is that right? Or is it hopelessly naive/optimistic?
-
Re: CopyMemory Function
Theoretically yes. However, it isn't that simple. There is neat hack I can show you that you can use, but first the issue and why it won't work.
The safearray defines the array. In a 2D array, after the safearray member that identifies the pointer to the 1st array element follows two 8-byte values defining the size & LBound of each dimension (we'll call these the bounds description(s)).
To convert 2D to 1D (which I think is what you are referring to when saying transposing), the safearray structure's nr dimensions needs to be changed, no biggie, but also its first 8-byte bounds description; again no biggie. However, what happens to the 2nd 8 byte description if the array is released? Will it be cleaned up or leaked -- don't, know never tried it.
To convert 1D to 2D is even harder. Since the 1D has only one 8-byte bounds description, there is no guarantee there is another 8 bytes of free memory directly after that where another, new bounds description can be written. If not and attempted - crash.
Not only the hurdles above, but if code is already referencing the array somewhere, I'm sure it will crash after the transposition.
Here is the hack I promised. NOTE THAT THIS IS ONLY SAFE in the immediate routine it is used in. A new API will be referenced here, paste & copy to new form for testing. This will not work for arrays containing strings since VarPtrArray api does not work with string arrays.
Note that you can change values while this hack is in play. You cannot resize the array. This hack allows you to view/modify the array in a different way, but is only temporary. It is possible to make it permanent, but I wouldn't recommend it -- too many things can go wrong. Best to hack, view/edit, unhack.
Add 2 command buttons & paste this.
Code:
Option Explicit
Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)
Private Declare Function VarPtrArray Lib "msvbvm60.dll" Alias "VarPtr" (ByRef Ptr() As Any) As Long
Private Type SAFEARRAYBOUND
cElements As Long
lLbound As Long
End Type
Private Type SafeArray ' http://msdn.microsoft.com/en-us/library/ms221482.aspx
cDims As Integer ' array ptr +0: nr of dimensions
fFeatures As Integer ' +2: see link
cbElements As Long ' +4: how many bytes per array item
cLocks As Long ' +8: whether array is locked or not
pvData As Long ' +12: pointer to first element in the array
rgSABound(0 To 1) As SAFEARRAYBOUND '+16: pointer to last dimension's count & LBound structure
End Type ' stored in right to left order shown in VB's ReDim statement
Private Sub Command1_Click()
' 1D to 2D array re-referencing
Dim a() As Long, t() As Long
Dim SA As SafeArray
Dim x As Long, y As Long
ReDim a(1 To 200) ' size & populate actual data
For x = 1 To 200
a(x) = x
Next
For x = 1 To 10 ' print 1st 10 elemnents
Debug.Print a(x);
Next: Debug.Print
' ok now let's say we want to convert it to a 2D array
' like this: (1 to 10, 1 to 20)
' 10 columns, 20 rows
With SA ' define new array description
.cbElements = 4 ' 4 bytes per array item
.cDims = 2 ' 2D array
.pvData = VarPtr(a(1)) ' location of 1st array element
.rgSABound(0).cElements = 20 ' 20 rows
.rgSABound(0).lLbound = 1
.rgSABound(1).cElements = 10 ' 10 columns
.rgSABound(1).lLbound = 1
End With
CopyMemory ByVal VarPtrArray(t()), VarPtr(SA), 4& ' set overlay
' now we can reference the same array a different way.
' Note that t() vartype must be same as a() vartype
For x = 1 To 10 ' print 1st 10 elements of row 1
Debug.Print t(x, 1);
Next: Debug.Print
CopyMemory ByVal VarPtrArray(t()), 0&, 4& ' release overlay
End Sub
Private Sub Command2_Click()
' 2D to 1D referencing
Dim a() As Long, t() As Long
Dim SA As SafeArray
Dim x As Long, y As Long, c As Long
ReDim a(1 To 10, 1 To 20) ' size & populate actual data
For y = 1 To 20
For x = 1 To 10
c = c + 1
a(x, y) = c
Next
Next
For x = 1 To 10 ' print 1st 10 elemnents
Debug.Print a(x, 1);
Next: Debug.Print
' ok now let's say we want to convert it to a 1D array
' like this: (1 to 200)
With SA ' define new array description
.cbElements = 4 ' 4 bytes per array item
.cDims = 1 ' 1D array
.pvData = VarPtr(a(1, 1)) ' location of 1st array element
.rgSABound(0).cElements = 200 ' 200 items
.rgSABound(0).lLbound = 1
End With
CopyMemory ByVal VarPtrArray(t()), VarPtr(SA), 4& ' set overlay
' now we can reference the same array a different way.
' Note that t() vartype must be same as a() vartype
For x = 1 To 10 ' print 1st 10 elements of row 1
Debug.Print t(x);
Next: Debug.Print
CopyMemory ByVal VarPtrArray(t()), 0&, 4& ' release overlay
End Sub
Edited: The above hack works by creating a SafeArray structure and copying it over the unitialized array t's SafeArray structure (which there isn't one). Since in the SafeArray we are saying that the first array element is the same as pointer as the 1st array element of a(), both t() and a() are pointing at the same memory now. Again, do not resize or erase either array until after the hack has been removed.
-
Re: CopyMemory Function
You're unbelievable! Did you write this just now? Either way, the short cut I was after was actually much simpler (and so hopefully easier). It was just turning an "column" of the form (1, 1), (2, 1), (3, 1), etc, into a truly 1D array (1, 2, 3, 4, etc) and vice versa. Does that make sense?
-
Re: CopyMemory Function
Yeah, you're quite right. Trying to do anything but view is difficult. I've been experiencing some pretty nasty crashes.
-
Re: CopyMemory Function
You should be able to completely update and view the array contents in either array. If you are experiencing crashes, ensure you have the dimension bound descriptions correct. I purposely showed an example where the rows & columns were different values so you can see in what order the safearray members need to be added. Also note any other restrictions/warnings I mentioned in that previous post.
I have used these hacks for years. As an example, I have a routine that can receive an array of bytes, any LBound, any dimensions. I then hack the array to create an overlay that is zero bound, 1 or 2 dims as needed, to make looping easier and not dependent on the user supplied LBounds/dimensions. After updating the array, I simply remove the hack.
-
Re: CopyMemory Function
Yes, sorry I wasn't clear enough. They were brilliant for manipulation, copying, etc. I even managed a simple sort. But then I tried to use it as part of a function, and it didn't like it. You did warn me...
It's a terrible shame these don't work for strings by the way. Why is that? I know the elements are pointers in that case. But why should that make a difference?
-
Re: CopyMemory Function
Oddly, they work for variant strings, i.e. arrays of variants carrying strings
-
Re: CopyMemory Function
You can pass either array to a function. No crashes should occur and can't stess enough, the array cannot be resized while hacked.
Regarding strings.... VarPtrArray doesn't work with strings, but like all things there is almost always a workaround and here it is for strings.
More warnings.... This is about the only exception where the VarType of the two arrays will not be the same. One is String and the other is Long. Also, because the t array are longs, you cannot view the string contents. So t(1) = StrPtr(s(x,y)), but if needed for array manipulation, that is possible as shown in the example, swapping two strings.
Code:
Dim s() As String, t() As Long
Dim SA As SafeArray, ptr As Long
ReDim s(0 To 1, 0 To 4)
s(0, 0) = "LaVolpe"
s(1, 0) = "Visual Basic"
Debug.Print s(0, 0), s(1, 0)
' convert 2D string to 1D Long
With SA
.cbElements = 4
.cDims = 1
.pvData = VarPtr(s(0, 0))
.rgSABound(0).cElements = 10
.rgSABound(0).lLbound = 1
End With
CopyMemory ByVal VarPtrArray(t()), VarPtr(SA), 4&
ptr = t(2)
t(2) = t(1)
t(1) = ptr
CopyMemory ByVal VarPtrArray(t()), 0&, 4&
Debug.Print s(0, 0), s(1, 0)
Warning as usual: Memory hacks are always crash-potential if not done exactly correct.
-
Re: CopyMemory Function
You're a legend. I'm going to spend a bit of time looking at this tomorrow. But I get the feeling I'm going to use it a huge amount in some of the general utility stuff I write. Thank you so much for sharing it. The only thing I need to do to complete the picture is get it to work for a string array pointed to by a variant, like the ones we were talking about earlier. I thought I might be able to do this by passing the VarPtrArray function altogether. This was my attempt:
Code:
Sub Test()
'Define an array of doubles
Dim Dbl(1 To 4, 1 To 1) As Double
Dbl(1, 1) = 545.5432552
Dbl(2, 1) = 89042.42
Dbl(3, 1) = 545.5432552
Dbl(4, 1) = 1
'And parse it to a variant-taking procedure
Parse Dbl
End Sub
Sub Parse(v As Variant)
Dim vPtr As Long
'Let vPtr be the location of the [long] pointer in the variant
CopyMemory vPtr, ByVal VarPtr(v) + 8&, 4&
'Find out where that pointer is pointing to:
CopyMemory vPtr, ByVal vPtr, 4&
'...and this is where we put the overlay, so
Dim SA As SafeArray
With SA 'define new array description
.cbElements = 8 'bytes per array item
.cDims = 1 '1D array
.pvData = vPtr + 12 'location of 1st array element
.rgSABound(0).cElements = 200 ' 200 items
.rgSABound(0).lLbound = 1
End With
CopyMemory vPtr, VarPtr(SA), 4& ' set overlay
'Do funky stuff...
CopyMemory vPtr, 0&, 4& ' release overlay
End Sub
but it didn't actually do anything.
-
Re: CopyMemory Function
You're lucky it didn't crash... You need an uninitialized array for the hack to work, you are missing that...
Code:
Sub Parse(v As Variant)
Dim tArray() As Double ' < added, same VarType as passed array
Dim vPtr As Long
'Let vPtr be the location of the [long] pointer in the variant
CopyMemory vPtr, ByVal VarPtr(v) + 8&, 4&
'Find out where that pointer is pointing to:
CopyMemory vPtr, ByVal vPtr, 4&
'...and this is where we put the overlay, so
Dim SA As SafeArray
With SA 'define new array description
.cbElements = 8 'bytes per array item
.cDims = 1 '1D array
' .pvData = vPtr + 12 'location of 1st array element
CopyMemory .pvData, ByVal vPtr+12, 4&
.rgSABound(0).cElements = 200 ' << don't default to 200, it must be same as total elements in passed array
.rgSABound(0).lLbound = 1
End With
' >>> fixed the CopyMemory calls
CopyMemory ByVal VarPtrArray(tArray), VarPtr(SA), 4& ' set overlay
'Do funky stuff... with tArray
CopyMemory ByVal VarPtrArray(tArray), 0&, 4& ' release overlay
End Sub
Think of VarPtrArray as VarPtr for arrays. Really, that's all it is.
Edited: oops, you had .pvData incorrect, fixed now.
-
Re: CopyMemory Function
Right, right. By the way, I can't quite work out why it's not problematic to have two variables--i.e. the start of the t() and a() arrays--pointing to the same bit of memory. Isn't that a bit dodgy?
-
Re: CopyMemory Function
It is very dodgy, same problem if you had 2 variables pointing to the same string for the same reasons: If one gets erased/released, the other is now pointing to unused memory and when code tries to access the array item -- access violation. That is why the hack should be employed when needed and removed immediately when done.
-
Re: CopyMemory Function
So why not just save it in a temporary variable somewhere, ZeroMemory it after copying it across to t(), and then replace it when you're done (zeroing t)?
-
Re: CopyMemory Function
Why? 1 reason: speed. There is no equal-sized array created, no CopyMemory of xxx bytes per array element.
If speed isn't a concern, the safest way is to copy the array using NewArray()=OldArray(). But that doesn't support transposing arrays, so the safest is to create a new array with the desired dimensions and loop thru the OldArray, one item at a time and adding it to the NewArray().
But if different bounds are needed (transposing) and looping is undesirable, then copying with CopyMemory as we discussed in previous posts is probably the ticket. Of course if the array items were pointers (i.e., string/object arrays or variant arrays that contain strings/objects), you'd have to use ZeroMemory else you could simply Erase the array.
-
Re: CopyMemory Function
Sorry, that wasn't quite what I meant. I meant: isn't it dodgy to have just the 4 bytes copied in the following line
Code:
CopyMemory ByVal VarPtrArray(tArray), VarPtr(SA), 4& ' set overlay
shared? If so, why not ZeroMemory the first 4 bytes of a() and then put them back afterwards. Wouldn't that make the whole thing a bit more stable? I guess not or someone would have done it. But it seems like it would.
-
Re: CopyMemory Function
No, here you are messing with pointers not 4 bytes from the SA structure. The 4 bytes (Long) is the pointer to the SA variable in your code. The VarPtrArray of the Array points to a SafeArray structure. An unitialized array has a VarPtrArray value of zero, much like a null string has a StrPtr of zero. What that line is doing is overwriting the zero pointer with a pointer to the SA structure in the code. Therefore, VB thinks the array is initialized. As long as hack is in play, the uninitialized array acts like a sized, filled array.
Since the array is not really initialized and it definitely contains no data. When the hack is removed the zero is reapplied to the VarPtrArray location and VB now thinks the array is uninitialized and won't try to clear the array later.
-
Re: CopyMemory Function
Hi again. I think I know what you mean. What I was trying to suggest was: why not do it like this?
Code:
Option Explicit
Private Type SABnd
nElmts As Long
nLBnd As Long
End Type
Private Type SafeArray 'http://msdn.microsoft.com/en-us/library/ms221482.aspx
nDims As Integer 'ArrayPtr +0: number of dimensions
fFlags As Integer ' +2: see link
nBytes As Long ' +4: number of bytes per array item
nLocks As Long ' +8: whether or not array is locked
nPtr As Long ' +12: pointer to first element in array
nBnds(0 To 1) As SABnd ' +16: pointer to last dim's count & LBound structure
End Type ' stored in right to left order
Sub faf()
Dim a() As Double, t() As Double
Dim SA As SafeArray
Dim x As Long, y As Long, c As Long
ReDim a(1 To 200, 1 To 1) ' size & populate actual data
For x = 1 To 200
c = c + 1
a(x, 1) = c * 2
Next x
With SA 'define new array description
.nBytes = 8 '4 bytes per array item
.nDims = 1 'Make 1D array
.nPtr = VarPtr(a(1, 1)) 'location of 1st array element
.nBnds(0).nElmts = 200 '200 items
.nBnds(0).nLBnd = 1 'LBound
End With
Dim Tmp As Long
'Store a's settings in Tmp
CopyMemory Tmp, ByVal VarPtrArray(a()), 4&
'Now zero a's and overlay t's
ZeroMemory ByVal VarPtrArray(a()), 4&
CopyMemory ByVal VarPtrArray(t()), VarPtr(SA), 4&
'Do all sorts of stuff with t here
'.................................
'Now zero t's and restore a's
ZeroMemory ByVal VarPtrArray(t()), 4&
CopyMemory ByVal VarPtrArray(a()), Tmp, 4&
ZeroMemory Tmp, 4&
End Sub
Isn't this safer? Or have I done something stupid?
-
Re: CopyMemory Function
You can experiment, but I'd recommend against trying to swap SafeArray pointers. The SafeArray structure is more complicated than that. Depending on what the array contains, there can also be up to 16 bytes in front of that structure that are used to further describe the array & depends on the Features/Flags member of the structure. Might want to dig deeper into the link I provided.
And ZeroMemory does not release memory, it only fills the memory with zeros. So, you'd be leaking the bytes used by the structure.
-
Re: CopyMemory Function
Something else: How would you go about using some of the flags described here?
I don't understand the codes they use, e.g. FADF_BSTR 0x0100
-
Re: CopyMemory Function
The 0x0100 is a hex value, in vb it is &H100. So you could delcare:
Const FADF_BSTR As Long = &H100.
Flags are combined by using OR. X Or Y Or Z
A specific flag can be extracted to see if it exists:
If (Flags And FADF_BSTR) = FADF_BSTR then the flag is set else it is not.
Here is a project I wrote on PSC that attempted to help people better understand safearrays.
-
Re: CopyMemory Function
Wow. I shall read this with great interest. Thanks. By the way, if I can work out hnow to apply it, can that BSTR flag be used to help with the string conversion you coded for me above (where t() is set to longs)? Finally, what's "leaking" exactly?
-
Re: CopyMemory Function
The string example? Not sure, never attempted it. Strings/Objects are different obviously because of the pointers. Might consider handling arrays of pointers using previous methods of copying the array.
^^ Edited: I doubt you can do it. I told the safearray structure it contained strings by using the correct flags and also added the required data in the 1st 4 bytes before the SA structure. Since the overlay was declared as Long, vb still returns the strptr, not the string. Unfortunately, I know of no way to get the VarPtrArray of an uninitialized String array. I only know how to get the SA pointer for an initialized array, but the overlay requires the pointer to that pointer which is what VarPtrArray returns. I do recall that it is possible by creating a typelib -- saw it once on MSDN. At that point, I personally decided, it wasn't worth the trouble. I only mention it so you have some ideas of where to search if you get super curious.
Leaking. Ignore that, I misread what your example was doing.
Having 2 arrays pointing to the same .pvData is dodgy only if you resize either array. BTW: Using ZeroMemory for 4 bytes is same as CopyMemory dest, 0&, 4&
What you are doing isn't incorrect, but really not needed. No point in storing the value of VarPtrArray(a()), overwriting zero, then restoring the pointer. If your code attempted to access a() afterwards, you'd get an error because VB thinks the array is uninitialized. If both arrays are referencing the same data, you wouldn't get the error.
One more note that I made in previous posts. I strongly recommended keeping the VarTypes of both arrays the same. That is a good suggestion, however, there is one exception that works well for most VarTypes. If the overlay array is declared as Byte, then it becomes more or less universal. Here is another real-world example. In many projects I process pixel data from images. Sometimes the pixel data is an array of longs and sometimes bytes, depending on the need. Well if the array is Longs and I sent that array to a function that requires a byte array, I hack the long array, overlay with a byte array, do the manipulation needed, then remove the hack.
-
Re: CopyMemory Function
Right. I might have a quick go at doing everything with Byte arrays later then--once I've got things working with specific ones. By the way, how do you suggest best implementing all this? Suppose I've got a load of functions that work on 1d arrays. Obviously I don't want to put all sorts of overlay code at the start and end of each of them. So how best to implement this? A function?