views:

2769

answers:

5

In Delphi 2009 I'm finding that any time I use TThread.CurrentThread in an application, I'll get an error message like the following when the application closes:

Exception EAccessViolation in module ntdll.dll at 0003DBBA.
Access violation at address 7799DBBA in module 'ntdll.dll'.  Write of
address 00000014.

Unless it's just my machine, you can replicate this in a few seconds: create a new Delphi Forms Application, add a button to the form, and use something like the following for the button's event handler:

procedure TForm1.Button1Click(Sender: TObject);
begin
  TThread.CurrentThread;
end;

On both my Vista machine and my XP machine I'm finding that, if I don't click the button everything's fine, but if I do click the button I get the above error message when I close the application.

So... I'm wondering if this is a bug, but at the same time I think it's rather likely that I'm simply not understanding something very basic about how you're supposed to work with TThreads in Delphi. I am a bit of a Delphi newbie I'm afraid.

Is there something obviously wrong with using TThread.CurrentThread like that?

If not, and you have Delphi 2009, do you get the same problem if you implement my simple sample project?


Update: As François noted below, this actually is a bug in Delphi 2009 at the moment - you can vote for it here.


Update: This bug was fixed in Delphi 2010.

A: 

I think CurrentThread is added in 2009 (or 2007). I have 2006 at home. But are you sure it is a class property?

Gamecat
Yep, pretty sure - the interface declares the property like: class property CurrentThread: TThread read GetCurrentThread;
MB
Oh, and yes, I believe I saw Barry Kelly (Delphi compiler guy) mention in another thread that CurrentThread was new in Delphi 2009.
MB
+14  A: 

Unfortunately it seems like a bug linked to the call order of the finalization section in the Classes unit:

DoneThreadSynchronization clears the ThreadLock structure, then
FreeExternalThreads wants to destroy the Thread object you just created when calling CurrentThread, and
that requires the ThreadLock to be already initialized in the call to
EnterCriticalSection(ThreadLock) in TThread.RemoveQueuedEvents...

UPDATE:
There is now a workaround patch in the QC report.

François
Ah, I see - thanks. Do you know if it's a reported bug that one can vote for?
MB
Many thanks for submitting it François. On first read of your answer I thought it was an issue that was already reported, but now I read it again I see you actually found the problem - nice one.
MB
the same patch as below :)
gabr
Yes! And it works! Just wanted to to end this post on a more positive note and to increase the patch visibility... ;-)
François
The QC is not available so I can not get the workaround patch, and the patch is also not included in the last Delphi 2009 Update 3 :P
mjustin
@Mjustin: the patch that is in QC is exactly the same as the one below in gabr's answer; you can always grab it from SO if QC is down. (by the way, I could access the QC report just now)
François
+10  A: 

Until CodeGear issues a fix, you can use the patch below. Save it into a standalone unit and use it anywhere in your program. I'll try to add it to the QC, too.

This version works with D2009 (original), update 1 and update 2.

{ Fix Delphi 2009's invalid finalization order in Classes.pas.
  Written by Primoz Gabrijelcic, http://gp.17slon.com.
  No rights reserved - released to public domain.
}
unit FixD2009Classes;

interface

implementation

uses
  Windows,
  SysUtils,
  Classes;

type
  TCode = array [0..109] of byte;

{$WARN SYMBOL_PLATFORM OFF}

procedure PatchClasses;
{$IFDEF ConditionalExpressions}
{$IF RTLVersion = 20}
var
  i         : integer;
  oldProtect: cardinal;
  pCode     : ^TCode;
  tmp       : DWORD;
const
  COffsets_Call: array [1..12] of integer = (0, 15, 24, 34, 49, 59, 69, 79, 89, 94, 99, 109);
  COffset_UnRegisterModuleClasses = 106;
  COffset_DoneThreadSynchronization = 94;
  COffset_FreeExternalThreads = 99;
  CCallDelta = COffset_FreeExternalThreads - COffset_DoneThreadSynchronization;
{$IFEND}
{$ENDIF}
begin
{$IFDEF ConditionalExpressions}
{$IF RTLVersion = 20}
  pCode := pointer(cardinal(@TStreamReader.ReadToEnd) + COffset_UnRegisterModuleClasses);
  Win32Check(VirtualProtect(pCode, COffsets_Call[High(COffsets_Call)], PAGE_READWRITE, oldProtect));
  try
    for i := Low(COffsets_Call) to High(COffsets_Call) do
      if pCode^[COffsets_Call[i]] <> $E8 then
        raise Exception.Create('Unexpected version of Classes - cannot patch');
    tmp := PDword(@pCode^[COffset_DoneThreadSynchronization+1])^;
    PDword(@pCode^[COffset_DoneThreadSynchronization+1])^ :=
      PDword(@pCode^[COffset_FreeExternalThreads+1])^ + CCallDelta;
    PDword(@pCode^[COffset_FreeExternalThreads+1])^ := tmp - CCallDelta;
  finally VirtualProtect(pCode, COffsets_Call[High(COffsets_Call)], oldProtect, oldProtect); end;
{$IFEND}
{$ENDIF}
end;

initialization
  PatchClasses;
end.
gabr
+1 Thanks gabr! You just provided me with a workaround for a problem I've been tracking down for days!
Smasher
+4  A: 

Patch unit for Delphi 2009 Update 3.

{ Fix Delphi 2009's invalid finalization order in Classes.pas.
  Written by Primoz Gabrijelcic, http://gp.17slon.com.
  No rights reserved - released to public domain.

  D2009 update 3 only.
}
unit FixD2009Classes;

interface

implementation

uses
  Windows,
  SysUtils,
  Classes;

type
  TCode = array [0..144] of byte;

{$WARN SYMBOL_PLATFORM OFF}

procedure PatchClasses;
{$IFDEF ConditionalExpressions}
{$IF RTLVersion = 20}
var
  i         : integer;
  oldProtect: cardinal;
  pCode     : ^TCode;
  tmp       : DWORD;
const
  COffsets_Call: array [1..12] of integer = (0, 15, 24, 42, 47, 58, 73, 91, 101, 111, 134, 139);
  COffset_UnRegisterModuleClasses = 107;
  COffset_DoneThreadSynchronization = 134;
  COffset_FreeExternalThreads = 139;
  CCallDelta = COffset_FreeExternalThreads - COffset_DoneThreadSynchronization;
{$IFEND}
{$ENDIF}
begin
{$IFDEF ConditionalExpressions}
{$IF RTLVersion = 20}
  pCode := pointer(cardinal(@TStreamReader.ReadToEnd) + COffset_UnRegisterModuleClasses);
  Win32Check(VirtualProtect(pCode, COffsets_Call[High(COffsets_Call)], PAGE_READWRITE, oldProtect));
  try
    for i := Low(COffsets_Call) to High(COffsets_Call) do
      if pCode^[COffsets_Call[i]] <> $E8 then
        raise Exception.Create('Unexpected version of Classes - cannot patch');
    tmp := PDword(@pCode^[COffset_DoneThreadSynchronization+1])^;
    PDword(@pCode^[COffset_DoneThreadSynchronization+1])^ :=
      PDword(@pCode^[COffset_FreeExternalThreads+1])^ + CCallDelta;
    PDword(@pCode^[COffset_FreeExternalThreads+1])^ := tmp - CCallDelta;
  finally VirtualProtect(pCode, COffsets_Call[High(COffsets_Call)], oldProtect, oldProtect); end;
{$IFEND}
{$ENDIF}
end;

initialization
  PatchClasses;
end.
gabr
A: 

The patch didn't work for me if Build with runtime packages is checked.

Tommi Rouvali