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

How do I execute a .bat-file in a DETACHED_PROCESS?

我有一个简单的 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','.','e','x','e',' ','D',':','\','M','y','T','e','x','t','F','i','l','e','.','t','x','t',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 并将 MyTextFile.txt 替换为 MyBatFile.bat,则它不起作用。 MyBatFile.bat的内容:

C:\Windows\System32\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[] = {'c','m','d','.','e','x','e',' ','/','C',' ','D',':','\','M','y','B','a','t','F','i','l','e','.','b','a','t',0};
    BOOL result = CreateProcess
    (   
        _T("C:\Windows\System32\cmd.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;
}

当我执行上面的代码时,我看到命令提示符快速闪过,但它似乎没有执行 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, int startIndex, 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, 0,                     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,
        NULL,
        ::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, int startIndex, 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, int startIndex, 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"

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