I have an application that spawns multiple CreateProcess threads and I'm successfully redirecting the stdout and stderr output to text files for each one.
However, I've discovered the feature whereby the stdout/strderr handles are inherited by all such threads and not just the ones I want them to be inherited by. So I've embarked on a journey to use the InitializeProcThreadAttributeList, UpdateProcThreadAttribute functions and EXTENDED_STARTUPINFO_PRESENT and a STARTUPINFOEX structure in the CreateProcess function to get around this but I'm stuck.
If I use PROC_THREAD_ATTRIBUTE_HANDLE_LIST as the Attribute argument in UpdateProcThreadAttribute procedure it expects the lpValue parameter to be a pointer to a list of handles to be inherited by the child process.
For the List I've tried using a
TList<Cardinal>
and also creating an array of Cardinals but couldn't get either approaches to compile!
Question: How do I create and populate such a list?
Secondly, in this example it is using the functions and procedures from kernel32.dll but they also exist in the Windows unit too (I'm using Delphi 10.3) although the definitions differ:
For example, InitializeProcThreadAttributeList( nil, 1, 0, vAListSize );
won't compile using the Windows unit due to the nil argument because Types of actual and formal var parameters must be identical but I have no such issue using the one in kernel32
Question: Which version of these functions/procedures should I be using?
Thanks.
In case it is useful, here's my code to implement all of this:
function InitializeProcThreadAttributeList(
lpAttributeList: Pointer;
dwAttributeCount: DWORD;
dwFlags: DWORD;
var lpSize: SIZE_T
): BOOL; stdcall; external kernel32;
function UpdateProcThreadAttribute(
lpAttributeList: Pointer;
dwFlags: DWORD;
Attribute: DWORD_PTR;
lpValue: Pointer;
cbSize: SIZE_T;
lpPreviousValue: PPointer;
lpReturnSize: PSIZE_T
): BOOL; stdcall; external kernel32;
function DeleteProcThreadAttributeList(
lpAttributeList: Pointer
): BOOL; stdcall; external kernel32;
// see https://devblogs.microsoft.com/oldnewthing/20111216-00/?p=8873
function CreateProcessWithInheritedHandles(
lpApplicationName: LPCWSTR;
lpCommandLine: LPWSTR;
lpProcessAttributes,
lpThreadAttributes: PSecurityAttributes;
const Handles: array of THandle;
dwCreationFlags: DWORD;
lpEnvironment: Pointer;
lpCurrentDirectory: LPCWSTR;
const lpStartupInfo: TStartupInfo;
var lpProcessInformation: TProcessInformation
): Boolean;
const
PROC_THREAD_ATTRIBUTE_HANDLE_LIST = $00020002;
// this flag is ignored these days, and struct size is used, but we may as well follow the letter of the docs
EXTENDED_STARTUPINFO_PRESENT = $00080000;
type
TStartupInfoEx = record
StartupInfo: TStartupInfo;
lpAttributeList: Pointer;
end;
var
Handle: THandle;
StartupInfoEx: TStartupInfoEx;
size: SIZE_T;
begin
Assert(Length(Handles)>0);
StartupInfoEx.StartupInfo := lpStartupInfo;
StartupInfoEx.StartupInfo.cb := SizeOf(StartupInfoEx);
StartupInfoEx.lpAttributeList := nil;
Win32Check(not InitializeProcThreadAttributeList(nil, 1, 0, size) and (GetLastError=ERROR_INSUFFICIENT_BUFFER));
GetMem(StartupInfoEx.lpAttributeList, size);
try
Win32Check(InitializeProcThreadAttributeList(StartupInfoEx.lpAttributeList, 1, 0, size));
try
Win32Check(UpdateProcThreadAttribute(
StartupInfoEx.lpAttributeList,
0,
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
@Handles[0],
Length(Handles) * SizeOf(Handles[0]),
nil,
nil
));
for Handle in Handles do begin
Win32Check(SetHandleInformation(Handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT));
end;
Result := CreateProcess(
lpApplicationName,
lpCommandLine,
lpProcessAttributes,
lpThreadAttributes,
True,
dwCreationFlags or EXTENDED_STARTUPINFO_PRESENT,
lpEnvironment,
lpCurrentDirectory,
StartupInfoEx.StartupInfo,
lpProcessInformation
);
finally
DeleteProcThreadAttributeList(StartupInfoEx.lpAttributeList);
end;
finally
FreeMem(StartupInfoEx.lpAttributeList);
end;
end;
From your post it would seem that there are some declarations of InitializeProcThreadAttributeList
, UpdateProcThreadAttribute
and DeleteProcThreadAttributeList
in the Windows
unit in the latest versions of Delphi, but your post implies that they are incorrectly declared. The above code is known to work correctly.
Update: I've updated the code to include the EXTENDED_STARTUPINFO_PRESENT
process creation flag as suggested by @blerontin. I don't believe that it is necessary because the size of the startup info struct is what the system uses to make that determination. However, the docs say it should be used, and it's harmless to include it.
I've also looked at the declarations of InitializeProcThreadAttributeList
and UpdateProcThreadAttribute
in the latest Delphi Windows
unit in the RTL (version 11.3 as I write) and they are indeed bogus.