arrays - 将数组复制到 Range.Value2 SafeArray.pvData 成功,但 Excel 无法更新

标签 arrays vba excel

编辑

实际上,没有直接的方法来编辑内存中的范围值。
感谢@AndASM 的详细回答和 Carl;好的预感,这是正确的。那一刻我一定是太纠结了所有的倒车,忘记了Value2只是一个属性(property)。

同时,我深入研究了一些其他测试和使用 OllyDbg 进行调试,发现了一些有趣的事情:

  • 单元格排列在 16 x 1024 的区域中。持有的结构
    区域很可能是工作表,但我还不能确认;
  • 每次Value属性被调用,绝对工作表
    偏移量(行,列)用于查找相应的区域和
    然后在区域内进行一些索引以获得实际值;
  • 创建了一个 VARIANT 类型的 2D SAFEARRAY;
  • 值不是在连续块中检索,而是单独检索。
    这意味着范围 (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
    

    哪里130836704Range.Value2 SafeArray.pvData129997032SortArray 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_Value2Range.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/

    相关文章:

    arrays - Swift - 遍历嵌套的字典数组

    ms-access - 对子窗体的 Form 属性的引用无效 (ms Access 2007)

    regex - 在 Excel 单元格上使用 Perl 和 Regex 来组合任何不带空格的前导数字

    javascript - 是否可以使用 excel VBA 直接写入 chrome 本地存储?

    java - C++ 和 Java 中的大括号初始化

    arrays - 为什么 Swift 编译器会以 16 字节的倍数为基本类型的数组预分配空间? (64 位英特尔机器)

    php - 如何在 PHP 中打印每个子节点和孙节点旁边的最高级别父节点

    excel - 如何过滤字符串文本单元格?

    c - C99中局部静态数组的线程安全

    excel - 用户表单输入后重复主动过滤器