C++执行CMD命令

17

我遇到了一个严重的问题。我需要通过C++执行CMD命令行,但不希望控制台窗口显示出来。因此我不能使用system(cmd),因为会显示窗口。

我尝试过winExec(cmd, SW_HIDE),但这也不起作用。CreateProcess是另一个我尝试过的,但这只能运行程序或批处理文件。

最终我尝试了ShellExecute

ShellExecute( NULL, "open",
    "cmd.exe",
    "ipconfig > myfile.txt",
    "c:\projects\b",
    SW_SHOWNORMAL
);

有人能看出上面的代码有什么问题吗?我使用SW_SHOWNORMAL,直到我知道这个可以正常工作。

我真的需要一些帮助。目前还没有发现任何问题,而我已经尝试了相当长的一段时间。如果有任何建议,都非常感谢 :)


你检查过返回码了吗? - Collin
1
我知道你已经得到了答案,但通常最好说一下它为什么不起作用。 - Deanna
为什么不调用WMI_函数并将结果写入文件,没有窗口,只有你需要的数据。 - graham.reeds
6个回答

19

将输出重定向到自己的管道是更整洁的解决方案,因为它避免了创建输出文件,但以下方法同样有效:

ShellExecute(0, "open", "cmd.exe", "/C ipconfig > out.txt", 0, SW_HIDE);

你不会看到命令行窗口,输出结果也如预期一样被重定向了。

除了/C之外,你的代码可能会失败,因为你将路径指定为"c:\projects\b"而不是"c:\\projects\\b"


8

这是我实现的一个DosExec函数,它可以(静默地)执行任何DOS命令,并将生成的输出作为Unicode字符串检索。

// Convert an OEM string (8-bit) to a UTF-16 string (16-bit) 
#define OEMtoUNICODE(str)   CHARtoWCHAR(str, CP_OEMCP)

/* Convert a single/multi-byte string to a UTF-16 string (16-bit).
 We take advantage of the MultiByteToWideChar function that allows to specify the charset of the input string.
*/
LPWSTR CHARtoWCHAR(LPSTR str, UINT codePage) {
    size_t len = strlen(str) + 1;
    int size_needed = MultiByteToWideChar(codePage, 0, str, len, NULL, 0);
    LPWSTR wstr = (LPWSTR) LocalAlloc(LPTR, sizeof(WCHAR) * size_needed);
    MultiByteToWideChar(codePage, 0, str, len, wstr, size_needed);
    return wstr;
}

/* Execute a DOS command.

 If the function succeeds, the return value is a non-NULL pointer to the output of the invoked command. 
 Command will produce a 8-bit characters stream using OEM code-page.

 As charset depends on OS config (ex: CP437 [OEM-US/latin-US], CP850 [OEM 850/latin-1]),
 before being returned, output is converted to a wide-char string with function OEMtoUNICODE.

 Resulting buffer is allocated with LocalAlloc.
 It is the caller's responsibility to free the memory used by the argument list when it is no longer needed. 
 To free the memory, use a single call to LocalFree function.
*/
LPWSTR DosExec(LPWSTR command){
    // Allocate 1Mo to store the output (final buffer will be sized to actual output)
    // If output exceeds that size, it will be truncated
    const SIZE_T RESULT_SIZE = sizeof(char)*1024*1024;
    char* output = (char*) LocalAlloc(LPTR, RESULT_SIZE);

    HANDLE readPipe, writePipe;
    SECURITY_ATTRIBUTES security;
    STARTUPINFOA        start;
    PROCESS_INFORMATION processInfo;

    security.nLength = sizeof(SECURITY_ATTRIBUTES);
    security.bInheritHandle = true;
    security.lpSecurityDescriptor = NULL;

    if ( CreatePipe(
                    &readPipe,  // address of variable for read handle
                    &writePipe, // address of variable for write handle
                    &security,  // pointer to security attributes
                    0           // number of bytes reserved for pipe
                    ) ){


        GetStartupInfoA(&start);
        start.hStdOutput  = writePipe;
        start.hStdError   = writePipe;
        start.hStdInput   = readPipe;
        start.dwFlags     = STARTF_USESTDHANDLES + STARTF_USESHOWWINDOW;
        start.wShowWindow = SW_HIDE;

// We have to start the DOS app the same way cmd.exe does (using the current Win32 ANSI code-page).
// So, we use the "ANSI" version of createProcess, to be able to pass a LPSTR (single/multi-byte character string) 
// instead of a LPWSTR (wide-character string) and we use the UNICODEtoANSI function to convert the given command 
        if (CreateProcessA(NULL,                    // pointer to name of executable module
                           UNICODEtoANSI(command),  // pointer to command line string
                           &security,               // pointer to process security attributes
                           &security,               // pointer to thread security attributes
                           TRUE,                    // handle inheritance flag
                           NORMAL_PRIORITY_CLASS,   // creation flags
                           NULL,                    // pointer to new environment block
                           NULL,                    // pointer to current directory name
                           &start,                  // pointer to STARTUPINFO
                           &processInfo             // pointer to PROCESS_INFORMATION
                         )){

            // wait for the child process to start
            for(UINT state = WAIT_TIMEOUT; state == WAIT_TIMEOUT; state = WaitForSingleObject(processInfo.hProcess, 100) );

            DWORD bytesRead = 0, count = 0;
            const int BUFF_SIZE = 1024;
            char* buffer = (char*) malloc(sizeof(char)*BUFF_SIZE+1);
            strcpy(output, "");
            do {                
                DWORD dwAvail = 0;
                if (!PeekNamedPipe(readPipe, NULL, 0, NULL, &dwAvail, NULL)) {
                    // error, the child process might have ended
                    break;
                }
                if (!dwAvail) {
                    // no data available in the pipe
                    break;
                }
                ReadFile(readPipe, buffer, BUFF_SIZE, &bytesRead, NULL);
                buffer[bytesRead] = '\0';
                if((count+bytesRead) > RESULT_SIZE) break;
                strcat(output, buffer);
                count += bytesRead;
            } while (bytesRead >= BUFF_SIZE);
            free(buffer);
        }

    }

    CloseHandle(processInfo.hThread);
    CloseHandle(processInfo.hProcess);
    CloseHandle(writePipe);
    CloseHandle(readPipe);

    // convert result buffer to a wide-character string
    LPWSTR result = OEMtoUNICODE(output);
    LocalFree(output);
    return result;
}

我试图使用ConsoleScreenBuffer而不是管道,没想到这是可能的,谢谢。 - TrisT
我不理解你对 WaitForSingleObject 的使用,似乎并不需要它,即使需要也没有必要做整个循环的操作,可以直接使用 INFINITE 等待直到返回。 此外,你使用 + 作为位标志的方法令我十分惊讶。请使用按位或运算符。 - TrisT

5

2
如果是 cmd /c 命令行,那么 > 重定向当然可以工作。 - Eryk Sun

2
我在 Github 上有一个类似的程序(已测试过 Windows 7 和 10)。

https://github.com/vlsireddy/remwin/tree/master/remwin

这是一个服务器程序,它:
  1. 在Windows的“本地连接”命名接口上监听UDP端口(5555),并接收UDP数据包。
  2. 接收到的UDP数据包内容在cmd.exe上执行(请注意,cmd.exe在运行命令后不会关闭,并且输出字符串[执行的命令的输出]通过同一UDP端口反馈给客户端程序)。
  3. 换句话说,UDP数据包中接收到的命令->解析UDP数据包->在cmd.exe上执行->将输出发送回同一端口到客户端程序。
这不会显示“控制台窗口”,没有必要手动在cmd.exe上执行命令,remwin.exe可以在后台运行,是一个轻量级的服务器程序。

嗨,看起来有点过于严格了,我放了一个经过验证的有效链接,并用经过测试的代码回答了具体问题,不明白你删除评论的建议。你可以运行代码[MSVC],检查它的输出,测试它,如果不合适,就删除它。 - particlereddy
看了你的回答,我觉得你解决了这个问题。然而,Stack Overflow 不鼓励提供以外部链接形式呈现解决方案的回答。 - Jolta
1
超过一半的代码都是从微软文章中复制的,包括注释,但没有给出任何来源标识。 我想UDP代码也是这样的。 是要增加GitHub的浏览量吗? - TrisT

0

你可以使用

 string command = "start /B cmd /c " + myCommand;
 system(command.c_str());

希望这对你有用


0
补充@Cédric Françoys的答案,我在他的代码中为Windows构建修复了一些问题: 缺少函数定义: 为了使代码编译,添加以下函数定义:
#define UNICODEtoANSI(str)   WCHARtoCHAR(str, CP_OEMCP)

LPSTR WCHARtoCHAR(LPWSTR wstr, UINT codePage) {
    int len = (int)wcslen(wstr) + 1;    
    int size_needed = WideCharToMultiByte(codePage, 0, wstr, len, NULL, 0, NULL, NULL);
    LPSTR str = (LPSTR)LocalAlloc(LPTR, sizeof(CHAR) * size_needed);
    WideCharToMultiByte(codePage, 0, wstr, len, str, size_needed, NULL, NULL);
    return str;
}

不安全的CRT字符串函数调用:

为了使代码编译通过,请用以下调用替换strcpystrcat

strcpy_s(output, sizeof(output), "");

strcat_s(output, RESULT_SIZE, buffer);

移除冗余的空终止符:

在 do-while 循环中移除:

buffer[bytesRead] = '\0';

因为 strcat_s 已经处理了这个问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接