And another…

As I said in my last post, while I haven’t come across a code sample in the help as bad as the action manager one, I do find a slight air of dubiousness amongst the code examples generally. I don’t think this is a new phenomenon mind – the D7 help is in places just as bad. Neatly, the case of the example code for StrUpper and StrUpper illustrates this.

So, here’s what it looked like in the D7 help:

uses SysUtils;
const
  S: PChar = 'A fUnNy StRiNg'
begin
  Canvas.TextOut(5, 10, string(StrLower(S)) + ' ' +   string(StrUpper(S)));
end;

Horrible or what! Not only does it fail to compile due to a missing semi-colon, but even when that’s fixed, it becomes apparent that the code’s author doesn’t seem to realise that the StrXXX routines work ‘in place’, unlike their native string equivalents. What he or she seemed to be thinking of was this:

const
  S: string = 'A fUnNy StRiNg';
begin
  Caption := LowerCase(S) + ' ' + UpperCase(S);
end;

Naturally, this works fine, since LowerCase and UpperCase don’t attempt to modify what is passed to them.

So, what do we have in more recent Delphi versions? The following, to be exact (I’ve copied and pasted from the D2009 CHM, but the D2010 docwiki version is pretty much identical at my time of writing):

{
The following example uses an edit box, a label, and a
button on a form. When the button is clicked, the text in
the edit control is displayed in lower case in the label’s
caption.
}
var
  S: array[0..20] of Char = 'A fUnNy StRiNg';

procedure TForm1.Button1Click(Sender: TObject);
var
  lower, upper : string;
begin
  lower := string(StrLower(S));
  upper := string(StrUpper(S));
  Canvas.TextOut(5, 10, lower + ' ' + upper);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Label1.Caption := S;
end;

Note how the author has obviously taken the D7 version as a base. Nevertheless, he or she has gone on to create a version that not just compiles, but runs without creating an access violation. Well done! And yet: ‘When the button is clicked, the text in the edit control is displayed in lower case in the label’s caption.’ Oh really… Moreover, while there is a possibility that the author realises the TextOut call only works because the string casts created a copy of the source data, I wouldn’t be surprised if he or she just got lucky. Cf. this:

var
  S: array[0..20] of Char = 'A fUnNy StRiNg';

procedure TForm1.FormCreate(Sender: TObject);
var
  lower, upper : PChar;
begin
  lower := StrLower(S);
  upper := StrUpper(S);
  Caption := lower + ' ' + upper;
end;

By simplifying the surrounding code a bit, the nature of the StrXXX functions is made clear, and the ‘example’ is rendered broken. (If you don’t immediately see this, just try it out.)

Of course, the question might then get thrown back at me of what should a code sample for something like StrLower look like. To that, my first thought is that there need not be example code here at all — use of PChar buffers is not really a beginner topic, so if you need a code sample for something as simple as StrLower, then you really shouldn’t be coding at the level of PChar buffers in the first place. Nonetheless, how about this:

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils;

var
  Buffer: PChar;
begin
  Buffer := StrNew('A fUnNy StRiNg');
  try
    Writeln(Buffer);
    Writeln(StrLower(Buffer));
    //Buffer has now been converted to all lower case.
    //This doesn't matter to StrUpper of course.
    Writeln(StrUpper(Buffer));
    //However, we have lost the original casing, as
    //the next line shows.
    Writeln(Buffer);
  finally
    StrDispose(Buffer);
  end;
  Writeln;
  Write('Press ENTER to exit...');
  ReadLn;
end.

Or, perhaps even better, why not actually demonstrate the difference between StrXXX and their native string equivalents?

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils;

const
  Source = 'A fUnNy StRiNg';

var
  Buffer: PChar;
  S: string;
begin
  Writeln('*** Native string version ***');
  S := Source;
  Writeln(S);
  Writeln(LowerCase(S));
  Writeln(UpperCase(S));
  Writeln(S);
  Writeln;
  Writeln('*** PChar version ***');
  Buffer := StrNew(Source);
  try
    Writeln(Buffer);
    Writeln(StrLower(Buffer));
    //Buffer has now been converted to all lower case.
    //This doesn't matter to StrUpper of course.
    Writeln(StrUpper(Buffer));
    //However, we have lost the original casing, as
    //the next line shows.
    Writeln(Buffer);
  finally
    StrDispose(Buffer);
  end;
  Writeln;
  Write('Press ENTER to exit...');
  ReadLn;
end.

Any thoughts?

5 thoughts on “And another…

  1. Your last example is excellent.
    – contrasting similar String and PChar functions in one example clarifies their differences and helps users making the distinction and warns them about the consequences of intermixing
    – it’s good practice to use StrUpper/StrLower together with StrNew and StrDispose
    – the comments tell what is happening
    – creating a console app may be ‘ancient’, but for string examples I greatly prefer writeln above ‘Canvas.TextOut’. Changing labels or captions is simple, but not enough when the example needs more than one output line. (Memo.Lines.Add is not good for use in a code snippet.)

    • Victor — thanks for that. Just to add to what you say, the main problem I have with VCL-based examples for the StrXXX functions specifically is that you simply shouldn’t be using the latter in a purely VCL context, given the VCL presents itself with native Delphi strings. While any code example will inevitably be ‘unrealistic’ simply in being an ‘example’, this goes beyond that, since it may mislead newbies into thinking the StrXXX functions are what you should indeed be using in a VCL context, when you should in fact be using their native string equivalents.

  2. Pingback: A fUnNy StRiNg « The Programming Works

Leave a comment