2010-10-20 176 views
3

My C Win32應用程序應允許傳遞完整的命令行以便其他程序啓動,例如,將argv []傳遞給CreateProcess()的方法

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

myapp.exe可能看起來像

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 
    // ... 
} 

我能想到的一些解決方法:

它們都很長並且重新實現繁瑣的Windows命令行解析邏輯,這已經是CommandLineToArgvW()的一部分。

有沒有針對我的問題的「標準」解決方案?標準(Win32,CRT等)解決方案的實現算作解決方案。

回答

5

這實際上比您想象的要容易。

1)有一個API,GetCommandLine()將返回你整串

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

2)CreateProcess()允許指定的命令行,所以用它作爲

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

將會做你需要什麼。

3)通過解析命令行,您可以找到exe名稱的起始位置,並將該地址傳遞給CreateProcess()。這可能與

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

終於可以輕鬆完成:CreateProcess(NULL, strstr(GetCommandLine(), argv[i]), ...);

編輯:現在我看到你已經考慮這一選項。如果您擔心績效處罰,那麼與流程創建相比,它們並沒有什麼優勢。

+0

'的strstr()'樣部分是有問題的。這個用例怎麼樣:'myapp.exe/SetTitle C:\ app.exe/RunMinimized C:\ app.exe/ArgToApp.exe'。實際的命令從第二場比賽開始。 – 2010-10-20 22:13:53

+0

@Ilia我看到了,在這種情況下,你應該自己解析它。無論如何,不​​要在速度上放肆。 – ruslik 2010-10-20 22:25:58

0

我認爲這實際上比你想象的更普遍。

http://blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx

這最終取決於個別方案如何記號化的命令行到argv陣列(甚至CommandLineToArgv在理論上(也許在實踐中,如果有什麼意見的一個說的是真的)當它將argv初始化爲main()時,其行爲可能與CRT不同),所以甚至沒有一套可以遵循的標準規則。

但無論如何,簡短的回答是:不,不幸的是沒有簡單/標準的解決方案。你將不得不推出你自己的函數來處理引號和反斜槓等。

0

我解決它如下:你的Visual Studio安裝,你可以找到一些用於創建C庫的標準代碼的副本。特別是如果你看看VC \ crt \ src \ stdargv.c,你會發現「wparse_cmdline」函數的實現,它從GetCommandLineW API的結果創建argc和argv。我創建了此代碼的增強版本,該代碼還創建了一個「cmdv」指針數組,它指向每個argv指針開始的位置處的原始字符串。然後,您可以按照您的意願對argv參數進行操作,並且當您想將「休息」傳遞給CreateProcess時,您只需傳入cmdv [i]即可。

該解決方案的優點是使用完全相同的解析代碼,仍然像往常一樣提供argv,並允許您傳遞原始文件而無需重新引用或重新轉義它。

1

我也遇到了同樣的問題。事情是,我們不需要解析整個字符串,如果我們可以分開GetCommandLine()的結果,那麼你可以把它們放在一起。

根據微軟的文檔,你應該只考慮反斜槓和引號。

你可以找到他們的文件here

然後,您可以撥打Solve來獲取下一個參數起始點。

E.g. 
    "a b c" d e 

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

我解決了Microsoft documentation中的例子,所以擔心兼容性問題。 遞歸調用Solve函數,可以得到整個argv數組。

這裏的文件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; 
} 

運行結果是:

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 

這裏的Solve功能的所有源代碼,文件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; 
}