开发者

Way to pass argv[] to CreateProcess()

开发者 https://www.devze.com 2023-01-20 21:36 出处:网络
My C Win32 application should allow passing a full command line for another program to start, e.g. myapp.exe /foo /bar \"C:\\Program Files\\Some\\App.exe\" arg1 \"arg 2\"

My C Win32 application should allow passing a full command line for another program to start, e.g.

myapp.exe /foo /bar "C:\Program Files\Some\App.exe" arg1 "arg 2"

myapp.exe may look something like

int main(int argc, char**argv)
{
  int i;

  for (i=1; i<argc; ++i) {
     if (!strcmp(argv[i], "/foo") {
        // handle /foo
     } else if (!strcmp(argv[i], "/bar") {
        // handle /bar
     } else {
        // not an option => start of a child command line
        break;
     }
  }

  // run the command
  STARTUPINFO si;
  PROCESS_INFORMATION pi;
  // customize the above...

  // I want this, but there is no such API! :(
  CreateProcessFromArgv(argv+i, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);

  // use startup info si for some operations on a process
  // ...
}

I can think about some workarounds:

  • use GetCommandLine() and find a substring corresponding to argv[i]
  • write something similar to ArgvToCommandLine() also mentioned in another SO question

Both of them lengthy and re-implement cumbersome windows command line parsing logic, which is already a part of CommandLineToArgvW().

Is there a "stan开发者_如何学Cdard" solution for my problem? A standard (Win32, CRT, etc.) implementation of workarounds counts as a solution.


It's actually easier than you think.

1) There is an API, GetCommandLine() that will return you the whole string

myapp.exe /foo /bar "C:\Program Files\Some\App.exe" arg1 "arg 2"

2) CreateProcess() allows to specify the command line, so using it as

CreateProcess(NULL, "c:\\hello.exe arg1 arg2 etc", ....) 

will do exactly what you need.

3) By parsing your command line, you can just find where the exe name starts, and pass that address to the CreateProcess() . It could be easily done with

char* cmd_pos = strstr(GetCommandLine(), argv[3]);

and finally: CreateProcess(NULL, strstr(GetCommandLine(), argv[i]), ...);

EDIT: now I see that you've already considered this option. If you're concerned about performance penalties, they are nothing comparing to process creation.


The only standard function which you not yet included in your question is PathGetArgs, but it do not so much. The functions PathQuoteSpaces and PathUnquoteSpaces can be also helpful. In my opinion the usage of CommandLineToArgvW in combination with the with GetCommandLineW is what you really need. The usage of UNICODE during the parsing of the command line is in my opinion mandatory if you want to have a general solution.


I solved it as follows: With your Visual Studio install you can find a copy of some of the standard code used to create the C library. In particular if you look in VC\crt\src\stdargv.c you will find the implementation of "wparse_cmdline" function which creates argc and argv from the result of GetCommandLineW API. I created an augmented version of this code which also created a "cmdv" array of pointers which pointed back into the original string at the place where each argv pointer begins. You can then act on argv arguments as you wish, and when you want to pass the "rest" on to CreateProcess you can just pass in cmdv[i] instead.

This solution has the advantages that is uses the exact same parsing code, still provides argv as usual, and allows you to pass on the original without needing to re-quote or re-escape it.


I have faced the same problems with you. The thing is, we don't need to parse the whole string, if we can separate the result of GetCommandLine(), then you can put them together afterwards.

According to Microsoft's documentation, you should consider only backslashes and quotes.

You can find their documents here.

And, then, you can call Solve to get the next parameter start point.

E.g. 
     "a b c" d e

First Part: "a b c"
Next Parameter Start: d e

I solved the examples in Microsoft documentation, so do worry about the compatibility. By calling the Solve function recursively, you can get the whole argv array.

Here's the file test.c

#include <stdio.h>

extern char* Solve(char* p);

void showString(char *str)
{
    char *end = Solve(str);

    char *p = str;

    printf("First Part: ");
    while(p < end){
        fputc(*p, stdout); 
        p++;
    }

    printf("\nNext Parameter Start: %s\n", p + 1);
}

int main(){
    char str[] = "\"a b c\" d e";
    char str2[] = "a\\\\b d\"e f\"g h";
    char str3[] = "a\\\\\\\"b c d";
    char str4[] = "a\\\\\\\\\"b c\" d e";

    showString(str);
    showString(str2);
    showString(str3);
    showString(str4);

    return 0;
}

Running result are:

First Part: "a b c"
Next Parameter Start: d e
First Part: a\\b
Next Parameter Start: d"e f"g h
First Part: a\\\"b
Next Parameter Start: c d
First Part: a\\\\"b c"
Next Parameter Start: d e

Here's all the source code of Solve function, file findarg.c

/**

This is a FSM for quote recognization.

Status will be 
    1. Quoted. (STATUS_QUOTE)
    2. Normal. (STATUS_NORMAL)
    3. End.    (STATUS_END)

    Quoted can be ended with a " or \0
    Normal can be ended with a " or space( ) or \0

    Slashes
*/

#ifndef TRUE
#define TRUE 1
#endif

#define STATUS_END    0
#define STATUS_NORMAL 1
#define STATUS_QUOTE  2

typedef char * Pointer;
typedef int STATUS;

static void MoveSlashes(Pointer *p){

    /*According to Microsoft's note, http://msdn.microsoft.com/en-us/library/17w5ykft.aspx */
    /*Backslashes are interpreted literally, unless they immediately precede a double quotation mark.*/

    /*Here we skip every backslashes, and those linked with quotes. because we don't need to parse it.*/
    while (**p == '\\'){

        (*p)++;

        //You need always check the next element
        //Skip \" as well.
        if (**p == '\\' || **p == '"')
            (*p)++;

    }
}

/*    Quoted can be ended with a " or \0  */
static STATUS SolveQuote(Pointer *p){
    while (TRUE){
        MoveSlashes(p);
        if (**p == 0)
            return STATUS_END;

        if (**p == '"')
            return STATUS_NORMAL;

        (*p)++;
    }
}

/* Normal can be ended with a " or space( ) or \0 */
static STATUS SolveNormal(Pointer *p){
    while (TRUE){
        MoveSlashes(p);
        if (**p == 0)
            return STATUS_END;

        if (**p == '"')
            return STATUS_QUOTE;

        if (**p == ' ')
            return STATUS_END;

        (*p)++;
    }
}

/*
    Solve the problem and return the end pointer.

    @param p The start pointer

    @return The target pointer.
*/
Pointer Solve(Pointer p){

    STATUS status = STATUS_NORMAL;

    while (status != STATUS_END){
        switch (status)
        {
        case STATUS_NORMAL:
            status = SolveNormal(&p); break;

        case STATUS_QUOTE:
            status = SolveQuote(&p); break;

        case STATUS_END:
        default:
            break;
        }

        //Move pointer to the next place.
        if (status != STATUS_END)
            p++;
    }

    return p;
}


I think it's actually harder than you think for a general case.

See What's up with the strange treatment of quotation marks and backslashes by CommandLineToArgvW.

It's ultimately up to the individual programs how they tokenize the command-line into an argv array (and even CommandLineToArgv in theory (and perhaps in practice, if what one of the comments said is true) could behave differently than the CRT when it initializes argv to main()), so there isn't even a standard set of esoteric rules that you can follow.

But anyway, the short answer is: no, there unfortunately is no easy/standard solution. You'll have to roll your own function to deal with quotes and backslashes and the like.

0

精彩评论

暂无评论...
验证码 换一张
取 消