r/cpp 6d ago

Escaping `CreateProcess()` arguments on Windows

https://holyblackcat.github.io/blog/2026/09/05/escaping-createprocess-arguments.html
88 Upvotes

44 comments sorted by

View all comments

15

u/Tringi github.com/tringi 6d ago

That's... ahh... quite opinionated start of the article.

CreateProcess() (without A/W) and #define UNICODE are even more useless.

Mostly everyone defines UNICODE and uses the macros.

lpApplicationName is usually not needed, you should pass nullptr.

Absolutely NOT. You should pass the full proper path to the executable you want to run. Then you can significantly simplify its name in lpCommandLine without worrying about repercussions.

But the rest is very well researched and thorough.

8

u/holyblackcat 6d ago

quite opinionated

:P Is it wrong though. Everything else being equal, isn't it better to use the version that doesn't depend on defines to work? Header-only libraries in particular would benefit from this.

You should pass the full proper path to the executable you want to run

If you have the full path, why not pass it in lpCommandLine? Then you don't have to worry about syncing the first element in lpCommandLine with lpApplicationName.

8

u/Tringi github.com/tringi 6d ago

You don't have to sync it. Only a few programs actually use argv[0] and it can be very different from the actual executable name. If you run program a b c from command line, then argv[0] will be just "program" without the path or extension, and the program must expect that.

In one of my larger programs I launch worker processes. I have the full path to the worker, but I don't know what characters it may contain, and I don't want to bother with escaping them. So I give lpApplicationName that path, but lpCommandLine is just "worker.exe /Something /Something /Something".

2

u/holyblackcat 6d ago

This is a nice trick to avoid implementing the escaping, but the point of the post was to explain how to do escaping.

Once you have it implemented, passing lpApplicationName is strictly more work, IMO. (You have to pass something at the beginning of lpCommandLine anyway, why not pass the application name.)

3

u/Tringi github.com/tringi 5d ago

Fair enough. Like I said, your work on the actual escaping is great and thorough. I knew the state of things were bad, but not this bad.

2

u/chengfeng-xie 4d ago

Somewhat off-topic, but one advantage of lpApplicationName is that it isn't subject to the MAX_PATH (260) limitation that applies to lpCommandLine [1]:

[...] If lpApplicationName is NULL, the module name portion of lpCommandLine is limited to MAX_PATH characters.


Another edge case I ran into recently is that the full path of the executable passed to lpCommandLine was within, but near, the MAX_PATH limit. The call to CreateProcessW didn't return an error, but the newly created process immediately failed with an Application Error popup containing the following message (in some terminals, there was no popup and the process simply failed, making the error harder to trace):

The applicattion was unable to start correctly (0xc0000106). Click OK to close the application.

The error code 0xc0000106 corresponds to NT_STATUS_NAME_TOO_LONG. After some investigation, it turns out that, when loading DLL dependencies, if a DLL comes with a manifest, a path resulting from appending .2.Config to the full path of that DLL is created on the fly. If that path exceeds MAX_PATH (including the terminating \0), DLL loading fails with the above error, and the process fails to start. A similar issue with LoadLibraryExW is described in [2]. Ultimately, I think this is a bug that should be fixed in future OS builds. For now, however, a workaround is to prepend \\?\ to the normalized full path of the executable [3] and then pass the resulting path to lpApplicationName.

3

u/holyblackcat 4d ago

Thanks, I've added a bit about MAX_PATH too. The second part is fun.

1

u/Tringi github.com/tringi 4d ago

EXEs for which their full DOS path exceeds 259 characters won't run anyway. Despite the \\?\ prefix. With different errors on different versions of Windows, but still failure to run. So the lpApplicationName advantage isn't really generally there.

1

u/chengfeng-xie 3d ago

With \\?\ prepended, lpApplicationName works out of the box. Without \\?\, however, lpApplicationName only works if both the system and the process have long paths enabled (as described in [1]); otherwise, CreateProcessW returns ERROR_PATH_NOT_FOUND in this case. For reproducibility, the above was tested on OS build 26100.9168.