Delphi 2 bug. Has it been fixed yet?

I found a bug which may not have yet been detected and fixed by Borland because it relates to the old (and obscure) art of writing Pascal text device drivers.

The code that produces this bug is straight-forward enough.

procedure TForm1.Button1Click(Sender: TObject);
var
  strm: TStream;
  f: System.Text;
  aline: string;
  b: integer;
begin
  strm := TFileStream.Create('c:\sometext.txt', fmOpenRead);
  AssignStream(f, strm);
  Reset(f);
  b := TTextRec(f).BufPos;
  Readln(f, aline);
  CloseFile(f);
  strm.Free
end;

Commenting out the line "b := TTextRec(f).BufPos" makes everything work fine.

Here is the code for my AssignStream procedure.

unit TextStrm;
(* Written by: Ian Hinson (ihinson@ozemail.com.au) 22 January, 2000

   AssignStream procedure enables any TStream descendant that
   stores text data to be read or written to as a text device *)

interface
uses SysUtils, Classes;

procedure AssignStream(var F: System.Text; stream: TStream);
{Stream must already be opened before assigning to Text }

implementation

type
  PStream = ^TStream;

function OutputText(var F: TTextRec): Integer;
begin
  with F do
  begin
    PStream(@UserData)^.WriteBuffer(Buffer, BufPos);
    BufPos:=0;
    OutputText := 0;
  end;
end;

function InputText(var F: TTextRec): integer;
begin
  with F do
  begin
    BufEnd := PStream(@UserData)^.Read(Buffer, BufSize);
    BufPos := 0;
    InputText := 0;
  end;
end;

function FlushInput(var F: TTextRec): integer;
begin
  with F do
  begin
    BufPos := 0;
    BufEnd := 0;
  end;
end;

function Ignore(var F: TTextRec): integer;
begin
  Ignore := 0;
end;

function OpenTextStream(var F: TTextRec): Integer;
begin
  with F do
  begin
    case Mode of
    fmInput:
      begin
       InOutFunc:=@InputText;
       FlushFunc:=@FlushInput;
      end;
    fmInOut:
      begin
        Mode:=fmOutput;
        InOutFunc:= @OutputText;
        FlushFunc:=@Ignore; // buffered. Use @OutputText for unbuffered.
        with PStream(@UserData)^ do Position := Size;
      end;
    fmOutput:
      begin
        Mode:=fmOutput;
        InOutFunc:=@OutputText;
        FlushFunc:=@Ignore; // buffered. Use @OutputText for unbuffered.
      end;
    end; {case Mode}
    CloseFunc:=@Ignore;
  end;
  OpenTextStream := 0;
end;

procedure AssignStream(var F: System.Text; Stream: TStream);
begin
  with TTextRec(F) do
  begin
    Mode:=fmClosed;
    BufSize:=SizeOf(Buffer);
    BufPtr:=@Buffer;
    OpenFunc:=@OpenTextStream;
    PStream(@UserData)^ := Stream;
    Name[0]:=#0;
  end;
end;

end.

Work-a-round I have found which make the bug go away are:
1. Declare f as a global variable, not a local variable, or
2. Change the method to a procedure then call the procedure from the method.

My guess is that the reason it won't work (unless the workarounds are used) has something to do with compiler optimisations that make f inaccessible outside the method.

What I'm really after here I guess is for someone (with Delphi4/5) to give it a try and report back.  ..But if you can find a flaw in my code, please let me know.
LVL 1
IanHinsonAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

rwilson032697Commented:
Hmm.. Some observations/questions...

1. Shouldn't b be defined as cardinal?

2. The line B:=... has no effect in your onclick procedure...

3. Have you tried compiling the code without optimisation?

4. What goes wrong when you leave the code in place?

Cheers,

Raymond.
0
rwilson032697Commented:
I can confirm the 'bug' is still there in D5.

What I think is happening is BufPos is uninitialised... If I initialise it to 0 in the OpenTextStream it runs correctly...

Cheers,

Raymond.
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
rwilson032697Commented:
Actually, initialising it in AssignStream would be better :-)

Cheers,
Raymond.
0
Cloud Class® Course: CompTIA Cloud+

The CompTIA Cloud+ Basic training course will teach you about cloud concepts and models, data storage, networking, and network infrastructure.

IanHinsonAuthor Commented:
Initialising BufPos to zero renders the whole thing ineffective and causes Readln to return nothing - hence it 'appears' to work.
Apparently Pascal automatically initialises BufPos with a magic value which may mean 'position unknown'.

Anyway, thanks for your trouble in trying it and letting me know that the bug is still there.

If you get any further thoughts about what's causing it, please let me know.

Ian.

0
rwilson032697Commented:
How weird! We do use a text device driver in a piece of code at work - I'll take a look at it when I get in...

Cheers,

Raymond.
0
rwilson032697Commented:
I've check out our code, and we do initialise bufpos to 0 as I suggested (in openTextStream). I can't see any other really significant changes so I'm a bit mystified.

Cheers,

Raymond.
0
IanHinsonAuthor Commented:
There's an interesting statement in Charles Petzold's book 'Programming Windows 95' (pg780):
"Global variables in a multithreaded program (as well as any allocated memory) are shared among all the threads in the program." ..
"Local automatic variables in a function are unique to each thread, because they are stored on the stack, and each thread has its own stack."

Recall that I could make the 'bug' go away by declaring f:System.Text as a *global* variable?
Another workaround (I did not previously mention) is that by using:

var
  fp: ^System.Text // local
begin
  New(fp) // allocate memory on heap
.....
  Dispose(fp)
end;
...then the problem *also* does not appear.

Couple this with the error message that says there is an Access Violation at a memory address which is the correct address that I am after in the first place, and it fits in with what Petzold is saying.

I now think this is not a bug but a wake-up-call to the realities of Win32 programming.
My guess as to what's happening is that Pascal calls functions assigned to TTextRec in a separate thread. Therefore they cannot access var F if it has been declared on the stack of the calling thread.

Since the text device being used at your work is working OK, there is something I'm curious to know: Is var F (ie. at work) being declared as a global variable or a local variable?

Ian.
0
rwilson032697Commented:
Unless you have a multithreaded app (eg: you use the Win32 threading or TThread) then I would say you only have a sngle thread in your app...

Here's a code snippet from our app:

procedure TTextDisplayer.get_size (..parameters...);
   var
     line : Anystring;
     f : text;
....

begin
....

   AssignTextEntity(f,txt);
   reset(f);

   while not eof(f) do
     begin
     readln(f,line);
     ...
     end;
   close(f);
end;

This works just fine - always has.

Cheers,

Raymond.
0
IanHinsonAuthor Commented:
Eureka!!

This fix was as simple as adding the lines:
 BufPos := 0;
 BufEnd := 0;

into the OpenTextStream function.

Thanks again for pointing me in the right direction.  Couldn't have solved it without your feedback and advice.

Ian.
0
rwilson032697Commented:
Glad to help!

Cheers,

Raymond.
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Delphi

From novice to tech pro — start learning today.