Autor Beitrag
Wolle92
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 1296

Windows Vista Home Premium
Delphi 7 PE, Delphi 7 Portable, bald C++ & DirectX
BeitragVerfasst: Fr 14.09.07 07:36 
Hallo,
weiß irgendjemand, wieso der Code nur funktioniert, wenn ich ein ShowMessage irgenwo in die Funktion schreibe?
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
function decode(text: PChar): PChar; export;
var current, text2, result2: String;
    i: Integer;
begin
  result2 := '';
  text2 := String(text);
  while not (text2 = '') do
  begin
    current := Copy(text2,1,Pos('z',text2)-1);
    text2 := Copy(text2,Pos('z',text2)+1,Length(text2)-Pos('z',text2));
    i := StrtoInt(current);
    result2 := result2 + Chr(Byte(i));
  end;
  result := PChar(result2);
end;

Und wenn irgendwo ShowMessage('Ein beliebiger Text!'); steht, kommt kein Fehler mehr... So gibts den Fehler '*' is not valid integer value! wobei das * für dieses Kästchen steht, wenn ein Zeichen nicht angezeigt werden kann...
Gausi
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 8554
Erhaltene Danke: 481

Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
BeitragVerfasst: Fr 14.09.07 07:44 
Du gibst einen Zeiger auf eine lokale Variable zurück. Das geht nicht. Da die String-Variable nach verlassen der Prozedur nicht mehr benötigt wird, kann der Platz wieder überschrieben werden, und der Zeiger wird ungültig.

Ob der Fehler nun daran liegt, weiß ich nicht. Ich könnte mir vorstellen, dass die Speicherstruktur durch diese Message anders aussieht, und das Programm deswegen zufällig durchläuft.

_________________
We are, we were and will not be.
Wolle92 Threadstarter
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 1296

Windows Vista Home Premium
Delphi 7 PE, Delphi 7 Portable, bald C++ & DirectX
BeitragVerfasst: Fr 14.09.07 07:55 
aber wie soll ich den wert denn sonst zurückgeben?
Gausi
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 8554
Erhaltene Danke: 481

Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
BeitragVerfasst: Fr 14.09.07 08:54 
Wenns innerhalb eines Programms ist, als String.

Bei dlls bin ich mir nicht sicher, wie das geht. Eine Möglichkeit beschreibt Luckie hier.

_________________
We are, we were and will not be.
Wolle92 Threadstarter
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 1296

Windows Vista Home Premium
Delphi 7 PE, Delphi 7 Portable, bald C++ & DirectX
BeitragVerfasst: Fr 14.09.07 09:00 
das versteh ich jetzt nicht so ganz... mit der DLL...
Gausi
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 8554
Erhaltene Danke: 481

Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
BeitragVerfasst: Fr 14.09.07 09:05 
Soll das denn ne Funktion für ne dll werden, oder willst du diese Funktion ganz normal in einem Programm benutzen?

_________________
We are, we were and will not be.
Stefan.Buchholtz
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 612

WIN 2000, WIN XP, Mac OS X
D7 Enterprise, XCode, Eclipse, Ruby On Rails
BeitragVerfasst: Fr 14.09.07 09:17 
Der Aufrufer muss einen Speicherblock in der passenden Größe erzeugen und ihn als Parameter an die DLL-Funktion übergeben, die ihn dann füllen kann. Von der Delphi-Runtime verwaltete Typen mit dynamischer Speicherverwaltung (Strings, dynamische Arrays, Varianten) dürfen nicht zu oder von einer DLL übergeben werden. Einzige Ausnahme: Eingabeparameter, die von der DLL-Funktion nicht in der Länge verändert werden.
Wenn das aufrufende Programm auch in Delphi geschrieben ist, kannst du natürlich auch ShareMem einbinden, dann können die dynamischen Typen problemlos zwischen DLL und Hauptprogramm ausgetauscht werden.

Willst du ShareMem nicht verwenden, müsste es so gehen (ungetestet):

ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
uses
  StrUtils;

function decode(const text: PChar; 
                const resultBuf : PChar;
                const resultBufSize: Integer): Integer; export;  
var 
  current : String;  
  text2   : String;
  i       : Integer;  
  idx     : Integer;
  p       : Integer;
begin  
  // text in lokale Variable kopieren statt einfach zuweisen - wird ja unten verändert
  SetLength(text2, StrLen(text));
  StrCopy(PChar(text2), text);

  idx := 0;
  while text2 <> '' do  
  begin
    p := Pos('z', text2); // das wird mehrfach gebraucht, ist effizienter so
    current := Copy(text2, 1, p-1);  
    text2 := LeftStr(text2, p + 1);  // LeftStr kopiert von der angegebenen Position bis zum Ende
    i := StrToInt(current);  
    if resultBufSize <= idx then
    begin
      // resultBuf zu klein
      Result := -1;
      exit;
    end; 
    resultBuf[idx] := Chr(Byte(i));
    Inc(idx);
  end;  
  Result := idx; // Anzahl der Zeichen in resultBuf zurückgeben  
end;


Stefan

_________________
Ein Computer ohne Windows ist wie eine Schokoladentorte ohne Senf.
Wolle92 Threadstarter
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 1296

Windows Vista Home Premium
Delphi 7 PE, Delphi 7 Portable, bald C++ & DirectX
BeitragVerfasst: Fr 14.09.07 09:46 
OK, aber ich verändere die Parameter doch überhaupt nicht... die werden doch alle in String-Variablen kopiert...
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19346
Erhaltene Danke: 1754

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Fr 14.09.07 10:00 
Der Punkt ist, dass du einen Zeiger auf eine bestimmte Speicherstelle zurückliefern wolltest, an dieser Speicherstelle aber gar kein gültiger Wert mehr steht, wenn die Funktion beendet ist. Denn die Variable war ja nur lokal definiert, existiert also nur während der Laufzeit der Prozedur. Genau das hat user profile iconGausi aber auch schon gesagt.

Die Lösung, die user profile iconStefan.Buchholtz gepostet hat, besteht darin, dass du vorher einen Zeiger auf einen Speicherbereich nimmst, dort den benötigten Speicher allozierst, d.h. reservierst, und dann die Funktion aufrufst und als Parameter diesen Zeiger sowie die maximale Größe an Daten, die da reingelegt werden dürfen übergibst.
Dieses Prinzip wird auch bei den Windows-API-Funktionen sehr oft verwendet...
Stefan.Buchholtz
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 612

WIN 2000, WIN XP, Mac OS X
D7 Enterprise, XCode, Eclipse, Ruby On Rails
BeitragVerfasst: Fr 14.09.07 10:23 
user profile iconWolle92 hat folgendes geschrieben:
OK, aber ich verändere die Parameter doch überhaupt nicht... die werden doch alle in String-Variablen kopiert...


Eine String-Variable enthält ja nicht direkt den Text, sondern das ist intern ein Zeiger auf einen Speicherblock, in dem der Text drin steht. Bei einer Zuweisung von String-Variablen wird erstmal nur der Zeiger kopiert, so dass beide String-Variablen auf den gleichen Speicherblock zeigen. Zusätzlich merkt sich die Delphi-Runtime, dass jetzt zwei String-Variablen auf den Block verweisen. Erst wenn eine der beiden Variablen verändert wird, wird der eigentliche Text in einen neuen Speicherblock kopiert.
Man muss relativ vorsichtig dabei sein, wenn man zwischen String und PChars castet. Beide sind ein Zeiger auf einen Speicherblock, in dem der Text steht, aber nur Strings haben diese automatische Speicherverwaltung - die nicht funktioniert, wenn der Speicher zwischen Programm und DLL ausgetauscht wird, ausser man verwendet ShareMem. Deswegen habe ich für den Parameter text einen neuen String mit SetLength erzeugt - SetLength erzeugt immer einen neuen Speicherblock der angegebenen Länge - und kopiere text explizit mit StrCopy da rein. Dann kann ich sicher sein, dass der Speicher, der von text2 verwendet wird, in der DLL angefordert wurde, so dass spätere Veränderungen von text2 innerhalb der Funktion unproblematisch sind.

Stefan

_________________
Ein Computer ohne Windows ist wie eine Schokoladentorte ohne Senf.
Wolle92 Threadstarter
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 1296

Windows Vista Home Premium
Delphi 7 PE, Delphi 7 Portable, bald C++ & DirectX
BeitragVerfasst: Fr 14.09.07 11:52 
ah, danke
Wolle92 Threadstarter
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 1296

Windows Vista Home Premium
Delphi 7 PE, Delphi 7 Portable, bald C++ & DirectX
BeitragVerfasst: Fr 14.09.07 13:15 
kann man resultBuf nicht auch als var anstatt als const deklarieren, und die Größe, falls nötig, in der Prozedur ändern? Ich halte es für sinnvoller, als dass man alles nochmal neu dekodieren muss, wenn resultBuf nur um eins zu klein ist...
Ich stelle mir das ungefähr so vor:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
var resultBuf2: PChar;
...
if resultBufSize <= idx then
begin
  Inc(resultBufSize);
  SetLength(resultBuf2, StrLen(resultBuf));
  StrCopy(resultBuf2, resultBuf);
  SetLength(resultBuf, StrLen(resultBuf)+1);
  StrCopy(resultBuf, resultBuf2);
end;

Wäre das nicht auch Möglich?
Stefan.Buchholtz
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 612

WIN 2000, WIN XP, Mac OS X
D7 Enterprise, XCode, Eclipse, Ruby On Rails
BeitragVerfasst: Fr 14.09.07 13:25 
user profile iconWolle92 hat folgendes geschrieben:
kann man resultBuf nicht auch als var anstatt als const deklarieren, und die Größe, falls nötig, in der Prozedur ändern? Ich halte es für sinnvoller, als dass man alles nochmal neu dekodieren muss, wenn resultBuf nur um eins zu klein ist...
Ich stelle mir das ungefähr so vor:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
var resultBuf2: PChar;
...
if resultBufSize <= idx then
begin
  Inc(resultBufSize);
  SetLength(resultBuf2, StrLen(resultBuf));
  StrCopy(resultBuf2, resultBuf);
  SetLength(resultBuf, StrLen(resultBuf)+1);
  StrCopy(resultBuf, resultBuf2);
end;

Wäre das nicht auch Möglich?


Nein, genau das geht ja eben nicht. Eine DLL darf keine Speicherblöcke freigeben oder in der Größe ändern, die es vom Hauptprogramm bekommen hat, und umgekehrt entsprechend genauso. Wenn man das macht, kommt die Speicherverwaltung von Delphi durcheinander und es hagelt Zugriffsverletzungen oder sonstige Fehler. Deswegen muss der Aufrufer den Speicherblock erzeugen.
Wenn ich die Funktion richtig verstanden habe, stehen in deinem übergebenen Text durch 'z' getrennte Zahlen drin und die Funktion extrahiert diese Zahlen und schreibt die entsprechenden ASCII-Zeichen in das Resultat. Das bedeutet, dass der Rückgabewert immer kürzer als der übergebene Text ist - somit ist die Länge des übergebenen Textes eine gute Größe für den Ergebnispuffer - das passt immer.

Stefan

_________________
Ein Computer ohne Windows ist wie eine Schokoladentorte ohne Senf.
Wolle92 Threadstarter
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 1296

Windows Vista Home Premium
Delphi 7 PE, Delphi 7 Portable, bald C++ & DirectX
BeitragVerfasst: Fr 14.09.07 14:01 
stimmt, könnte man theoretisch sogar noch durch 3 teilen, da die meisten Zeichen ja einen zweistelligen Code haben, einige einen dreistelligen und das z kommt auch noch dazu

...

Aber wenn ich resultBuf als const deklariere, dann kann ich es doch nicht verändern und bekomme kein Ergebnis...
Motzi
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2931

XP Prof, Vista Business
D6, D2k5-D2k7 je Prof
BeitragVerfasst: Fr 14.09.07 14:35 
Ich verweise einfach mal auf meine beiden Tutorials über Strings und Pointer. Beide zu finden auf www.manuel-poeter.de

Gruß, Motzi

_________________
gringo pussy cats - eef i see you i will pull your tail out by eets roots!
Wolle92 Threadstarter
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 1296

Windows Vista Home Premium
Delphi 7 PE, Delphi 7 Portable, bald C++ & DirectX
BeitragVerfasst: Di 18.09.07 16:23 
Öhm... mal ne ganz blöde Frage, aber wie kann ich die Länge von einem PChar festlegen? Ich bekomme gerade immer eine AccessViolation wenn ich die decode-Funktion benutzen will...
Stefan.Buchholtz
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 612

WIN 2000, WIN XP, Mac OS X
D7 Enterprise, XCode, Eclipse, Ruby On Rails
BeitragVerfasst: Di 18.09.07 16:49 
Du musst dir den Speicher für den PChar mit GetMem(pCharVar, laenge); besorgen und wenn du ihn nicht mehr brauchst mit FreeMem(pCharVar); wieder freigeben. Als Länge musst du die Maximallänge des Strings + 1 angeben. +1 deshalb, weil PChars nullterminiert sind, d.h. das Ende des Strings wird mit einem Nullbyte angezeigt, für das auch noch Platz da sein muss.

Stefan

_________________
Ein Computer ohne Windows ist wie eine Schokoladentorte ohne Senf.
Wolle92 Threadstarter
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 1296

Windows Vista Home Premium
Delphi 7 PE, Delphi 7 Portable, bald C++ & DirectX
BeitragVerfasst: Di 18.09.07 17:24 
Tja, jetzt gibt es in dem Programm eine Fehlermeldung "'666**' is not valid integer value" * = Kästchen für nicht darstellbares Zeichen.
An der Länge des codeBuf kann nicht liegen, der ist ja viel länger als das Ergebnis...
Aber: Muss man eventuell noch irgendwas machen, bevor man FreeMem anwendet??
Außerdem ist es qwieder so, dass es funktionert wenn ich an bestimmten Stellen ShopwMEssage benutze :?!?:

ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
procedure TForm2.Button1Click(Sender: TObject);
var vfl, ls, buffer: String;
    codeBuf: PChar;
    f: TextFile;
    li, i, codeBufSize: Integer;
    decode: Tcode;
    handle1: THandle;
begin
  try
    handle1 := LoadLibrary(PChar(ExtractFilePath(Application.ExeName)+'crypt.dll'));
    if handle1 = 0 then
    begin
      ShowMessage('Erforderliche DLL crypt.dll nicht gefunden...' + #13#10 +
      'Vokabelsammlung konnte nicht geladen werden!');
      Exit;
    end;
    @decode := GetProcAddress(handle1, 'decode');
    vfl := ExtractFilePath(Application.ExeName) + 'lists\' + ListView1.Selected.Caption + '.vfl';
    try
      AssignFile(f, vfl);
      Reset(f);
      ReadLn(f, ls);
      codeBufSize := Length(ls);
      GetMem(codeBuf, codeBufSize);
      decode(PChar(ls),codeBuf,codeBufSize);
      li := StrToInt(String(codeBuf));
      SetLength(Form1.ask, li);
      SetLength(Form1.answer, li);
      for i := 0 to li - 1 do
      begin
        ReadLn(f, buffer);
        codeBufSize := Length(buffer);
        FreeMem(codeBuf);
        GetMem(codeBuf, codeBufSize);
        decode(PChar(buffer),codeBuf,codeBufSize);
        Form1.ask[i] := String(codeBuf);
        ReadLn(f, buffer);
        codeBufSize := Length(buffer);
        FreeMem(codeBuf);
        GetMem(codeBuf, codeBufSize);
        decode(PChar(buffer),codeBuf,codeBufSize);
        Form1.answer[i] := String(codeBuf);
      end;
      Closefile(f);
      FreeMem(codeBuf);
    except
      on E: EInOutError do
      begin
        ShowMessage('Fehler beim lesen der Datei! Fehler-Code: ' + IntToStr(e.ErrorCode));
      end;
    end;
    Form1.Label5.Caption := 'Aktuelle Sammlung: ' + ListView1.Selected.Caption;
    Form1.Button3.Enabled := true;
    Button1.Enabled := false;
    FreeLibrary(handle1);
    Form2.Hide;
  except
    on EAccessViolation do
      ShowMessage('Bitte erst eine Sammlung auswählen!');
  end;
end;


Zuletzt bearbeitet von Wolle92 am Di 18.09.07 17:54, insgesamt 1-mal bearbeitet
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19346
Erhaltene Danke: 1754

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Di 18.09.07 17:50 
Naja, wo tritt der Fehler denn auf? Raten würde ich, dass das in Zeile 26 der Fall ist. Vermutlich, weil eben ein Nicht-Integer-Wert in codeBuf zurückgegeben wird...
Aber das müsstest du schon selbst überprüfen. Bzw.: Mit was für einem Dateiinhalt kann ich das testen? Also was steht in deiner vfl-Datei drin? ^^

Und noch was: Da der Beitrag hier offen im Internet steht, würde ich dir raten die E-Mail-Adresse aus deinem Quelltext zu entfernen, das Postfach könnte sonst ein wenig spamhaltig werden... ;-)
Wolle92 Threadstarter
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 1296

Windows Vista Home Premium
Delphi 7 PE, Delphi 7 Portable, bald C++ & DirectX
BeitragVerfasst: Di 18.09.07 17:57 
In meiner VFL-Datei stehen ASCII-Codes von Zeichen, die durch 'z' getrennt sind...
Im Anhang ist die Beispiel-VFL des Vokabeltrainers...
Die Dekodier-Funktion kannst du dir ja besorgen hier aus dem Beitrag...

OffTopic: Danke wegen der EMail-Adresse... bei mir kommen aber wenn dann immer nur so Schwälle... mal zwei Wochen jeden tag zehn Spam-Mails, dann 2 Wochen nix... Aber das ist ein anderes Thema
Einloggen, um Attachments anzusehen!