TStringList .add produces duplicates from random function - delphi

Having a problem I can't seem to put my finger on. I am trying to gather strings (random code with letters and numbers) from a function call and place into my TStringList variable. Relevant code is below.
If I run a test, the strings repeat for a given amount of time then a new one is produced. If I introduce a sleep(xx) or showmessage command to happen after each time a code is produced (see 'edits' below) it copies/returns to memo fine and everything looks fine. If I remove the 'delay' I get repeats from function again.
The part of function to add to TStringList:
..
AddToMemo:=TStringList.Create;
AddToMemo.Clear;
AddToMemo.Sorted:=False;
for loop := 1 to totalamount do
begin
sResult:=MakeCode(charspercode, cbUpperLowerCase, cbAvoidChars, customchars);
Sleep(50);
// (or):
//ShowMessage(sResult);
// ^ If I leave a Sleep or ShowMessage in, I can see sResult just fine and
// program works fine - results in memo are correct as well. If I remove
// it, I get repeated entries.
AddToMemo.add(sResult+IntToStr(loop));
// If I remove "sResult+" from AddToMemo.add the ".add"
// works - shows loop numbers in my updated memo
// If left in, I see one code (1st one produced) and no
// appended number at all in Memo produced.
end;
Result:=AddToMemo;
end;
Edit: As I mention below if I leave a ShowMessage or Sleep(xx) call in to pause between .add's, it works fine. If I remove it, I get a bunch of duplicate entries in final tmemo.
Edit: MakeCode is a function to return a single random string of chars+numbers (A..Z a..z 0..9). It works fine on it's own.
(Edit for Answer 2)
No exceptions showed up.
So if I do not include sleep() it may generate 500 strings but they are all repeats; after a given amount of time it does change. The amount of repeats from the function call decreases as I increase sleep command. At around Sleep(40); it shows up correctly from function. But of course this is time consuming and unacceptable.
The 'guts' of MakeCode()
function MakeCode(CharsPerCode: Integer; bULCase, bAvoidChars: Boolean; sCChars: String): String;
var
i: integer;
s: string;
begin
//(misc stuff here)
begin
randomize;
s[0]:=chr(CharsPerCode);
for i:=1 to CharsPerCode do
repeat
s[i]:=chr(random(128));
until
(s[i] in ['A'..'Z','a'..'z','0'..'9'])
end;
Result:=s;
end;

This is a behavior of Randomize. The random number generator is initialized by a calculation on the system clock. If you call it in each iteration in a quick loop, it is initialized with the same seed. That's why a Sleep(50) is changing the outcome. Call randomize for once, for instance, before starting to populate the string list.
...
AddToMemo.Clear;
AddToMemo.Sorted:=False;
Randomize; // <-- possibly here
for loop := 1 to totalamount do
...
function MakeCode(CharsPerCode: Integer; bULCase, bAvoidChars: Boolean; sCChars: String):
...
begin
// randomize; // <-- not here!
s[0]:=chr(CharsPerCode);
The below quote is from the Delphi documentation:
Do not combine the call to Randomize in a loop with calls to the
Random function. Typically, Randomize is called only once, before all
calls to Random.

Without seeing what MakeCode() is actually returning in sResult, my guess would be that sResult contains non-printable control characters (null characters in particular) that cause the Memo or even the RTL to skip subsequence characters.

You need to show some more Code, evtl. MakeCode. I would try the same with a constant String in sResult without MakeCode, do you get the same? try something like:
for loop := 1 to totalamount do
begin
try
sResult:=MakeCode(charspercode, cbUpperLowerCase, cbAvoidChars, customchars);
AddToMemo.add(sResult+IntToStr(loop));
except on e: exception do
showmessage(e.message);
end;
end;
Do you get any exceptions?

Related

I am trying to create a simple chat program by writing to a shared file on my network

I am trying to use a timer to check the the timestamp of the file to check if it has been modified, if this is true it must add the line from the text file to a richedit. The problem is that it continually adds the line to the richedit every 1/4 second (timer interval). I have tried different methods but can't get it right.
procedure TForm1.Timer1Timer(Sender: TObject);
Var
Filet : textfile;
filename, readtxt : string;
filedate1, filedate2 : integer;
begin
assignfile(filet, 'S:\share.talk');
filename := 'S:\share.talk';
filedate1 := FileAge(filename);
if filedate1 <> filedate2 then begin
reset(filet);
readln(filet, readtxt);
richedit1.lines.add(readtxt);
closefile(filet);
filedate2 := filedate1;
end;//if
end;
thanks for all help.
In your code
if filedate1 <> filedate2 then begin
reset(filet);
readln(filet, readtxt);
richedit1.lines.add(readtxt);
closefile(filet);
filedate2 := filedate1;
end;
the comparison between filedate and filedate2 assumes that these retain their values between calls to Timer1Timer. They do not, because they are declared local to Timer1Timer and are therefore 'forgotten' between calls because they are stored on the stack.
To get them to retain their values, remove the declaration on them local to Timer1Timer and declare them as fields of TForm1 instead.
Btw, be aware that with this design, you are going to run into other issues, like how to handle concurrent access to the network textfile, etc, but they are not related to the specific point you asked about.
The problem it that you are openning and closing file every time the timer ticks.Open the file on TForm1.FormCreate by a TFileStream with fmOpenReadWrite or fmOpenShareDenyNone parameters, close it on TForm1.FormDestroy, and read it on TForm1.Timer1Timer if the number of read bytes are greater than zero convert the buffer to string and add it to the richedit.
This is because everytime you are resetting the filedate1 and filedate2 when the procedure runs. A better way to implement this function is to return the lastwrite time from the procedure and call the procedure on timer with the lastwrite time that was returned to you. Then you can compare the current time and the last time and do the refresh. On the first loop pass the current time and then keep using the lastread time in all subsequent calls and the result of which will keep updating the lastread time.

Prevent inserting an empty memo

I am checking the contents of the memo before an insert query.
if memo1.lines.Text = '' then begin
showmessage('Warning:Missing data!');
abort;
end else ....
It works ok as long as there is nothing in the memo1.
However, when user hits enter inside the empty memo and the cursor moves
to the second line,running the query, fires the insert without a warning message,
though theres nothing in the memo.
Is there a way to prevent this ?
After the user hits enter your memo's text contains whitespace (the line-return), and so it doesn't equal ''.
You need to Trim the Text first. http://docwiki.embarcadero.com/Libraries/XE2/en/System.SysUtils.Trim
var
memoText : String;
...
memoText := Trim(memo1.lines.Text);
if memoText = '' then
begin
showmessage('Warning:Missing data!');
abort;
end else ....
If the user pressing RETURN is resulting in an additional line in the memo, then you must have the WantReturns property set to TRUE, and an OnKeyPress or OnKeyDown event handling the #13 key or VK_RETURN virtual key code.
If you set WantReturns to FALSE then the user must use CTRL+ENTER to insert a new line, and a simple striking of the RETURN key will not insert an empty line.
But if you are happy with the way your user interface currently behaves and simply want to check whether or not the memo contains only whitespace then you will have to validate this separately using Trim(Memo.Text) or some other mechanism for testing the content of the memo to meet your applications definition of "not empty".
As others have noted, using Trim() on Memo.Text simply in order to test for the presence of non-whitespace characters is potentially very inefficient, though how much of a concern this is in your case will depended a great deal on the expected content of the memo in your application.
A more efficient way to test for a non-whitespace string would be:
function ContainsOnlyWhitespace(const aString: String): Boolean;
var
i: Integer;
begin
result := FALSE;
for i := 1 to Length(aString) do
if (aString[i] > ' ') then
EXIT;
result := TRUE;
end;
Which would be used thus:
if ContainsOnlyWhitespace(Memo.Text) then
begin
// show warning message etc...
end;
This will be significantly more efficient than Trim() since it does not involve producing any new strings or modifying the string being tested.

Can't step into for loop

I've been having a strange problem that I've never seen before. Now I have a for loop:
var
a,counter: byte;
begin
a:=0;
for counter := 1 to 10 do//I put a breakpoint at this line
begin
a:=a*5;
a:=a+counter;
end;
end;
If I put a breakpoint at the line above and try to step into the loop I can't do it. The debugger immediately steps over the loop and goes to the end.In the end I get the right result, but I can't follow the loop step by step. I mean this is just a simple example and not the real task. I just want to know when in what circumstances does this happen? I definitely remember tracking through all the steps of a loop. I work with Delphi 2010.
Both lines of code in the loop can be completely optimized away; you do nothing with a outside the loop, so both of the assignments are unnecessary. After the optimization, the compiler is leaving
for counter := 1 to 10 do
;
Actually, if you didn't have a breakpoint there, the loop would be removed as well, as it does nothing.
If you're having problems with your code, and the info above doesn't help (using the variable a after the loop runs), you need to post your real code. This made-up code is very clear to analyze; the problem in your actual code may be this simple, or much more complex to analyze.
See does turning off optimization makes difference - in project options -> Compiling -> Code generation.
In a comment to Ken's answer, Mikayil hinted that the code is inside a procedure.
This would also be a sound assumption looking at the code.
So if we set up a test like this :
Procedure Test;
var
a,counter: byte;
begin
a:=0;
for counter := 1 to 10 do//I put a breakpoint at this line
begin
a:=a*5;
a:=a+counter;
end;
end;
begin
Test;
end.
Set optimization on : Result - as observed by Mikayil, no stepping into loop possible.
Set optimization off : Result - stepping into loop possible, just as ain suggested.
Now also take into consideration, Mikayil's question in Ken's answer :
whether the inability to step into the loop was because of the local scope of the a.
Ken answered no, but this is not the case :
var
a : byte; // scope of a is outside of the procedure
Procedure Test;
var
counter: byte;
begin
a:=0;
for counter := 1 to 10 do//I put a breakpoint at this line
begin
a:=a*5;
a:=a+counter;
end;
end;
begin
Test;
end.
Now it does not matter whether optimization is on or off, stepping into the loop is possible anyway.
So, ain is absolute correct in his answer. (Tested in XE2)
Update :
For enabling stepping into the loop there are three possibilities :
Set optimization off.
Declare a outside your local scope.
Insert a dummy operation using a after the loop. Like : if (a < counter) then;
Neither of these steps are uncommon debug procedures, which I find this question is all about.

Initialise string function result?

I've just been debugging a problem with a function that returns a string that has got me worried. I've always assumed that the implicit Result variable for functions that return a string would be empty at the start of the function call, but the following (simplified) code produced an unexpected result:
function TMyObject.GenerateInfo: string;
procedure AppendInfo(const AppendStr: string);
begin
if(Result > '') then
Result := Result + #13;
Result := Result + AppendStr;
end;
begin
if(ACondition) then
AppendInfo('Some Text');
end;
Calling this function multiple times resulted in:
"Some Text"
the first time,
"Some Text"
"Some Text"
the second time,
"Some Text"
"Some Text"
"Some Text"
the third time, etc.
To fix it I had to initialise the Result:
begin
Result := '';
if(ACondition) then
AppendInfo('Some Text');
end;
Is it necessary to initialise a string function result? Why (technically)? Why does the compiler not emit a warning "W1035 Return value of function 'xxx' might be undefined" for string functions? Do I need to go through all my code to make sure a value is set as it is not reliable to expect an empty string from a function if the result is not explicitly set?
I've tested this in a new test application and the result is the same.
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
S: string;
begin
for i := 1 to 5 do
S := GenerateInfo;
ShowMessage(S); // 5 lines!
end;
This is not a bug, but "feature":
For a string, dynamic array, method
pointer, or variant result, the
effects are the same as if the
function result were declared as an
additional var parameter following the
declared parameters. In other words,
the caller passes an additional 32-bit
pointer that points to a variable in
which to return the function result.
I.e. your
function TMyObject.GenerateInfo: string;
Is really this:
procedure TMyObject.GenerateInfo(var Result: string);
Note "var" prefix (not "out" as you may expect!).
This is SUCH un-intuitive, so it leads to all kind of problems in the code. Code in question - just one example of results of this feature.
See and vote for this request.
We've run into this before, I think maybe as far back as Delphi 6 or 7. Yes, even though the compiler doesn't bother to give you a warning, you do need to initialize your string Result variables, for precisely the reason you ran into. The string variable is getting initialized -- it doesn't start as a garbage reference -- but it doesn't seem to get reinitialized when you expect it to.
As for why it happens... not sure. It's a bug, so it doesn't necessarily need a reason. We only saw it happen when we called the function repeatedly in a loop; if we called it outside a loop, it worked as expected. It looked like the caller was allocating space for the Result variable (and reusing it when it called the same function repeatedly, thus causing the bug), rather than the function allocating its own string (and allocating a new one on each call).
If you were using short strings, then the caller does allocate the buffer -- that's long-standing behavior for large value types. But that doesn't make sense for AnsiString. Maybe the compiler team just forgot to change the semantics when they first implemented long strings in Delphi 2.
This is not a Bug. By definition no variable inside function is initialized, including Result.
So your Result is undefind on first call, and can hold anything. How it is implemented in compiler is irrelevant, and you can have different results in different compilers.
It seems like your function should be simplified like this:
function TMyObject.GenerateInfo: string;
begin
if(ACondition) then
Result := 'Some Text'
else
Result := '';
end;
You typically don't want to use Result on the right side of an assignment in a function.
Anyway, strictly for illustrative purposes, you could also do this, though not recommended:
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
S: string;
begin
for i := 1 to 5 do
begin
S := ''; // Clear before you call
S := GenerateInfo;
end;
ShowMessage(S); // 5 lines!
end;
This looks like a bug in D2007. I just tested it in Delphi 2010 and got the expected behavior. (1 line instead of 5.)
If you think that some automatic management of strings are made to make your life easier, you're only partly right. All such things are also done to make string logic consistent and side-effects free.
In plenty of places there are string passed by reference, passed by value, but all these lines are expecting VALID strings in whose memory-management counter is some valid, not a garbage value. So in order to keep strings valid the only thing for sure is that they should be initialized when they firstly introduced. For example, for any local variable string this is a necessity since this is the place a string is introduced. All other string usage including function(): string (that actually procedure(var Result: string) as Alexander correctly pointed out) just expects valid strings on the stack, not initialized. And validness here comes from the fact that (var Result: string) construction says that "I'm waiting for a valid variable that definetly was introduced before". UPDATE: Because of that the actual contents of Result is unexpected, but due to the same logic, if it's the only call to this function that has a local variable at the left, the emptiness of the string in this case is guaranteed.
Alex's answer is nearly always right and it answers why I was seeing the strange behaviour that I was, but it isn't the whole story.
The following, compiled without optimisation, produces the expected result of sTemp being an empty string. If you swap the function out for the procedure call you get a different result.
There seems to be a different rule for the actual program unit.
Admittedly this is a corner case.
program Project1;
{$APPTYPE CONSOLE}
uses System.SysUtils;
function PointlessFunction: string;
begin
end;
procedure PointlessProcedure(var AString: string);
begin
end;
var
sTemp: string;
begin
sTemp := '1234';
sTemp := PointlessFunction;
//PointlessProcedure(sTemp);
WriteLn('Result:' + sTemp);
ReadLn;
end.

Improve speed of own debug visualizer for Delphi 2010

I wrote Delphi debug visualizer for TDataSet to display values of current row, source + screenshot: http://delphi.netcode.cz/text/tdataset-debug-visualizer.aspx . Working good, but very slow. I did some optimalization (how to get fieldnames) but still for only 20 fields takes 10 seconds to show - very bad.
Main problem seems to be slow IOTAThread90.Evaluate used by main code shown below, this procedure cost most of time, line with ** about 80% time. FExpression is name of TDataset in code.
procedure TDataSetViewerFrame.mFillData;
var
iCount: Integer;
I: Integer;
// sw: TStopwatch;
s: string;
begin
// sw := TStopwatch.StartNew;
iCount := StrToIntDef(Evaluate(FExpression+'.Fields.Count'), 0);
for I := 0 to iCount - 1 do
begin
s:= s + Format('%s.Fields[%d].FieldName+'',''+', [FExpression, I]);
// FFields.Add(Evaluate(Format('%s.Fields[%d].FieldName', [FExpression, I])));
FValues.Add(Evaluate(Format('%s.Fields[%d].Value', [FExpression, I]))); //**
end;
if s<> '' then
Delete(s, length(s)-4, 5);
s := Evaluate(s);
s:= Copy(s, 2, Length(s) -2);
FFields.CommaText := s;
{ sw.Stop;
s := sw.Elapsed;
Application.MessageBox(Pchar(s), '');}
end;
Now I have no idea how to improve performance.
That Evaluate needs to do a surprising amount of work. The compiler needs to compile it, resolving symbols to memory addresses, while evaluating properties may cause functions to be called, which needs the debugger to copy the arguments across into the debugee, set up a stack frame, invoke the function to be called, collect the results - and this involves pausing and resuming the debugee.
I can only suggest trying to pack more work into the Evaluate call. I'm not 100% sure how the interaction between the debugger and the evaluator (which is part of the compiler) works for these visualizers, but batching up as much work as possible may help. Try building up a more complicated expression before calling Evaluate after the loop. You may need to use some escaping or delimiting convention to unpack the results. For example, imagine what an expression that built the list of field values and returned them as a comma separated string would look like - but you would need to escape commas in the values themselves.
Because Delphi is a different process than your debugged exe, you cannot direct use the memory pointers of your exe, so you need to use ".Evaluate" for everything.
You can use 2 different approaches:
Add special debug dump function into executable, which does all value retrieving in one call
Inject special dll into exe with does the same as 1 (more hacking etc)
I got option 1 working, 2 should also be possible but a little bit more complicated and "ugly" because of hacking tactics...
With code below (just add to dpr) you can use:
Result := 'Dump=' + Evaluate('TObjectDumper.SpecialDump(' + FExpression + ')');
Demo code of option 1, change it for your TDataset (maybe make CSV string of all values?):
unit Unit1;
interface
type
TObjectDumper = class
public
class function SpecialDump(aObj: TObject): string;
end;
implementation
class function TObjectDumper.SpecialDump(aObj: TObject): string;
begin
Result := '';
if aObj <> nil then
Result := 'Special dump: ' + aObj.Classname;
end;
initialization
//dummy call, just to ensure it is linked c.q. used by compiler
TObjectDumper.SpecialDump(nil);
end.
Edit: in case someone is interested: I got option 2 working too (bpl injection)
I have not had a chance to play with the debug visualizers yet, so I do not know if this work, but have you tried using Evaluate() to convert FExpression into its actual memory address? If you can do that, then type-cast that memory address to a TDataSet pointer and use its properties normally without going through additional Evaluate() calls. For example:
procedure TDataSetViewerFrame.mFillData;
var
DS: TDataSet;
I: Integer;
// sw: TStopwatch;
begin
// sw := TStopwatch.StartNew;
DS := TDataSet(StrToInt(Evaluate(FExpression)); // this line may need tweaking
for I := 0 to DS.Fields.Count - 1 do
begin
with DS.Fields[I] do begin
FFields.Add(FieldName);
FValues.Add(VarToStr(Value));
end;
end;
{
sw.Stop;
s := sw.Elapsed;
Application.MessageBox(Pchar(s), '');
}
end;

Resources