Following the documentation of CreateProcess
, and the example from MSDN, i'm trying to call CreateProcess
:
var
commandLine: string;
si: TStartupInfo;
pi: TProcessInformation;
begin
commandLine := 'C:\Windows\System32\cmd.exe';
si := Default(TStartupInfo);
si.cb := sizeof(si);
CreateProcess(
PChar(nil), //no module name (use command line)
PChar(commandLine), //Command Line
nil, //Process handle not inheritable
nil, //Thread handle not inheritable
False, //Don't inherit handles
0, //No creation flags
nil, //Use parent's environment block
PChar(nil), //Use parent's starting directory
si, //Startup Info
pi //Process Info
);
The call crashes with an access violation:
Exception EAccessViolation in module kernel32.dll at 0003B77B.
Access violation at address 7671B77B in module 'kernel32.dll'. Write of address 00B47EA6.
Now i understand why it's crashing for me, but i don't understand why it isn't crashing for the sample code on MSDN, nor do i understand why it wasn't failing for you David.
The documentation for CreateProcess
says that if the first parameter is null (as it is in my example, the MSDN example, and the other example) then CreateProcess
will modify the commandLine
argument:
lpCommandLine [in, out, optional]
...
The Unicode version of this function, CreateProcessW, can modify the contents of this string. Therefore, this parameter cannot be a pointer to read-only memory (such as a const variable or a literal string). If this parameter is a constant string, the function may cause an access violation.
When i look at the access violation, it's trying to write to address 0x00B47EA6
:
So CreateProcess
is trying to scribble over null terminator of my unicode string. There is some debate in the CreateProcess
page comments if CreateProcess
will try to modify the command line rather than making it longer.
It is entirely possible that my string
C:\Windows\System32\cmd.exe
is sitting in a read-only data section. The string itself has a reference count of -1
:
which happens when constant strings come from constants.
I can test this by copying the string into a buffer:
var
commandLine: string;
si: TStartupInfo;
pi: TProcessInformation;
l: Integer;
buffer: TByteDynArray;
commandLine := 'C:\Windows\System32\cmd.exe';
//Copy to writable buffer (including null terminator)
l := (Length(commandLine)+1)*sizeof(Char);
SetLength(buffer, l);
Move(commandLine[1], buffer[0], l);
si := Default(TStartupInfo);
si.cb := sizeof(si);
if not CreateProcess(
PChar(nil), //no module name (use command line)
// PChar(commandLine), //Command Line
@buffer[0],
nil, //Process handle not inheritable
nil, //Thread handle not inheritable
False, //Don't inherit handles
0, //No creation flags
nil, //Use parent's environment block
PChar(nil), //Use parent's starting directory
si, //Startup Info
{var}pi //Process Info
);
And that works successfully.
So after authoring my question, and researching it, i have answered my own problem. But i'd still like to know what the decent way to handle this is
How to call CreateProcess in Delphi?
How does everyone else call it? Is everyone else copying the string to a byte buffer?
ShellExecute
He's how you use ShellExecute
:
var
shi: TShellExecuteInfo;
shi := Default(TShellExecuteInfo);
shi.cbSize := SizeOf(TShellExecuteInfo);
shi.lpFile := PChar(commandLine);
shi.nShow := SW_SHOWNORMAL;
ShellExecuteEx(@shi);
You can replace PChar(commandLine) with PChar(WideString(commandLine)). This worked for me in Delphi XE6.
I suppose they have broken something in string casts as my old code in Delphi XE works without such strict conversion.