如何在 DETACHED_PROCESS 中执行 .bat 文件?

问题描述

我有一个简单的 C++ 控制台应用程序,它启动 notepad.exe 并加载文件 D:\MyTextFile.txt,然后控制台应用程序退出,但记事本仍在运行。代码效果很好:

int _tmain(int argc,_TCHAR* argv[])
{
    STARTUPINFO si;
    ZeroMemory( &si,sizeof(si) );
    si.cb = sizeof(si);
    PROCESS_informatION pi;
    ZeroMemory( &pi,sizeof(pi) );
    WCHAR pCmd[] = {'n','o','t','e','p','a','d','.','x',' ','D',':','\\','M','y','T','F','i','l',0};
    BOOL result = CreateProcess
    (   
        _T("C:\\Windows\\System32\\notepad.exe"),// Module name
        pCmd,// Command line (as modifiable array)
        NULL,// Process handle not inheritable
        NULL,// Thread handle not inheritable
        FALSE,// Set bInheritHandles to FALSE
        DETACHED_PROCESS,// Detach process
        NULL,// Use parent's environment block
        NULL,// Use parent's starting directory
        &si,// Pointer to STARTUPINFO structure
        &pi                                       // Pointer to PROCESS_informatION structure (returned)
    );
    return (result) ? 0 : -1;
}

但是,如果我用 cmd 替换记事本,用 MyBatFile.bat 替换 MyTextFile.txt 那么它就不起作用了。 MyBatFile.bat 的内容

C:\Windows\System32\notepad.exe   D:\MyTextFile.txt

修改后的控制台应用程序:

int _tmain(int argc,sizeof(pi) );
    WCHAR pCmd[] = {'c','m','/','C','B','b',0};
    BOOL result = CreateProcess
    (   
        _T("C:\\Windows\\System32\\cmd.exe"),// Pointer to STARTUPINFO structure
        &pi                                   // Pointer to PROCESS_informatION structure (returned)
    );
    return (result) ? 0 : -1;
}

当我执行上面的代码时,我看到一个命令提示符非常快地闪过,但它似乎没有执行 MyBatFile.bat。但是,如果我用 DETACHED_PROCESS 替换 CREATE_UNICODE_ENVIRONMENT 然后 MyBatFile.bat 被执行,但由于该进程不再分离命令提示符挂起,直到我关闭记事本,这是不希望的。有人知道如何修改我的代码以便能够在分离的进程中执行 MyBatFile.bat 吗?

解决方法

我无法解释原因,但如果我选择 CREATE_NO_WINDOW 而不是 DETACHED_PROCESS,它似乎适用于 .bat 文件(通过 cmd.exe)。以下代码似乎适用于 .exe 文件和 .bat 文件:

// RunDetached.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>

int getIndexOfStringIgnoreCare(WCHAR* bigStingToSearchThrough,WCHAR* subStingToFind);
int getFirstIndexOfChar(WCHAR* stringToInvestigate,int startIndex,WCHAR charToLookFor);
int getLastIndexOfChar(WCHAR* stringToInvestigate,WCHAR charToLookFor);

int _tmain(int argc,_TCHAR* argv[])
{
    WCHAR* pOriginalCmd = ::GetCommandLine();
    // Test code (modify paths to RunDetached.exe and MyFile.txt appropriately)
//    pOriginalCmd = _T("\"D:\\My Visual Studio Projects\\RunDetached\\debug\\RunDetached.exe\" \"C:\\Windows\\System32\\notepad.exe\" \"D:\\1.txt\"");

    int CmdLen = (int)wcslen(pOriginalCmd);

    // Determine where certain characters are located (excl means the particular index is not included,e.g. 
    // if indexExcl is 5 then index 4 is the last included index).
    int beginningOf1stArg   = getFirstIndexOfChar(pOriginalCmd,L'\"');
    int endOf1stArgExcl     = getFirstIndexOfChar(pOriginalCmd,beginningOf1stArg + 1,L'\"') + 1;
    int beginningOf2ndArg   = getFirstIndexOfChar(pOriginalCmd,endOf1stArgExcl   + 1,L'\"');
    int endOf2ndArgExcl     = getFirstIndexOfChar(pOriginalCmd,beginningOf2ndArg + 1,L'\"') + 1;
    int beginningOf3rdArg   = getFirstIndexOfChar(pOriginalCmd,endOf2ndArgExcl   + 1,L'\"');
    int endOfLastArgExcl    = getLastIndexOfChar (pOriginalCmd,CmdLen            - 1,L'\"') + 1;
    int beginningOfFileName = getLastIndexOfChar (pOriginalCmd,endOf2ndArgExcl   - 2,L'\\') + 1;
    int endOfFileNameExcl   = endOf2ndArgExcl - 1;
    if ((beginningOf1stArg < 0) || (endOf1stArgExcl     < 0) || (beginningOf2ndArg < 0) || (endOf2ndArgExcl < 0) ||
        (endOfLastArgExcl  < 0) || (beginningOfFileName < 0) || (endOfFileNameExcl < 0))
    {
        return -1;
    }

    // Determine the application to execute including full path. E.g. for notepad this should be:
    // C:\Windows\System32\notepad.exe (without any double-quotes)
    int lengthOfApplicationNameAndPathInChars = (endOf2ndArgExcl -1) - (beginningOf2ndArg + 1);  // Skip double-quotes
    WCHAR* lpApplicationNameAndPath = (WCHAR*)malloc(sizeof(WCHAR) * (lengthOfApplicationNameAndPathInChars + 1));
    memcpy(lpApplicationNameAndPath,&pOriginalCmd[beginningOf2ndArg + 1],sizeof(WCHAR) * (lengthOfApplicationNameAndPathInChars));
    lpApplicationNameAndPath[lengthOfApplicationNameAndPathInChars] = (WCHAR)0;  // Null terminate

    // Determine the command argument. Must be in modifyable memory and should start with the
    // application name without the path. E.g. for notepad with command argument D:\MyFile.txt:
    // "notepad.exe" "D:\MyFile.txt" (with the double-quotes).
    WCHAR* modifiedCmd = NULL;
    if (0 < beginningOf3rdArg)
    {
        int lengthOfApplicationNameInChars = endOfFileNameExcl - beginningOfFileName;  // Application name without path
        int lengthOfRestOfCmdInChars = CmdLen - beginningOf3rdArg;
        int neededCmdLengthInChars = 1 + lengthOfApplicationNameInChars + 2 + lengthOfRestOfCmdInChars; // Two double-quotes and one space extra

        modifiedCmd = (WCHAR*)malloc(sizeof(WCHAR) * (neededCmdLengthInChars + 1));  // Extra char is null-terminator
        modifiedCmd[0] = L'\"';                                                             // Start with double-quoute
        memcpy(&modifiedCmd[1],&pOriginalCmd[beginningOfFileName],sizeof(WCHAR) * (lengthOfApplicationNameInChars));
        modifiedCmd[1 + (lengthOfApplicationNameInChars)] = L'\"';
        modifiedCmd[1 + (lengthOfApplicationNameInChars) + 1] = L' ';
        memcpy(&modifiedCmd[1 + (lengthOfApplicationNameInChars) + 2],&pOriginalCmd[beginningOf3rdArg],sizeof(WCHAR) * lengthOfRestOfCmdInChars);
        modifiedCmd[neededCmdLengthInChars] = (WCHAR)0;
    }

    STARTUPINFO si;
    ZeroMemory( &si,sizeof(si) );
    si.cb = sizeof(si);
    PROCESS_INFORMATION pi;
    ZeroMemory( &pi,sizeof(pi) );

    BOOL result = CreateProcess                       // Start the process
    (
        lpApplicationNameAndPath,// Module name and full path
        modifiedCmd,// Command line
        NULL,// Process handle not inheritable
        NULL,// Thread handle not inheritable
        FALSE,// Set bInheritHandles to FALSE
        (0 <= getIndexOfStringIgnoreCare              // Special case for cmd.exe (don't 
            (lpApplicationNameAndPath,L"cmd.exe")) ? // know why but it seems to work)
            CREATE_NO_WINDOW : DETACHED_PROCESS,NULL,// Use parent's environment block
        NULL,// Use parent's starting directory
        &si,// Pointer to STARTUPINFO structure
        &pi                                           // Pointer to PROCESS_INFORMATION structure (returned)
    );
    free(lpApplicationNameAndPath);
    if (modifiedCmd != NULL)
    {
        free(modifiedCmd);
    }
    if (result) return 0;
    wchar_t msg[2048];
    FormatMessage
    (
        FORMAT_MESSAGE_FROM_SYSTEM,::GetLastError(),MAKELANGID(LANG_NEUTRAL,SUBLANG_SYS_DEFAULT),msg,sizeof(msg),NULL
    );
    fputws(msg,stderr);
    _flushall();
    return -1;
}

bool compareCharsIgnoreCase(WCHAR char1,WCHAR char2)
{
    if (char1 == char2)
    {
        return true;
    }
    const int UPPER_LOWER_CASE_OFFSET_IN_ASCII_TABLE = 'a' - 'A';
    if ((L'A' <= char1) && (char1 <= L'Z'))
    {
        return ((char1 + UPPER_LOWER_CASE_OFFSET_IN_ASCII_TABLE) == char2);
    }
    if ((L'a' <= char1) && (char1 <= L'z'))
    {
        return ((char1 - UPPER_LOWER_CASE_OFFSET_IN_ASCII_TABLE) == char2);
    }
    return false;
}

int getIndexOfStringIgnoreCare(WCHAR* bigStringToSearchThrough,WCHAR* subStringToFind)
{
    if ((bigStringToSearchThrough == NULL) || (subStringToFind == NULL))
    {
        return -1;
    }
    int bigStringLen = (int)wcslen(bigStringToSearchThrough);
    int subStringLen = (int)wcslen(subStringToFind);
    if ((5000 < bigStringLen) || (5000 < subStringLen))   // Sanity check
    {
        return -1;
    }
    for (int i = 0; i < (bigStringLen - subStringLen + 1); i++)
    {
        for (int j = 0; j < subStringLen; j++)
        {
            if (!compareCharsIgnoreCase(bigStringToSearchThrough[i + j],subStringToFind[j]))
            {
                break;
            }
            else if ((j + 1) == subStringLen)
            {
                return i;
            }
        }
    }
    return -1;
}


int getFirstIndexOfChar(WCHAR* stringToInvestigate,WCHAR charToLookFor)
{
    int stringLen = (int)wcslen(stringToInvestigate);
    if (5000 < stringLen)   // Sanity check
    {
        return -1;
    }
    for (int i = startIndex; i < stringLen; i++)
    {
        if (stringToInvestigate[i] == charToLookFor)
        {
            return i;
        }
    }
    return -1;
}

int getLastIndexOfChar(WCHAR* stringToInvestigate,WCHAR charToLookFor)
{
    int stringLen = (int)wcslen(stringToInvestigate);
    if (5000 < stringLen)   // Sanity check
    {
        return -1;
    }
    for (int i = min(stringLen - 1,startIndex); 0 <= i; i--)
    {
        if (stringToInvestigate[i] == charToLookFor)
        {
            return i;
        }
    }
    return -1;
}

所以,通过上面的代码,你可以同时做到

"RunDetached.exe" "C:\Windows\System32\notepad.exe"   "D:\MyTextFile.txt"

"RunDetached.exe" "C:\Windows\System32\cmd.exe" "/c" "D:\MyBatFile.bat"

并且您的呼叫应用程序不会挂起。用双引号将每个参数括起来很重要,因为我在代码中使用它们来查找不同的参数。