5
votes

How can I set a custom stack size in TThread? I am trying to reintroduce the constructor of TThread but it says that ThreadProc is missing yet its right there in System.Classes.

type
  TThreadHelper = class helper for TThread
    constructor Create(const CreateSuspended: Boolean = False; const StackSize: Integer = 0); reintroduce;
 end;

{ TThreadHelper }

constructor TThreadHelper.Create(const CreateSuspended: Boolean; const StackSize: Integer);
begin
  Self.FSuspended := not Self.FExternalThread;
  Self.FCreateSuspended := CreateSuspended and not Self.FExternalThread;
  if not Self.FExternalThread then
  begin
    Self.FHandle := BeginThread(nil, StackSize, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, Self.FThreadID);
    if Self.FHandle = 0 then
    raise EThread.CreateResFmt(@SThreadCreateError, [SysErrorMessage(GetLastError)]);
  end
  else
  begin
    Self.FHandle := Winapi.Windows.GetCurrentThread;
    Self.FThreadId := GetCurrentThreadId;
  end;
end;

[dcc32 Error] Project5.dpr(29): E2003 Undeclared identifier: 'ThreadProc'

4
The ThreadProc function is not for public use. It would have been defined in the interface part of the unit as well. Hence the compiler cannot see it. - TLama

4 Answers

7
votes

I do not know, if you can set stack size after a thread is created. Maybe SetThreadStackGuarantee can be helpful?

You can create a thread from scratch by using BeginThread, but it is quite complicated. I have here a workaround by using Detours. Note that there are several Detours variants. I think only the Cromis.Detours is x64 compatible.

unit IndividualStackSizeForThread;

interface

uses 
  System.Classes,
  Cromis.Detours { http://www.cromis.net/blog/downloads/cromis-ipc/ };

type
  TThreadHelper = class helper for TThread
    constructor Create(CreateSuspended: Boolean; StackSize: LongWord);
 end;

implementation

var
  TrampolineBeginThread: function(SecurityAttributes: Pointer; StackSize: LongWord;
    ThreadFunc: TThreadFunc; Parameter: Pointer; CreationFlags: LongWord; 
    var ThreadId: TThreadID): THandle = nil;

threadvar
  StackSizeOverride: LongWord;

function InterceptBeginThread(SecurityAttributes: Pointer; StackSize: LongWord;
  ThreadFunc: TThreadFunc; Parameter: Pointer; CreationFlags: LongWord;
  var ThreadId: TThreadID): THandle;
const
  STACK_SIZE_PARAM_IS_A_RESERVATION = $00010000; // http://msdn.microsoft.com/en-us/library/windows/desktop/ms682453(v=vs.85).aspx
begin
  if StackSizeOverride <> 0 then
  begin
    CreationFlags := CreationFlags or STACK_SIZE_PARAM_IS_A_RESERVATION;
    StackSize := StackSizeOverride;
    StackSizeOverride := 0;
  end;

  Result := TrampolineBeginThread(SecurityAttributes, StackSize, ThreadFunc, 
    Parameter, CreationFlags, ThreadId);
end;

constructor TThreadHelper.Create(CreateSuspended: Boolean; StackSize: LongWord);
begin
  StackSizeOverride := StackSize;
  inherited Create(CreateSuspended);
end;

initialization

TrampolineBeginThread := InterceptCreate(@BeginThread, @InterceptBeginThread);

finalization

InterceptRemove(@TrampolineBeginThread, @InterceptBeginThread);

end.

I do not know why Embt does not allow programmer to specify the stack size, if someone knows the reason, it will be very interesting to me.

3
votes

There is simply no way to control the stack size using TThread. For whatever reason, the designers of TThread failed to include a stack size parameter in the constructor of TThread. This is clearly an omission. You should call BeginThread or CreateThread directly.

If you are simply desperate to make your hack work then you'll need to find the address of the ThreadProc function declared in the implementation section of the Classes unit. Some possible approaches:

  1. Disassemble TThread.Create at runtime to read out the address of ThreadProc.
  2. Create a dummy thread that looks at its call stack to find the address of ThreadProc.
  3. Hook BeginThread using a detour. Create a dummy thread. Note the address of the thread procedure passed. That is ThreadProc.

A good source of ideas for this sort of hacking is the source code for madExcept.

Another way to apply the hack would again be to use a detour on BeginThread. You could then use a thread local variable to supply the stack size. A value of high(LongWord) for that thread local variable would mean "use the value passed as a parameter", and any other value would be the value used by the detoured BeginThread.

3
votes

As David points out, you can't control the stack size for threads created with TThread class. You would have to create the actual thread yourself using either BeginThread or CreateThread.

However, if you don't need different stack sizes for each thread in your application:
Then you can set the default stack size using linking options for Min stack size and Max stack size.

Either set the option in project options, or use the directives: {$M minstacksize,maxstacksize} {$MINSTACKSIZE number} {$MAXSTACKSIZE number}

3
votes

For Delphi 10.3 RIO and later, there is now an overloaded constructor that allows you to set the ReservedStackSize for TThread on the Windows platform.

{$IF Defined(MSWINDOWS)}
    constructor Create(CreateSuspended: Boolean; ReservedStackSize: NativeUInt); overload;
{$ENDIF MSWINDOWS}