目录

C++ 调用cmd的几种方法

调用cmd来执行响应的命令,windows实际上也给了一些接口,但是有些在执行某些命令的时候,却不能够执行,比如 winsat。

system

这个命令使用 VS 的同学一定不陌生 当我们想要执行某个命令或打开某个程序时

1
system("calc");

注:该函数是阻塞的

WinExec

1
WinExec("ipconfig", SW_SHOWNORMAL); //第二个参数表示显示cmd命令框

注:该函数是非阻塞的,也就是说,当输入的命令需要长时间来执行时,程序并不会等待执行结果,而是直接往下运行

ShellExecute

上述两种执行cmd的方法比较少用。 先看这个这个函数原型,共有六个参数

1
2
3
4
5
6
7
8
HINSTANCE ShellExecuteA(
  HWND   hwnd,          //指定父窗口,一般为NULL
  LPCSTR lpOperation,   //打开方式
  LPCSTR lpFile,        //要打开的文件,要执行的程序
  LPCSTR lpParameters,  //参数
  LPCSTR lpDirectory,   //缺省目录,一般为NULL
  INT    nShowCmd       //命令框打开方式
);

各个参数含义

  • HWND hwnd 指定父窗口句柄(通常为NULL)
  • LPCSTR lpOperation 指定动作,表示打开Filename的方式:      open     正常打开      runas    以管理员身份打开      print     打印Filename指定的文件      explore   表示浏览由FileName参数指定的文件夹      find      从lpDirectory指定的目录开始搜索      edit      启动编辑器并打开文档进行编辑
  • LPCSTR lpFile, 表示要打开的文件,比如cmd.exe,calc(计算器)
  • LPCSTR lpParameters, 打开程序所执行的参数
  • LPCSTR lpDirectory, 缺省目录,一般为NULL
  • INT nShowCmd 命令框打开方式     SW_HIDE(0)隐藏窗口并激活另一个窗口。     SW_MAXIMIZE(3)最大化指定的窗口。     SW_MINIMIZE(6)最小化指定的窗口并激活z顺序中的下一个顶级窗口。     SW_RESTORE(9)激活并显示窗口。如果窗口最小化或最大化,Windows会将其恢复到原始大小和位置。应用程序应在还原最小化窗口时指定此标志。     SW_SHOW(5)激活窗口并以当前大小和位置显示它。     SW_SHOWDEFAULT(10)根据启动应用程序的程序传递给CreateProcess函数的STARTUPINFO结构中指定的SW_标志设置show状态。应用程序应该使用此标志调用ShowWindow来设置其主窗口的初始显示状态。     SW_SHOWMAXIMIZED(3)激活窗口并将其显示为最大化窗口。     SW_SHOWMINIMIZED(2)激活窗口并将其显示为最小化窗口。     SW_SHOWMINNOACTIVE(7)将窗口显示为最小化窗口。活动窗口保持活动状态。     SW_SHOWNA(8)以当前状态显示窗口。活动窗口保持活动状态。     SW_SHOWNOACTIVATE(4)显示最近大小和位置的窗口。活动窗口保持活动状态。     SW_SHOWNORMAL(1)激活并显示一个窗口。如果窗口最小化或最大化,Windows会将其恢复到原始大小和位置。应用程序应在首次显示窗口时指定此标志。
  • 最常用的几个:SW_SHOWNORMAL(正常显示)、SW_HIDE(隐藏)

例子:

1
ShellExecute(NULL, "runas", "cmd", "/c ipconfig >> D:\\disk.txt", NULL, SW_SHOWNORMAL);

含义:以管理员身份运行cmd,执行ipconfg命令,将输出重定向到C:\disk.txt中,命令框正常显示

注:和WinExec一样,该函数也是非阻塞的

阻塞式调用ShellExecute

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
	SHELLEXECUTEINFO ShExecInfo = { 0 };
    ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
    ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
    ShExecInfo.hwnd = NULL;
    ShExecInfo.lpVerb = "runas";
    ShExecInfo.lpFile = "cmd";
    ShExecInfo.lpParameters = "/c winsat disk >> D:\\disk.txt";
    ShExecInfo.lpDirectory = NULL;
    ShExecInfo.nShow = SW_HIDE;
    ShExecInfo.hInstApp = NULL;
    ShellExecuteEx(&ShExecInfo);
    WaitForSingleObject(ShExecInfo.hProcess, INFINITE);

每个参数的含义和ShellExecute都一样。其中上述命令参数中 /c:

  • /c 是执行完命令后关闭命令窗口。
  • /k 是执行完命令后不关闭命令窗口。

但上述方法却不是万能的,有些命令在cmd中可以执行,但如果按照上述方法打开cmd就不能执行,比如 winsat,会出现winsat不是内部命令或可执行文件 的错误。那是因为应用程序发生了重定向 原因链接

使用CreatProcess(最好使用这个)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
bool RunCmdAndOutPutRedirect(const std::string &outPutFile, const std::string &cmd, bool wait = false)
{
    cout<< "outPutFile:" << outPutFile << " cmd:" << cmd << " wait:" << wait;
    STARTUPINFOA si;
    PROCESS_INFORMATION pi;


SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
HANDLE handle = CreateFileA(outPutFile.c_str(),
                            FILE_APPEND_DATA,
                            FILE_SHARE_WRITE | FILE_SHARE_READ,
                            &sa,
                            CREATE_ALWAYS,
                            FILE_ATTRIBUTE_NORMAL,
                            NULL);
if (!handle)
{
    cout << "CreateFile failed:" << GetLastError();
    return false;
}

memset(&si, 0, sizeof(STARTUPINFO));
memset(&pi, 0, sizeof(PROCESS_INFORMATION));
si.cb = sizeof(STARTUPINFO);
si.dwFlags |= STARTF_USESTDHANDLES;
si.wShowWindow = SW_HIDE;
si.hStdInput = NULL;
si.hStdError = NULL;
si.hStdOutput = handle;

if (!CreateProcessA(NULL,
    (LPSTR)cmd.c_str(),
    NULL,
    NULL,
    TRUE,
    CREATE_NO_WINDOW,
    NULL,
    NULL,
    &si,
    &pi))
{
    cout << "CreateProcess failed:" << GetLastError();
    return false;
}

if (wait)
{
    WaitForSingleObject(pi.hProcess, INFINITE);
}
cout << "WaitForSingleObject finish return:" << GetLastError();
CloseHandle(handle);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return true;

}

void Task()
{
	PVOID oldValue = NULL;
	if (Wow64DisableWow64FsRedirection(&oldValue))
	{
		string filePath = "D:\\diak.txt";
		string cmd = "winsat disk";
		RunCmdAndOutPutRedirect(filePath, cmd, true); //true表示阻塞调用
	}
	else
	{
		LOG_WARN << "重定向失败:" << GetLastError();
	}
	if (Wow64RevertWow64FsRedirection(oldValue) == FALSE)
	{
		return;
	}
}

如何调用winsat(解决应用程序重定向问题)

需要调用windows的两个函数:

Wow64DisableWow64FsRedirection()

Wow64RevertWow64FsRedirection()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
PVOID oldValue = NULL;
if (Wow64DisableWow64FsRedirection(&oldValue))
{
    //ShellExecute(NULL, "runas", "cmd", "/c winsat disk >> D:\\disk.txt", NULL, SW_SHOWNORMAL);
    //执行cmd命令
    SHELLEXECUTEINFO ShExecInfo = { 0 };
    ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
    ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
    ShExecInfo.hwnd = NULL;
    ShExecInfo.lpVerb = "runas";
    ShExecInfo.lpFile = "cmd";
    ShExecInfo.lpParameters = "/k winsat disk >> D:\\disk.txt";
    ShExecInfo.lpDirectory = NULL;
    ShExecInfo.nShow = SW_HIDE;
    ShExecInfo.hInstApp = NULL;
    ShellExecuteEx(&ShExecInfo);
    WaitForSingleObject(ShExecInfo.hProcess, INFINITE);
}
if (Wow64RevertWow64FsRedirection(oldValue) == FALSE)
{
    return;
}

就很好的解决了重定向的问题。 Wow64DisableWow64FsRedirection() 主要是为了关闭重定向 Wow64RevertWow64FsRedirection() 主要是为了恢复重定向 这两个函数一定要成对出现,关闭完成任务后一定要记得恢复。要不然会对其他的产生一定的影响。

任务计划程序

实际上,调用cmd还有另外一种方法,就是使用任务计划程序。这种方法也可以解决上面重定向的问题,但使用起来会比较麻烦一些。