Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Array über Schleife ausfüllen (https://www.delphipraxis.net/206950-array-ueber-schleife-ausfuellen.html)

haentschman 11. Feb 2021 14:01

Delphi-Version: 10.1 Berlin

Array über Schleife ausfüllen
 
Hallo...8-)

Ich möchte ein dynamisches Array über eine Schleife füllen. Für die Demo wird die Schleifenvariable als String in den Parameter geschrieben.
Delphi-Quellcode:
function TGhostscript.PDFMerge(FileName: string; FileList: TStrings): Boolean;
var
  Parameters: array of PAnsiChar;
  InitError: Integer;

  procedure CreateMergeFiles;
  var
    I: Integer;
  begin
    for I := 0 to FileList.Count - 1 do
    begin
      Parameters[I + 6] := PAnsiChar(IntToStr(I));//PAnsiChar(AnsiString(FileList[I]));
    end;
  end;

begin
  if FDLLHandle = 0 then
  begin
    if not LoadDLL(FDLLPath) then
    begin
      Result := False;
      Exit;
    end;
  end;
  try
    SetLength(Parameters, FileList.Count + 6);

    Parameters[0] := '';
    Parameters[1] := '-dNOPAUSE';
    Parameters[2] := '-dBATCH';
    Parameters[3] := '-dPDFSETTINGS=/ebook';
    Parameters[4] := '-sDEVICE=pdfwrite';
    Parameters[5] := PAnsiChar(AnsiString('-sOutputFile=' + FileName));

//    Parameters[6] := PAnsiChar(AnsiString(FileList[0]));
//    Parameters[7] := PAnsiChar(AnsiString(FileList[1]));
//    Parameters[8] := PAnsiChar(AnsiString(FileList[2]));
//    Parameters[9] := PAnsiChar(AnsiString(FileList[3]));

    CreateMergeFiles;
Ergebnis nach jedem Schleifendurchlauf:
1:
[6] = 0
[7] = nil
[8] = nil
[9] = nil
I = 0

2:
[6] = 1
[7] = 1
[8] = nil
[9] = nil
I = 1

3:
[6] = 2
[7] = 2
[8] = 2
[9] = nil
I = 2

3:
[6] = 3
[7] = 3
[8] = 3
[9] = 3
I = 3

:shock:

Wenn ich die Parameter hard codiere (auskommentierter Text):
[6] = 0
[7] = 1
[8] = 2
[9] = 3

Erleuchtet mich mal. Ist normalerweise das 1*1 ... Arrays. :roll:

Was ich schon probiert habe (ohne Erfolg):
* Index des Parameters in der Schleife extern berechnet
* Mit jedem Schleifendurchlauf Array um 1 vergrößert
* Schleife rückwärts
* Parameter als private Variable
* Übergabe als var Parameter an CreateMergeFiles
* Optimierung EIN/AUS
...

PS: Einen Delphi Entwickler habe ich schon geschockt...:P Er konnte es auch nicht verstehen!

Gausi 11. Feb 2021 14:16

AW: Array über Schleife ausfüllen
 
Nutzt du eine sehr alte Delphi-Version (vor D2009), oder eine neuere? Denn in den neueren Versionen ist diese Zeile das Problem, denke ich:
Delphi-Quellcode:
Parameters[I + 6] := PAnsiChar(IntToStr(I));
IntToStr liefert einen Unicode-String, mit zwei Byte pro Zeichen, wovon jedes zweite oft gleich 0 ist (zumindest bei Zahlen ist das der Fall). Ein Cast auf PAnsiChar bewirkt dann, dass der String nach dem ersten Zeichen zu Ende ist - das zweite Byte ist dann der Nullterminator.

Mit
Delphi-Quellcode:
PAnsiChar(AnsiString(IntToStr(I)))
sollte es klappen.

Edit: Ok, nach dem zweiten Lesen ist das keine direkte Antwort auf die Frage, aber vielleicht liegt da ja eine Nebenwirkung der Casts...

haentschman 11. Feb 2021 14:32

AW: Array über Schleife ausfüllen
 
Danke...:P

BERLIN

Zitat:

Mit PAnsiChar(AnsiString(IntToStr(I))) sollte es klappen.
...hast Recht, aber die Werte sind immer gleich obwohl I sich geändert hat.
PS: PAnsiChar(AnsiString(IntToStr(I))) ändert nichts an der Tatsache. :cry:

Es schaut aus, daß die Parameter[6 + x] einen gleichen "Pointer" hätten...was aber nicht der Realität entpricht. :?

Die Frage ist: Wo liegt der Unterschied zwischen dem direktem Zuweisen und der Schleife...:gruebel:

Uwe Raabe 11. Feb 2021 14:45

AW: Array über Schleife ausfüllen
 
Nur so ne Idee: Unterschied zwischen
Delphi-Quellcode:
Parameters[I + 6] := PAnsiChar(IntToStr(I));
und
Delphi-Quellcode:
Parameters[6] := PAnsiChar(AnsiString(FileList[0]));
ist, dass IntToStr einen String zurückgibt, dessen Scope nur temporär ist. Gleiches gilt für die Konvertierung über AnsiString. Wenn die PAnsiChar gültig bleiben sollen, dann musst du den Speicher dafür selbst bereitstellen.

himitsu 11. Feb 2021 15:04

AW: Array über Schleife ausfüllen
 
Nein, mit PChar auf einen veränderlichen String geht sowas garnicht.

Delphi nutzt für das Result dieses IntToStr-Aufrufs (
Delphi-Quellcode:
PAnsiChar(IntToStr(I))
) die selbe Variable, denn es ist in der Schleife auch der selbe Code :zwinker:, also im nächsten Durchlauf sind somit die vorherigen Pointer ungültig.
Selbes gilt in Schleifen für alle Funktionsaufrufe und Casts.

Und du kannst froh sein, dass hier der neue String im nächsten Durchlauf zufällig auf der selben Speicheradresse gelandet ist, womit die alten PChar rein zufällig wieder auf "diesen" String zeigten ... der neue String-Variableninhalt hätte aber auch genauso gut sonstwo landen können.


Man kann nun z.B. mit Sowas wie Delphi-Referenz durchsuchenNewStr arbeiten,
oder vor dem PChar/PAnsiChar muß der String irgendwo "sicher" gespeichert werden (RefCount größer 1)
z.B. durch eine "dauerhafte" Kopie der Strings in einer TStringList oder einem TArray<string>, mindestens so lange wie diese PChar's benötigt werden.
Und wer bissl verrückt mutig ist, der könnte auch die Referenzzählung kurz anheben (+1) und am Ende über das PChar-Array und einen String-Cast die Referenzzählung aller Zeiger wieder zurücksetzen (-1).


Bzw., da hier ANSI (warum nicht UTF-8 ?) nötig ist, ein TArray<AnsiString> / TArray<UTF8String> oder eine TAnsiStringList, zum Speichern.



PS: Wenn dieses PChar-Array "Parameters" nur als "Ausgabe" genutzt wird, dann darf man es auch gern als AnsiString-Array deklarieren.
Einer DLL-Funktion ist es egal, da Delphi-Strings intern auch die Merkmale des PChars enthalten (Zeiger zeigt auf das erste Char und am Ende folgt eine #0 ... genauer sind es hier sogar zwei #0#0).

haentschman 11. Feb 2021 15:06

AW: Array über Schleife ausfüllen
 
Liste der Anhänge anzeigen (Anzahl: 2)
Zitat:

Wenn die PAnsiChar gültig bleiben sollen, dann musst du den Speicher dafür selbst bereitstellen.
:gruebel: Ich weise doch nur einen String dem Parameter zu?
Zitat:

dass IntToStr einen String zurückgibt, dessen Scope nur temporär ist.
Test ???:
Delphi-Quellcode:
procedure CreateMergeFiles;
  var
    A: Integer;
    B: Integer;
    I: Integer;
    Temp: string;
  begin
    A := 10;
    B := -1;
    for I := 0 to FileList.Count - 1 do
    begin
      Inc(A);
      Inc(B);
      Temp := IntToStr(A);
      FParameters[B + 6] := PAnsiChar(AnsiString(Temp));
    end;
  end;
Zitat:

dass IntToStr einen String zurückgibt, dessen Scope nur temporär ist.
Zitat:

z.B. durch eine "dauerhafte" Kopie der Strings in einer TStringList
Gut. Aber die Strings in der FileListe sind doch seperate Pointer auf die Strings...oder? Trotzdem sind die Parameter falsch...

haentschman 11. Feb 2021 15:21

AW: Array über Schleife ausfüllen
 
Liste der Anhänge anzeigen (Anzahl: 1)
Mit gefüllter FileList:

Zitat:

Wenn dieses PChar-Array "Parameters" nur als "Ausgabe" genutzt wird
wegen:
Delphi-Quellcode:
InitError := FGsApiInitWithArgs(FGsInstance, Length(FParameters), FParameters);
...er will array of PAnsiChar

himitsu 11. Feb 2021 15:26

AW: Array über Schleife ausfüllen
 
Zitat:

Ich weise doch nur einen String dem Parameter zu?
siehe mein letzter Post.

Nein, du weißt einen "anderen" String (nach Funktionsaufruf IntStStr(S) oder Typecast ala Ansistring(S) ) als PChar zu.
Und im nächsten SchleifenDurchlauf nochmal genau die selben Variablen, wodurch ihr vorherriger Inhalt überschrieben wird und deine alten Zeiger somit ins Nirvana zeigen.


Zitat:

er will array of PAnsiChar
egal ... darfst gern ein Array-of-AnsiString in ein Array-of-PAnsiChar casten, oder schreib bei dir den Funktionsheader um, damit man ein Array-of-AnsiString reingeben kann. (der DLL ist es egal, wenn es es nur ein Lesezugriff ist)

PS: genau sowas "ändere" ich bei Header-Imports gern mal, damit man möglichs mit Delphi-Typen arbeiten kann und eben nicht unbedingt mit PChars rumhantieren muß.

haentschman 11. Feb 2021 15:34

AW: Array über Schleife ausfüllen
 
Zitat:

siehe mein letzter Post.
Values [6-9] aus TStringList/TStrings...:lol:

himitsu 11. Feb 2021 15:43

AW: Array über Schleife ausfüllen
 
Und was hatte ich zum AnsiString-Cast gesagt?

Delphi-Quellcode:
FParameters[B + 6] := PAnsiChar(AnsiString(FileList[I]));
// Delphi macht daraus ein
ImpliziteAnsiStringVariable := AnsiString(FileList[I]);
FParameters[B + 6] := PAnsiChar(ImpliziteAnsiStringVariable);
Also ist das Selbe, wie mit deiner expliziten Temp-Variable.
Delphi-Quellcode:
Temp := IntToStr(A);
FParameters[B + 6] := PAnsiChar(AnsiString(Temp));
// wird zu
Temp := IntToStr(A);
ImpliziteAnsiStringVariable := AnsiString(Temp);
FParameters[B + 6] := PAnsiChar(ImpliziteAnsiStringVariable);
Also in der Schleife sind alle vorherrigen PAnsiChar ungültig, weil sie auf einen nicht mehr existierenden String-Inahlt zeigen, da die "selbe" Variable im nächsten Durchlauf überschieben wird.
Und das Schlimmste, direkt nach Verlassen der CreateMergeFiles sind auch noch die kompletten Variablen Temp und ImpliziteAnsiStringVariable futsch, da sie am Funktionsende freigegeben wurden, womit dann auch noch der letzte PAnsiChar ungültig ist.


Da eine TStringList (und nur diese ... es darf nicht auf andere TStrings bezogen werden, wie z.B. TMemoStrings eines TMemo.Lines) intern jede Zeile/Strings als einzelnen "String" speichert, nicht wie z.B. ein Memo nur den kompletten Text speichert und beim Zugriff auf .Strings[] die Zeile als neuer String rauskopiert wird.
Daher kann man auch "längerfristig" die Zeilen einer TStringList auch als PChar speichern (so lange dazwischen eben keine weiteren Funktionen/Casts sind, welche einen "zusätzlichen" String generieren, der nichts mit dem in der StringList zu tun hat)

haentschman 11. Feb 2021 15:52

AW: Array über Schleife ausfüllen
 
:roll:
Bitte, Bitte...wie ist es richtig? :cry:

Zitat:

FParameters[B + 6] := PAnsiChar(AnsiString(FileList[I]));
warum funktioniert das?
Delphi-Quellcode:
FParameters[6] := PAnsiChar(AnsiString(FileList[0]));

himitsu 11. Feb 2021 15:58

AW: Array über Schleife ausfüllen
 
Du muß die generierten AnsiString für jede Zeilen bis zum Ende von PDFMerge gespeichert haben, (also irgendwo genügend AnsiString-Variablen für jedes Item)
dann bleiben die PAnsiChar-Referenzen auch so lange gültig.
(alternativ kann man auch an der Referenzzählung rumpfuschen ... und Dieses am Ende wieder rückgängig, weil sonst Speicherleck)

Oder du erstellst eben für jeden PAnsiChar einen eigenen Speicher, wie z.B. mit
Delphi-Quellcode:
NewStr
. (am Ende dann DisposeStr, weil wegen Speicherleck)

Oder eben du änderst die Signatur deiner Funktion, oder castest beim Aufruf das Array.
Dann könnte man auch mit einem Array-of-AnsiString arbeiten, wo dieses Array dann seine eigenen String-Referenzen hat, damit im nächsten Durchlauf die alten "Kopien" weiterhin gültig bleiben.

Oder mit zwei Arrays arbeiten ... einmal mit AnsiString für die String-Referenzen und die Kopie als PAnsiChar auf die jeweiligen AnsiStringArrayItems.

Uwe Raabe 11. Feb 2021 16:17

AW: Array über Schleife ausfüllen
 
Um das Problem mal zu veranschaulichen:
Delphi-Quellcode:
program Project773;

{$APPTYPE CONSOLE}

function GetP(S: string): PAnsiChar;
begin
  Result := PAnsiChar(AnsiString(S));
end;

var
  P1: PAnsiChar;
  P2: PAnsiChar;
begin
  P1 := GetP('Hallo Welt');
  Writeln('P1:', P1);
  P2 := GetP('Hurz');
  Writeln('P1:', P1);
  Writeln('P2:', P2);
  Readln;
end.
Der Result von GetP ist quasi in dem Moment schon ungültig, wo er zurückgegeben wird, weil er auf irgendwo auf den Stack zeigt - und zwar auf eine Stelle, die nicht mehr gültig ist.

haentschman 11. Feb 2021 16:43

AW: Array über Schleife ausfüllen
 
Liste der Anhänge anzeigen (Anzahl: 1)
Danke an Alle...:P

Mit den Informationen habe ich es dann auch geschnallt. :oops:
Zitat:

Du muß die generierten AnsiString für jede Zeilen bis zum Ende von PDFMerge gespeichert haben
...war der Auslöser zum Probieren.

Jetzt geht es: :P
Zwischenspeicherung in TList<AnsiString>
Delphi-Quellcode:
function TSEAMGhostscript.PDFMerge(FileName: string; FileList: TStrings): Boolean;
var
  ParametersTemp: TList<AnsiString>;
  InitError: Integer;

  procedure CreateMergeFiles;
  var
    I: Integer;
  begin
    for I := 0 to FileList.Count - 1 do
    begin
      ParametersTemp.Add(AnsiString(FileList[I])); // <--
      FParameters[I + 6] := PAnsiChar(ParametersTemp[I]); // <--
    end;
  end;

begin
  if FDLLHandle = 0 then
  begin
    if not LoadDLL(FDLLPath) then
    begin
      Result := False;
      Exit;
    end;
  end;
  try
    ParametersTemp := TList<AnsiString>.Create; //<--
    try
      SetLength(FParameters, FileList.Count + 6);

      FParameters[0] := '';
      FParameters[1] := '-dNOPAUSE';
      FParameters[2] := '-dBATCH';
      FParameters[3] := '-dPDFSETTINGS=/ebook';
      FParameters[4] := '-sDEVICE=pdfwrite';
      FParameters[5] := PAnsiChar(AnsiString('-sOutputFile=' + FileName));
      CreateMergeFiles;

      InitError := FGsApiInitWithArgs(FGsInstance, Length(FParameters), FParameters);
      Result := (InitError = 0);
      if InitError <> 0 then
      begin
        if Assigned(FOnError) then
        begin
          FOnError(Self, Format('Fehlercode: %d', [InitError]));
        end;
      end;
    finally
      ParametersTemp.Free;
    end;
  finally
    FGsApiExit(FGsInstance);
  end;

  if not Result then
  begin
    if Assigned(FOnError) then
    begin
      FOnError(Self, Format('Fehler beim Erstellen: %s', [FileName]));
    end;
  end;
end;

himitsu 11. Feb 2021 17:24

AW: Array über Schleife ausfüllen
 
Hach, an was man sich so alles gewöhnen könnte.
http://docwiki.embarcadero.com/RADSt...Dynamic_Arrays

Delphi-Quellcode:
Temp: TArray<AnsiString>;

for S in FileList do
begin
  Temp := Temp + [AnsiString(S)];
  FParameters := FParameters + [PAnsiChar(Temp[High(Temp)])];

...
SetLength(FParameters, FileList.Count); // die 6 Zusätzlichen jetzt noch nicht dazu

// Und am Ende gibt sich Temp von selber frei.
bzw.
Delphi-Quellcode:
Temp := ['-sOutputFile=' + FileName];
FParameters := [
  '',
  '-dNOPAUSE',
  '-dBATCH',
  '-dPDFSETTINGS=/ebook',
  '-sDEVICE=pdfwrite',
  PAnsiChar(Temp[0])];
for S in FileList do begin
  Temp := Temp + [S]; // implizit ein + [AnsiString(S)];
  FParameters := FParameters + [PAnsiChar(Temp[High(Temp)])];
end;


Aber wie gesagt, ich würde FGsApiInitWithArgs einfach ein TArray<AnsiString> geben, anstatt einem TArray<PAnsiChar>,
und mir den ganzen PChar-Quatsch einfach schenken.
Delphi-Quellcode:
FParameters := [
  '',
  '-dNOPAUSE',
  '-dBATCH',
  '-dPDFSETTINGS=/ebook',
  '-sDEVICE=pdfwrite',
  '-sOutputFile=' + FileName];
for S in FileList do
  FParameters := FParameters + [S];

// wäre beides String gewesen, dann ginge sogar sowas
FParameters := ['', '-dNOPAUSE', '-dBATCH', '-dPDFSETTINGS=/ebook',
  '-sDEVICE=pdfwrite', '-sOutputFile=' + FileName] + FileList.ToStringArray;

Rein "technisch" bestünde sogar die Möglichkeit in einen "String" einen "AnsiString" reinzumachen,
da die LongStrings in Delphi sowohl CodePage als auch CharSize im String speichern.
Also in einer TStringList die Codierung der "Strings" auf ANSI umzuschreiben.
(ich weiß aber nicht, in wie weit die anfangs mit Delphi2009 eingeführten Casts noch aktiv sind, weil die waren arschlangsam, wenn "überall" bei jeder Stringzuweisung/Auslesen die Kodierung geprüft wird)

Ist aber nur was für echt Hartgesottene:
Delphi-Quellcode:
// CP_ACP: TStringList -> intern schonmal die CodePage ändern -> TArray<String> -> TArray<AnsiString>
// CP_UTF8: TStringList -> intern schonmal die CodePage ändern -> TArray<String> -> TArray<UTF8String>

type
  TStringListAccess = class(TStrings)
    FList: TStringItemList;
  end;

var
  SL: TStringList;
  AA: TArray<AnsiString>;
begin
  SL := TStringList.Create;
  SL.Add('123');
  SL.Add('äöü');

  {for var i := SL.Count-1 downto 0 do begin
    var S := SL[i];
    SetCodePage(RawByteString(Pointer(S)), CP_UTF8, True); // CP_ACP für ANSI
    SL[i] := S;
  end;}
  for var i := SL.Count-1 downto 0 do
    SetCodePage(RawByteString(Pointer(TStringListAccess(SL).FList[i].FString)), CP_UTF8, True); // CP_ACP für ANSI

  AA := TArray<AnsiString>(SL.ToStringArray);


Alle Zeitangaben in WEZ +1. Es ist jetzt 14:12 Uhr.

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