编辑
实际上,没有直接的方法来编辑内存中的范围值。
感谢@AndASM 的详细回答和 Carl;好的预感,这是正确的。那一刻我一定是太纠结了所有的倒车,忘记了Value2
只是一个属性(property)。
同时,我深入研究了一些其他测试和使用 OllyDbg 进行调试,发现了一些有趣的事情:
区域很可能是工作表,但我还不能确认;
Value
属性被调用,绝对工作表偏移量(行,列)用于查找相应的区域和
然后在区域内进行一些索引以获得实际值;
这意味着范围 (row,col) 中的每个“点”都被发送到
为其返回值(显然是变体)的索引过程
对应的 SAFEARRAY 元素;
Range.Value2(row,col)
,整个过程对所有的范围内的值。想象一下,如果你这样做,性能会受到影响
在一个过程中多次,或者更糟糕的是,在一个循环中。
只是不要;你最好创建一个
Value2
的副本和通过索引解决它;
SAFEARRAY.pvData
是基于列的 (col,row)
, 不是基于行的,其中可能会被发现违反直觉并且与 VBA 索引不一致
模式,即
(row,col)
.如果您需要,这可能会派上用场直接在内存中访问 pvData 并保持维度一致性。
例如,如下所示的范围
1, 2, 3, 4
5, 6, 7, 8
将存储在
pvData
按以下顺序: 1, 5, 2, 6, 3, 7, 4, 8
我希望以上有所帮助。
总而言之,在 Excel 中没有任何此类导出函数的情况下,最好的方法是创建
Value2
的副本。 ,将其排序/操作以达到所需的结果并将其分配回范围属性。我最近完成了 QuickSort 的一个变体,并打算在 Excel 中实现。该算法是有效的,如果不是为了将数组值放入该范围所花费的额外时间,它确实会带来作为插件的值(value)。转置仅适用于小于 65537,而“将变体数组粘贴到范围”在大型排序上花费的时间太长。
因此,我编写了一些程序,允许将范围内的 2D 值复制到 1D 数组(排序需要 1D)和(排序完成后)将它们放回去,所有这些都基于 SAFEARRAY 和 MemCopy(RtlMoveMemory) 和,或者,WriteProcessMemory。
就内存操作而言,一切正常:
- 范围值被复制到数组(从一个 SafeArray.pvData 到另一个);
- 数组值(运行排序算法后)成功复制到 Range.Value2 SafeArray.pvData。
尽管如此,范围并没有更新,因为它似乎回到了旧值(更多关于下面的代码)。
为什么“Range.Value2 = SomeOther2dArray”会起作用而不直接在内存中修改数据?我有一种感觉,我在这里错过了一些东西。是否还需要公式排序/更新?
下面是主要程序:
Public Sub XLSORT_Array2()
With Application
screenUpdateState = .ScreenUpdating
statusBarState = .DisplayStatusBar
calcState = .Calculation
eventsState = .EnableEvents
.ScreenUpdating = False
.DisplayStatusBar = False
.Calculation = xlCalculationManual
.EnableEvents = False
End With
Dim rngSort As Range
Dim arrSort() As Variant
Dim arrTemp As Variant
Dim i As Long
Dim dblTime As Double
Dim dblInitTime As Double: dblInitTime = Timer
Set rngSort = Selection
If Not rngSort Is Nothing Then
If rngSort.Cells.Count > 1 And rngSort.Areas.Count = 1 Then
dblTime = Timer
ReDim arrSort(1 To rngSort.Cells.Count)
Debug.Print Timer - dblTime & vbTab & "(Redim)"
'just testing Excel memory location
'Debug.Print VarPtr(rngSort.Value2(1, 1))
dblTime = Timer
SA_Duplicate arrSort, rngSort.Value2
Debug.Print Timer - dblTime & vbTab & "(Copy)"
dblTime = Timer
SORTVAR_QSWrapper arrSort, 1, rngSort.Cells.Count
Debug.Print Timer - dblTime & vbTab & "(Sort)"
'this would be the fastest method
'variants are copied to memory
'yet the range does not update with the new values
SA_Duplicate rngSort.Value2, arrSort
'dblTime = Timer
'looping = too slow
'For i = 1 To rngSort.Cells.Count
' rngSort.Cells(i).Value = arrSort(i)
'Next
'this works, but it's too slow, as well
'If rngSort.Cells.Count > 65536 Then
' ReDim arrTemp(LBound(rngSort.Value2, 1) To UBound(rngSort.Value2, 1), LBound(rngSort.Value2, 2) To UBound(rngSort.Value2, 2))
' SA_Duplicate arrTemp, arrSort
' rngSort.Value2 = arrTemp
'Else
' rngSort.Value2 = WorksheetFunction.Transpose(arrSort)
' Debug.Print "Transposed"
'End If
'Debug.Print Timer - dblTime & vbTab & "(Paste)"
End If
End If
With Application
.ScreenUpdating = screenUpdateState
.DisplayStatusBar = statusBarState
.Calculation = calcState
.EnableEvents = eventsState
End With
Debug.Print VarPtr(rngSort.Value2(1, 1)) & vbTab & Mem_ReadHex(ByVal VarPtr(rngSort.Value2(1, 1)), rngSort.Cells.Count * 16)
Set rngSort = Nothing
Debug.Print Timer - dblInitTime & vbTab & "(Total Time)" & vbNewLine
End Sub
假设范围内的值是 4、3、2 和 1。
之前
SA_Duplicate arrSort, rngSort.Value2
内存中写道:130836704 05000000 00000000 00000000 00001040 05000000 00000000 00000000 00000840 05000000 00000000 00000000 00000040 05000000 00000000 00000000 0000F03F
129997032 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
哪里
130836704
是 Range.Value2 SafeArray.pvData
和 129997032
是 SortArray SafeArray.pvData
.每个 16 字节的批处理表示从内存中读取的变体实际数据(无 LE 转换,仅采用十六进制),前 2 个字节表示 VarType。在这种情况下,vbDouble。复制后,正如预期的那样,内存显示:
130836704 05000000 00000000 00000000 00001040 05000000 00000000 00000000 00000840 05000000 00000000 00000000 00000040 05000000 00000000 00000000 0000F03F
129997032 05000000 00000000 00000000 00001040 05000000 00000000 00000000 00000840 05000000 00000000 00000000 00000040 05000000 00000000 00000000 0000F03F
排序完成后,SortArray SafeArray.pvData 读取:
129997032 05000000 00000000 00000000 0000F03F 05000000 00000000 00000000 00000040 05000000 00000000 00000000 00000840 05000000 00000000 00000000 00001040
执行后
SA_Duplicate rngSort.Value2, arrSort
,内存显示Range.Value2 SafeArray.pvData已经更新:129997032 05000000 00000000 00000000 0000F03F 05000000 00000000 00000000 00000040 05000000 00000000 00000000 00000840 05000000 00000000 00000000 00001040
130836704 05000000 00000000 00000000 0000F03F 05000000 00000000 00000000 00000040 05000000 00000000 00000000 00000840 05000000 00000000 00000000 00001040
到目前为止一切看起来都很好,除了
Debug.Print VarPtr(rngSort.Value2(1, 1)) & vbTab & Mem_ReadHex[...]
显示值翻转回初始顺序:130836704 05000000 00000000 00000000 00001040 05000000 00000000 00000000 00000840 05000000 00000000 00000000 00000040 05000000 00000000 00000000 0000F03F
请分享您认为有效的任何想法或方法。任何帮助表示赞赏。不得不等待 Excel 大约 4 秒(对 1,000,000 + 单元格进行排序)令人沮丧,而即使是最具挑战性的排序也比这少。
提前致谢!
最佳答案
好吧,您没有提供几个重要部分的工作实现,尤其是 SA_Duplicate
,所以这主要是猜测工作。但是,我认为答案可能很简单。Range.Value2
是一个属性,而不是一个变量。所以在幕后它实际上是两个函数,让我们称之为 Range.let_Value2
和 Range.get_Value2
.
话虽如此,您如何期望电话SA_Duplicate rngSort.Value2, arrSort
上类?因为我看到的是SA_Duplicate rngSort.get_Value2, arrSort
.我认为 rngSort.get_Value2
正在创建一个新的 SafeArray,然后将数据从 Excel 的内部数据结构复制到该 SafeArray。如果我是对的,那么您将数据写入 VBA 稍后丢弃的临时缓冲区,而 Excel 已经忘记了。
您需要使用 rngSort.let_Value2 arrSort
通常称为 rngSort.Value2 = arrSort
.
作为旁注,如果 get_Value2
分配一个新数组,就像我认为的那样,SA_Duplicate
调用是不必要的,您可能能够就地对返回的数组进行排序。记得转给let_Value2
通过在完成后将数组变量分配回属性。
关于arrays - 将数组复制到 Range.Value2 SafeArray.pvData 成功,但 Excel 无法更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34031319/