AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

Umgang mit PChar - Speichermanagement

Ein Thema von cmrudolph · begonnen am 25. Dez 2007 · letzter Beitrag vom 28. Dez 2007
Antwort Antwort
Seite 1 von 2  1 2      
cmrudolph

Registriert seit: 14. Aug 2006
29 Beiträge
 
Delphi 7 Professional
 
#1

Umgang mit PChar - Speichermanagement

  Alt 25. Dez 2007, 23:28
Guten Abend,

ich entwickle gerade ein Programm, welches eine Plugin-Schnittstelle aufweist. Die Plugins sollen dann in Form von DLLs vorliegen.
Das dynamische laden, übergeben von Callbackfunktionen und fast alles andere was dazu gehört funktioniert schon prächtig. Was mir jedoch Probleme bereitet ist der Umgang mit PChars. Da dies mein erstes Projekt ist, bei welchem ich Strings zwischen DLL und Hauptprogramm austauschen muss, kam ich noch nie in die Verlegenheit mich genauer mit den PChars beschäftigen zu müssen.

Wann und wie muss ich Speicher reservieren lassen?
Ich weiß bereits, dass der PChar Typecast nur verwendet werden kann, wenn ich eine damit gesetzte Variable nicht über den Funktionsrahmen hinaus verwenden möchte, weil der Speicher mit dem Beenden der Funktion wieder freigegeben wird. Somit fällt das also für die Rückgabe einer Funktion weg.

Weiterhin habe ich gelesen, dass StrAlloc und StrDispose nicht mehr verwendet werden sollten. Was verwende ich alternativ, wenn nicht PChar?

Um ein besonders hässliches Codefragment zu zeigen:
Delphi-Quellcode:
function CurlWriteFuncPChar(ptr: Pointer; size: size_t; nmemb: size_t; pppc: Pointer): size_t; stdcall;
var
  oldLen, newLen: Cardinal;
  oldData, newData: PChar;
  p1: PPChar;
  p2: PChar;
begin
  p1 := PPPChar(pppc)^;
  oldData := p1^;
  oldLen := StrLen(oldData);
  newLen := oldLen + size * nmemb;
  newData := StrAlloc(newLen + 1);
  StrCopy(newData, oldData);
  p2:=StrAlloc(size*nmemb+1);
  p2[size * nmemb] := #0;
  CopyMemory(p2, ptr, size * nmemb);
  StrCat(newData, p2);
  StrDispose(p2);
  StrDispose(oldData);
  p1^ := newData;
  Result := size * nmemb;
end;
Diese Callbackfunktion funktioniert, ist aber hässlich. Das geht doch bestimmt auch eleganter.
Die Funktion bekommt als Übergabewerte:
- ptr: Pointer auf Daten
- size / nmemb: Blockgröße sowie Blockzahl
- pppc: Pointer auf Pointer auf PChar, dort sollen die Daten hin. Es wurde 1 Byte Speicher mit StrAlloc reserviert und mit einem Nullbyte gefüllt.

mfG, Christian
  Mit Zitat antworten Zitat
Der_Unwissende

Registriert seit: 13. Dez 2003
Ort: Berlin
1.756 Beiträge
 
#2

Re: Umgang mit PChar - Speichermanagement

  Alt 26. Dez 2007, 11:14
Hi,

Zitat von cmrudolph:
Ich weiß bereits, dass der PChar Typecast nur verwendet werden kann, wenn ich eine damit gesetzte Variable nicht über den Funktionsrahmen hinaus verwenden möchte, weil der Speicher mit dem Beenden der Funktion wieder freigegeben wird. Somit fällt das also für die Rückgabe einer Funktion weg.
Wo hast Du denn die Information her? Also für einen Typecast mag das stimmen, kommt aber darauf an worauf Du den Typecast anwendenst. Was Du unterscheiden musst sind Variablen die auf dem Stack landen und solche, die auf dem Heap erzeugt werden. Auf dem Stack landen immer nur primitive Typen (sowas wie Integer, Cardinal, Double, ..., Char, Bool, String und Records). Der Stack ist ein einfacher Stapel, der jeder Funktion zur Verfügung steht. Die Funktionen wissen in welcher Reihenfolge Variablen dort abgelegt wurden und können die entsprechend runter nehmen (wie ein Tellerstapel, man kann immer nur das oberste Element runternehmen oder oben rauf legen). Das Besondere dabei ist, dass sich alle Funktionen den gleichen Stapel teilen, ruft also Funktion X die Funktion Y auf, so sichert X alle verwendeten Variablen in dem die erstmal auf dem Stack abgelegt werden. Y legt dann noch eigene rauf (ist jetzt alles etwas vereinfacht gesagt). Wichtig ist jedenfalls, dass wenn Y fertig ist, X weiterrechnen möchte. Dazu muss X also seine "alten" Variablen vom Stapel nehmen und mit denen Arbeiten. Hier muss also sichergestellt werden, dass die obersten Variablen genau die sind, die X abgelegt hat (Reihenfolge muss auch stimmen!). Das geht nur, wenn Y den Stapel so zurückgibt, wie er ursprünglich übergeben wurde.
Und genau das macht dann die lokalen Variablen lokal. Diese werden einfach auf dem Stack erzeugt und müssen mit dem Verlassen der Routine auch von dort entfernt werden. Entsprechend wird der belegte Speicher frei gegeben und Referenzen auf diesen Speicher wären ungültig.

Als Alternative steht dem Programmierer immer der Heap zur Verfügung. In dieser Datenstruktur werden beliebige Variablen verwaltet. Variablen trifft es nicht ganz, eigentlich müsste man eher von den Daten sprechen, die durch die Variablen referenziert werden. Jedenfalls steht der Heap quasi global zur Verfügung. Ein Datum, dass im Heap gespeichert wird muss entweder explizit frei gegeben werden (oder wird dies aut. beim Programmende, ist allerdings häufig eher unsauber).
Auf dem Heap wiederum landen Daten immer dann, wenn man Speicher alloziert. Implizit geschieht dies für alle Klassen, erzeugte Exemplare landen immer im Heap (und müssen auch mit Free freigegeben werden). Für Interfaces (bzw. die Klassen, die diese implementieren) sieht es dann ganz analog aus, allerdings kümmert sich die Referenzzählung dann selbstständig um die Freigabe.
Aber auch Speicher, den Du mit New oder GetMem allozierst landet auf dem Heap. Hier muss man dann wirklich etwas zwischen Variable und dem allozierten Speicher unterscheiden:

Delphi-Quellcode:
procedure doFoo;
var p: ^Integer;
begin
  p := New(Integer);
  p^ := 10;
  ...
end;
Hier würde jetzt p eine lokale Variable sein, der Zeiger p wird also nach dem Verlassen der Prozedur frei gegeben. Der allozierte Speicher auf den p zeigt(e) bleibt aber reserviert und enthält ein Integer mit dem Wert 10. Würdest Du die Adresse auf die p zeigt also an eine andere Routine weiterreichen, dann könnte auch diese (unabhängig davon ob doFoo beendet wurde) noch auf diesen Speicher zugreifen. Ganz wichtig, man muss auch sicherstellen, dass dieser Speicher irgendwann mal frei gegeben wird. Passiert zwar automatisch mit dem Programmende, aber bis dahin kann doFoo sehr oft aufgerufen werden (entsprechend viel Speicher wird ggf. einfach unerreichbar reserviert bleiben). Wird Speicher nicht frei gegeben und ist auch nicht mehr erreichbar, dann spricht man gerne von einem Speicherloch (macht den Rechner schön langsam und führte unter Win 9x auch gerne mal zu Fehlern wegen Mangel an Speicher, sollte natürlich nirgendwo überhaupt vorkommen! Gibt aber Memorymanager, die einen darauf hinweisen).

Ja, jedenfalls kannst Du so auch Speicher reservieren, der als PChar verwendet wird. Ein PChar ist nichts anderes als ein Zeiger auf einen Null-Terminierten String (nicht mit dem Delphi-String verwechseln!). Ein PChar speichert einfach nur die Adresse einer Speicherzelle. Hier steht das erste Zeichen, der String endet mit dem ersten vorkommen von chr(0). Wenn Du wiederum einen Delphi-String verwendest, entspricht das dem Delphi-AnsiString. Im Prinzip passiert hier das gleiche, zusätzlich reserviert Delphi jedoch noch Speicher für die Längeninformation (steht an einem negativen Offset vor dem ersten Zeichen). Hier findest Du auch die Begründung, warum StrAlloc als veraltet gilt, da hier explizit ein Zeiger auf ein AnsiChar bekommst. Statt mit diesen zu arbeiten, wird eben der Einsatz von AnsiString empfohlen, das im Prinzip auf gleiche Art und Weise funktioniert.

Das eigentliche Problem bei der Verwendung von Strings in DLLs liegt darin, dass Delphi den Speicher für Strings automatisch verwaltet, andere Sprachen dies jedoch nicht unbedingt tun. Da DLLs aber gerade aus verschiedenen Sprachen nutzbar sind, würde es hier zu interessanten Effekten bei der Mischung kommen.
Der sicherste Weg besteht deshalb einfach darin, dass Du entsprechend viel Speicher allozierst und den Ansi-String dorthin kopierst. Das ganze kannst Du ggf. auch in eine eigene Routine auslagern:

Delphi-Quellcode:
procedure StrToPChar(const s: String; const size: size_t; out p: PChar);
begin
  p := PChar(GetMem(length(s) * size_t + 1));
  p^[length(s) * size_t] := chr(0);
  copyMemory(s[1], p, length(s) * size_t]);
end;
Ist jetzt ungetestet. Gerade beim CopyMemory nochmal schaun, ich verwechsel da immer gerne Src und Dest in der Reihenfolge (klar, könnte mal in die Hilfe schauen...). Jedenfalls sollte das eigentlich klappen, man muss natürlich auch irgendwann p (bzw. den Speicher der durch p referenziert wird) wieder frei geben.

Gruß Der Unwissende
  Mit Zitat antworten Zitat
cmrudolph

Registriert seit: 14. Aug 2006
29 Beiträge
 
Delphi 7 Professional
 
#3

Re: Umgang mit PChar - Speichermanagement

  Alt 26. Dez 2007, 12:49
Recht herzlichen Dank für die ausführlichen Ausführungen. Die Informationen zum PChar() Befehl hatte ich aus der Delphi Hilfe entnommen (PChar -> String Abhängigkeiten).
Zitat:
Gelegentlich muss ein langer String in einen null-terminierten String konvertiert werden, wenn Sie beispielsweise eine Funktion aufrufen, die einen PChar als Parameter benötigt. Wenn Sie einen String in einen PChar umwandeln müssen, sind Sie dafür verantwortlich, dass der resultierende PChar verfügbar bleibt. Da für lange Strings eine Referenzzählung durchgeführt wird, wird der Abhängigkeitswert des Strings um Eins erhöht, obwohl der eigentliche Referenzzähler nicht erhöht wird. Sobald der Referenzzähler den Wert Null erreicht, wird der String freigegeben, trotz der noch vorhandenen Abhängigkeit. Der umgewandelte PChar ist ebenfalls nicht mehr verfügbar, obwohl die Routine, an die er übergeben wurde, möglicherweise noch darauf zugreift.
Wie ein PChar / AnsiString / ShortString intern aussieht war mir bereits bekannt. Die Differenzierung zwischen Stack und Heap sowie deren Verwendung brachte jedoch Klarheit. Dieses ständige "dem User durch Automatisierung helfen" stört mich bei Delphi mittlerweile gewaltig. Den Ausführungen nach kommt das Problem, welches ich zur Zeit habe auch daher. Ich bekomme nämlich immer eine Access Violation wenn das Programm beendet wird und ich vorher Speicher reserviert aber auch wieder freigegeben habe. Ich denke, da versucht Delphi automatisch den reservierten Speicher freizugeben, der jedoch schon freigegeben ist. Sollte ich den Pointer nach der Freigabe ggf auf nil setzen? (Werde ich nachher mal probieren...)

Befindet sich die Result Variable auf dem Heap oder Stack?
Falls diese auf dem Stack ist, dann muss sie ja von der aufrufenden Funktion initialisiert worden sein.
(Liegt die nicht auf dem Stack direkt unter den Parametern der Funktion?)

Teilen sich DLL und Programm den gleichen Speicherbereich? Will meinen, kann die DLL den Speicher, der von dem Programm reserviert wurde einfach wieder freigeben, in ihn schreiben und lesen?

Wo finde ich weitere Informationen wann Delphi welchen Speicher automatisch reserviert und wieder freigibt? Also beispielsweise: ich habe einem AnsiString, weise einen Wert zu und Delphi kümmert sich ums Speichermanagement.

Wo werden nur die Pointer gespeichert (wie bei den Klassen) und wo wird die Variable direkt gespeichert (wie bei einem Integer)?
/* Edit:
Habe Informationen in der Delphi Hilfe gefunden, diese Frage ist beantwortet.
Für alle die es nachlesen wollen:
Delphi Sprachreferenz -> Der Speichermanager
*/

Ein Haufen Fragen, vielleicht hat ja jemand Lust ein wenig Licht ins Dunkel zu bringen.

mfG,
Christian
  Mit Zitat antworten Zitat
Ghostwalker

Registriert seit: 16. Jun 2003
Ort: Schönwald
1.299 Beiträge
 
Delphi 10.3 Rio
 
#4

Re: Umgang mit PChar - Speichermanagement

  Alt 26. Dez 2007, 13:44
Variablen stehen in der Regel auf dem Stack. Genauso die Zeigervariablen. Die Adressen, auf den Speicherbereich, auf den ein Zeiger zeigt, stehen im Heap, genauso wie Objektinstanzen.

Um die Verwaltung von String, Ansistring und Widestring brauchst du dich nicht kümmern, das erledigt Delphi für dich. Lediglich bei PChar, PWidechar mußt du dich selbst kümmern, da sie ja keine Strings im herkömmlichen Sinn sind, sondern Zeiger. Auch bei dynamischen Array's mußt du dich um die Freigabe/Reservierung (oder in dem fall um die Dimensionierung) kümmern.
Uwe
e=mc² or energy = milk * coffee²
  Mit Zitat antworten Zitat
Der_Unwissende

Registriert seit: 13. Dez 2003
Ort: Berlin
1.756 Beiträge
 
#5

Re: Umgang mit PChar - Speichermanagement

  Alt 26. Dez 2007, 15:48
Zitat von cmrudolph:
Ich bekomme nämlich immer eine Access Violation wenn das Programm beendet wird und ich vorher Speicher reserviert aber auch wieder freigegeben habe. Ich denke, da versucht Delphi automatisch den reservierten Speicher freizugeben, der jedoch schon freigegeben ist. Sollte ich den Pointer nach der Freigabe ggf auf nil setzen? (Werde ich nachher mal probieren...)
Eine AV kommt in der Regel dadurch zustande, dass man generell auf ein ungültiges Datum zugreift, das kann man auf ganz unterschiedliche Arten erreichen. Hier gibt es auch die Möglichkeit, dass Delphi etwas frei geben möchte, was bereits durch den Nutzer frei gegeben wurde (bzw. umgekehrt). Sehr viel häufiger kommt es aber vor, dass der Benutzer etwas freigeben oder benutzen möchte, was er selbst schon frei gegeben hat.
Es kommt hier also ein wenig darauf an, wie Du den Speicher reservierst und/oder wieder frei gibst.

Zitat von cmrudolph:
Befindet sich die Result Variable auf dem Heap oder Stack?
Auch das kann man so nicht richtig beantworten. Hat ein wenig was mit der Aufrufkonvention zu tun (schau mal nach register, pascal, ccall, stdcall, ...). Das sollten erstmal die sein, die Delphi verwendet, aber andere Sprachen kennen auch noch jvm oder so. Jedenfalls kann die Aufrufkonvention auch gleich darüber entscheiden, ob eine Variable auf dem Stack abgelegt oder über ein Register übergeben wird. Wichtig ist es, dass man hier die Variable und das Referenzierte Datum unterscheidet.
Wird eine Zahl (Byte, Short, Word, ...), ein Char, ein Boolscher Wert, ein String, ein statisches Array, ein Set, ein Record oder eine Enumeration (Aufzählungstyp mit Type) übergeben, kannst Du die alle wie eine lokale Kopie betrachten (da kümmer sich dann auch Delphi um das Freigeben). Natürlich kannst Du die auch an weitere Routinen durchreichen (wird eben erneut eine Kopie erzeugt, bzw. bei einem String kommt dann auch eine Referenzzählung zum Einsatz).
Bei allem was Du dyn. selbst erzeugst (Objekt-Exemplare, dyn. Arrays oder eben direkt reservierter Speicher mit GetMem bzw. New, StrAlloc usw.) führt immer zur Speicherreservierung auf dem Heap.

Zitat von cmrudolph:
Teilen sich DLL und Programm den gleichen Speicherbereich? Will meinen, kann die DLL den Speicher, der von dem Programm reserviert wurde einfach wieder freigeben, in ihn schreiben und lesen?
Ja, das kannst Du. Die DLL wird dyn. in den Speicherbereich der restlichen Applikation gebunden, der Austausch von Adressen über diese Grenzen sind möglich (und nötig). Insbesondere musst Du auf diese Art und Weise sogar bei z.B. PChars den Speicher frei geben (die DLL kann eben nicht wie bei einem String entscheiden ob dieses Datum noch referenziert wird oder nicht).
  Mit Zitat antworten Zitat
Benutzerbild von Luckie
Luckie

Registriert seit: 29. Mai 2002
37.621 Beiträge
 
Delphi 2006 Professional
 
#6

Re: Umgang mit PChar - Speichermanagement

  Alt 27. Dez 2007, 12:31
Was Strings und DLLs angeht: http://www.michael-puff.de/Artikel/2...String_DLL.php
Michael
Ein Teil meines Codes würde euch verunsichern.
  Mit Zitat antworten Zitat
cmrudolph

Registriert seit: 14. Aug 2006
29 Beiträge
 
Delphi 7 Professional
 
#7

Re: Umgang mit PChar - Speichermanagement

  Alt 27. Dez 2007, 22:11
Soweit habe ich das jetzt alles verstanden. Eine Frage ist jedoch noch aufgetaucht:
Kann es sein, dass SetLength nicht in einer DLL ausgeführt werden sollte, wenn die Daten vom Hauptprogramm weiterverarbeitet werden sollen?

mfG
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.184 Beiträge
 
Delphi 12 Athens
 
#8

Re: Umgang mit PChar - Speichermanagement

  Alt 27. Dez 2007, 22:42
Alles, was das Speichermanagement von Strings, dynamischen Arrays und ähnlichem betrifft, darf nicht außerhalb (auch nicht in einem Anderem) Speichermanager ausgeführt werden.

Normalerweise hat die Anwenung einen eigenen Speichermanager und die DLL ihren eigenen.
Also hast du soweit Recht (SetLength in DLL nicht mit einem String aus der Anwendung)-

Aber bei Verwendung von ShareMem [OH], FastMM oder anderer SharedMemoryManager ist dieses wiederrum möglich, da dort beide (EXE&DLL ... wenn dieses in beiden installiert) den selben MM verwenden.
$2B or not $2B
  Mit Zitat antworten Zitat
cmrudolph

Registriert seit: 14. Aug 2006
29 Beiträge
 
Delphi 7 Professional
 
#9

Re: Umgang mit PChar - Speichermanagement

  Alt 28. Dez 2007, 12:31
Gut, nun verwende ich kein Array of PChar mehr, sondern eine verkettete Liste. Das funktioniert auch ganz gut.

Ich skizziere einmal den Ablauf des Programmes:
Die Verkettete Liste sieht wie folgt aus:
Delphi-Quellcode:
type
  PItemList = ^TItemList;
  PPItemList = ^PItemList;
  TItemList = record
    Next: PItemList;
    Text: PChar;
  end;
Es wird die Prozedur testproc der DLL aufgerufen. Diese sieht wie folgt aus:

Delphi-Quellcode:
procedure testproc(Text: PChar; res: PPItemList); stdcall;
var
  actItem: PItemList;
begin
  actItem:=res^;
  if not Assigned(actItem) then begin
    GetMem(res^,sizeof(TItemList));
    actItem:=res^;
    actItem^.Next:=nil;
    GetMem(actItem^.Text,StrLen(Text)+1);
    StrLCopy(actItem^.Text,Text,StrLen(Text)+1);
  end;
end;
Im aufrufenden Programm sieht die Prozedur folgendermaßen aus:
Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
var
  resItems,actItem: PItemList;
  testproc: procedure (Text: PChar; res: PPItemList); stdcall;
  hdll: Cardinal;
begin
  hdll:=LoadLibrary('test.dll');
  @testproc:=GetProcAddress(hdll,'testproc');
  resItems:=nil;
  testproc('Testtext',@resItems);
  if Assigned(resItems) then begin
    actItem:=resItems;
    while Assigned(actItem) do begin
      ShowMessage(actItem^.Text);
      actItem:=actItem^.Next;
      //FreeMem(resItems,sizeof(TItemList));
      //resItems:=actItem;
    end;
  end;
  @testproc:=nil;
  FreeLibrary(hdll);
end;
Wenn ich die beiden auskommentierten Zeilen Quelltext wieder aktiviere, dann bekomme ich beim beenden des Programmes eine Access Violation. Wenn sie auskommentiert sind, dann läuft alles wie es soll. Aber dann bleibt doch der von mir manuell reservierte Speicher reserviert...

mfG, Christian
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.184 Beiträge
 
Delphi 12 Athens
 
#10

Re: Umgang mit PChar - Speichermanagement

  Alt 28. Dez 2007, 14:30
GetMem und FreeMem weisen den zum Modul (EXE oder DLL) gehörigen Speichermanager an einen Speicherblock zu reservieren oder freizugeben.

Also muß du FreeMem im selben Modul (in deinem Fall der DLL) aufrufen, wo auch GetMem ausgeführt wurde.


Lösung 1: wurde schonmal gesagt, du verwendes Delphi-Referenz durchsuchenShareMem, FastMM, oder ähnliches, welches für beide Module den selben Speichermanager einrichten,

Lösung 2: du definierst in der DLL eine Funktion/Prozedur, welche FreeMem aufruft,
gibst diese wie "testproc" frei
und rufst statt FreeMem in der EXE dann diese Procedure auf, welche dann wiederum FreeMem in der DLL ausführt.

...
$2B or not $2B
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 1 von 2  1 2      


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 13:17 Uhr.
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