![]() |
Warum ist PChar ^= @String[1]?
Ich bin gerade dabei meine C-Kenntnisse zu vertiefen und dabei ist mir etwas aufgefallen. Ein PChar entspricht einem char* in C(++), sein Ende wird durch #0 angezeigt. Ein String ist ebenfalls ein Pointer auf ein Char, jedoch sind 8 Byte Daten, unter anderem die Länge des Strings:
Zitat:
Daraus ergibt sich mein Problem @String[1] ist ein char* aber woher soll eine API-Funktion wissen, wie lang der String ist, wenn nicht zufällig im Speicherbereich danach binäre Nullen stehen? Danke im Voraus, d3g |
Wie wärs mit: weil Delphi erkennt, dass ein PChar erwartet wird und ein #0 hinten anhängt?
|
Moin Zusammen,
wenn ein Parameter vom Typ string übergeben wird, dann wird intern ein Aufruf der Prozedure UniqueString ausgeführt, der, wenn ich das eben im CPU Fenster richtig gesehen habe, immer eine #00 ans Ende anfügt. |
UniqueString wird immer beim Casten von Strings aufgerufen. Allerdings frage ich mich, man kann eine String auch so an eine API übergeben:
Code:
Wie funktioniert denn das jetzt? laut einer Webseite wird hier keine verborgene Prozedur aufgerufen, also auch kein UniqueString und ist infolgedessen die schnellste Lösung einen String in eine nullterminierede Zeichenfolge zu casten.
Messagebox(hWnd, pointer(s), 'Titel der Msgbox', 0);
|
Ein AnsiString fürht 2 Längeninformationen mit sich. Zum einen in den 8 Bytes vor der Adresse, auf die die Variable zeigt, und zum anderen ist jeder String sowieso Nullterminiert. Wandelt man nun einen String per Typecast in einen PChar mit p := PChar(s) um, so wird geprüft, ob der String leer ist (also die Variable intern NIL ist). Wenn dies der Fall ist, wird aus dem NIL-String ein #0-String.
Zusammengefasst: PChar('') ergibt #0 und Pointer('') ergibt nil. |
Hallo Leute,
danke für eura Antworten, leider bin ich immer noch nicht schlauer... Zitat:
Zitat:
Zitat:
Irgendwie hab ich das Gefühl auf der Leitung zu stehen... MfG, d3g |
Die Delphi-Strings sind schon nullterminiert.. das einzige was sie von den PChars unterscheidet sind die 8 Bytes (Länge und Referenzzähler) vor der eigentlichen Zeichenkette, wobei der Pointer, der den String referenziert auf das erste Zeichen der Zeichenkette zeigt (die 8 Bytes liegen also eigentlich hinter der eigentlichen Adresse des Strings), und die Spezialbehandlungen die Delphi intern ausführt. Ein PChar ist ein Pointer auf ein Char, daher ist auch naheliegend, dass @String[1] als PChar angesehen werden kann, schließlich stellt der Ausdruck einen Pointer auf das erste Zeichen der Zeichenkette an..
Aber eine ähnliche Diskussion hat es schon einmal gegeben: ![]() |
Moin d3g,
wenn Du folgende Deklaration hast (Voraussetzung: Für Strings ist die Standardeinstellung eingestellt, also string entspricht HugeString/LongString):
Delphi-Quellcode:
dann enthält sBuffer die Adresse des ersten Zeichens des Strings, wenn der String nicht leer ist.
var
sBuffer : string; Du könntest also z.B. schreiben:
Delphi-Quellcode:
Im ersten Falle wird dem Compiler mitgeteilt, dass er den Pointer sBuffer als PChar verwenden soll. Da dieser auf das erste Zeichen des Strings zeigt klappt der Typecast.
var
dwSize : DWORD; dwSize := MAX_COMPUTERNAME_LENGTH+1; SetLength(sBuffer,MAX_COMPUTERNAME_LENGTH+1); GetComputerName(PChar(sBuffer),dwSize); // oder GetComputerName(@sBuffer[1],dwSize); Bei der zweiten Variante wird die Adresse des ersten Zeichens des Strings übergeben. Würdest Du nur @sBuffer schreiben, so würdest Du die Adresse des Pointers (!) übergeben, was ja naheliegender Weise nicht das ist, was Du willst. Gäbe es nicht die Möglichkeit den Typ string von Longstring auf Shortstring zu ändern, könnte man sogar folgendes in Betracht ziehen: Man importiert die Funktion selber:
Delphi-Quellcode:
und kann dan sogar folgendes schreiben:
function GetComputerName(const AsName : string;const ApdwSize : PDWORD) : LongBool; stdcall; external 'kernel32.dll' name 'GetComputerNameA';
Delphi-Quellcode:
Das funktioniert dann, weil sBuffer die Adresse des ersten Zeichens enthält.
dwSize := MAX_COMPUTERNAME_LENGTH+1;
SetLength(sBuffer,dwSize); GetComputerName(sBuffer,@dwSize); Ich hab' jetzt allerdings nicht überprüft, ob bei einer solchen Konstruktion der String auch Nullterminiert ist, wenn man ihn an eine Funktion übergibt, die den Text ausgeben soll, und nicht als Buffer benötigt. Da standardmässig allerdings alle Longstrings nullterminiert sein sollen würde ich mal davon ausgehen. Nimmst Du eine Shortstring muss das schiefgehen, da hier nicht auf das erste Zeichen des Strings gezeigt wird, sondern auf die Länge des Strings. |
Zitat:
@Christian: Ich stelle gar nicht infrage, dass ein String ein Pointer auf ein Zeichen ist. Mein Problem liegt darin, dass ein String, wenn er denn nullteminiert ist, doch eine absolut überflüssige Konstruktion wäre. Man könnte einen String intern genauso wie ein PChar behandeln und die Spezialbehandlungen für PChar implementieren. Wäre ein String nullterminiert, warum dann ein Typecast? Irgendwie steh ich immer noch auf'm Schlauch... MfG, d3g |
Moin d3g,
Der Typ string ist, standardmässig ein Longstring, der
Diese beiden Felder hat ein PChar nicht. Ein string ist ein dynamisches Array, was ein PChar auch nicht ist. BTW: Ein string wird nur deshalb intern nullterminiert, damit man keine Probleme mit dem Aufruf von C Funktionen, wie z.B. denen der Windows API bekommt. Selbst für Assembler ist ein Längefeld praktischer ;-) Stellt man auf Shortstring um (dadurch verändert sich automatisch die Bedeutung des Schlüsselwortes string!), so ist die erste Stelle des strings (string[0]!) ein Längenfeld, erst dann folgt der Inhalt. Ausserdem ist ein Shortstring nicht nullterminiert. Wie ich in meinem letzten Beispiel glaubte demonstriert zu haben ( ;-) )könnte man sich die Funktionen der Windows API so importieren, dass man einen string als PChar verwenden kann, aber: Das setzt voraus, dass man nicht auf ShortString umstellt. Die Art wie Borland die Deklaration der Parametertypen beim Import der Windows API Funktionen durchgeführt hat, ist auch der Grund dafür, dass Delphi stur auf einem Typecast besteht. Für den Compiler ist string kein Pointer, und kann somit nicht als Pointer übergeben werden. Wie gesagt: Es hindert Dich niemand daran, dieses Problem durch eigenen Import der Funktionen, und entsprechender Vergabe der Parametertypen zu umgehen, aber dann wärest Du auch dafür verantwortlich, dass das nicht zu problemen führt, und, falls das für Dich interessant ist. Jeder andere wird wohl erstmal Probleme bekommen Deine Sourcen zu lesen. Ich weiss auch zugegebener Massen nicht (ich hab's halt noch nicht ausprobiert), was passiert, wenn Du an so einer Stelle dann mal ein Stringliteral übergibst. Im Prinzip könnte der Compiler das natürlich auch verwalten, denn er muss ja zu jedem Zeitpunkt "wissen" welcher Datentyp sich nun exakt hinter string verbirgt, aber dies ist nicht so implementiert, dass das vorher beschriebene problemlos funktioniert. Der Witz am Typ string ist übrigens, dass er aufgrund des Längenbytes nicht die Bufferoverflow Probleme kennt, wie man es ja immer wieder so schön bei den Sicherheitsproblemen diverser Software lesen kann, und, dass er, aufgrund des Referenzzählers eine andere (bessere !?) Verwaltung des erforderlichen Speichers ermöglicht. Wie schon erwähnt, hat der Längenzähler noch den Vorteil, dass die Stringfunktionen der Intel CPUs mit einem Zähler arbeiten, man also nicht mühsam auf eine Null testen muss, sondern direkt das Längenfeld verwenden kann. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 01:22 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