在编程中,大部分程序并不向外界提供调用的接口,即使提供了,也不会公开,这种程序本身就是一个封闭的系统,除了在显示器上将结果显示出来之外,根本就不提供对外接口,这时就需要一些编程技巧。
1.可执行外部程序的几个函数( system WinExec ShellExecute CreateProcess )
要想执行一个命令或将某一个程序作为一个命令运行,有几种方法,下面分别说明。
(1).调用system函数
system函数在执行时会自动打开一个DOS命令提示符的窗口,在窗口中执行该命令,并将命令结果显示在DOS窗口中。
windows操作系统下system () 函数详解(主要是在C语言中的应用)
用法: int system(char *command);
头文件:<stdlib.h>
(2).调用WinExec函数
WinExec函数同样是将命令串在DOS窗口中执行,但用户可以选择该DOS窗口是否显示。同时该命令主要是完成对可执行文件的调用执行操作,要调用内部命令一般不用此命令。WinExec函数的原型如下:
UINT WinExec(LPCSTR lpCmdLine,UINTuCmdShow)
lpCmdLine:是一个字符串指针,该指针指向的地址中可以保存任何一个非NULL的合法的DOS命令串,该命令串的格式、个数等属性取决于该DOS命令本身的要求。
uCmdShow:DOS命令提示符窗口所显示方式。
(3).调用ShellExecute函数和ShellExecuteEx函数
system函数和WinExec函数都是在早期16位操作系统中使用的,虽然在VC中都可以使用,但对于Windows2000/XP及以后操作系统,最好使用ShellExecute函数。该函数的原型为:
HINSTANCE ShellExecute(
HWND hwnd, //父窗口的句柄
LPCTSTR lpVerb, //个字符串指针,指向一个动作的字符串eg open
LPCTSTR lpFile, //指定一个文件或目录串的指针。
LPCTSTR lpParameters, //如果lpFile中指向的是可执行文件,并且该文件有命令行参数,则可以通过该指针指向保存参数的缓冲区中。
LPCTSTR lpDirectory, //可执行文件所在的目录
INT nShowCmd //窗口的显示方式
);
返回值:如果调用成功,返回一个大于32的值。
该函数的作用主要是执行可执行文件,一般不宜用作内部命令的调用。
(4).调用CreateProcess函数
和ShellExecute函数作用差不多的一个函数是CreateProcess函数,CreateProcess函数的原型是:
BOOL CreateProcess(
LPCTSTR lpApplicationName, //指向一个以空结尾的串,它指定了要执行的模块
LPTSTR lpCommandLine, //指向一个以空结尾的串,该串定义了要执行的命令行。
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles, //新进程是否从调用进程继承句柄。
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo, //指向一个STARTUPINFO结构,该结构定义了新进程的主窗口将如何显示。
LPPROCESS_INFORMATION lpProcessInformation //指向PROCESS_INFORMATION结构,该结构接受关于新进程的表示信息。返回进程和线程句柄,还包括进程和线程ID。
);
大多数情况下,并不一定要填入 STARTUPINFO结构,但无论如何必须提供它。
一个简单的处理办法就是调用GetStartupInfo函数,该函数只有一个参数,是一个指向STARTUPINFO结构的指针:
VOID GetStartupInfo(LPSTARTUPINFOlpStartupInfo);
该函数用来取得当前进程的StartupInfo结构,有了当前进程的默认值,对于子进程,只需要修改要改的项即可。
(5)代码示例:
void CMyShortCutDlg::OnBnClickedQqButton()//启动QQ程序
{
if(WinExec("D:\\QQ\\Bin\\QQ.exe",SW_SHOW) <32)
MessageBox(_T("Can't WinExec"),MB_OK);
}
void CMyShortCutDlg::OnBnClickedCquptButton()//打开网页http://www.cqupt.edu.cn
{
ShellExecute(NULL,_T("open"),_T("http://www.cqupt.edu.cn"),NULL,NULL,SW_SHOWNORMAL);
}
void CMyShortCutDlg::OnBnClickedReadmeButton()//打开当前目录下的文件readme.txt
{
if(ShellExecute (NULL,_T("open"),_T("readme.txt"),NULL,NULL,SW_SHOW) <(HANDLE) 32)
MessageBox(_T("Can't Open Readme\n"));
}
void CMyShortCutDlg::OnBnClickedCalculateButton()//启动计算器程序
{
if(WinExec("calc.exe",SW_SHOW) <32)
MessageBox(_T("Can't WinExec"),MB_OK);
}
void CMyShortCutDlg::OnBnClickedMail163Button()//打开网易163邮箱主页
{
ShellExecute(NULL,_T("open"),_T("http://mail.163.com/"),NULL,NULL,SW_SHOWNORMAL);
}
void CMyShortCutDlg::OnBnClickedIpsearchButton()
{
ShellExecute(NULL,_T("open"),_T("http://ip.chinaz.com/"),NULL,NULL,SW_SHOWNORMAL);
}
void CMyShortCutDlg::OnBnClickedMailtoButton()
{
ShellExecute(NULL,_T("open"),_T("mailto:904788895@qq.com"),NULL,NULL,SW_SHOWNORMAL);
//打开新邮件窗口,并自动填入收件人地址、邮件主题和邮件正文。若邮件正文包括多行文本,则必须在每行文本之间加入换行转义字符%0a
//mailto:904788895@qq.com?subject=Hello&Body=This%20is%20a%20test
}
void CMyShortCutDlg::OnBnClickedVs2005Button()
{
if(ShellExecute (NULL,_T("open"),_T("F:\\VS2005开发\\Common7\\IDE\\devenv.exe"),NULL,NULL,SW_SHOW) <(HANDLE) 32)
MessageBox(_T("Can't ShellExecute\n"));
}
2.编程实例:使用重定向接收外部程序运行结果
要将中间过程的显示作为另一种输入或将整个过程的中间结果显示到用户指定的界面中。
要解决将中间过程中产生的显示信息显示到指定位置,或在产生过程中实时读取的问题,通常有两种解决方式,即“重定向”和“管道操作”。
1.重定向技术
重定向的符号有两个,一个是重定向“输入设备”的,用半角“<”表示;一个是重定向“输出设备”的,用半角符号“>”表示。除此之外,如果重定向的设备选为文件名,则还可以用半角符号“>>”来表示,该符号表示追加。
dir>a.txt dir命令的作用是列出当前目录下的文件列表输出到显示器上,但由于使用了重定向,并且将输出内容重定向到a.txt文件.
如果用户只是想看当前时间,而不是修改当前时间,只需要再回车一下即可。对于这样的交互同样可以使用重定向,首先建立一个文本文件rt.txt,其文件的内容只有一个回车。然后在命令提示符的状态下输入“time<rt.txt”,默认情况下,该命令除了显示当前时间之外,还会显示一个时间修改的提示符等待新时间(用户的键盘输入),但由于采用了重定功能,所以此时系统不再等待键盘的输入,而改由从rt.txt文件中读到内容作为输入,由于rt.txt文件中是一个回车命令,则该回车命令将代替键盘的输入,从而用户不再需要回车,而直接正常结束了time命令本身。
重定向符号“>”与“>>”的区别在于,每次重定向时,如果重定向的文件存在,则前者会将内容清空,然后将重定向内容填入文件中;而后者则是在保存原有内容的前提下,将重定向的内容追加到文件的尾部。
需要注意的问题:
ping一台主机则需要几秒,而程序并不是执行完命令后才返回的,而是调用后马上就返回了,因此程序在调用后,需要等待一定时间(等待进程执行结束),再读取生成的文件。
CString::GetBuffer(0)
GetBuffer(0)返回的是指向CString对象所构造的字串指针,为GetBuffer(0)由系统自动计算字串所要的空间长度。
BOOLres=CreateProcess(NULL,strCommand.GetBuffer(0),NULL,NULL,NULL,
NORMAL_PRIORITY_CLASS| CREATE_NO_WINDOW,NULL,NULL,&si,&pi);
//等待进程执行完毕 WaitForSingleObject(pi.hProcess,INFINITE);
2.编程实例:使用管道接收外部程序运行结果
(1)管道技术
管道(pipe)技术是将某一个设备的输出,通过“管道”作为另一个设备的输入。从原理上讲,重定向技术与管道技术很相似,前者将某一设备的输出由默认输出设备改为另一个指定的输出设备上;后者将一个设备的输出作为另一个设备的输入。
管道需要有一个输出端和一个输入端,同重定向一样,二者既可以是设备,也可以是文件,甚至是操作系统提供的内部命令。在DOS命令行中,管道的符号是半角“|”符号,例如在DOS命令提示符中,输入“dir”会将当前目录下的文件列表显示于屏幕上,但如果输入“dir|more”则表示将列表通过管道发送到more命令中。
创建匿名管道的函数为CreatePipe函数,如下所示:
BOOL CreatePipe(
PHANDLE hReadPipe, //读句柄的指针
PHANDLE hWritePipe, //写句柄的指针
LPSECURITY_ATTRIBUTES lpPipeAttributes, //安全属性结构的指针
DWORD nSize //管道的大小
);
(2)读取由管道传送的DOS命令结果
与重定向不同的是,管道只需要两个步骤,首先是创建一个进程,该进程完成指定的DOS命令,随后就是将命令的执行结果通过管道直接显示到指定的文本框中。
在用ReadFile函数读的时候,只有当另一个进程或线程写满了管道,ReadFile函数才会返回真,并且返回读到的字节数;否则返回错误。
在用WriteFile函数到管道中,如果管道缓冲区不满,则写操作不会结束;如果所有字节写完之前,管道已满,则WriteFile函数无法返回,直到另一个进程或线程使用ReadFile读取管道中的内容后,使空间可以使用后,WriteFile才能继续写入剩下的字节。
要点:
if(!CreatePipe(&hRead,&hWrite,&sa,0))
si.hStdOutput=hWrite; //将输出重定向到管道中 *****
CloseHandle(hWrite);//结束后,关闭管道写句柄,不再写入
匿名管道通常是单向的 ,读操作之前写句柄必须关闭。
(3)代码示例:
void CmdDialog::OnBnClickedExecutecmdButton()
{
// TODO: 在此添加控件通知处理程序代码
//单击“执行命令”按钮后要执行的操作
UpdateData(TRUE);
CString cs_method;
if(IsDlgButtonChecked(IDC_REDIRECT_RADIO))//选中“重定向”方式读取数据
{
GetDlgItem(IDC_REDIRECT_RADIO)->GetWindowText(cs_method);
MessageBox(cs_method);
ReadCmdResultByRediret();
}
if(IsDlgButtonChecked(IDC_PIPE_RADIO))//选择“管道”方式读取数据
{
GetDlgItem(IDC_PIPE_RADIO)->GetWindowText(cs_method);
MessageBox(cs_method);
ReadCmdResultByPipe();
}
UpdateData(FALSE);
m_ExecuteCmdButton.EnableWindow(FALSE);
}
void CmdDialog::ReadCmdResultByRediret()
{
//根据用户输入的命令串,生成重定向命令
CString strCommand,strFilename=_T("Redirect.txt");
m_strCommand.TrimRight(_T(" "));
if (m_strCommand=="")
return;
//eg: cmd.exe /c "dir">Redirect.txt
strCommand.Format(_T("cmd.exe /c \"%s\">%s"),m_strCommand,strFilename);
SetDlgItemText(IDC_strCommand_EDIT,strCommand);
//创建一个不要出现DOS窗口的、隐藏的命令执行线程 //***201311
STARTUPINFO si;
ZeroMemory(&si,sizeof(si));
si.cb=sizeof(STARTUPINFO);
si.wShowWindow=SW_HIDE;//隐藏的窗口
si.dwFlags=STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
PROCESS_INFORMATION pi;
BOOL res=CreateProcess(NULL,strCommand.GetBuffer(0),NULL,NULL,NULL,
NORMAL_PRIORITY_CLASS| CREATE_NO_WINDOW,NULL,NULL,&si,&pi);
if (!res)
{
MessageBox(_T("创建线程出错!"));
return;
}
//等待进程执行完毕
WaitForSingleObject(pi.hProcess,INFINITE);
//如果打开文件失败,则有可能文件仍在使用中,可以多次读取
char buff[MAXREADBUFFLEN]={0};
BOOL bSuccess=FALSE;
try
{
CFile file;
if (file.Open(strFilename,CFile::modeReadWrite,NULL))
{
file.Read((char *)buff,MAXREADBUFFLEN);
file.Close();
bSuccess=TRUE;
}
}
catch (CFileException e)
{
//e.m_cause;
Sleep(1000);
}
//如果打开文件成功,则删除临时文件,并显示出结果 //*****201311
if (bSuccess)
{
DeleteFile(strFilename); //删除临时文件 Redirect.txt
CharStrToCString(&m_strResult,buff);
// m_strResult.Format(_T("%s"),(char *)buff);
}
else
MessageBox(_T("程序执行出错!"),_T("出错提示"));
}
void CmdDialog::ReadCmdResultByPipe()
{
//创建一个管道,用于接收命令执行结果
SECURITY_ATTRIBUTES sa;
ZeroMemory(&sa,sizeof(sa));
sa.nLength=sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor=NULL;
sa.bInheritHandle=TRUE;
HANDLE hRead,hWrite;
if (!CreatePipe(&hRead,&hWrite,&sa,0))
{
MessageBox(_T("创建管道出错!"));
return;
}
//创建一个没有DOS命令框的、隐藏窗口的进程来执行用户输入的命令
STARTUPINFO si;
ZeroMemory(&si,sizeof(si));
si.cb=sizeof(STARTUPINFO);
GetStartupInfo(&si);//返回进程在启动时被指定的 STARTUPINFO 结构
si.hStdError=hWrite; //将错误也重定向到管道中
si.hStdOutput=hWrite; //将输出重定向到管道中 *****
si.wShowWindow=SW_HIDE; //隐藏的窗口 SW_HIDE
//STARTF_USESHOWWINDOW 使用wShowWindow成员
//STARTF_USESTDHANDLES 使用hStdInputh StdOutput和hStdError成员
//hStdInput用于标识键盘缓存
//StdOutput和hStdError用于标识控制台窗口的缓存
si.dwFlags=STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
PROCESS_INFORMATION pi;
CString strCommand;
strCommand.Format(_T("cmd.exe /c %s"),m_strCommand);
BOOL res=CreateProcess(NULL,strCommand.GetBuffer(0),
NULL,NULL,TRUE,NULL,NULL,NULL,&si,&pi);
if (!res)
{
MessageBox(_T("创建线程出错!"));
return;
}
//WaitForSingleObject(pi.hProcess,INFINITE);
CloseHandle(hWrite);//结束后,关闭管道写句柄,不再写入 [匿名管道 需要先关掉写管道 后面才能读入数据]
//从管道中读取已写入的数据,并显示出来
CString strTemp;
char cBuff[4096]={0};
DWORD dwRead=0;
m_strResult="";
while (true)
{
if (!ReadFile(hRead,cBuff,4095,&dwRead,NULL))
break;
cBuff[dwRead]='\0';
// strTemp.Format("%s",cBuff);
CharStrToCString(&strTemp,cBuff);
m_strResult+=strTemp;
}
}
运行结果: