2015-12-01 19 views
10

EDITSao chép một mảng để Range.Value2 SafeArray.pvData thành công, nhưng Excel thất bại trong việc cập nhật

Thật vậy, không có cách nào trực tiếp chỉnh sửa các giá trị phạm vi trong bộ nhớ. Cảm ơn @AndASM về câu trả lời chi tiết và Carl; tốt linh cảm, nó đã được tại chỗ. Tôi hẳn đã quá rối trong tất cả sự đảo chiều tại thời điểm đó, quên rằng Value2 chỉ là một tài sản.

Trong khi đó, tôi nghiên cứu sâu hơn một chút với một số xét nghiệm khác và gỡ lỗi với OllyDbg và tìm thấy một vài điều thú vị:

  1. Các tế bào được sắp xếp theo 16 x 1024 khu vực này. Cấu trúc giữ các khu vực có thể rất tốt là trang tính, nhưng tôi chưa thể xác nhận;
  2. Mỗi khi thuộc tính Value được gọi, bảng tuyệt đối khoảng cách (hàng, cột) được sử dụng để tìm khu vực tương ứng và sau đó để lập chỉ mục bên trong khu vực để nhận giá trị thực;
  3. Một SAFEARRAY 2D của loại VARIANT được tạo;
  4. Giá trị không được truy lục trong một khối liền kề, nhưng riêng lẻ. Điều này có nghĩa là mỗi "điểm" trong một phạm vi (hàng, col) được gửi đến thủ tục lập chỉ mục để trả lại giá trị (biến thể, hiển nhiên) cho phần tử SAFEARRAY tương ứng của nó;
  5. Do đó, mỗi khi bạn truy lục giá trị qua Range.Value2(row,col), toàn bộ quá trình được nhắc lại cho tất cả các giá trị trong phạm vi. Hãy tưởng tượng hit hiệu suất nếu bạn làm điều này nhiều lần trong một thủ tục hoặc thậm chí tệ hơn, bên trong một vòng lặp. Chỉ cần không; bạn nên tạo một bản sao của Value2 và giải quyết nó bằng cách lập chỉ mục;
  6. cuối, nhưng không kém phần quan, sự phân bố của các giá trị bên trong SAFEARRAY.pvData là cột trụ sở (col,row), không chèo dựa trên, mà có thể được tìm thấy phản trực giác và mâu thuẫn với chế độ VBA indexing , đó là (row,col). Điều này có thể hữu ích nếu bạn cần truy cập pvData trực tiếp trong bộ nhớ và duy trì tính nhất quán về kích thước. Như một ví dụ, một loạt như hình dưới đây

    1, 2, 3, 4 
        5, 6, 7, 8 
    

    sẽ được lưu trữ trong pvData theo thứ tự sau đây:

    1, 5, 2, 6, 3, 7, 4, 8 
    

Tôi hy vọng giúp trên. Để tổng hợp, nếu không có bất kỳ chức năng được xuất trong Excel, cách tốt nhất là tạo bản sao Value2, sắp xếp/thao tác nó theo kết quả mong muốn và gán nó trở lại thuộc tính phạm vi.


Gần đây tôi đã hoàn thành một biến thể của QuickSort và dự định triển khai trong Excel. Thuật toán có hiệu quả và thực sự sẽ mang lại giá trị như một bổ trợ, nếu không phải dành thêm thời gian cho việc đưa các giá trị mảng vào phạm vi. Việc chuyển đổi chỉ hoạt động với ít hơn 65537, trong khi 'dán biến thể mảng thành phạm vi mất quá nhiều thời gian trên các loại lớn.Vì vậy, tôi đã viết một vài thủ tục cho phép sao chép các giá trị 2D từ phạm vi vào một mảng 1D (1D là cần thiết để phân loại) và (sau khi sắp xếp xong) đưa chúng trở lại, tất cả dựa trên SAFEARRAY và MemCopy (RtlMoveMemory) và, luân phiên, WriteProcessMemory.

Tất cả hoạt động tốt, theo như hoạt động bộ nhớ có liên quan: - giá trị dải ô được sao chép vào mảng (từ một SafeArray.pvData sang giá trị khác); - giá trị mảng (sau khi chạy bản đồ sắp xếp) được sao chép thành công vào Range.Value2 SafeArray.pvData.

Tuy nhiên, phạm vi không cập nhật, vì nó dường như lật ngược trở lại các giá trị cũ (nhiều hơn về điều đó trong mã bên dưới). Tại sao "Range.Value2 = SomeOther2dArray" hoạt động và không sửa đổi dữ liệu trực tiếp trong bộ nhớ? Tôi có cảm giác tôi đang thiếu điều gì đó ở đây. Là một công thức sắp xếp/cập nhật cần thiết, là tốt?

Dưới đây là thủ tục chính:

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 

Hãy nói rằng các giá trị trong phạm vi 4, 3, 2, và 1. Trước SA_Duplicate arrSort, rngSort.Value2 bộ nhớ đọc:

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 

nơi 130836704Range.Value2 SafeArray.pvData129997032SortArray SafeArray.pvData. Mỗi lô 16 byte đại diện cho biến thể dữ liệu thực tế, như đọc từ bộ nhớ (không có dịch LE, chỉ trong hex), với 2 byte đầu tiên cho biết VarType. Trong trường hợp này, vbDouble.

Sau khi sao chép, như mong đợi, bộ nhớ đọc:

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 

Sau khi loại xong, SortArray SafeArray.pvData đọc:

129997032 05000000 00000000 00000000 0000F03F 05000000 00000000 00000000 00000040 05000000 00000000 00000000 00000840 05000000 00000000 00000000 00001040 

Sau khi thực hiện SA_Duplicate rngSort.Value2, arrSort, bộ nhớ cho thấy Range.Value2 SafeArray.pvData đã được cập nhật:

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 

Tất cả trông vẫn khoẻ mạnh cho đến nay, ngoại trừ việc Debug.Print VarPtr(rngSort.Value2(1, 1)) & vbTab & Mem_ReadHex[...] cho thấy rằng các giá trị lật trở lại trật tự ban đầu:

130836704 05000000 00000000 00000000 00001040 05000000 00000000 00000000 00000840 05000000 00000000 00000000 00000040 05000000 00000000 00000000 0000F03F 

Hãy chia sẻ bất kỳ suy nghĩ hay phương pháp bạn thấy hiệu quả. Bất kỳ trợ giúp được đánh giá cao. Đó là bực bội phải chờ đợi cho Excel khoảng 4 giây (sắp xếp 1.000.000 + tế bào), ngay cả khi loại khó khăn nhất mất ít hơn thế.

Cảm ơn trước!

+0

Tôi nghĩ rằng nó có thể được sâu sắc để biết những gì SA_Duplicate hiện, nhưng kể từ khi chỉ định nó là kết quả xuống đường để range.Value2 chỉ hoạt động, tôi giả định dữ liệu mới là chính xác? Vì vậy, tôi nghi ngờ Excel nội bộ giữ dữ liệu của nó trong một số cấu trúc khác hơn là an toàn, và các thuộc tính .Value/.Value2 chỉ là các trình bao thực hiện bản dịch; điều này có nghĩa là thao tác bộ nhớ trực tiếp của biến thể những thuộc tính này trở lại là vô nghĩa. –

Trả lời

1

Vâng, bạn đã không cung cấp việc triển khai hoạt động của một số bộ phận quan trọng, đặc biệt là SA_Duplicate, vì vậy, đây chủ yếu là công việc đoán. Nhưng, tôi nghĩ câu trả lời có thể khá đơn giản.

Range.Value2 là thuộc tính, không phải là biến. Vì vậy, đằng sau hậu trường nó thực sự là hai chức năng, cho phép gọi chúng là Range.let_Value2Range.get_Value2.

Điều đó nói rằng, làm thế nào để bạn mong đợi cuộc gọi SA_Duplicate rngSort.Value2, arrSort hoạt động? Bởi vì những gì tôi thấy là SA_Duplicate rngSort.get_Value2, arrSort.Tôi giả định rằng rngSort.get_Value2 đang tạo một SafeArray mới, sau đó sao chép dữ liệu từ cấu trúc dữ liệu nội bộ của Excel vào SafeArray đó. Nếu tôi là chính xác, sau đó bạn sẽ ghi dữ liệu của bạn vào một bộ đệm tạm thời mà VBA sau đó loại bỏ và Excel đã quên.

Bạn cần sử dụng rngSort.let_Value2 arrSort thường được gọi là rngSort.Value2 = arrSort.

Là một lưu ý phụ, nếu get_Value2 phân bổ mảng mới như tôi nghĩ, cả hai cuộc gọi SA_Duplicate là không cần thiết và bạn có thể sắp xếp mảng được trả về tại chỗ. Hãy nhớ chuyển nó đến let_Value2 bằng cách gán biến mảng trở lại thuộc tính khi bạn hoàn tất.

Các vấn đề liên quan