当前位置:  开发笔记 > 后端 > 正文

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

如何解决《将数组复制到Range.Value2SafeArray.pvData成功,但Excel无法更新》经验,为你挑选了0个好方法。

编辑

实际上,没有直接的方法来编辑内存中的范围值.感谢@AndASM的详细解答和Carl; 良好的预感,它是现货.在那一刻,我一定是纠结于所有逆转,忘记Value2只是一个财产.

与此同时,我通过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中实现.该算法是有效的,并且如果不是将数组值放入范围所花费的额外时间,那么它将真正带来作为加载项的价值.转置仅适用于小于65537,而'粘贴变体阵列到范围在大型排序上需要太长时间.

因此,我编写了一些程序,允许将范围内的2D值复制到一维数组(排序需要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 +单元格),即使是最具挑战性的排序也要少于此.

提前致谢!

推荐阅读
凹凸曼00威威_694
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有