Author: Lou Adler
How can I properly use CreateProcess to instantiate a new process?
Answer:
What's a Process
Before I give you the code to execute a program in Windows with CreateProcess, I
feel we should delve a bit into the concept of a what a process is. With Win32,
Microsoft changed nomenclature to help make the distinction of new concepts more
clear for developers. Unfortunately, not everyone understood it - including myself
at first. In Win16 a process was the equivalent to an application. That was just
fine because Windows 3.1 was (and still is) a non-preemptive multitasking system -
there's no such thing as threads.
But with the move to Win32 (Win95 and NT), many people have made the mistake of
equating a thread to a process. It's not an unusual thing considering the
familiarity with an older concept. However, threads and processes are both distinct
concepts and entities. Threads are children of processes; while processes, on the
other hand, are inert system entities that essentially do absolutely nothing but
define a space in memory for threads to run - threads are the execution portion of
a process and a process can have many threads attached to it. That's it. I won't go
into the esoteric particulars of memory locations and addressable space and the
like. Suffice it to say that processes are merely memory spaces for threads.
That said, executing a program in Win32 really means loading a process and its
child thread(s) in memory. And the way you do that in Win32 is with CreateProcess.
Mind you, for backward compatibility, the Win16 calls for executing programs,
WinExec and ShellExecute are still supported in the Windows API, and still work.
But for 32-bit programs, they're considered obsolete. Okay, let's dive into some
code.
The following code utilizes the CreateProcess API call, and will execute any
program, DOS or Windows.
1
2 {Supply a fully qualified path name in ProgramName}
3
4 procedure ExecNewProcess(ProgramName: string);
5 var
6 StartInfo: TStartupInfo;
7 ProcInfo: TProcessInformation;
8 CreateOK: Boolean;
9 begin
10
11 { fill with known state }
12 FillChar(StartInfo, SizeOf(TStartupInfo), #0);
13 FillChar(ProcInfo, SizeOf(TProcessInformation), #0);
14 StartInfo.cb := SizeOf(TStartupInfo);
15
16 CreateOK := CreateProcess(PChar(ProgramName), nil, nil, nil, False,
17 CREATE_NEW_PROCESS_GROUP + NORMAL_PRIORITY_CLASS,
18 nil, nil, StartInfo, ProcInfo);
19
20 { check to see if successful }
21 if CreateOK then
22 //may or may not be needed. Usually wait for child processes
23 WaitForSingleObject(ProcInfo.hProcess, INFINITE);
24 end;
Okay, while the code above works just fine for executing an application, one my
readers pointed out that it doesn't work with programs that include a command line
argument. Why? Because CreateProcess' first parameter expects a fully qualified
program name (path\executable) and nothing else! In fact, if you include a command
line in that parameter, CreateProcess will do nothing. Yikes! In that case, you
have to use the second argument. In fact, you can use the second parameter even for
just executing a program with no command line. Given that, ExecNewprocess would be
changed as follows:
{Supply a fully qualified path name in ProgramName
and any arguments on the command line. As the help file
states: "If lpApplicationName is NULL, the first white space-delimited
token of the command line specifies the module name..." In English,
the characters before the first space encountered (or if no space is
encountered as in a single program call) is interpreted as the
EXE to execute. The rest of the string is the argument line.}
25 procedure ExecNewProcess(ProgramName: string);
26 var
27 StartInfo: TStartupInfo;
28 ProcInfo: TProcessInformation;
29 CreateOK: Boolean;
30 begin
31
32 { fill with known state }
33 FillChar(StartInfo, SizeOf(TStartupInfo), #0);
34 FillChar(ProcInfo, SizeOf(TProcessInformation), #0);
35 StartInfo.cb := SizeOf(TStartupInfo);
36
37 CreateOK := CreateProcess(nil, PChar(ProgramName), nil, nil, False,
38 CREATE_NEW_PROCESS_GROUP + NORMAL_PRIORITY_CLASS,
39 nil, nil, StartInfo, ProcInfo);
40
41 { check to see if successful }
42 if CreateOK then
43 //may or may not be needed. Usually wait for child processes
44 WaitForSingleObject(ProcInfo.hProcess, INFINITE);
45 end;
I know, it's a bit of complex call. And the documentation and online help aren't much help in getting information on it. I think the biggest problem people have working with the WinAPI through Delphi is that the help topics are directed towards C/C++ programmers, not Delphi programmers. So on the fly, Delphi programmers have to translate the C/C++ conventions to Delphi. This has caused a lot of confusion for me and others who have been exploring threads and processes. With luck, we'll see better documentation emerge from either Borland or a third-party source.
|