34

Hello I have the following code but it isn't working as expected, can't figure out what the problem is.

Basically, I'm executing a process (a .NET process) and passing it command line arguments, it is executed successfully by CreateProcess() but CreateProcess() isn't passing the command line arguments

What am I doing wrong here??

int main(int argc, char* argv[])
{
    PROCESS_INFORMATION ProcessInfo; //This is what we get as an [out] parameter

    STARTUPINFO StartupInfo; //This is an [in] parameter

    ZeroMemory(&StartupInfo, sizeof(StartupInfo));
    StartupInfo.cb = sizeof StartupInfo ; //Only compulsory field

    LPTSTR cmdArgs = "name@example.com";

    if(CreateProcess("D:\\email\\smtp.exe", cmdArgs, 
        NULL,NULL,FALSE,0,NULL,
        NULL,&StartupInfo,&ProcessInfo))
    { 
        WaitForSingleObject(ProcessInfo.hProcess,INFINITE);
        CloseHandle(ProcessInfo.hThread);
        CloseHandle(ProcessInfo.hProcess);

        printf("Yohoo!");
    }  
    else
    {
        printf("The process could not be started...");
    }

    return 0;
}

EDIT: Hey one more thing, if I pass my cmdArgs like this:

// a space as the first character
LPTSTR cmdArgs = " name@example.com";

Then I get the error, then CreateProcess returns TRUE but my target process isn't executed.

Object reference not set to an instance of an object
Jeff Atwood
  • 63,320
  • 48
  • 150
  • 153
akif
  • 12,034
  • 24
  • 73
  • 85
  • How do you detect that the arguments are not passed? – sharptooth Jul 16 '09 at 06:40
  • I check the output of my executed process, if there aren't any arguments passed the target process prints an error and terminates – akif Jul 16 '09 at 06:44
  • I suppose there could be some other problem. Can you insert a delay into the beginning of the program started and attach a debugger to it after it starts? – sharptooth Jul 16 '09 at 08:30
  • In the beginning of the program (static void Main() function) you insert smth like System.Threading.Thread.Sleep( 20000 ); and set a breakpoint onto a line following that statement. Then you compile the C# program and make your master program start the C# program. When the C# program starts it will be suspended on that statement for 20 seconds - that should be enough for you to do Tools->Debug Processes in VisualStudio and attach to the C# program. When 20 seconds pass the C# program will stop in the debugger and you can then debug it step-by-step. – sharptooth Jul 16 '09 at 13:11
  • This explanation from Microsoft might help, did for me, was in the same confusion. http://support.microsoft.com/kb/175986 – Andrei-Niculae Petre Apr 09 '13 at 16:17

8 Answers8

29

You should specify also the module name in parameters: LPTSTR cmdArgs = "App name@example.com"; It should be the whole command line (including argv[0]).

Jeff Atwood
  • 63,320
  • 48
  • 150
  • 153
EFraim
  • 12,811
  • 4
  • 46
  • 62
  • I get the error Object reference not set to an instance of an object – akif Jul 16 '09 at 07:04
  • 1
    This helps. It would never know if I didn't come across this answer. – david.healed Oct 27 '10 at 06:14
  • 3
    I have to say that MSDN isn't too clear on this: "Because `argv[0]` is the module name, C programmers generally repeat the module name as the first token in the command line." and "If `lpApplicationName` is NULL, the first white space–delimited token of the command line specifies the module name." The latter doesn't seem to apply as `lpApplicationName` is supplied. – Deanna Mar 14 '13 at 16:44
  • 6
    Exactly, many programs start with the evaluation of arguments from argv [1] because argv [0] usually contains the module name. If module name is not in the commando line, this is missing. And eventually you lose the first Prameter, this is a big problem and it cost me too much time to figure it out. – Alexander Drichel Nov 06 '13 at 07:50
24

If the first parameter to CreateProcess() is non-NULL, it will use that to locate the image to launch.

If it is NULL, it will parser the 2nd argument to try to get the executable to launch from the 1st token.

In either case, the C runtime will use the second argument to populate the argv array. So the first token from that parameter shows up in argv[0].

You probably want something like the following (I've change the smtp.exe program to echoargs.exe - a simple utility I have to help figure out just this kind of issue):

int main(int argc, char* argv[])
{
    PROCESS_INFORMATION ProcessInfo; //This is what we get as an [out] parameter

    STARTUPINFO StartupInfo; //This is an [in] parameter
    char cmdArgs[] = "echoargs.exe name@example.com";

    ZeroMemory(&StartupInfo, sizeof(StartupInfo));
    StartupInfo.cb = sizeof StartupInfo ; //Only compulsory field


    if(CreateProcess("C:\\util\\echoargs.exe", cmdArgs, 
        NULL,NULL,FALSE,0,NULL,
        NULL,&StartupInfo,&ProcessInfo))
    { 
        WaitForSingleObject(ProcessInfo.hProcess,INFINITE);
        CloseHandle(ProcessInfo.hThread);
        CloseHandle(ProcessInfo.hProcess);

        printf("Yohoo!");
    }  
    else
    {
        printf("The process could not be started...");
    }

    return 0;
}

Here's the output I get from that program:

echoargs.exe name@example.com
[0]: echoargs.exe
[1]: name@example.com

Yohoo!
Michael Burr
  • 333,147
  • 50
  • 533
  • 760
7

It doesn't look like you are using CreateProcess correctly, see http://msdn.microsoft.com/en-us/library/ms682425%28VS.85%29.aspx.

  • The command line to be executed. The maximum length of this string is 32,768 characters, including the Unicode terminating null character. If lpApplicationName is NULL, the module name portion of lpCommandLine is limited to MAX_PATH characters.

  • The lpCommandLine parameter can be NULL. In that case, the function uses the string pointed to by lpApplicationName as the command line.

  • If both lpApplicationName and lpCommandLine are non-NULL, the null-terminated string pointed to by lpApplicationName specifies the module to execute, and the null-terminated string pointed to by lpCommandLine specifies the command line. The new process can use GetCommandLine to retrieve the entire command line. Console processes written in C can use the argc and argv arguments to parse the command line. Because argv[0] is the module name, C programmers generally repeat the module name as the first token in the command line.

So in your case, you need this as the command argument and should probably pass a NULL for the first parameter to get the behaviour your want.

// NOTE THE Null-Terminated string too!
LPTSTR cmdArgs = "D:\\email\\smtp.exe name@example.com\0";
Jeff Atwood
  • 63,320
  • 48
  • 150
  • 153
Ray Hayes
  • 14,896
  • 8
  • 53
  • 78
6

Below is a cut down version of the code used by the Zeus IDE to run external processes:

bool createProcess(const char *pszTitle, const char *pszCommand)
{
  STARTUPINFO StartInfo;

  memset(&StartInfo, 0, sizeof(StartInfo));

  StartInfo.cb      = sizeof(StartInfo);
  StartInfo.lpTitle = (pszTitle) ? (char *)pszTitle : (char *)pszCommand;

  StartInfo.wShowWindow = SW_NORMAL;
  StartInfo.dwFlags    |= STARTF_USESHOWWINDOW;

  if (CreateProcess(0, (char *)pszCommand, 
                    0, 0, TRUE,
                    CREATE_NEW_PROCESS_GROUP, 0, 0, 
                    &StartInfo, &ProcessInfo))
  {
    lErrorCode = 0;
  }
  else
  {
    lErrorCode = GetLastError();
  }

  return (lErrorCode == 0);
}

The pszCommand would be the full executable path and file name and arguments so for example:

pszCommand = "D:\\email\\smtp.exe name@example.com";

From what I can tell, the only real difference between the two is that in the Zeus example, the dwCreationFlags argument is set to the CREATE_NEW_PROCESS_GROUP value.

Jeff Atwood
  • 63,320
  • 48
  • 150
  • 153
jussij
  • 10,370
  • 1
  • 33
  • 49
  • 1
    casting a `const char *` to `char *` is dangerous and invokes UB – phuclv Feb 15 '19 at 04:50
  • I'm tempted to say it is okay in this case. Rationale: the 2nd argument to `CreateProcess` is `LPTSTR` (not `LPCTSTR`) presumably because of `main(int argc, char *argv[])`. The API does not copy the arguments. Bottom line, as long as the process being called does not violate its `argv[]` the `(char*)` should be fine. – Andreas Spindler Oct 24 '19 at 07:19
  • @jussij, what does pszTitle consist of ? What data does it store? – Elvin Feb 18 '20 at 08:52
4

You can add a space as first character of the cmdArgs string:

LPTSTR cmdArgs = " name@example.com";

Apparently Windows appends the 2nd argument string to the application name represented by the first argument, and the result is passed as command line arguments to the executable. So adding a space will properly separate the arguments.

kundrata
  • 651
  • 3
  • 12
  • 24
3

Try this:

LPTSTR cmdArgs = "name@example.com";
CString szcmdline("D:\\email\\smtp.exe");
szcmdline += _T(" ") + cmdArgs ;

//Leave first param empty and pass path + argms in 
    if(CreateProcess(NULL, szcmdline, second
Jeff Atwood
  • 63,320
  • 48
  • 150
  • 153
aJ.
  • 34,624
  • 22
  • 86
  • 128
  • Error in that code, you're putting the value into szcmdline_in but using szcmdline. Any reason why to bring MFC strings into the example? – Ray Hayes Jul 16 '09 at 06:56
  • No specific reason.It was just for symbolic. I will change the szcmdline_in to szcmdline. Thanks. – aJ. Jul 16 '09 at 07:10
1

The Unicode version of this function, CreateProcessW, can modify the contents of this string. Therefore, this parameter cannot be a pointer to read-only memory (such as a const variable or a literal string). If this parameter is a constant string, the function may cause an access violation.

Therefore you can try using LPTSTR cmdArgs = _tcsdup("name@example.com").

Another problem is: how does the target process reads the arguments? using argv[0] as application name? Then you shoud append the application name as the first parameter too.

Jeff Atwood
  • 63,320
  • 48
  • 150
  • 153
Vlagged
  • 503
  • 5
  • 17
  • 1
    Or drop the memory management responsibility back where it belongs, the compiler: TCHAR[] cmdArgs = _T("name@example.com"); – MSalters Jul 16 '09 at 08:41
  • False sense of security here. Ask yourself why you have to bend around the API and Microsoft hasn't fixed it for 30 years or so. As I mentioned earlier it is likely Windows requires a `char*` (or `LPTSTR`) because of `int main(int argc, char* argv[])`. Just think of it. There's no real solution for the Win32 API should the callee write to `argv[]`. Actually, by declaring `LPTSTR` the leave that as an option. – Andreas Spindler Oct 24 '19 at 07:35
  • The new process runs in a different address space; what it does to `argv` does not affect the caller of `CreateProcess()` in any way. – user2894959 Nov 06 '19 at 20:03
0

You are not allocating memory for your string.

Instead of:

LPTSTR cmdArgs = "name@example.com";

try:

TCHAR cmdArgs[] = "name@example.com";

Edit: then call:

 CreateProcess("D:\\email\\smtp.exe", &cmdArgs[0], ...

This will create a local array on the stack and then pass a pointer to that array.

Jeff Atwood
  • 63,320
  • 48
  • 150
  • 153
morechilli
  • 9,827
  • 7
  • 33
  • 54
  • error C2440: 'initializing' : cannot convert from 'char (*)[20]' to 'char *' – akif Jul 16 '09 at 08:14
  • Actually just `cmdArgs` would be fine; `&cmdArgs[0]` is just a more complicated way of saying the same thing. – Harry Johnston Jan 28 '15 at 00:08
  • Clear your concepts constant strings literals doesnt require memory allocation like arrays char str[], you can use pointers like char *str = "Arguements" aswell and indeed better solution here. – Haseeb Mir Jan 27 '20 at 01:52