Rudy Velthuis has written an excellent article about PChar in Delphi. Whenever someone comes up to me with a PChar/string question, I refer them to that article.
There are, however, a few caveats one needs to be aware of when mixing string and PChar that he does not mention. This is what I want to cover in this brief article.
The problems mixing string and PChar described here are caused by null characters (or #0 in Delphi syntax). Since Delphi strings have a length counter, It is perfectly legal for them to contain #0. Since PChar are null-terminated, by definition they cannot contain #0.
However, a few Delphi routines that seem to operate on strings internally actually operate on PChar. They will therefore not work as expected when the string they are given contains #0.
You cannot replace #0 in a string
If you call StringReplace to replace #0, it simply won’t work.
function RemoveNull(AInput: string): string;
Result := StringReplace(AInput, #0, '', [rfReplaceAll]); // Won’t work
It seems that all replacing routines internally cast to PChar making them useless for this. Instead you will have to do a character by character comparison as in this routine by David Heffernan.
StrPCopy stops at #0
The StrPCopy routine takes a string as an input and copies it into a PChar. I used to use this routine in a TStringBuilder-like class which used a PChar to refer to an internal buffer containing the string being built. It also had a length counter, so one would not have to scan the PChar to figure out its length.
But even though StrPCopy takes a string as its input and could thus have access to its length, it does not copy all character, but stops at the first #0.
If you really want to copy all characters, you’ll have to use a routine such as Move that copies bytes not characters as in this example (again by David Heffernan).
Beware of implicit conversions
Since the compiler supports implicitly converting between PChar and string, you will often get away with passing a PChar where a routine takes a string parameter. The compiler will just generate a temporary string from the PChar and pass that instead.
Hence, this code will compile and work just fine:
function GetSubstring(AInput: PChar; AStart, ASubstringLength: Integer): string;
Result := Copy(AInput, AStart, ASubstringLength); // Works, but is slow
Unfortunately, it has horrible performance, because the compiler needs to know the length of the PChar in order to built that temporary string. And the only way to figure out the length of a PChar is to check every character until the first #0.
See this question I asked on Stack Overflow how to fix this with a PChar-equivalent for the Copy routine.