2015-12-01 9 views
10

EDYCJAKopiowanie tablicę do Range.Value2 SafeArray.pvData udane, ale Excel nie zaktualizować

Rzeczywiście, nie ma bezpośredni sposób edytowania wartości klasy w pamięci. Dzięki @AndASM za szczegółową odpowiedź i Carl; dobre przeczucie, było trafne. W tym momencie musiałem być zbyt splątany, zapominając, że Value2 to tylko własność.

Tymczasem ja zagłębił nieco głębiej z innych testów i debugowania OllyDbg i znalazłem kilka ciekawych rzeczy:

  1. Komórki są rozmieszczone w 16 x 1024 dziedzinach. Struktura zawierająca obszary może równie dobrze być arkuszem roboczym, ale nie mogę jeszcze potwierdzić;
  2. każdym razem właściwość Value wywoływana jest bezwzględne arkusz przesunięcia (wiersz, kolumna) wykorzystywane są do znalezienia odpowiedniego obszaru i następnie przez pewien indeksowania wewnątrz obszaru uzyskać rzeczywistą wartość;
  3. Tworzy się SAFEARRAY 2D typu VARIANT;
  4. Wartości nie są pobierane w sąsiednim bloku, ale pojedynczo. Oznacza to, że każdy "punkt" w zakresie (wiersz, kol.) Jest wysyłany do procedury indeksowania , aby zwrócić wartość (wariant, oczywiście) dla odpowiadającego jej elementu SAFEARRAY o wartości ;
  5. W związku z powyższym, za każdym razem, gdy pobierzesz wartość przez Range.Value2(row,col), cały proces zostanie powtórzony dla wszystkich wartości w zakresie. Wyobraź sobie wydajność, jeśli robisz to kilka razy w trakcie procedury lub, co gorsza, w pętli. Po prostu nie rób; lepiej jest utworzyć kopię Value2 i adresując go poprzez indeksowanie;
  6. końcu, a zwłaszcza rozkład wartości wewnątrz SAFEARRAY.pvData jest kolumnowe (col,row) nie wierszowa, które można znaleźć intuicyjne i w sprzeczności z trybu VBA indeksowania , która (row,col). Może się to przydać, jeśli potrzebujesz uzyskiwania dostępu do danych pvData bezpośrednio w pamięci i zachowania spójności wymiarów. Jako przykład zakres jak poniżej

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

    byłyby przechowywane w pvData w następującej kolejności:

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

Mam nadzieję, że powyższe pomaga. Podsumowując, w przypadku braku takiej wyeksportowanej funkcji w programie Excel najlepszym sposobem jest utworzenie kopii Value2, sortowanie/manipulowanie nią w kierunku pożądanego wyniku i przypisanie jej z powrotem do właściwości zakresu.


Niedawno zakończyłem odmianę QuickSort i zamierzam wprowadzić ją w programie Excel. Algorytm jest skuteczny i naprawdę przyniósłby wartość jako dodatek, gdyby nie dodatkowy czas poświęcony na umieszczenie wartości tablicowych w zakresie. Transpozycja działa tylko dla mniej niż 65537, podczas gdy "tablica wariantów wklejania do zakresu zajmuje zbyt wiele czasu na dużych sortach.

Tak, napisałem kilka procedur, które pozwoliłyby skopiować wartości 2D z zakresu do tablicy 1D (1D jest potrzebne do sortowania) i (po sortowaniu jest zrobione) odkładając je, wszystko oparte na SAFEARRAY i MemCopy (RtlMoveMemory) i, na przemian, WriteProcessMemory.

Wszystko działa dobrze, jeśli chodzi o operacje związane z pamięcią: - wartości zakresu są kopiowane do tablicy (z jednej karty SafeArray.pvData na drugą); - wartości tablic (po uruchomieniu sortowania) zostaną pomyślnie skopiowane do pliku Range.Value2 SafeArray.pvData.

Mimo to zakres nie aktualizuje się, ponieważ wydaje się, że cofa się do starych wartości (więcej o tym w poniższym kodzie). Dlaczego "Range.Value2 = SomeOther2dArray" działa i nie modyfikuje danych bezpośrednio w pamięci? Mam przeczucie, że czegoś tu brakuje. Czy potrzebne jest również sortowanie/aktualizacja formuły?

Oto główny procedura:

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 

Powiedzmy wartości w przedziale są 4, 3, 2 i 1. Przed SA_Duplicate arrSort, rngSort.Value2 pamięć brzmi:

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 

gdzie 130836704 to Range.Value2 SafeArray.pvData i 129997032 jest SortArray SafeArray.pvData. Każda 16-bajtowa partia reprezentuje wariant rzeczywistych danych, odczytanych z pamięci (bez tłumaczenia LE, tylko w języku heksagonalnym), z pierwszymi 2 bajtami wskazującymi typ VarType. W tym przypadku vbDouble.

Po kopii, zgodnie z oczekiwaniami, pamięć brzmi:

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 

Po sortowania jest kompletny, SortArray SafeArray.pvData brzmi:

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

Po wykonaniu SA_Duplicate rngSort.Value2, arrSort pamięć pokazuje, że Range.Value2 SafeArray.pvData została zaktualizowana:

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 

Wszystko w porządku patrząc tak daleko, poza tym, że Debug.Print VarPtr(rngSort.Value2(1, 1)) & vbTab & Mem_ReadHex[...] pokazuje, że wartości przerzucony z powrotem do pierwotnej kolejności:

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

Proszę podzielić jakieś przemyślenia lub metod, które znalazłeś skuteczne. Każda pomoc jest doceniana. To frustrujące, że trzeba czekać na Excela około 4 sekund (sortowanie 1 000 000 + komórek), gdy nawet najbardziej wymagający sort zajmuje mniej.

Z góry dzięki!

+0

Myślę, że to może być wnikliwe wiedzieć, co robi SA_Duplicate, ale ponieważ po prostu przypisanie jego wyniku w dół do zakresu.Value2 po prostu działa, zakładam, że nowe dane są poprawne? Podejrzewam, że Excel wewnętrznie przechowuje dane w innej strukturze niż zabezpieczenie, a właściwości .Value/.Value2 są po prostu powłokami, które wykonują tłumaczenie; oznaczałoby to, że bezpośrednia manipulacja pamięcią wariantu powrotu tych właściwości jest bezcelowa. –

Odpowiedz

1

Cóż, nie dostarczyłeś działającej implementacji kilku ważnych części, zwłaszcza SA_Duplicate, więc jest to głównie odgadywanie. Ale myślę, że odpowiedź jest najprostsza.

Range.Value2 jest właściwością, a nie zmienną. Więc za kulisami są to właściwie dwie funkcje, możesz je nazwać Range.let_Value2 i Range.get_Value2.

To powiedziawszy, jak można się spodziewać połączenia SA_Duplicate rngSort.Value2, arrSort? Ponieważ to, co widzę, to SA_Duplicate rngSort.get_Value2, arrSort.Zakładam, że rngSort.get_Value2 tworzy nową SafeArray, a następnie kopiuje dane z wewnętrznych struktur danych Excela do tego SafeArray. Jeśli mam rację, zapisujesz swoje dane w tymczasowym buforze, który VBA później odrzuca, a program Excel już zapomniał.

Należy użyć rngSort.let_Value2 arrSort, który jest zwykle nazywany rngSort.Value2 = arrSort.

Na marginesie, jeśli get_Value2 przydziela nową tablicę, jak myślę to robi, zarówno rozmowy SA_Duplicate są niepotrzebne i może być w stanie uporządkować wracającą tablicę w miejscu. Pamiętaj, aby przekazać go do let_Value2, przypisując zmienną tablicy z powrotem do właściwości, gdy skończysz.

Powiązane problemy