Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   Delphi Unterschiedliche Ergebnisse TList.Sort 32bit / 64bit CompareItems (https://www.delphipraxis.net/213053-unterschiedliche-ergebnisse-tlist-sort-32bit-64bit-compareitems.html)

Alex_ITA01 16. Mai 2023 12:35

Unterschiedliche Ergebnisse TList.Sort 32bit / 64bit CompareItems
 
Hallo zusammen,
ich stehe aktuell auf dem Schlauch.
Die Sortierfunktion von TList gibt mir ein anderes Ergebnis bei einem 32bit Compilat wie bei einem 64bit Compilat.
Könnt ihr mir sagen warum?

Delphi 11, Windows 10 64bit

Delphi-Quellcode:
unit Unit3;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Math;

type
  PMyStruct = ^TMyStruct;
  TMyStruct = packed record
    Value1 : LongInt;
    Value2 : Double;
    Value3 : Byte;

    function GetDataStr: String;
  end;

type
  TForm3 = class(TForm)
    Memo1: TMemo;
    Memo2: TMemo;
    procedure FormCreate(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form3: TForm3;

implementation

{$R *.dfm}

procedure TForm3.FormCreate(Sender: TObject);
var
  aList : TList;
  aData : PMyStruct;
  i: Integer;

  function CompareItems(const Item1,Item2: PMyStruct): Integer;
  begin
    Result := Math.CompareValue(Item1^.Value1, Item2^.Value1);
    if Result <> 0 then
      exit;

    Result := Math.CompareValue(Item2^.Value2, Item1^.Value2);
    if Result <> 0 then
      exit;

    Result := Math.CompareValue(Item1^.Value3, Item2^.Value3);
  end;

begin
  aList := TList.Create;
  try
    for i := 0 to 5 do
    begin
      New(aData);
      aData^.Value1 := 1429078;
      aData^.Value2 := 6.220;
      aData^.Value3 := i;

      aList.Add(aData);
    end;

    for i := 0 to 5 do
    begin
      New(aData);
      aData^.Value1 := 1429079;
      aData^.Value2 := 6.220;
      aData^.Value3 := i;

      aList.Add(aData);
    end;

    for i := 0 to 5 do
    begin
      New(aData);
      aData^.Value1 := 1429080;
      aData^.Value2 := 6.220;
      aData^.Value3 := i;

      aList.Add(aData);
    end;

    for i := 0 to 0 do
    begin
      New(aData);
      aData^.Value1 := 1429081;
      aData^.Value2 := 6.220;
      aData^.Value3 := i;

      aList.Add(aData);
    end;

    for i := 0 to 1 do
    begin
      New(aData);
      aData^.Value1 := 1429081;
      aData^.Value2 := 5.810;
      aData^.Value3 := i;

      aList.Add(aData);
    end;

    for i := 0 to 4 do
    begin
      New(aData);
      aData^.Value1 := 1429082;
      aData^.Value2 := 5.810;
      aData^.Value3 := i;

      aList.Add(aData);
    end;

    for i := 0 to 5 do
    begin
      New(aData);
      aData^.Value1 := 1429083;
      aData^.Value2 := 5.810;
      aData^.Value3 := i;

      aList.Add(aData);
    end;

    for i := 0 to 5 do
    begin
      New(aData);
      aData^.Value1 := 1429084;
      aData^.Value2 := 5.810;
      aData^.Value3 := i;

      aList.Add(aData);
    end;

    Memo1.Clear;
    Memo1.Lines.Add('VOR Sortierung');
    for i := 0 to aList.Count - 1 do
    begin
      aData := aList.Items[i];

      Memo1.Lines.Add('(' + i.ToString +') - aData: ' + aData^.GetDataStr);
    end;

    aList.Sort(@CompareItems);

    Memo2.Clear;
    Memo2.Lines.Add('NACH Sortierung');
    for i := 0 to aList.Count - 1 do
    begin
      aData := aList.Items[i];

      Memo2.Lines.Add('(' + i.ToString +') - aData: ' + aData^.GetDataStr);
    end;

  finally
    FreeAndNil(aList);
  end;
end;

{ TMyStruct }

function TMyStruct.GetDataStr: String;
begin
  Result := Value1.ToString + '/' + FloatToStrF(Value2, ffFixed, 15, 3) + '/' + Value3.ToString;
end;

end.
Ergebnis nach Sortierung 32bit:
NACH Sortierung
(0) - aData: 1429078/6,220/0
(1) - aData: 1429078/6,220/1
(2) - aData: 1429078/6,220/2
(3) - aData: 1429078/6,220/3
(4) - aData: 1429078/6,220/4
(5) - aData: 1429078/6,220/5
(6) - aData: 1429079/6,220/0
(7) - aData: 1429079/6,220/1
(8) - aData: 1429079/6,220/2
(9) - aData: 1429079/6,220/3
(10) - aData: 1429079/6,220/4
(11) - aData: 1429079/6,220/5
(12) - aData: 1429080/6,220/0
(13) - aData: 1429080/6,220/1
(14) - aData: 1429080/6,220/2
(15) - aData: 1429080/6,220/3
(16) - aData: 1429080/6,220/4
(17) - aData: 1429080/6,220/5
(18) - aData: 1429081/6,220/0
(19) - aData: 1429081/5,810/0
(20) - aData: 1429081/5,810/1
(21) - aData: 1429082/5,810/0
(22) - aData: 1429082/5,810/1
(23) - aData: 1429082/5,810/2
(24) - aData: 1429082/5,810/3
(25) - aData: 1429082/5,810/4
(26) - aData: 1429083/5,810/0
(27) - aData: 1429083/5,810/1
(28) - aData: 1429083/5,810/2
(29) - aData: 1429083/5,810/3
(30) - aData: 1429083/5,810/4
(31) - aData: 1429083/5,810/5
(32) - aData: 1429084/5,810/0
(33) - aData: 1429084/5,810/1
(34) - aData: 1429084/5,810/2
(35) - aData: 1429084/5,810/3
(36) - aData: 1429084/5,810/4
(37) - aData: 1429084/5,810/5

Ergebnis nach Sortierung 64bit:
NACH Sortierung
(0) - aData: 1429082/5,810/3
(1) - aData: 1429082/5,810/4
(2) - aData: 1429083/5,810/0
(3) - aData: 1429083/5,810/1
(4) - aData: 1429082/5,810/2
(5) - aData: 1429081/5,810/0
(6) - aData: 1429081/5,810/1
(7) - aData: 1429082/5,810/0
(8) - aData: 1429082/5,810/1
(9) - aData: 1429083/5,810/2
(10) - aData: 1429084/5,810/2
(11) - aData: 1429084/5,810/3
(12) - aData: 1429084/5,810/4
(13) - aData: 1429084/5,810/5
(14) - aData: 1429084/5,810/1
(15) - aData: 1429083/5,810/3
(16) - aData: 1429083/5,810/4
(17) - aData: 1429083/5,810/5
(18) - aData: 1429084/5,810/0
(19) - aData: 1429078/6,220/5
(20) - aData: 1429079/6,220/0
(21) - aData: 1429079/6,220/1
(22) - aData: 1429079/6,220/2
(23) - aData: 1429078/6,220/4
(24) - aData: 1429078/6,220/0
(25) - aData: 1429078/6,220/1
(26) - aData: 1429078/6,220/2
(27) - aData: 1429078/6,220/3
(28) - aData: 1429079/6,220/3
(29) - aData: 1429080/6,220/3
(30) - aData: 1429080/6,220/4
(31) - aData: 1429080/6,220/5
(32) - aData: 1429081/6,220/0
(33) - aData: 1429080/6,220/2
(34) - aData: 1429079/6,220/4
(35) - aData: 1429079/6,220/5
(36) - aData: 1429080/6,220/0
(37) - aData: 1429080/6,220/1

Anmerkung:
Erste Durchlauf von "Result := Math.CompareValue(Item1^.Value1, Item2^.Value1);" unter 32bit ist
Item1^.Value1 -> 1429078
und
Item2^.Value1 -> 1429081

Erste Durchlauf von "Result := Math.CompareValue(Item1^.Value1, Item2^.Value1);" unter 64bit ist
Item1^.Value1 -> 1429081
und
Item2^.Value1 -> 1429081

Das heißt, die Liste selber ruft meine Comparefunktion schon unterschiedlich auf...

Uwe Raabe 16. Mai 2023 12:51

AW: Unterschiedliche Ergebnisse TList.Sort 32bit / 64bit CompareItems
 
Du kannst eine lokale Funktion nicht als Pointer übergeben. Das geht spätestens bei 64-Bit wegen der anderen Aufrufkonventionen schief. Das @ beim Sort ist schon ein markanter Hinweis für eine Fehlerursache.

Alex_ITA01 16. Mai 2023 12:57

AW: Unterschiedliche Ergebnisse TList.Sort 32bit / 64bit CompareItems
 
Liste der Anhänge anzeigen (Anzahl: 1)
ok, wie muss es dann heißen?
Selbst in der Hilfe steht es doch so... Woher soll man das dann wissen :roll:

Andreas13 16. Mai 2023 13:23

AW: Unterschiedliche Ergebnisse TList.Sort 32bit / 64bit CompareItems
 
Wahrscheinlich klappt es, wenn Du die function CompareItems(..) direkt in TForm3 = class(TForm) verschiebst.

Uwe Raabe 16. Mai 2023 13:26

AW: Unterschiedliche Ergebnisse TList.Sort 32bit / 64bit CompareItems
 
Entweder so:
Delphi-Quellcode:
type
  PMyStruct = ^TMyStruct;
  TMyStruct = packed record
    Value1 : LongInt;
    Value2 : Double;
    Value3 : Byte;

    class function Compare(const A, B: TMyStruct): TValueRelationship; static;
    function GetDataStr: String;
  end;

class function TMyStruct.Compare(const A, B: TMyStruct): TValueRelationship;
begin
  Result := CompareValue(A.Value1, B.Value1);
  if Result <> 0 then
    exit;

  Result := CompareValue(A.Value2, B.Value2);
  if Result <> 0 then
    exit;

  Result := CompareValue(A.Value3, B.Value3);
end;

function CompareItems(PItem1,PItem2: Pointer): Integer;
begin
  Result := TMyStruct.Compare(PMyStruct(PItem1)^, PMyStruct(PItem2)^);
end;

...
    aList.Sort(CompareItems);
...
Oder gleich mit einer generischen Liste (Das spart übrigens auch die Freigabe der Records):
Delphi-Quellcode:
type
  TMyStruct = packed record
    Value1 : LongInt;
    Value2 : Double;
    Value3 : Byte;

    constructor Create(const AValue1: LongInt; const AValue2: Double; const AValue3: Byte);
    class function Compare(const A, B: TMyStruct): Integer; static;
    function GetDataStr: String;
  end;

  TMyStructList = class(TList<TMyStruct>)
  public
    constructor Create;
  end;

constructor TMyStruct.Create(const AValue1: LongInt; const AValue2: Double; const AValue3: Byte);
begin
  Value1 := AValue1;
  Value2 := AValue2;
  Value3 := AValue3;
end;

class function TMyStruct.Compare(const A, B: TMyStruct): Integer;
begin
  Result := CompareValue(A.Value1, B.Value1);
  if Result <> 0 then
    exit;

  Result := CompareValue(A.Value2, B.Value2);
  if Result <> 0 then
    exit;

  Result := CompareValue(A.Value3, B.Value3);
end;

function TMyStruct.GetDataStr: String;
begin
  Result := Value1.ToString + '/' + FloatToStrF(Value2, ffFixed, 15, 3) + '/' + Value3.ToString;
end;

constructor TMyStructList.Create;
begin
  inherited Create(TComparer<TMyStruct>.Construct(TMyStruct.Compare));
end;

...
  var aList := TMyStructList.Create;
  try
    for var i := 0 to 5 do
      aList.Add(TMyStruct.Create(1429078, 6.220, i));

    for var i := 0 to 5 do
      aList.Add(TMyStruct.Create(1429079,6.220, i));

    for var i := 0 to 5 do
      aList.Add(TMyStruct.Create(1429080,6.220, i));

    for var i := 0 to 0 do
      aList.Add(TMyStruct.Create(1429081,6.220, i));

    for var i := 0 to 1 do
      aList.Add(TMyStruct.Create(1429081,5.810, i));

    for var i := 0 to 4 do
      aList.Add(TMyStruct.Create(1429082,5.810, i));

    for var i := 0 to 5 do
      aList.Add(TMyStruct.Create(1429083,5.810, i));

    for var i := 0 to 5 do
      aList.Add(TMyStruct.Create(1429084,5.810, i));

    Memo1.Clear;
    Memo1.Lines.Add('VOR Sortierung');
    for var i := 0 to aList.Count - 1 do
    begin
      var aData := aList.Items[i];

      Memo1.Lines.Add('(' + i.ToString +') - aData: ' + aData.GetDataStr);
    end;

    aList.Sort;

    Memo2.Clear;
    Memo2.Lines.Add('NACH Sortierung');
    for var i := 0 to aList.Count - 1 do
    begin
      var aData := aList.Items[i];

      Memo2.Lines.Add('(' + i.ToString +') - aData: ' + aData.GetDataStr);
    end;

  finally
    FreeAndNil(aList);
  end;
...

Alex_ITA01 16. Mai 2023 13:50

AW: Unterschiedliche Ergebnisse TList.Sort 32bit / 64bit CompareItems
 
Danke Uwe, habe Variante 1 genommen.
Auf Generic konnte ich nicht so leicht umbauen aber Variante 1 funktioniert. Danke.

Was lernen wir daraus? Nicht auf die Hilfe hören :-D

Uwe Raabe 16. Mai 2023 13:58

AW: Unterschiedliche Ergebnisse TList.Sort 32bit / 64bit CompareItems
 
Zitat:

Zitat von Alex_ITA01 (Beitrag 1522492)
Was lernen wir daraus? Nicht auf die Hilfe hören :-D

Vermutlich stammt die Hilfe noch von Delphi 1 oder so.

Stevie 16. Mai 2023 22:36

AW: Unterschiedliche Ergebnisse TList.Sort 32bit / 64bit CompareItems
 
Mir war so, als ob ich dieses Problem schonmal gesehen habe und jupp, hier isses.

Zitat:

Zitat von Alex_ITA01 (Beitrag 1522492)
Was lernen wir daraus? Nicht auf die Hilfe hören :-D

Ich seh in der Hilfe keine verschachtelte Routine...

Habs mal reported

himitsu 16. Mai 2023 22:58

AW: Unterschiedliche Ergebnisse TList.Sort 32bit / 64bit CompareItems
 
Methoden und diese Inline, sowie die Generischen haben einen "unsichtbaren" ersten Self-Parameter.

Somit stimmt die Signatur dann nicht, im Vergleich mit einer "reinen" Prozedur.
Bei Methoden und Klassenmethoden kann man das mit Static beheben, aber für Untermethoden leider nicht.

Stevie 17. Mai 2023 00:04

AW: Unterschiedliche Ergebnisse TList.Sort 32bit / 64bit CompareItems
 
Zitat:

Zitat von himitsu (Beitrag 1522509)
Methoden und diese Inline, sowie die Generischen haben einen "unsichtbaren" ersten Self-Parameter.

Somit stimmt die Signatur dann nicht, im Vergleich mit einer "reinen" Prozedur.
Bei Methoden und Klassenmethoden kann man das mit Static beheben, aber für Untermethoden leider nicht.

Ja, sie haben einen unsichtbaren ersten Parameter (unter 32bit nur wenn etwas aus dem äußere Scope referenziert wird, unter 64bit immer).
Dieser hat aber nix mit dem Self-Parameter wie bei Methoden (entweder Referenz zur Instanz oder zu Klasse) zu tun, sondern hier wird die Addresse zum umgebenden Stackframe übergeben.
Darüber wird der Zugriff auf die Variablen außerhalb der Routine realisiert.

Wie David auf die von mir verlinkten Stackoverflow richtig erklärt, gibt es diesen unter Win64 immer, egal ob man auf den umgebenden Scope zugreift oder nicht.
Unter Win32 gibt es diesen nur wenn man auf den umgebenden Scope zugreift - deshalb funktioniert es dort.

Unter Win64 werden die ersten vier Parameter in den Registern rcx, rdx, r8 und r9 übergeben. Also sollten Item1 und Item2 in rcx und rdx sein, da wirds auch durch den Aufruf aus Sort heraus übergeben.
Dadurch, dass die Implementierung aber nested ist, nimmt der Compiler aber an, dass diese in rdx und r8 sind, denn in rcx erwartet er den Stackzeiger, wenn die Routine lokal aufgerufen wird.

Das kann man sehr einfach auch überprüfen, wenn man einfach mal in das FormCreate
Delphi-Quellcode:
CompareItems(nil, nil);
reinschreibt, dort einen Stoppunkt setzt und sich das Disassembly anschaut:

Delphi-Quellcode:
mov rcx,rbp
xor edx,edx
xor r8,r8
call CompareItems
Wie man sieht, wird rbp (Frame pointer) in rcx übergeben, wie ich zuvor beschrieben habe. Verschiebe ich CompareItems nach außerhalb schauts so aus:

Delphi-Quellcode:
xor ecx,ecx
xor edx,edx
call CompareItems


Alle Zeitangaben in WEZ +1. Es ist jetzt 20:34 Uhr.
Seite 1 von 2  1 2      

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz