4

TL;DR:

CreateProcess(?, ?, ?, ...) for:

  • Pass current process params (i.e. "batchfile" %*)
  • correctly connect stdin and stdout
  • creation flags?

I have the following problem:

  • I need to launch a given 3rd party executable with a custom environment and custom parameters. (Both semi-fixed)
  • I cannot use a batch file, because the (again, 3rd party) side invoking the module directly calls CreateProcess
  • I need to pass on any additional paramers passed

So, what I'd like to do is create a very simple executable launcher that would be the equivalent of a batch file like:

set PATH=...
set WHATEVER=...
...\3rd-pty-tool.exe -switch1 -switch2 %*
exit /B %ERRORLEVEL%

And I certainly don't want to mess with any bat2exe converter stuff - just too ugly when I have Visual Studio around anyway.

Running another executable via CreateProcess is trivial in principle:

STARTUPINFO info={sizeof(info)};
PROCESS_INFORMATION processInfo;
if (CreateProcess(?, ?, ?, ?, ?, ?, ?, ?, &info, &processInfo))
{
    WaitForSingleObject(processInfo.hProcess, INFINITE);
    CloseHandle(processInfo.hProcess);
    CloseHandle(processInfo.hThread);
}

Setting up the environment for the child process via _putenv et al. is also pretty easy.

What is not trivial to me is however what to pass on to CreateProcess:

BOOL WINAPI CreateProcess(
  _In_opt_    LPCTSTR               lpApplicationName,
  _Inout_opt_ LPTSTR                lpCommandLine,
  _In_opt_    LPSECURITY_ATTRIBUTES lpProcessAttributes,
  _In_opt_    LPSECURITY_ATTRIBUTES lpThreadAttributes,
  _In_        BOOL                  bInheritHandles,
  _In_        DWORD                 dwCreationFlags,
  _In_opt_    LPVOID                lpEnvironment,
  _In_opt_    LPCTSTR               lpCurrentDirectory,
  _In_        LPSTARTUPINFO         lpStartupInfo,
  _Out_       LPPROCESS_INFORMATION lpProcessInformation
);
  • How to I get at the %* equivalent for the current Win32 process?
  • Pass only lpApplicationName, only lpCommandLine or both?
  • What to do about handle inheritance and creation flags?
  • How to I correctly forward / return stdin and stdout?

Not a dupe: CreateProcess to execute Windows command

Community
  • 1
  • 1
Martin Ba
  • 37,187
  • 33
  • 183
  • 337
  • 1
    I don't know what `%*` means, but you are probably looking for [GetCommandLine](https://msdn.microsoft.com/en-us/library/windows/desktop/ms683156.aspx) – Igor Tandetnik May 30 '16 at 14:53
  • Also of interest: [Creating a Child Process with Redirected Input and Output](https://msdn.microsoft.com/en-us/library/windows/desktop/ms682499.aspx) – Igor Tandetnik May 30 '16 at 14:55
  • @Igor `%*`as in a batch file. & thanks, that redirection article is a good one. – Martin Ba May 30 '16 at 19:17

1 Answers1

2

Should be reasonably straightforward.

BOOL WINAPI CreateProcess(
  _In_opt_    LPCTSTR               lpApplicationName,
  _Inout_opt_ LPTSTR                lpCommandLine,
  _In_opt_    LPSECURITY_ATTRIBUTES lpProcessAttributes,
  _In_opt_    LPSECURITY_ATTRIBUTES lpThreadAttributes,
  _In_        BOOL                  bInheritHandles,
  _In_        DWORD                 dwCreationFlags,
  _In_opt_    LPVOID                lpEnvironment,
  _In_opt_    LPCTSTR               lpCurrentDirectory,
  _In_        LPSTARTUPINFO         lpStartupInfo,
  _Out_       LPPROCESS_INFORMATION lpProcessInformation
);

Let's take it in order.

  • lpApplicationName - if you have the full path to the executable you want to run, put it here. That ensures that you get the executable you were expecting, even if another executable with the same name is on the PATH.

  • lpCommandLine - the first element is the executable name. If you've specified lpApplicationName this doesn't need to be fully qualified, or even be the executable's actual name, but it does need to be present. This must be a writable buffer, it cannot be a constant string.

If your extra arguments can go at the end of the command line, this is easy:

wchar_t buffer[1024];
wcscpy_s(buffer, _countof(buffer), GetCommandLine());
wcscat_s(buffer, _countof(buffer), L" -switch1 -switch2");

Otherwise, you'll need to parse the command line to find the right place to insert the arguments, something like this:

while (*lpCmdLine == L' ') lpCmdLine++;
while (ch = *lpCmdLine) 
{
    if (ch == L'"') for (lpCmdLine++; ch = *lpCmdLine; lpCmdLine++) if (ch == L'"') break;
    if (ch == L' ') break;
    lpCmdLine++;
}
while (*lpCmdLine == L' ') lpCmdLine++;
  • lpProcessAttributes and lpThreadAttributes - NULL.

  • bInheritHandles - TRUE, since you want the child to inherit the standard handles.

  • dwCreationFlags - none needed in your scenario, so 0.

  • lpEnvironment - NULL to pass the current environment. In some situations you'd want to explicitly construct a new environment, or a modified copy of your environment, but since your process exists only to launch the child that shouldn't be necessary.

  • lpCurrentDirectory - NULL to inherit your current directory.

  • lpStartupInfo - call GetStartupInfo to fill this out with the same settings as the current process, or just leave it empty as in the code you posted.

  • lpProcessInformation - this is an output parameter, used as shown in your code. In your scenario, where one application is standing in for another, you might want to keep the process handle and use it to wait for the child process to exit before exiting yourself. (This isn't necessary if you know that your parent won't get confused if you exit before your child does.)

You don't need to do anything special about the standard handles, apart from ensuring that bInheritHandles is set. The default is for the child to keep the same standard handles as the parent.

Harry Johnston
  • 35,639
  • 6
  • 68
  • 158
  • Thanks Harry. Very nice sum up! *Actually*, my current test app sets `bInhertHandles := FALSE` and it *still* works. Both binaries involved are "console executables". Seems no handle inheritance is needed in that case. I'm planning to put up the full example once I have it. – Martin Ba Jun 01 '16 at 07:05
  • 1
    Yeah, I decided against discussing that, as an unnecessary complication. In Windows 7 and earlier, if the standard handles are still pointed to the original console handles, you don't need handle inheritance. This is because they're not real kernel handles, they're "psuedohandles". – Harry Johnston Jun 01 '16 at 21:37
  • 1
    I don't *think* it will work without handle inheritance if the standard handles have been redirected to a file or pipe, though I haven't checked. Also, if I understand rightly, in newer versions of Windows console handles *are* real kernel handles. It's possible that Windows special-cases both of these situations, but to ensure maximum compatibility I'd recommend turning inheritance on in this scenario. (For more complicated scenarios there is an option to specify exactly which handles are inherited, which is probably the best choice in the general case.) – Harry Johnston Jun 01 '16 at 21:42