Reputation: 1149
A bit of background first.
I'm trying to merge multiple 2D arrays. Usualy I would loop through each element of new array and add them to existing array or place arrays' values on separate sheet and create new array from it but I'm working with large data.
Not long ago I found CopyMemory function and got realy excited by it, I've tested it on simple chunks of data first.
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Sub Test()
Dim varr0(), varr1(), Border As Long
varr0 = Application.Transpose(Range("a1").CurrentRegion.Value)
Border = UBound(varr0, 2)
varr1 = Application.Transpose(Range("a21").CurrentRegion.Value)
ReDim Preserve varr0(1 To UBound(varr0, 1), 1 To UBound(varr0, 2) + UBound(varr1, 2))
CopyMemory varr0(1, Border + 1), varr1(1, 1), UBound(varr1, 1) * UBound(varr1, 2) * 16
Range(Cells(1, 10), Cells(1, 10).Offset(UBound(varr0, 2) - 1, UBound(varr0, 1) - 1)).Value = Application.Transpose(varr0)
End Sub
Clearly it was a success (or so I thought) and I decided to work with pieces of my actual data, from there it went downhill.
Sub Test_2()
Dim varr0(), varr1(), Border As Long, ws As Worksheet
varr0 = Application.Transpose(ThisWorkbook.Worksheets("Sheet1").Range("a1").CurrentRegion.Value)
Border = UBound(varr0, 2)
For Each ws In ThisWorkbook.Worksheets
If ws.Name <> "Sheet1" Then
varr1 = Application.Transpose(ws.Range("a1").CurrentRegion.Value)
ReDim Preserve varr0(1 To UBound(varr0), 1 To UBound(varr0) + UBound(varr1))
CopyMemory varr0(1, Border + 1), varr1(1, 1), UBound(varr1, 1) * UBound(varr1, 2) * 16
Border = UBound(varr0, 2)
End If
Next
ThisWorkbook.Worksheets("ws1").Range(Cells(1, 11), Cells(1, 11).Offset(UBound(varr0, 2) - 1, UBound(varr0, 1) - 1)).Value = Application.Transpose(varr0)
End Sub
Whenever I execute it, Excel crashes (it doesn't identify the error but warns that something went wrong (thanks cap)).
The only thing I can think of is that new data had strings in it.
Per Source Variant
requires only 16 bytes.
It seems I was calculating memory to copy incorrectly so I slightly modified my macro.
Sub Test_6()
Dim varr0(), varr1(), Border As Long, ws As Worksheet, MemUsage As Long
varr0 = Application.Transpose(ThisWorkbook.Worksheets("Sheet1").Range("a1").CurrentRegion.Value)
Border = UBound(varr0, 2)
For Each ws In ThisWorkbook.Worksheets
If ws.Name <> "Sheet1" Then
varr1 = Application.Transpose(ws.Range("a1").CurrentRegion.Value)
ReDim Preserve varr0(1 To UBound(varr0, 1), 1 To UBound(varr0, 2) + UBound(varr1, 2))
MemUsage = VarPtr(varr1(UBound(varr1, 1), UBound(varr1, 2))) - VarPtr(varr1(1, 1))
CopyMemory varr0(1, Border + 1), varr1(1, 1), MemUsage + 16 + Len(varr1(UBound(varr1, 1), UBound(varr1, 2)))
Border = UBound(varr0, 2)
End If
Next
ThisWorkbook.Worksheets("Sheet1").Range(Cells(1, 11), Cells(1, 11).Offset(UBound(varr0, 2) - 1, UBound(varr0, 1) - 1)).Value = Application.Transpose(varr0)
End Sub
In watch window I can clearly see that merge was successful, but shortly after CopyMemory
line Excel crashes again.
Upvotes: 4
Views: 1230
Reputation: 14383
I can only guess that the API is reading a contiguous range of bytes in memory whereas the operating system may store large portions of data at split locations. Remember that VBA is using APIs to do its work. Once you override VBA and try to do the same job better the onus of proof is on you.
The following code will write the values of non-contiguous ranges of any source into a worksheet it creates. Note that the number of ranges is unlimited but is hard-coded.
Private Sub TestAppend()
' 17 Nov 2017
Dim WsS As Worksheet, WsT As Worksheet ' Source and Target
Dim Arr() As Variant
Dim Rl As Long ' last row
Dim i As Long
Set WsS = ActiveSheet
On Error Resume Next
Set WsT = Worksheets("Temp")
If Err Then
Set WsT = Worksheets.Add(Sheet1)
WsT.Name = "Temp"
End If
On Error GoTo 0
ReDim Arr(1)
Arr(0) = Range("A1").CurrentRegion.Value
Arr(1) = Range("E1").CurrentRegion.Value
For i = 0 To UBound(Arr)
With WsT
Rl = .Cells(.Rows.Count, "A").End(xlUp).Row
.Cells(Rl, "A").Resize(UBound(Arr(i)), UBound(Arr(i), 2)).Value = Arr(i)
End With
Next i
End Sub
Upvotes: 1