A fUnNy StRiNg

0

CR in his recent post about the quality of Delphi help discussed the StrUpper/StrLower code example. I was interested why the original D7 example causes an access violation while the similar D2009 example runs without AV. So I have written the following tests (in Delphi 2009):

{$WRITEABLECONST OFF}
var
  S1: PChar = 'A fUnNy StRiNg 1';
  S2: string = 'A fUnNy StRiNg 2';
  S3: array[0..20] of Char = 'A fUnNy StRiNg 3';

const
  S4: array[0..20] of Char = 'A fUnNy StRiNg 4';

procedure TForm1.Button1Click(Sender: TObject);
var
  S: string;

begin
  S:= S1;
  Canvas.TextOut(5, 10, string(StrLower(PChar(S))));
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  S: string;

begin
  S:= S2;
  UniqueString(S);
  Canvas.TextOut(5, 10, string(StrLower(PChar(S))));
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  Canvas.TextOut(5, 10, string(StrLower(S3)));
end;

procedure TForm1.Button4Click(Sender: TObject);
begin
  Canvas.TextOut(5, 10, string(StrLower(S4)));
end;

Usually inplace functions like StrUpper/StrLower results in AV because the compiler places string constants in read-only memory. A string should be copied to the heap beforehand. In Button1Click procedure the string copy is made implicitly by assigning PChar to string; in Button2Click procedure one should call UniqueString to create a copy.
But that is not the case with the Button3Click example (which is actually the example from Delphi 2009 help). No copy is created here, still the example runs fine and demonstrates that the static arrays of characters are not placed in read-only memory. More than that, the Button4Click is also OK (I have explicitly included {$WRITEABLECONST OFF} directive, though it is OFF by default, to make the case more clear).

Delphi class helpers: a practical example.

2

Using the standard VCL TFileStream class you can’t set a sharing mode for a new file until Delphi 2010. A trick like this

var
  Stream: TStream;

begin
  Stream:= TFileStream.Create('TEST', fmCreate or fmShareDenyWrite);
...

is useless since fmCreate or fmShareDenyWrite = fmCreate, and you can see from VCL sources that a new file is created without sharing. There is a bug report #71632 on QC currently marked as “fixed” (in Delphi 2010, and I am using Delphi 2009).
If one need to create a new file with sharing enabled in Delphi 2009 or prior Delphi version, a class helper looks like a good workaround. Let us create a “helper” unit which extends TFileStream functionality by adding an additional constructor:

unit MyClasses;

interface

uses RTLConsts, Windows, SysUtils, Classes;

type
  TMyFileStream = class helper for TFileStream
    constructor CreateNew(const AFileName: string; ShareMode: LongWord = 0);
  end;

implementation

{
  Valid ShareMode values are:
    FILE_SHARE_READ,
    FILE_SHARE_WRITE,
    FILE_SHARE_DELETE,
  or any combination like FILE_SHARE_READ or FILE_SHARE_WRITE, etc
}
constructor TMyFileStream.CreateNew(const AFileName: string; ShareMode: LongWord);
var
  FileHandle: THandle;

begin
  FileHandle:= CreateFile(PChar(AFileName), GENERIC_READ or GENERIC_WRITE,
  ShareMode, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);

  if FileHandle = INVALID_HANDLE_VALUE then
    raise EFOpenError.CreateResFmt(@SFOpenErrorEx,
          [ExpandFileName(AFileName), SysErrorMessage(GetLastError)]);

  inherited Create(FileHandle);

//  FFileName := AFileName;
end;

end.

Now we can test the constructor. Create a new VCL application and add the “MyClasses.pas” unit to the “uses” clause of the main form; drop a button component to the main form and create an event handler:

procedure TForm1.Button1Click(Sender: TObject);
var
  Stream, TestStream: TStream;
  Buf, OutBuf: Cardinal;

begin
//  Stream:= TFileStream.CreateNew('TEST');
  Stream:= TFileStream.CreateNew('TEST', FILE_SHARE_READ);
  try
    Buf:= 12345678;
    Stream.WriteBuffer(Buf, SizeOf(Buf));
    TestStream:= TFileStream.Create('TEST', fmOpenRead or fmShareDenyNone);
    try
      TestStream.ReadBuffer(OutBuf, SizeOf(OutBuf));
      ShowMessage(IntToStr(OutBuf));
    finally
      TestStream.Free;
    end;
  finally
    Stream.Free;
  end;
end;

We can see that file sharing for a newly created file really works.
Note that we use TFileStream.CreateNew syntax (not TMyFileStream.CreateNew). The only problem with CreateNew constructor is TFileStream.FileName property – since TFileStream.FFileName field is declared as strict private it is not accessible from a class helper, and the field cannot be set in the CreateNew constructor.

A hidden feature of $POINTERMATH directive in Delphi 2009

2

The $POINTERMATH directive (introduced in Delphi 2009) allows to treat a typed pointer as a scaled ordinal or as an array. Here is a code sample that wouldn’t compile without $POINTERMATH turned ON

{$POINTERMATH ON}
procedure TForm1.Button1Click(Sender: TObject);
var
  P, P1: PInteger;
  N: Integer;

begin
  P:= @Self;
  N:= 2;
  P1:= P + N;           // treats P as a scaled ordinal
  N:= P[N];             // treats P as an array
  ShowMessage(Format('%p -- %p -- %d', [P, P1, N]));
end;

But the following code is also OK, though $POINTERMATH is turned OFF:

{$POINTERMATH OFF}
procedure TForm1.Button1Click(Sender: TObject);
var
  P, P1: PByte;
  N: Integer;

begin
  P:= @Self;
  N:= 2;
  P1:= P + N;           // treats P as a scaled ordinal
  N:= P[N];             // treats P as an array
  ShowMessage(Format('%p -- %p -- %d', [P, P1, N]));
end;

So the byte pointers (PByte type) ignore $POINTERMATH settings. Pointer arithmetics is always enabled for pointers into bytes [at least in Delphi 2009, can’t say about future versions], like it was enabled for PChar type in old pre-unicode Delphi versions.