Delphi - random access violation in DLL Thread

Viewed 69

I do something wrong, but what exactly?

I have simple DLL with thread. From App I put message in DLL queue, and DLL thread put message to callback.

When I use push in the application not in main thread everything fine. Also everything is fine if I don't use DLL at all, and use thread with queue just in the application (or use DLL without TThread). But when I push from main thread - I get random AV (in random time, in a random place)

uTestThread.pas

unit uTestThread;

interface

uses
  Winapi.Windows, System.Classes, System.SyncObjs,
  System.Generics.Collections;

type
  TTestCallback = procedure(Data: Pointer);

  TTestThread = class(TThread)
  private
    FLock: TRTLCriticalSection;
    FQueue: TQueue<Pointer>;
    FCallback: TTestCallback;
  protected
    procedure Execute; override;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Push(Data: Pointer);
    property Callback: TTestCallback read FCallback write FCallback;
  end;

implementation

{ TTestThread }

constructor TTestThread.Create;
begin
  InitializeCriticalSection(FLock);
  FQueue:=TQueue<Pointer>.Create;
  Inherited Create(True);
end;

destructor TTestThread.Destroy;
begin
  FQueue.Free;
  DeleteCriticalSection(FLock);
  inherited;
end;

procedure TTestThread.Execute;
var
  O: Pointer;
begin
  while not Terminated do
  begin
    EnterCriticalSection(FLock);
    try
      if FQueue.Count > 0 then
      begin
        if Assigned(FCallback) then
        begin
          O:=FQueue.Dequeue;
          FCallback(O);
        end;
      end;
    except //on E: Exception do
      //OutputDebugString(PChar('==='+E.Message+'==='));
    end;
    LeaveCriticalSection(FLock);
  end;
end;

procedure TTestThread.Push(Data: Pointer);
begin
  EnterCriticalSection(FLock);
  try
    FQueue.Enqueue(Data);
  finally
    LeaveCriticalSection(FLock);
  end;
end;

end.

DLL

library lib;

uses
  uTestThread in 'src\test\uTestThread.pas';

{$R *.res}

var
  TT: TTestThread;

procedure Push(Data: TTestDataOut);
begin
  TT.Push(Data);
end;

procedure TestThreadCreate(Callback: TTestCallback);
begin
  TT:=TTestThread.Create;
  TT.Callback:=Callback;
  TT.Start;
end;

exports
  TestThreadCreate,
  Push;

begin

end.

App form

unit frmMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, uTestThread;

type
  TForm2 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    procedure AddX;
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

procedure TestThreadCreate(Data: TTestCallback); external 'lib.dll';
procedure Push(Data: Pointer); external 'lib.dll';

implementation

{$R *.dfm}

procedure TestCallback(Data: TTestDataOut);
begin
//  OutputDebugString(PChar(TStringStream(Data).DataString));
  TObject(Data).Free;
end;

{ TForm2 }

procedure TForm2.AddX;
var
  I: Integer;
  S: string;
  O: Pointer;
begin
  for I:=0 to 9999 do
  begin
//    S:='F'+IntToStr(I)+' zzzzzzzzzzzzzzzzzzzzzzzzzWWzzzzzzzzzzzsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssszzzx';
//    O:=TStringStream.Create(S);
    O:=TObject.Create;
    Push(O);
  end;

  OutputDebugString(PChar('End'));
end;

procedure TForm2.Button1Click(Sender: TObject);
begin
  TestThreadCreate(TestCallback);
end;

procedure TForm2.Button2Click(Sender: TObject);
begin
//  Everything fine !!!
//
//  TThread.CreateAnonymousThread(procedure
//    begin
//      AddX;
//    end).Start;

//  Get random AV !!!
  AddX;
end;

end.

AV example

---------------------------
GExperts Debugger Exception Notification
---------------------------
Project lib.exe raised exception class EAccessViolation with message 'Access violation at address 004059B1. Read of address FFFFFFFC'.
---------------------------
ThreadId=2644
ProcessId=130
ThreadName=""
ExceptionMessage="Access violation at address 004059B1. Read of address FFFFFFFC"
ExceptionName="EAccessViolation"
ExceptionDisplayName="$C0000005"
ExceptionAddress=004059B1
FileName="GETMEM.INC"
LineNumber=1973
---------------------------

To demonstrate the problem, I use "while True do"

I know that FCallback should also be protected, but for demonstration I skip this

I dont create a thread in DllMain because:

https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-best-practices

As lock I tried to use TRTLCriticalSection / TCriticalSection / TMonitor / TMutex

As message I tried to use Pointer / WideString / PChar (and copy string)

As callback I tried to use Interface and Pointer to Procedure

As queue I tried TQueue<> / TList<> / TList / Array

I suspect something wrong with callback, because if I use queued item in DLL memory everything also fine

0 Answers
Related