CreateProcess is a little tricky to use. Thus I write the full example code here so you don’t have to worry.

uses SysUtils, JwaWindows, JwsclStrings;

procedure StartApp(const App, Parameters, CurDir : TJwString);
var
  StartupInfo: {$IFDEF UNICODE}TStartupInfoW{$ELSE}TStartupInfoA{$ENDIF};
  ProcInfo : TProcessInformation;
  pEnv : Pointer;

  pCurDir,
  pCmdLine : TJwPChar;
begin
  ZeroMemory(@StartupInfo, sizeof(StartupInfo));
  StartupInfo.cb          := SizeOf(StartupInfo);
  StartupInfo.lpDesktop   := ‘winsta0\default’;

  CreateEnvironmentBlock(@pEnv, 0, true);

  try
    if Length(Parameters) > 0 then
      pCmdLine := TJwPChar(‘"’+App+‘" ‘ + Parameters)
    else
      pCmdLine := TJwPChar(‘"’+App+‘" ‘);

    pCurDir := Nil;
    if Length(CurDir) > 0 then
      pCurDir := TJwPChar(CurDir);

   if not
{$IFDEF UNICODE}CreateProcessW{$ELSE}CreateProcessA{$ENDIF}(
    TJwPChar(App),//__in_opt     LPCTSTR lpApplicationName,
    pCmdLine, //__inout_opt  LPTSTR lpCommandLine,
    nil,//__in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
    nil,//__in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
    true,//__in         BOOL bInheritHandles,
    CREATE_NEW_CONSOLE or CREATE_UNICODE_ENVIRONMENT,//__in         DWORD dwCreationFlags,
    pEnv,//__in_opt     LPVOID lpEnvironment,
    pCurDir,//__in_opt     LPCTSTR lpCurrentDirectory,
    StartupInfo,//__in         LPSTARTUPINFO lpStartupInfo,
    ProcInfo,//__out        LPPROCESS_INFORMATION lpProcessInformation
  ) then
   raiseLastOsError;
  finally
    DestroyEnvironmentBlock(pEnv);
  end;
  CloseHandle(ProcInfo.hProcess);
  CloseHandle(ProcInfo.hThread);

Here some explanations:

  • What is a TJwString and TJwPchar?
    These types are declared in the JWSCL unit JwsclStrings and define a String or WideString and its C counterparts PChar or PWideChar. It depends on the compiler directive UNICODE whether they become the ansi or unicode version. Define UNICODE in your project options and rebuilt your project to use the unicode version.
    The same reason applies to this line.

    {$IFDEF UNICODE}CreateProcessW{$ELSE}CreateProcessA{$ENDIF}

    Here we use either the unicode or ansicode version of CreateProcess.

  • ZeroMemory(@StartupInfo, sizeof(StartupInfo));

    Is it really necessary to fill this block with zeroes?
    Yes, it is. CreateProcess uses values that are not zero as input and thus may fail if the values are incorrect.

  • StartupInfo.cb          := SizeOf(StartupInfo);

    What is the member cb for?
    The size member of this structures defines which version is used. In future if new members are added, CreateProcess can determine which version (with or without the new members) is used and thus ignore the new member’s values (they aren’t there anyway).

  • StartupInfo.lpDesktop   := ‘winsta0\default’;

    What does this line do?
    The lpDesktop member defines on which windowstation (here: “winsta0″) and desktop (here: “default”) the new application is shown. The value “winsta0\default” is a default value and should be sufficient for 99% of all cases. However Windows versions older than Vista come with a bug that does not show the application on “winsta0\defaul” if not specified. This bug only occurs in some rare occasions and is fixed in Vista and Server 2008.

  • What is CreateEnvironmentBlock for?
    CreateEnvironmentBlock creates all the command variables for the user you can see when you type “set” into command prompt (cmd.exe).
  • Why do you repeat the applications path in the command line parameters?
    pCmdLine := TJwPChar(‘"’+App+‘" ‘ + Parameters);

    It is standard in C world to repeat the application path as the first (zero based) parameter. So we have to add the app name in front of the remaining parameters. Otherwise the first parameter would be recognized as the application path and thus be ignored. In this case ParamStr(0) returns application path though but ParamStr(1) returns the second parameter. Example: pCmdLine = ’1 2 3′; In this example ParamStr(0) would be the path to exe. ParamStr(1) would be “2″ and not “1″ because “1″ is considered as the path to exe. ParamStr(0) uses GetModuleFileName as an exception.

    pCmdLine := TJwPChar(‘"’+App+‘" ‘);

    If no additional parameters are specified we just add the application name as the first parameter.

  • Why do we use a P(Wide)Char (TJwPChar resolves to this depending on compiler directive UNICODE) and set it to nil if Parameters or CurDir are empty?
    The reason is CreateProcess. If those pointers aren’t nil, the function consider them as input and does not ignore them. So if you empty the input parameters, CreateProcess considers an empty current directory string as input and tries to use it. Of course the operation fails and thus CreateProcess returns an error.
  • Does CloseHandle kill the newly created process?
    No, it does not. CreateProcess returns two handles so you could control the process or thread. However closing an application needs more action than just closing a handle. The handle however should be closed because it consumes memory in your process. So if you don’t need them, close it.