![]() |
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:
Ergebnis nach jedem Schleifendurchlauf:
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; 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! |
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:
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.
Parameters[I + 6] := PAnsiChar(IntToStr(I));
Mit
Delphi-Quellcode:
sollte es klappen.
PAnsiChar(AnsiString(IntToStr(I)))
Edit: Ok, nach dem zweiten Lesen ist das keine direkte Antwort auf die Frage, aber vielleicht liegt da ja eine Nebenwirkung der Casts... |
AW: Array über Schleife ausfüllen
Danke...:P
BERLIN Zitat:
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: |
AW: Array über Schleife ausfüllen
Nur so ne Idee: Unterschied zwischen
Delphi-Quellcode:
und
Parameters[I + 6] := PAnsiChar(IntToStr(I));
Delphi-Quellcode:
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.
Parameters[6] := PAnsiChar(AnsiString(FileList[0]));
|
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:
) 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.
PAnsiChar(IntToStr(I))
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 ![]() 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). |
AW: Array über Schleife ausfüllen
Liste der Anhänge anzeigen (Anzahl: 2)
Zitat:
Zitat:
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:
Zitat:
|
AW: Array über Schleife ausfüllen
Liste der Anhänge anzeigen (Anzahl: 1)
Mit gefüllter FileList:
Zitat:
Delphi-Quellcode:
...er will array of PAnsiChar
InitError := FGsApiInitWithArgs(FGsInstance, Length(FParameters), FParameters);
|
AW: Array über Schleife ausfüllen
Zitat:
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:
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ß. |
AW: Array über Schleife ausfüllen
Zitat:
|
AW: Array über Schleife ausfüllen
Und was hatte ich zum AnsiString-Cast gesagt?
Delphi-Quellcode:
Also ist das Selbe, wie mit deiner expliziten Temp-Variable.
FParameters[B + 6] := PAnsiChar(AnsiString(FileList[I]));
// Delphi macht daraus ein ImpliziteAnsiStringVariable := AnsiString(FileList[I]); FParameters[B + 6] := PAnsiChar(ImpliziteAnsiStringVariable);
Delphi-Quellcode:
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.
Temp := IntToStr(A);
FParameters[B + 6] := PAnsiChar(AnsiString(Temp)); // wird zu Temp := IntToStr(A); ImpliziteAnsiStringVariable := AnsiString(Temp); FParameters[B + 6] := PAnsiChar(ImpliziteAnsiStringVariable); 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) |
AW: Array über Schleife ausfüllen
:roll:
Bitte, Bitte...wie ist es richtig? :cry: Zitat:
Delphi-Quellcode:
FParameters[6] := PAnsiChar(AnsiString(FileList[0]));
|
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:
. (am Ende dann DisposeStr, weil wegen Speicherleck)
NewStr
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. |
AW: Array über Schleife ausfüllen
Um das Problem mal zu veranschaulichen:
Delphi-Quellcode:
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.
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. |
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:
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; |
AW: Array über Schleife ausfüllen
Hach, an was man sich so alles gewöhnen könnte.
![]()
Delphi-Quellcode:
bzw.
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.
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