In the Book, Windows Via C/C++ Jeffrey Richter mentioned about the peculiarity of command line parameter of CreateProcess function.Normally for most of the Windows API, the input parameters are constant. But for CreateProcess, we’ve to pass the a non const string argument as command line parameter because internally the API modifying the input parameter and the the original data passed will be restored back to its original form, when the API call returns.
Let’s a dig a bit more.
The CreateProcess API takes the following form
[sourcecode language='cpp']
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
);
[/sourcecode]
lpCommandLine parameter of the API is actually specified in LPTSTR which is defined as non-const TCHAR pointer. Effectively we’re not getting anything back from the API but still we’ve to pass a writable memory location. Otherwise there’s a chance to face an access violation as the API tries to modify the passing pointer.
For e.g
[sourcecode language='cpp']
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
CreateProcess( NULL, _T(“NOTEPAD”), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi );
[/sourcecode]
The above call makes an access violation as the Microsoft C/C++ compiler placing the constant strings in the read only memory. If you use old version of a Microsoft compiler you may not face this access violation as the constant data is stored at read/write memory location.
The best thing we can do is to copy the string to a temporary buffer and pass to the API. We can hope that Microsoft may fix this issue in their future release.
CreateProcess is actually defined as
[sourcecode language='cpp']
#ifdef _UNICODE
#define CreateProcess CreateProcessW
#else
#define CreateProcess CreateProcessA
#endif
[/sourcecode]
If UNICODE is not defined in your project, the API may call ANSI version (CreateProcessA) function which is actually a wrapper function. Inside ANSI version of the most of the APIs, temporary string buffers are defined and the values are converted from ANSI to UNICODE. These string buffers are passed to the UNICODE version of the API which is actually doing the task(remember that Windows is a UNICODE Operating System). So that access violation may not occur for non-unicode applications.
We can solve this problem as follows
[sourcecode language='cpp']
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
TCHAR cmdline[] = _T( “NOTEPAD”);
CreateProcess( NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi );
[/sourcecode]
Okay we’ve solved the problem. In his recent blog post, Raymond Chen has explained the some more interesting stuff on the same story.
Basically, somebody back in the 1980’s wanted to avoid allocating memory. (Another way of interpreting this is that somebody tried to be a bit too clever.)
The CreateProcess temporarily modifies the string you pass as the lpCommandLine in its attempt to figure out where the program name ends and the command line arguments begin. Now, it could have made a copy of the string and made its temporary modifications to the copy, but hey, if you modify the input string directly, then you save yourself an expensive memory allocation operation. Back in the old days, people worried about avoiding memory allocations, so this class of micro-optimization is the sort of thing people worried about as a matter of course. Of course, nowadays, it seems rather antiquated.
Now, there may also be good technical reasons (as opposed to merely performance considerations) for avoiding allocating memory on the heap. When a program crashes, the just in time debugger is launched with the CreateProcess function, and you don’t want to allocate memory on the heap if the reason the program crashed is that the heap is corrupted. Otherwise, you can get yourself into a recursive crash loop: While trying to launch the debugger, you crash, which means you try to launch the debugger to debug the new crash, which again crashes, and so on. The original authors of the CreateProcess function were careful to avoid allocating memory off the heap, so that in the case the function is being asked to launch the debugger, it won’t get waylaid by a corrupted heap.
Whether these concerns are still valid today I am not sure, but it was those concerns that influenced the original design and therefore the interface.