Why does ShowMessage give unexpected output when my string contains #0 characters? - string

Today I stumbled upon something mysterious. This line of code:
showmessage(menuMain.player[2] + ' ready!');
Generates this message (For example menuMain.player[2] = Player):
Player
But if I put code this way (For example menuMain.player[2] = Player):
showmessage('Test: ' + menuMain.player[2]);
It will generate this message:
Test: Player
According to the debugger, the exact value of the string (buffer := menuMain.player[2] + ' ready!') is this:
'Player'#0#0#0#0#0#0#0#0' ready!'
I do honestly believe this is a compiler glitch, because I have the exact same line in another block of code, and it works flawlessly.
Now the tough part for me, is that me being dumb, or this is indeed a glitch?

the exact value of buffer string (buffer = menuMain.player[2] + ' ready!'): 'Player'#0#0#0#0#0#0#0#0' ready!'
The problem is the embedded null characters (the #0). Those characters act as string terminators, which means the string stops being processed at that point for most WinAPI functions, including those that output or paint text. When the first #0 is found, the string is considered to have ended.
You can test this easily enough yourself, with code something like this:
var
TestStr: string;
begin
TestStr := 'This is a test';
ShowMessage(TestStr); // Outputs This is a test
TestStr[5] := #0;
ShowMessage(TestStr); // Outputs This
end;
The remaining question, of course, is how did you end up with those embedded nulls in the first place? Since you've not posted the code that populates menuMain.player, it's impossible to say, but that's that's the area you need to inspect, because string array elements in Delphi do not contain nulls on their own. You can also check that yourself:
var
TestArr: array[1..2] of string;
begin
TestArr[1] := 'Player one';
TestArr[2] := 'Player two';
ShowMessage(TestArr[1] + ' defeated ' + TestArr[2]);
end;
So the answer to your question
Now the tough part for me, is that me being dumb, or this is indeed a glitch?
It's a glitch, but the glitch is somewhere in your code. It's not a glitch in Delphi or its strings.

Related

Exercice with type String in Pascal

(Translated from German to Englisch)
I need help in this exercise :
Thread: String processing The user can make simple changes to an input sentence.
conditions
The program displays a menu for the user to select the following action. This is also displayed again after the action has been completed until the user terminates the program (a loop is therefore required).
The menu contains the following items, which should be executed when the specified letter is entered:
A. Enter the sentence
B. Determine the number of words
C. Determine the number of characters that are less than their sequence character
D. Replace all the words in the sentence with their uppercase initials
X. end
If the user enters a different letter, nothing happens or the menu is output again.
If the menu item A is selected, a prompt is issued to enter a set which is read into a string variable. This variable can not be changed by the actions of menu items B, C and D! Possibly. A copy of the set has to be prepared beforehand in another string variable.
In menu point B the number of all words in the block is to be counted. For simplicity, you can assume that there is always one space between two words. At the beginning and end of the sentence there are no spaces. The number of words is output after the calculation (e.g., "The set is 4 words").
If the user executes menu item C, the set is traversed character-by-character, and for each character it is checked whether it is smaller than its trailing character. Here is a simple character comparison (you can also write directly something like '1' <'d'). The number of characters so found is then output (e.g., "13 characters found in the sentence less than the trailing character").
In menu item D, the sentence is traversed and every word contained in it is replaced by its upper-case initial character. The capitalization is of course only made if the first character is a letter, otherwise the character remains unchanged. You can assume that the sentence never starts or ends with a space. Between two words there is always exactly one space and so it should be between the initial letters. For example, from "123 good mood" becomes "1 G L".
It is not permissible here to build up a completely new string piece by piece! Instead, you should work in a loop on a copy of the original sentence with pos, copy, length, delete and insert! It is also forbidden to "gather" the initial characters all at the beginning or end of the string; These should be inserted directly into the string at the position of the corresponding word!
Furthermore, a string can not be accessed at menu point D, because the work with string routines is to be practised explicitly here. Menu items B, C and D may only be selectable if a record has already been entered. Otherwise nothing happens or a fault message is entered when entering B, C or D in the menu and the menu is output again.
Each call to the menu items B, C or D will always work on the original set entered by the user and not on a set that has already been altered by previously executed menu items!
By entering the menu item A again, the entered block can be overwritten by a new one.
With an 'X' the user can terminate the program.
Use wherever it is the predefined string functions and do not write it yourself with difficulty loops, etc.! However, the use of the strreplace or reverseString functions is forbidden!
Here's my work till now, I only have problems with part D:
program Project2;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
const
lz = ' ';
var
Satz: string;
Buchstabe: char;
i, p, j, zaehler2, index, count: integer;
writeln('A) Write Sentence');
readln(Satz);
'D':
begin
index := 2;
insert(lz, Satz, length(Satz)+1);
count := (pos(lz,Satz));
repeat
delete(Satz, index,(count - index));
index := index + 2;
count := pos(lz,copy(Satz,index,(length(Satz)-index)))+index-1;
until ;
writeln(uppercase(Satz));
end
I'm glad you've found your own solution, well done!
While you've been doing that, I've been writing the answer below and, as I
have finished it, I thought I'd post it here to show you another way
to go about the problem of extracting words from a string. There are
dozens of ways of doing that task, but I hope the one I've used is fairly
easy to follow.
Maybe the reason you were having a problem with this is that your string-indexing
expressions are a bit too complicated. I bet if you come back to your code in 6 months
it will take you a while to figure out what it is supposed to be doing and longer
to tell whether it is actually doing it. The key to avoiding problems like that
is to break your code up into chucks which are easier to follow and easier
to test. So instead of just telling you what your repeat condition should be,
I'll show you a way which is easier to follow.
The first thing to do is to extract a singe word from the input. So, first thing
I've written is a function, ExtractFirstWord which returns the first word in
the input string, whether or not the input includes spaces, and also returns
a Remainder string which is what is left or the input string after the first
word (and any spaces immediately following it have been removed). This is done
using some simple while loops which are coded to skip over the leading spaces
and then build a string from the non-space characters which follow.
Code
const
lz = ' ';
var
Satz: string;
FirstWord : String;
Remainder : String;
function ExtractFirstWord(const InputStr : String; var Remainder : String) : String;
var
P : Integer;
WordStart : Integer;
begin
Result := '';
P := 1;
// The following skips over any spaces at the start of InputStr
while (P <= Length(InputStr)) and (InputStr[P] = lz) do
Inc(P);
// Now we know where the first word starts
WordStart := P;
// Now we can get the first word, if there is one
while (P <= Length(InputStr)) and (InputStr[P] <> lz) do
Inc(P);
Result := Copy(InputStr, WordStart, P - WordStart);
Remainder := Copy(InputStr, P, Length(InputStr));
// the following is one way to remove spaces at the start of Remainder
while (Length(Remainder) > 0) and (Remainder[1] = lz) do
Delete(Remainder, 1, Length(lz));
// instead you could do something simlar to the first `while` loop above
end;
begin
Satz := ' cat dog ';
repeat
FirstWord := ExtractFirstWord(Satz, Remainder);
FirstWord := UpperCase(FirstWord);
Satz := Remainder;
writeln('First word: ', FirstWord, ' remainder: ', Remainder);
until Remainder = '';
readln;
end.
This particular way of doing it is not an ideal fit with the other requirements
specified in your task but should be easily adaptable to them. E.g, the upper-casing of words could be done "in place" on the input string by upper-casing the current character of it in the second While loop.
Btw, if you are using Delphi or Free Pascal/Lazarus, there is a much simpler
way of extracting the words from a string. It uses a TStringList. Try
looking it up in the online help and have a thing about how you might use it
to do the task.

inno setup - Delete arbitrary substring from a string

I need to delete a substring which is arbitrary each time. For example:
..\HDTP\System\*.u should become ..\System\*.u and/or ..\New Vision\Textures\*.utx should become ..\Textures\*.utx. More specifically: Ignore the first three characters, delete whatever comes after, until the next \ character (including that character), leave the rest of the string intact. Could you, please, help me with this? I know, I have the worst explaining skills in the whole world, if something isn't clear, I'll try to explain again.
This is a little bit of copy and split work for Inno Setup but I have here a function for you with some extra comments.
Read it carefully as it isn't tested properly and if you have to edit it you will
have to know what it is doing ;)
function FormatPathString(str : String) : String;
var
firstThreeChars : String;
charsAfterFirstThree : String;
tempString : String;
finalString : String;
dividerPosition : Integer;
begin
firstThreeChars := Copy(str, 0, 3); //First copy the first thee character which we want to keep
charsAfterFirstThree := Copy(str,4,Length(str)); //copy the rest of the string into a new variable
dividerPosition := Pos('\', charsAfterFirstThree); //find the position of the following '\'
tempString := Copy(charsAfterFirstThree,dividerPosition+1,Length(charsAfterFirstThree)-dividerPosition); //Take everything after the position of '\' (dividerPosition+1) and copy it into a temporary string
finalString := firstThreeChars+tempString; //put your first three characters and your temporary string together
Result := finalString; //return your final string
end;
And this is how you would call it
FormatPathString('..\New Vision\Textures\*.utx');
You will have to rename the function and the var's so that it will match your program but I think this will help you.

How to get last string in TStringList

I've been searching for days on how to do this, and nothing is exactly what I need to do (or I just don't understand how to implement the solution).
What I need to do is parse a string, which is a street address, into a TStringList, and then set those strings from the list to variables I can then pass to the rest of the program. I already have the list working ok:
var
AddressList : TStringList;
i : integer;
begin
AddressList := TStringList.Create;
AddressList.Delimiter := ' ';
AddressList.DelimitedText := RawAddressStr; //RawAddressStr is parsed from another file and the string result is something like '1234 Dark Souls Lane Wyoming, MI 48419'
for i := 0 to AddressList.Count-1 do
AddressList.Strings[i]; //Not sure what to do here
The issue is that the address isn't always the same length. The address will sometimes be like '1234 Dark Souls Two Lane Wyoming...' or '1234 Dark Lane Wyoming...'
So I need to be able to use this TStringList to set the Zip, State and City into variables for later use. I would use TStringList.Find, but the ZIP code isn't always the same. Is there a way to get the last string and then go backwards from there? (Going backwards because once I get the City, State ZIP I can remove that from the RawAddressStr and then set the rest to the address string.)
Thanks.
Edit, here's the code I needed (thanks to below comments):
AddressList := TStringList.Create;
AddressList.Delimiter := ' ';
AddressList.DelimitedText := RawAddressStr;
for i := 0 to AddressList.Count-1 do
LastIndex := AddressList.Count - 1;
ZipStr := AddressList[LastIndex];
StateStr := AddressList[LastIndex - 1];
CityStr := AddressList[LastIndex - 2];
Now I can use these with StringReplace to take out the City, State Zip from the full address string, and set that as the Address string to use.
I am a little bit unsure of exactly what you're looking for since you ask
of how to get the last string of your StringList,
then at the end of the question you ask
Is there a way to get the last string and then go backwards from
there?
If you want to get the last string of a StringList you can use
var AddressList : TStringList;
MyString: string;
begin
MyString := AddressList.Last; //this...
MyString := AddressList.Strings[AddressList.Count-1]; //...and this is essentially the same thing
end;
if you would like to for-loop in reverse or backwards you should write:
for i := AddressList.Count-1 downto 0 do
AddressList.Strings[i]; //Not sure what to do here
Notice that it says "DOWNTO" and not "TO".
Now, if you would like to stop at a specific string, lets say the ZIP code,
you need to make your software understand what it is reading.
Which one of the delimited strings is the City?
Which one is the address?
To the software, a string is a string, it doesn't know, or even care what it is reading.
So I would like to suggest that you have a database of citys which it can compare the strings
of AddressList t,o and see if there is a match.
You could also implement some logic in to your algorithm.
If you know that the last string of your delimited AdressList string always is the City-name,
then you know you have the city name right there which you can use.
If you know that everything between the ZIP code and the City Name is the Street Address,
then just copy everything between the ZIP and the City-name and use that as a Street-name information.

Combine String and Character in Delphi

function leftstr(s:string; n:Integer):string;
var
i:integer;
t:string;
begin
//init var t
t := '';
for i := 0 to n do
begin
//main loop
t := t + s[i];
end;
//return results
Result:=t;
end;
So when I run this function and use ShowMessage to get the value of t in each increment of i, t is always blank. Can someone tell me the problem? This is compiled using Delphi XE6
The problem is that strings use 1-based indexing and you are accessing out of bounds. Your loop should be:
for i := 1 to n do
Probably what happens is that s[0] refers to some part of the string's meta data (length or reference count, I cannot remember which) that happens to contain a zero byte. This is then interpreted as a null terminator when the string is passed to ShowMessage.
If you had enabled the range checking feature in your compiler options, the compiler would have inserted code that would have found the error for you. This range checking feature is an astonishingly neglected and overlooked feature.
Of course, the function is a little pointless, as well as rather inefficient. You can use LeftStr from the StrUtils unit. Or plain old Copy from the System unit.

Function with PWideChar parameter taking only first char

I've just encountered a weird problem. I am trying to load a model to OpenGL and in the part where I load textures I use auxDIBImageLoadA(dibfile:PWideChar) function. Here is my code calling it
procedure CreateTexture(var textureArray: array of UINT; strFileName: string; textureID: integer); // Vytvožení textury
var
pBitmap: PTAUX_RGBImageRec;
begin
if strFileName = '' then exit;
MessageBox(0,PWideChar(strFileName),nil,SW_SHOWNORMAL);
pBitmap := auxDIBImageLoadA(PWideChar(strFileName));
if pBitmap = nil then exit;
...
The MessageBox is just for control. This is what happens: I run the application, a box with "FACE.BMP" appears. Okay. But then I get an error saying "Failed to open DIB file F". When i set the stFileName to xFACE.BMP, I get an "Failed to open DIB file x". So for some reason it appears that the function is taking only the first char.
Am I missing something? I'm using glaux.dll which I downloaded like 5 times from different sources, so it should be bug-free (I hope, every OpenGL site referred to it).
That's odd, functions ending in "A" generally take PAnsiChar pointers, and those ending in "W" take PWideChar pointers. Is there a auxDIBImageLoadW call also? If there is use that one, or try with PAnsiChar, since the PWideChar you pass (two bytes per position) would look like a string one character long if it is evaluated as a 1-byte string.
You need to convert your Unicode string to ANSI. Do it like this
pBitmap := auxDIBImageLoadA (PAnsiChar(AnsiString(strFileName)))
You would be better off calling the Unicode version though
pBitmap := auxDIBImageLoadW (PWideChar(strFileName))

Resources