2009. január 24., szombat

Using CreateProcess to execute programs


Problem/Question/Abstract:

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.

{Supply a fully qualified path name in ProgramName}

procedure ExecNewProcess(ProgramName: string);
var
  StartInfo: TStartupInfo;
  ProcInfo: TProcessInformation;
  CreateOK: Boolean;
begin

  { fill with known state }
  FillChar(StartInfo, SizeOf(TStartupInfo), #0);
  FillChar(ProcInfo, SizeOf(TProcessInformation), #0);
  StartInfo.cb := SizeOf(TStartupInfo);

  CreateOK := CreateProcess(PChar(ProgramName), nil, nil, nil, False,
    CREATE_NEW_PROCESS_GROUP + NORMAL_PRIORITY_CLASS,
    nil, nil, StartInfo, ProcInfo);

  { check to see if successful }
  if CreateOK then
    //may or may not be needed. Usually wait for child processes
    WaitForSingleObject(ProcInfo.hProcess, INFINITE);
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.}

procedure ExecNewProcess(ProgramName: string);
var
  StartInfo: TStartupInfo;
  ProcInfo: TProcessInformation;
  CreateOK: Boolean;
begin

  { fill with known state }
  FillChar(StartInfo, SizeOf(TStartupInfo), #0);
  FillChar(ProcInfo, SizeOf(TProcessInformation), #0);
  StartInfo.cb := SizeOf(TStartupInfo);

  CreateOK := CreateProcess(nil, PChar(ProgramName), nil, nil, False,
    CREATE_NEW_PROCESS_GROUP + NORMAL_PRIORITY_CLASS,
    nil, nil, StartInfo, ProcInfo);

  { check to see if successful }
  if CreateOK then
    //may or may not be needed. Usually wait for child processes
    WaitForSingleObject(ProcInfo.hProcess, INFINITE);
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.

Nincsenek megjegyzések:

Megjegyzés küldése