|
-
Jun 11th, 2009, 03:20 PM
#1
Thread Starter
Lively Member
CopyMemory Function
Hi All. I'm not convinced this is quite the right place for me to post. But I put this question in an Excel forum and no-one replied, so hopefully someone here will be able to help me out.
(By way of background, I've had to learn to use VBA as part of my job recently. I've therefore got about 6 months VBA experience and am not quite sure what VB is to be honest. So, if you could put things in idiot language, it would be much appreicated).
Anyway, I recently came across this article and, stupidly, thought I'd try to incorporate it into a sorting algorithm I've written. I can get it to work with strings like this:
Code:
Option Explicit
Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
(lpDest As Any, lpSource As Any, ByVal cbCopy As Long)
Sub Trial()
Dim s() As String
ReDim s(0 To 2) As String
s(0) = "Jack": s(1) = "Jill": s(2) = "John"
Swap s, 0, 2
End Sub
Sub Swap(ByRef s() As String, a As Long, b As Long)
Dim t As Long
CopyMemory t, ByVal VarPtr(s(a)), 4
CopyMemory ByVal VarPtr(s(a)), ByVal VarPtr(s(b)), 4
CopyMemory ByVal VarPtr(s(b)), t, 4
End Sub
But I'd like to get it to work with variant arrays as well so I can manipulate data I've imported from Excel sheets. I tried it like this (I heard somewhere that you needed to use a 16 rather than a 4 for variants):
Code:
Option Explicit
Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
(lpDest As Any, lpSource As Any, ByVal cbCopy As Long)
Sub Trial()
Dim v() As Variant
v = Array("Jack", "Jill", "John")
Swap v, 0, 2
End Sub
Sub Swap(ByRef v() As Variant, a As Long, b As Long)
Dim t As Long
CopyMemory t, ByVal VarPtr(v(a)), 16
CopyMemory ByVal VarPtr(v(a)), ByVal VarPtr(v(b)), 16
CopyMemory ByVal VarPtr(v(b)), t, 16
End Sub
but to no avail. The Swap routine swaps the variables alright, but when returning to the main routine VBA tells me I've used a bad dll convention.
Last edited by James_B; Jun 11th, 2009 at 04:28 PM.
-
Jun 11th, 2009, 09:22 PM
#2
Re: CopyMemory Function
For your string example, you do realize CopyMemory is not required. Though the advantage of using CopyMemory in this case is that it is overall faster vs having VB create strings.
Code:
Sub Swap(ByRef s() As String, a As Long, b As Long)
Dim t As String
t = s(a)
s(a) = s(b)
s(b) = t
End Sub
Your Variant routine fails because you are trying to copy 16 bytes of data to "t" which is a Long, only 4 bytes. You're lucky you didn't crash. You need to change "t" to a 16 byte object -- but don't use Variant, recommend using a Long array:
Code:
Sub Swap(ByRef v() As Variant, a As Long, b As Long)
Dim t(0 To 3) As Long ' 4 Longs * 4 bytes each = 16 bytes
CopyMemory t(0), ByVal VarPtr(v(a)), 16
CopyMemory ByVal VarPtr(v(a)), ByVal VarPtr(v(b)), 16
CopyMemory ByVal VarPtr(v(b)), t(0), 16
End Sub
CopyMemory, when used incorrectly is a crash waiting to happen.
P.S. Just as the first example can use simple VB commands and not CopyMemory, so can the second, Variant, example.
-
Jun 12th, 2009, 07:11 PM
#3
Thread Starter
Lively Member
Re: CopyMemory Function
Awesome. You're a star. Thank you kindly. I''ll try this out as soon as I can (I don't have Excel at home).
 Originally Posted by LaVolpe
For your string example, you do realize CopyMemory is not required. Though the advantage of using CopyMemory in this case is that it is overall faster vs having VB create strings.
Right, I know the easy way; I was just looking to speed up my sorting (and general handling) a bit. Speaking of which: presumably I could use the same idea as part of an insertion sort?--something like
Code:
Option Explicit
Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
(lpDest As Any, lpSource As Any, ByVal cbCopy As Long)
Sub Trial()
Dim v() As Variant
v = Array("Andrew", "Jill", "John", "Bill")
InsertBefore v, 3, 1
End Sub
Sub InsertBefore(ByRef v() As Variant, b As Long, a As Long)
Dim t(0 to 3) As Long
CopyMemory t(0), ByVal VarPtr(v(b)), 16
CopyMemory ByVal VarPtr(v(a + 1)), ByVal VarPtr(v(a)), 16 * (b - a)
CopyMemory ByVal VarPtr(v(a)), t(0), 16
End Sub
Would this work?
Last edited by James_B; Jun 12th, 2009 at 07:15 PM.
-
Jun 12th, 2009, 07:32 PM
#4
Re: CopyMemory Function
Looks fine. Don't pass b less than a though, you'd get a negative value in (b - a). Also ensure a is never the UBound of the array.
-
Jun 12th, 2009, 07:44 PM
#5
Thread Starter
Lively Member
Re: CopyMemory Function
Many thanks. Just to be clear on what I'm doing here, then: A variant array is, in effect, an array of 16-byte pointers that point to different locations in my computer's RAM? And each element follows on from the next without gaps? So, instead of putting a(n + 1) = a(n) in a loop somewhere, I can just redirect a whole chain of pointers? Is this vaguely what I'm doing? (As you'll gather, I'm fairly new to this. I recorded my first macro 6 months ago, and opened my first spreadsheet only a few months before that).
Another question (if I may): What about the ZeroMemory function? When might I want to use that, and why? (I ask because I came across it in this article).
-
Jun 12th, 2009, 08:00 PM
#6
Re: CopyMemory Function
I'll answer zeromemory first. It simply writes zeroes to memory. Let's say you have an array of Longs and you want to zero them out. There are 3 ways this can be done:
1. ReDim myArray(100)
2. Loop thru each array item and set each to zero
3. ZeroMemory myArray(0), 101* 4 ' 101 because array is really 0 to 100
Don't get confused with VB arrays, strings, variants, longs, and others can be handled differently. First, arrays are contiguous memory. Arrays are never split into different memory addresses; every array item follows the previous array item, directly, in memory.
1. Variable length strings: Each array item is a pointer, a long value, 4 bytes. The 2nd array item can be found 4 bytes after the 1st array item.
2. Long, Singles: Each array item is a value, 4 bytes. The 2nd array item can be found 4 bytes after the 1st array item.
3. Integer, Boolean: Each array item is a value, 2 bytes. The 2nd array item can be found 2 bytes after the 1st array item.
4. Bytes: Each array item is a value: The 2nd array item can be found 1 byte after the 1st array item.
5. Double, Currency, Date: Each array item is a value, 8 bytes. The 2nd array item can be found 8 bytes after the 1st array item.
6. Variant. Each array item can be many things, variants after all. However, variants are 16 bytes, so te 2nd array item can be found 16 bytes after the 1st array item. Variants are like a UDT/TYPE. It can contain values or pointers to other things within its 16 byte structure.
The above can be verified. VarPtr(array(0)) returns the memory address of that array item. VarPtr(array(1)) will be n bytes greater.
Fixed Length/Static length strings are different and handled differently; won't go into that since it is very rarely, if ever used. Example:
Dim s() As String * 255
Objects are like strings. Each array item is a pointer, 4 bytes, to the object in the array.
So, the question: ... array of 16-byte pointers... Not quite. It is an array containing 16 bytes per element. Each pointer points to the first byte of those 16 bytes. When you modified the variant routine to copymemory t(0), you were copying 16 bytes of data that started at memory location v(a) to the memory location pointed to by t(0). Hope that didn't confuse you more.
Last edited by LaVolpe; Jun 12th, 2009 at 08:04 PM.
-
Jun 12th, 2009, 08:20 PM
#7
Thread Starter
Lively Member
Re: CopyMemory Function
 Originally Posted by LaVolpe
Array of 16-byte pointers...Not quite. It is an array containing 16 bytes per element. Each pointer points to the first byte of those 16 bytes. When you modified the variant routine to CopyMemory t(0), you were copying 16 bytes of data that started at memory location v(a) to the memory location pointed to by t(0). Hope that didn't confuse you more.
Oh. I thought I was just shifting pointers: hence the increased speed. Surely copying 16 bytes from one location to another can't copy one element of an array to somewhere else. After all, the element could be a dictionary, or another array, or whatever, right? Or am I missing something?
-
Jun 12th, 2009, 08:25 PM
#8
Re: CopyMemory Function
For Strings and Objects, you are shifting pointers. For the other variable types I mentioned, you are shifting data. But all said and done, you are still shifting memory, be it 1 byte or 1000+ byte increments.
Regarding the 16 bytes, you are correct. The Variant is a 16 byte object. It's structure is documented. First 2 bytes identify what the variant contains, other bytes (depends on what it contains) may contain the actual value (as is the case for simple numbers), or may contain pointers to other things, like to a string in memory, to a class instance, to a dictionary, etc.
-
Jun 12th, 2009, 08:34 PM
#9
Thread Starter
Lively Member
Re: CopyMemory Function
I see. Thanks again. You're a mine of useful information. Returning to the previous question, then: When exactly in practice might I want to use a ZeroMemory function? I mean, why bother? What happens if I just keep switching pointers without zeroing anything, and why?
-
Jun 12th, 2009, 08:42 PM
#10
Re: CopyMemory Function
In the example I gave, one could use ReDim to erase and reset an array to zero. Knowing how VB arrays work, VB simply destroys the old array and creates a new one and initializes the data with zeros. But I think it is more efficient to reuse the old array and just zero out the memory. Less cpu cycles involved.
Edited: But don't do this if the array contains non-numerical data. So don't use it if the array is strings, variants, classes, etc. If you do, you will be leaking memory like crazy.
Here's another example I have used. When I create a memory bitmap, really a DIB section, I want to ensure that every pixel/byte in that image is initially zero (or black). Via documentation, creating the DIB section with the appropriate APIs does not guarantee the DIB is initialized with zero data.
With few exceptions, you probably would not use zeromemory for every day use, it is one of those APIs that gets put in the back of your brain should you ever want to use it. In fact I never use it any longer. I generally use FillMemory instead. It is nearly identical to ZeroMemory except FillMemory allows you to choose which byte is used. In other words, you can fill a Byte() array with all 5's, 100's, etc, with FillMemory, but can only fill it with zeros using ZeroMemory. ZeroMemory uses zeros, FillMemory uses anything between 0 and 255.
Last edited by LaVolpe; Jun 12th, 2009 at 08:56 PM.
-
Jun 12th, 2009, 09:16 PM
#11
Thread Starter
Lively Member
Re: CopyMemory Function
I see, I think. But suppose I decided never to bother zeroing anything. What would go wrong? Would my code just be less efficient? Or could I get errors resulting?
-
Jun 12th, 2009, 09:51 PM
#12
Re: CopyMemory Function
Nothing. No, Nope. VB releases memory to all variables and objects when your program closes or when the variables go out of scope, whichever occurs first. Regardless, using ZeroMemory is absolutely not required in VB.
-
Jun 14th, 2009, 11:14 AM
#13
Thread Starter
Lively Member
Re: CopyMemory Function
Interesting. The dude who wrote this seems to think that, when you're using CopyMemory, you often have to clean up by using ZeroMemory. He reckons you get Access Violation errors if you don't. Though perhaps this is because he's doing something different to what we've been discussing, namely picking up whole blocks of data and moving them?
-
Jun 14th, 2009, 01:24 PM
#14
Re: CopyMemory Function
ZeroMemory is a good idea in the code at that link. That code is doing something different: Shifting UDTs, not strings, values or Variants. UDTs are a different animal (more like your variant example). The code at that site is not swapping, it is shifting in order to create a spot for a new entry. And since the UDT he is using (TypeID) has variable length strings and Font objects (both of which are pointers to memory), you can't leave two array items having the same pointers when they shouldn't. Explained in more detail below.
The error/crash that would happen is that VB thinks the string & font member of that UDT is still valid and when it tries to access it or clear it, crash can happen.
Also, trying to explain more about that code.
1. The routine is resizing the array, then shifting a block of data to make room for a new item.
2. Let's assume the entire block is being shifted one array item
3. The item in array(0) is copied to array(1), and array(1) is copied to array(2), etc. Ok?
4. So, at this moment, array(0) and array(1) are exactly the same. No other item is duplicated.
5. Since the code is not writing some cached item into array(0), not swapping, it is leaving array(0) "blank" other than setting the lID member to some new number. The string & font members are not being modified & that is the crash potential.
6. This means that the string & font pointers in array(0) and array(1) are identical also
7. This means that should VB clear the array later, it will try to release/destroy the string at those pointers when array(0) is released, then try again when array(1) is released and array(1) pointers are now invalid because they were already released: memory access violation.
8. Even if not clearing the array... If array(0)'s string is later set to another string, what happens? Well, array(0) string is good to go, the string pointed to was destroyed and new one created, new pointer assigned. But what is array(1) string pointer pointing to? It is pointing to the string that was just destroyed when array(0) was changed, because they both had the same pointers before the change. When VB goes to retrieve array(1) string, access violation.
9. ZeroMemory is useful in this case. There are other methods too.
10. When a pointer is zero, then VB knows there is no memory associated. So if a string's pointer is zero, it is same as vbNullString. If an object's pointer is zero, it is the same as Nothing.
One thing to keep in mind. To be absolutely safe with CopyMemory with arrays, one must fully understand how VB stores arrays in memory and how/when vb uses pointers.
Last edited by LaVolpe; Jun 14th, 2009 at 02:37 PM.
-
Jun 14th, 2009, 02:08 PM
#15
Re: CopyMemory Function
Read my last post first.
To expand a little more on pointers
String pointers are different than pointers to objects: stdFont, stdPicture, classes, etc.
VarPtr() is assigned/unique to all variables. VarPtr points to the memory address of the variable. If variable is a number, it points to where the value of that variable can be found. If it points to a string, it points to where the string pointer can be found. So for strings, VarPtr is a pointer to a pointer. If VarPtr points to an object, then it points to the ObjPtr of that object, an instance of that object. Another pointer to a pointer scenario. Objects are different too. When you set variables to the same object, each VarPtr is different for each variable, but they all point to the same object: ObjPtr(). ObjPtr for each variable is the same. The object maintains a reference count to how many variables are pointing to it. When you set variables to the same string, VB copies the string, so each VarPtr is different and each is pointing to a different pointer. Confused yet; hopefully not, more to come.
StrPtr() returns the address where the string content can be found. VarPtr() points to StrPtr(). Every string variable's VarPtr is different and the address that VarPtr points to is different for each of those also.
So if you set string2=string1, their StrPtrs() point to different memory addresses, though, at those addresses, the exact same data can be found. Likewise, if you do string1=vbNullString, then string1's StrPtr is now zero.
ObjPtr() returns the address where the object can be found. VarPtr() points to ObjPtr(). Every variable's VarPtr is different but they all point to same memory address where the object's pointer can be found; they all point to ObjPtr(). The object maintains its own reference count. When the reference count goes to zero, vb destroys the object.
So if you do: Set object2=object1, the ObjPtr() of each is the same, the object's reference count is increased by one. Likewise, setting object1 to Nothing, decreases the reference count and object1's ObjPtr is now zero.
Using the UDT example from the previous post, what happens if you call ZeroMemory on array(5) if array(5) string & font pointers are non-zero? The answer: Memory Leak. In this case, you are telling vb that those items do not exist. So when VB eventually clears the array or code reassigns values to those UDT members, vb does not try to destroy them and they remain in memory. Do this enough times and "Out of Memory" can occur.
Edited: Something to play with to visualize what I just posted
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 Sub Command1_Click()
Dim o As Object, s As String, ptr As Long
Set o = Me
s = Me.Caption
CopyMemory ptr, ByVal VarPtr(s), 4&
Debug.Print "string VarPtr points to: "; ptr; " StrPtr() returns: "; StrPtr(s)
CopyMemory ptr, ByVal VarPtr(o), 4&
Debug.Print "object VarPtr points to: "; ptr; " ObjPtr() returns: "; ObjPtr(o)
' press Ctrl+G to see what was printed to your immediate window
End Sub
Last edited by LaVolpe; Jun 14th, 2009 at 03:00 PM.
-
Jun 14th, 2009, 05:44 PM
#16
Thread Starter
Lively Member
Re: CopyMemory Function
This seems hugely helpful. Thanks; again. I'll need to read your posts a few times more and play around with your example in order to get my head round this. The most obvious consequence of what you're saying, however, seems to be that, for any routine where you're merely re-arranging the elements in an array (e.g. a sorting algorithm), you're never going to need to use ZeroMemory: is that right?
-
Jun 14th, 2009, 07:06 PM
#17
Re: CopyMemory Function
I'd hate to say "never". If array items are simply re-arranged without the array growing/shrinking during the routine, that could be a safe assumption. But the real answer is that you would know whether or not to use it. And to know, is to understand vb pointers.
-
Jun 14th, 2009, 07:12 PM
#18
Thread Starter
Lively Member
Re: CopyMemory Function
Darn, I was really enjoying your advice up until that point. Now I have to understand things. Doubtless I'll get back to when I get back to work and can try some of these things out in VBA.
-
Jun 16th, 2009, 03:06 AM
#19
Thread Starter
Lively Member
Re: CopyMemory Function
Hi again LaVolpe. I've read your posts a few times through, and this is starting to make sense to me. It's great info by the way. So thanks. I must admit, though, I still can't envisage a time when re-arranging an array could lead to the memory-related issues you mention. But perhaps the main thing you're saying is 'just keep it in mind'?
On a separate though connected note, I often need to convert '2d' 'horizontal' and 'vertical' arrays (i.e. (1, 1 to n) and (1 to n, 1)) into 'true' 1d arrays; and I've recently started noticing that the Application.Transpose method has its limits (I don't know how it works exactly, but if an array is too big, it produces an error). Can you see any obvious problems with using the following instead (as long as I'm only dealing with single rows and columns of data)?
Code:
Option Explicit
Declare Sub ZeroMemory Lib "kernel32.dll" Alias "RtlZeroMemory" _
(pDest As Any, ByVal nLen As Long)
Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" _
(pDest As Any, pSource As Any, ByVal nLen As Long)
Sub Make2dVer1d(byref vIn() as variant)
dim nElmts as long
Dim vOut() As Variant
nElmts = UBound(vIn, 1)
redim vOut(1 to nElmts)
CopyMemory ByVal VarPtr(vOut(1)), ByVal VarPtr(vIn(1, 1)), nElmts * 16
ZeroMemory ByVal VarPtr(vIn(1, 1)), nElmts * 16
vIn = vOut
End Sub
Sub Make2dHor1d(byref vIn() as variant)
Dim vOut() As Variant
dim nElmts as long
nElmts = CountDimsInHorArr(vIn) 'see the function below
redim vOut(1 to nElmts)
CopyMemory ByVal VarPtr(vOut(1)), ByVal VarPtr(vIn(1, 1)), nElmts * 16
ZeroMemory ByVal VarPtr(vIn(1, 1)), nElmts * 16
vIn = vOut
End Sub
public function CountDimsInHorArr(byref vIn() as variant) as long
dim i as long
on error goto Continue
for i = 1 to 100000000
if VarType(vIn(1, i)) < 0 then
'I don't care what the type is. I just want to generate an error
'if i has exceeded the bounds of the array
end if
next i
Continue:
CountDimsInHorArr = i - 1
end function
Last edited by James_B; Jun 16th, 2009 at 03:23 AM.
-
Jun 17th, 2009, 10:05 AM
#20
Re: CopyMemory Function
You may error. The last parameter in CopyMemory is a long value, so if 16*nElmts > 2147483647 then you will error. Also if nElmts > 2147483647 you will generate an error when trying to size the new array.
Side note: If you are only messing with 2D to 1D conversions, you really don't need the CountDimsInHorArr function. This should work just as well:
Code:
nElmts = Abs(UBound(vIn, 1) - LBound(vIn, 1) + 1) * _
Abs(UBound(vIn, 2) - LBound(vIn, 2) + 1)
-
Jun 17th, 2009, 05:53 PM
#21
Thread Starter
Lively Member
Re: CopyMemory Function
Good point re the Long limit. Thanks. I don't quite see your point with the CountDimsInHorArr function. Probably I just haven't explained myself clearly. By a horizontal 2d array, I mean an array of the form: (1, 1), (1, 2), (1, 3),...which I sometimes have to work with since I often have to either importing data from, or exporting data to, a row in Excel. (I realise this is probably all a bit cowboy by your standards, but this is where I'm at at the moment). Previously, I've been turning a 1d array (say v) into a horizontal array via something like: Application.Transpose(Application.Transpose(v)), which seems a bit clumsy even by my standards. Does this make sense?
-
Jun 18th, 2009, 08:31 AM
#22
Re: CopyMemory Function
I may have misunderstood your function, however, you don't need some loop with error checking.
 Originally Posted by James B
'I don't care what the type is. I just want to generate an error
'if i has exceeded the bounds of the array
To get the upper bound of any dimension: UBound(array, 1) for 1st dim, UBound(array, 2) for 2nd dim, etc. Subtract the LBound for that dim, add 1 and you have the number of elements.
-
Jun 18th, 2009, 08:56 AM
#23
Thread Starter
Lively Member
Re: CopyMemory Function
Sure, but I'm wanting to count the number of dimensions in this case. I think I just explained things badly initially. That bit was intended to convert an array of the form
Code:
v(1, 1) = a
v(1, 2) = b
....
v(1, n) = ...
into a 1d array. Hence I needed to determine n.
-
Jun 18th, 2009, 10:57 AM
#24
Re: CopyMemory Function
Let's say v is dim'd as v(1,1000), ok?
UBound(v,1) = 1
UBound(v,2) = 1000
-
Jun 18th, 2009, 03:12 PM
#25
Thread Starter
Lively Member
Re: CopyMemory Function
Ah, I see. Many thanks. I'll try that one out tomorrow. By the way, how does this forum work in terms of ratings? How, for instance, have you accrued your little green markers or whatever they are, and how does one acknowledge someone else's useful contributions?
-
Jun 18th, 2009, 03:20 PM
#26
Re: CopyMemory Function
Look at the bottom of the poster's info block (left side). See the scale icon? Click on it. The forum is set up so that you can't flood a single member's reputation. If you attempt to, you will get a message saying that you need to spread some reputation around a bit before rating him/her again. This simply means that you need to rate some others before hand. Here is the link regarding ratings
-
Jun 18th, 2009, 05:36 PM
#27
Thread Starter
Lively Member
Re: CopyMemory Function
Thanks. By the way, is it safe to assume that each individual column of an array is contiguous in terms of its allocation? That is, if I have an array of longs--say, arr(0 to 99, 0 to 5)--and I do something like
Code:
Dim vOut(0 To 99) As Long
CopyMemory ByVal vOut(0), ByVal VarPtr(arr(0, 3)), 400
ZeroMemory ByVal VarPtr(arr(0, 3)), 400
then will it always (ever?) give me the 4th column of arr?
-
Jun 18th, 2009, 08:03 PM
#28
Re: CopyMemory Function
Arrrays are always contiguous memory; never any gaps.
I think of 2d arrays like a table. The 1st dimension are the columns and the 2nd dimension are the rows. Thinking of an Excel sheet, memory starts a A1 continues thru A99, then wraps to B1 and continues to B99, wraps to C1... and on. So the data in the Excel sheet is read left to right, top to bottom.
The way I think of 2D arrays may not be the way you are describing them. However, knowing how 2D arrays are stored in memory is important when using CopyMemory
So... Long arr(0 to 99, 0 to 5) is stored in this order:
1. arr(0 to 99, 0) are the 1st 400 contiguous bytes
2. arr(0 to 99, 1) are the next 400 contiguous bytes
....
5. arr(0 to 99, 5) are the last 400 contiguous bytes
The above being said:
"CopyMemory ByVal vOut(0), ByVal VarPtr(arr(0, 3)), 400"
will start at the Long value at arr(0,3) and copy the entire row (#3) from column 0 to 99
However, example for comparison
"CopyMemory ByVal vOut(0), ByVal VarPtr(arr(1, 3)), 400
will start at the Long value at arr(1,3) and copy 99 columns of row 3 and the 1st column of row 4.
-
Jun 22nd, 2009, 12:16 PM
#29
Thread Starter
Lively Member
Re: CopyMemory Function
Makes sense. Thanks again.
By the way, I see what you mean regarding the UBound function. I was thinking UBound(vArr, 2) would tell me how many data-items there were in the 2nd column of vArr. Silly really, as I doubt you could have an array with variable column lengths; there'd just be empty elements.
Anyway, I think I've come across the kind of situation you were warning me about, and I'm not sure how to sort it out. I want to build a function that inserts one block of data from one location in an array into a different location of the array. The below does something like what I want,
Code:
Sub InsAfterIn1dArr(ByRef vIn As Variant, _
a As Long, b As Long, Optional n As Long = 1)
'where a < b
Dim bTemp() As Long
ReDim bTemp(1 To 4 * n) As Long
CopyMemory bTemp(1), ByVal VarPtr(vIn(a)), 16 * n
CopyMemory ByVal VarPtr(vIn(a)), _
ByVal VarPtr(vIn(a + n)), 16 * (b - n)
CopyMemory ByVal VarPtr(vIn(b - n + 1)), bTemp(1), 16 * n
End Sub
i.e. it inserts n elements of data, beginning at element a, before element b. Problem is, some strange things are happening to my arrays: some of the elements start to read "Variant/Unsupported Data Type" in the locals window at some point in my routine, and I suspect the problem is the aforementioned functioned. I've tried using ZeroMemory to tidy things up but to no avail. Do you have any idea as to what's going wrong, or do you think my problems might lie elsewhere?
-
Jun 22nd, 2009, 01:37 PM
#30
Re: CopyMemory Function
Not sure what you are attempting, but I'll think out loud by following your code. Maybe what I write will trigger something:
If A=2, B=8, N=3 then
1. After 1st CopyMemory call
bTemp has copies of 2,3,4
2. After 2nd call: (a) written over by (a+n) with (b-n) elements
2,3,4,5,6 are now copies of 5,6,7,8,9
3. After 3rd call: (b-n+1) written over by bTemp with n elements
6,7,8 are now what used to be 2,3,4
Yes, it triggered something: Item 9 is duplicated. It exists in both item 9 and item 6, because 9 was not written over
Changing the second call's last parameter to (b-n-1) might do the trick
1. After 1st CopyMemory call
bTemp has copies of 2,3,4
2. After 2nd call: (a) written over by (a+n) with (b-n-1) elements
2,3,4,5 are now copies of 5,6,7,8
3. After 3rd call: (b-n+1) written over by bTemp with n elements
6,7,8 are now what used to be 2,3,4
FYI: Errors if (b-n-1) < 0 or if (a+n) > UBound, be sure to prevent that from happening
Note: I interpretted your post as saying that B would not be written over. Following your code, it will always be written over. You may need to adjust your calcs appropriately
Last edited by LaVolpe; Jun 22nd, 2009 at 01:57 PM.
-
Jul 2nd, 2009, 01:20 PM
#31
Thread Starter
Lively Member
Re: CopyMemory Function
Many thanks. (Sorry for the delay in replying by the way: I've been on holiday). You're quite right. It was my logic that was at fault rather than the use of the CopyMemory function. I've sorted it now. By the way, why do we need to use ByVal when we're exchanging pointers like this?
-
Jul 2nd, 2009, 02:27 PM
#32
Re: CopyMemory Function
The use of ByVal is required because CopyMemory was declared with parameters as ANY. Notice ByVal not used in 1st 2 parameters. If not used, ByRef is default.
Code:
Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
(lpDest As Any, lpSource As Any, ByVal cbCopy As Long)
If we did CopyMemory VarPtr(a(0)), VarPtr(b(0)), 16 then, and being ByRef vs ByVal:
VB will pass the pointer to the result of VarPtr(a(0)), not the acutal result of VarPtr(a(0)). Same goes for VarPtr(b(0))
Now, if CopyMemory were declared differently, using ByVal vs ByRef:
Code:
Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
(ByVal lpDest As Long, ByVal lpSource As Long, ByVal cbCopy As Long)
In the above case VB would pass the value returned by VarPtr(a(0)).
There is no harm in changing the declaration as I did above. Leaving it as ByRef and use of Any for the parameters allows the API to be nearly 100% generic. One can pass anything, including UDTs as is very common like in this example used in subclassing routines (DRAWITEMSTRUCT is a UDT here)
Code:
Dim DI as DRAWITEMSTRUCT
CopyMemory DI, ByVal lParam, Len(DI)
' note that if the API was declared ByVal and using LONGs for the parameters,
' the above would be modified, as shown below, so it could be used for the UDT
CopyMemory VarPtr(DI), lParam, Len(DI)
Bottom line, depending on how you declare the API, dictates whether or not you use ByVal in your API calls.
-
Jul 5th, 2009, 07:03 AM
#33
Thread Starter
Lively Member
Re: CopyMemory Function
Makes sense. Thanks. It's a nifty little function this. By the way, a book I'm reading recommends using Classes to encapsulate API calls. Thing is, I don't really understand what Classes are, or how to use them. Do you know of any good websites that explain such things?
-
Jul 5th, 2009, 11:31 AM
#34
Re: CopyMemory Function
James, see if this tutorial sheds any light
Last edited by LaVolpe; Jul 5th, 2009 at 07:54 PM.
Reason: typo
-
Jul 6th, 2009, 04:55 AM
#35
Thread Starter
Lively Member
Re: CopyMemory Function
Thanks very much. That was good stuff. I can't see any immediate application of it to what I'm currently doing. But it seems to make sense so I'll keep it in mind. After all, I've only just started using VBA, so I'm probably doing fairly basic stuff.
By the way, I got a CopyMemoryish-sounding error the other day after I'd shut my computer lid (putting it to sleep) and reopened it. I'm not sure why this happened, so I'm looking over some of my functions again to make sure I'm not allocating different strings to the same bit of memory and that kind of thing. A question arose, though: What exactly does the Erase function do? Does it erase an array's underlying data, or does it just "unpoint" it, or what?
-
Jul 6th, 2009, 08:03 AM
#36
Re: CopyMemory Function
Are you talking about VB's Erase in regards to an array?
1. If the array is static vs dynamic. Not positive on this one. Something similar to ZeroMemory is used on the array. All data is zeroed out, if the array contained object/string pointers, the objects/strings are released and destroyed as needed. So the array remains, just reset to all zeroes, Nothing, whatever the default value would be, as one would expect.
2. If the array is dynamic, then the object/strings are released/destroyed as needed, the array itself is released/destroyed. There is no footprint of the array left behind.
If your array contains pointers to the same object/string, then you should expect errors when the array is erased and/or resized.
-
Jul 8th, 2009, 05:29 PM
#37
Thread Starter
Lively Member
Re: CopyMemory Function
I see what you're saying. I was worried that Erasing an array would delete the array's underlying data (e.g. the actual strings, if you know what I mean) without actually releasing its pointers, thus casing problems. But it seems this isn't a problem.
-
Jul 18th, 2009, 07:18 AM
#38
Thread Starter
Lively Member
Re: CopyMemory Function
Hi again. Since our discussions, I've had a fair bit of joy using the CopyMemory to transpose arrays. I'm wondering if there's a way of using it to recreate the Join function. As such, I have a couple of questions:
a] do you know how VBA's Join function works? and
b] can you think of a way of building a faster version via CopyMemory?
I've had a few ideas, but I'm not sure if they're feasible in practice or even desirable in theory (since they may just be recreating what the Join function does). One idea was to calculate the number of characters in the array, e.g.
Code:
for i = LBound(sArr, 1) to UBound(sArr, 1)
nLen = nLen + Len(sArr(i))
next i
then create a ByteArray 2 * (nLen + 1) characters long, e.g.
Code:
ReDim bStr(1 To 2 * (nLen + 1)) As Byte
and then to try to point sStr's various characters to the characters in the array, though I'm not quite sure how to do this. Perhaps
Code:
Do
CopyMemory bStr(n), StrPtr(sArr(i)), Len(sArr(i)) * 2
n = n + Len(sArr(i))
Loop
But then this might be what Join does anyway. Plus, I'm well into territory I don't understand at this point. Another idea was just to do something like
Code:
CopyMemory bStr(1), ByVal VarPtr(sArr(1)), nLen * 4
or perhaps
Code:
Str = Space$(nLen)
CopyMemory Str, ByVal VarPtr(sArr(1)), nLen * 4
and be done with. But this is probably hopelessly naive.
-
Jul 18th, 2009, 11:28 AM
#39
Re: CopyMemory Function
I don't know if you can really speed up VB's JOIN function; it is pretty well optimized in my opinion, plus it has some optional parameters that you'd have to code for if you really wanted to try to replicate it. The idea if you should try to do it.
1. Count characters in each array item (LenB is faster than using Len but returns 2*Len)
2. Create a new string that is: TotalCharCount + (ArraySize-1)*Len(JoinChars)
3. Loop thru each array item
:: Copy from array's StrPtr to the StrPtr of the new String, keeping last offset
:: Copy the JoinChar(s) to the last offset, update last offset
:: The loop might look something like the following (air code coming); would need some dummy proofing
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 Function JoinIt(stringArray() As String, sJoinChars As String) As String
Dim lJoinLen As Long, lSize As Long
Dim joinPtr As Long, newStrPtr As Long
Dim Z As Long
lJoinLen = LenB(sJoinChars)
' count first
For Z = 0 To UBound(stringArray)
lSize = lSize + LenB(stringArray(Z))
Next
JoinIt = String$((lSize + (UBound(stringArray) * lJoinLen)) \ 2, vbNullChar)
If LenB(JoinIt) Then
newStrPtr = StrPtr(JoinIt)
joinPtr = StrPtr(sJoinChars)
' transfer
For Z = 0 To UBound(stringArray) - 1
lSize = LenB(stringArray(Z))
If lSize Then
CopyMemory ByVal newStrPtr, ByVal StrPtr(stringArray(Z)), lSize
newStrPtr = newStrPtr + lSize
End If
If lJoinLen Then
CopyMemory ByVal newStrPtr, ByVal joinPtr, lJoinLen
newStrPtr = newStrPtr + lJoinLen
End If
Next
' handle last array item separately, no join char appended
lSize = LenB(stringArray(Z))
If lSize Then CopyMemory ByVal newStrPtr, ByVal StrPtr(stringArray(Z)), lSize
End If
End Function
Private Sub Command1_Click()
Dim s(0 To 25) As String
Dim v As Long
For v = 1 To 26
s(v - 1) = Chr$(v + 64)
Next
Debug.Print JoinIt(s(), "(:)")
End Sub
Keep in mind that VB's String data (at StrPtr) is stored with 2 bytes per character
Edited: Also note that I did not test for vbNullString, just testing for Len of the string.
vbNullString has a StrPtr of zero and len of zero
"" has a StrPtr of non-zero and a len of zero
Edited one more time: Provided an actual working example.
The loop above can be optimized a tad more by using 2 loops, one when the join char(s) are non-zero length and another when the len is zero, thinking it is possible to Join using vbNullString or "". The zero-length loop wouldn't need to check the len of sJoinChars, one less IF comparison per array item.
FYI: Compiling the above with 2 loops as previously mentioned, I can't beat VB but came close to matching (no high rez timing used)
With a 900,000 array, VB: .110 sec, above code: .186 sec
Last edited by LaVolpe; Jul 18th, 2009 at 01:24 PM.
-
Jul 18th, 2009, 02:04 PM
#40
Thread Starter
Lively Member
Re: CopyMemory Function
Right, yes, I see your method. And I like your style of coding a lot. Part of what I was wondering, however, was whether there's any way of incorporating VarPtrs into this?
My (perhaps incorrect) understanding of an array of strings is that its various elements are stored next to each other in terms of their memory location. So if the array is
Code:
vArr = Array("AA", "BB", "CC", ...),
then "AA" will be stored next to "BB" which will be stored next to "CC" and so on. So I was wondering if there was any way of telling a joining string to look at these memory locations--ideally as a whole--rather than copying over each bit at a time. That is, I was wondering if I could get the first character of the string to look at VarPtr(vArr(1)), the third character to look at VarPtr(vArr(2)), the fifth character VarPtr(vArr(3)), and so on. Does this sound possible?
Last edited by James_B; Jul 18th, 2009 at 02:08 PM.
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
|