【问题标题】:How to execute commands from an attached console如何从附加的控制台执行命令
【发布时间】:2019-01-10 02:38:25
【问题描述】:

我正在编写一个 WinAPI GUI 程序,该程序需要调用 ftp 和可能的其他控制台程序,同时让它们的控制台输出相应地进行操作。在读取所有输出之前等待 ftp 完成执行是不行的。

我目前的方法是调用 CreateProcess() 创建一个 cmd.exe 进程,该进程可能隐藏丑陋的控制台窗口,AttachConsole() 使其成为我自己的,GetStdHandle() 获取输入和输出句柄, SetConsoleCursorPosition()控制台缓冲区 的末尾,WriteConsole() 使用 ftp\ndir\n 等命令。然而,这个命令被写入但没有被执行。但是,我可以手动使用相同的控制台(使用 CreateProcess()CREATE_NEW_CONSOLE 标志)输入 ftp 按回车键并执行。

之前涉及的方法:

  1. 直接使用CreateProcess() 调用ftp 并重定向输入/输出。

    CreateProcess() 进程结束之前无法获得ftp 输出。

  2. 使用system()

    在获得任何输出之前被告知不要使用它。

我当前的精简代码:

// Next two structures might be a bit misleading, they were used for the 1. previous
// approach
PROCESS_INFORMATION piProcInfo;
ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION)); 
STARTUPINFO siStartInfo;
ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
siStartInfo.cb = sizeof(STARTUPINFO); 
siStartInfo.hStdError = g_hChildStd_OUT_Wr;
siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
siStartInfo.hStdInput = g_hChildStd_IN_Rd;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
SECURITY_ATTRIBUTES security;
security.nLength = sizeof(SECURITY_ATTRIBUTES); 
security.lpSecurityDescriptor = NULL;
security.bInheritHandle = FALSE;
CreateProcess( NULL, "cmd", &security, &security, FALSE, NORMAL_PRIORITY_CLASS |
 CREATE_NEW_CONSOLE, NULL, NULL, &siStartInfo, &piProcInfo); 
uint32_t pidConsole = piProcInfo.dwProcessId;
while ( ! AttachConsole(pidConsole) ){};
HANDLE myConsoleIn, myConsoleOut;
myConsoleIn = GetStdHandle(STD_INPUT_HANDLE);
myConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE);
Sleep(100);
CONSOLE_SCREEN_BUFFER_INFO myConsoleCursorInformation = {};
GetConsoleScreenBufferInfo(myConsoleOut,&myConsoleCursorInformation);
SetConsoleCursorPosition(myConsoleOut,myConsoleCursorInformation.dwSize);
CHAR myConsoleBuffer[200]="dir\n";
DWORD myConsoleProcessed;
WriteConsole( myConsoleOut, myConsoleBuffer, 4, &myConsoleProcessed, NULL);

我怎样才能让写在控制台中的命令执行? 除了我尝试用尾随 \n 结束命令之外,还有其他方法吗? ie。使用 WriteConsole() 带有 dir\n 或 ftp\n 参数。

我考虑在键入所需命令后向相关进程发送按键。然而,创建的控制台不仅需要手动按下回车键,还需要手动输入 dirftp 或任何命令。

请随时指出任何遗漏的信息!

【问题讨论】:

    标签: c++ winapi c++17 windows-console


    【解决方案1】:

    如何让写在控制台中的命令执行?有没有 替代我尝试用尾随 \n 结束命令的尝试,即。 使用带有 dir\n 或 ftp\n 参数的 WriteConsole()。

    试试下面的代码看看是否有效:

    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));
    
    const wchar_t *cmdPath = L"C:\\Windows\\System32\\cmd.exe";
    wchar_t *cmdArgs = (wchar_t *)L"C:\\Windows\\System32\\cmd.exe /k dir";
    
    BOOL result = CreateProcess(cmdPath, cmdArgs, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
    DWORD errCode = GetLastError();
    if (!result)
    {
        std::cout << "Create Process failed: " << GetLastError() << std::endl;
    }
    

    /K 运行命令,然后返回到 CMD 提示符。 这对于测试、检查变量很有用

    如果您想要“运行命令然后终止”,请使用 /C。

    更新: 使用管道与子进程(cmd.exe)通信的完整代码。

    HANDLE g_hChildStd_IN_Rd = NULL;
    HANDLE g_hChildStd_IN_Wr = NULL;
    HANDLE g_hChildStd_OUT_Rd = NULL;
    HANDLE g_hChildStd_OUT_Wr = NULL;
    
    #define BUFSIZE 1024
    
    void ErrorExit(LPCTSTR lpszFunction)
    {
        LPVOID lpMsgBuf;
        LPVOID lpDisplayBuf;
        DWORD dw = GetLastError();
    
        FormatMessage(
            FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM |
            FORMAT_MESSAGE_IGNORE_INSERTS,
            NULL,
            dw,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            (LPTSTR)&lpMsgBuf,
            0, NULL);
    
        lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT,
            (lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR));
        StringCchPrintf((LPTSTR)lpDisplayBuf,
            LocalSize(lpDisplayBuf) / sizeof(TCHAR),
            TEXT("%s failed with error %d: %s"),
            lpszFunction, dw, lpMsgBuf);
        MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);
    
        LocalFree(lpMsgBuf);
        LocalFree(lpDisplayBuf);
        ExitProcess(1);
    }
    
    void ReadFromPipe(void)
    {
        DWORD dwRead, dwWritten;
        CHAR chBuf[BUFSIZE];
        BOOL bSuccess = FALSE;
        HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    
        for (;;)
        {
            DWORD bytesAvail = 0;
            if (!PeekNamedPipe(g_hChildStd_OUT_Rd, NULL, 0, NULL, &bytesAvail, NULL)) {
                std::cout << "Failed to call PeekNamedPipe" << std::endl;
            }
            if (bytesAvail) {
                DWORD n;
                BOOL success = ReadFile(g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &n, NULL);
                if (!success || n == 0) {
                }
                bSuccess = WriteFile(hParentStdOut, chBuf,n, &dwWritten, NULL);
            }
            else
            {
                break;
            }
        }
    
    }
    
    void WriteToPipe(void)
    {
        DWORD dwWritten;
        BOOL bSuccess = FALSE;
        CHAR buf[] = "dir\n";
    
        bSuccess = WriteFile(g_hChildStd_IN_Wr, buf, sizeof(buf)-1, &dwWritten, NULL);
    }
    
    int main()
    {
        STARTUPINFO si;
        PROCESS_INFORMATION pi;
    
        SECURITY_ATTRIBUTES saAttr;
    
        printf("\n->Start of parent execution.\n");
    
        // Set the bInheritHandle flag so pipe handles are inherited. 
    
        saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
        saAttr.bInheritHandle = TRUE;
        saAttr.lpSecurityDescriptor = NULL;
    
        // Create a pipe for the child process's STDOUT. 
    
        if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0))
            ErrorExit(TEXT("StdoutRd CreatePipe"));
    
        // Ensure the read handle to the pipe for STDOUT is not inherited.
    
        if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
            ErrorExit(TEXT("Stdout SetHandleInformation"));
    
        // Create a pipe for the child process's STDIN. 
    
        if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0))
            ErrorExit(TEXT("Stdin CreatePipe"));
    
        // Ensure the write handle to the pipe for STDIN is not inherited. 
    
        if (!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0))
            ErrorExit(TEXT("Stdin SetHandleInformation"));
    
        ZeroMemory(&si, sizeof(si));
        si.cb = sizeof(si);
        ZeroMemory(&pi, sizeof(pi));
    
    
        si.cb = sizeof(STARTUPINFO);
        si.hStdError = g_hChildStd_OUT_Wr;
        si.hStdOutput = g_hChildStd_OUT_Wr;
        si.hStdInput = g_hChildStd_IN_Rd;
        si.dwFlags |= STARTF_USESTDHANDLES;
    
        TCHAR cmdPath[] = TEXT("C:\\Windows\\System32\\cmd.exe");
    
        BOOL result = CreateProcess(cmdPath, NULL, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
        DWORD errCode = GetLastError();
        if (!result)
        {
            std::cout << "Create Process failed: " << GetLastError() << std::endl;
        }
    
        for (;;)
        {
            ReadFromPipe();
    
            WriteToPipe();
        }
    }
    

    【讨论】:

    • 感谢您的回答!我确实得到了执行 /k 时写下的任何命令,但之后控制台立​​即变得无用。我仍然有同样的问题没有解决方案。假设我运行“cmd /k ftp”。我无法根据需要向正在运行的 ftp 程序发送更多命令!
    • @JuanManuelLópezManzano 您可以通过管道与创建的进程进行通信。 Here 是您可以参考的示例。
    • 韩那是我之前的尝试“1.”。在创建的控制台关闭之前,输出似乎不可用。我需要根据输出采取行动。
    • @JuanManuelLópezManzano 抱歉迟到了。我通过添加适用于我的完整代码来更新我的答案。你可以检查一下。
    • 还不能让它工作。我看到您没有使用 WinAPI 创建 GUI。 printf 调用是否在 WinAPI GUI 中工作?通过使用 ConPTY 方法,我有点接近我的目标,但是听管道给了我一个我无法解析的未知编码文本。你有任何 WinAPI 工作示例吗?感谢您的帮助,谢谢。
    猜你喜欢
    • 1970-01-01
    • 2015-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-20
    • 1970-01-01
    • 2014-12-17
    相关资源
    最近更新 更多