原创文章,转载请注明出处,谢谢!
作者:清林,博客名:飞空静渡
前言: 我们在写程序时,有时会需要调用到外部的一些程序,因此在linux下,我们会fork(windows下是CreateProcess)一个子进程,然后exec这个子进程,然后在父进程中等待子进程结束并获得子进程的退出码。进一步,如果我们想拦截子进程的输出,并把子进程的输出保存下来,或是处理信号等等时,就得另外再处理这些情况。但有没有其它一些库或程序可以帮我们做好这些吃力不讨好的事呢,这是有的,下面我将介绍两种方法,一个是gtk下的,一个是kde下的。
第一:在gtk下的需要需要安装glimm,glib和sigc库。下面给出一个函数,看下怎么使用。
int execute_command( const Glib::ustring & command, Glib::ustring & output, Glib::ustring & error, bool use_C_locale ) { int exit_status = -1 ; std::string std_out, std_error ; try { if ( use_C_locale ) { std::vector<std::string> envp, argv; envp .push_back( "LC_ALL=C" ) ; envp .push_back( "PATH=" + Glib::getenv( "PATH" ) ) ; argv .push_back( "sh" ) ; argv .push_back( "-c" ) ; argv .push_back( command ) ; Glib::spawn_sync( ".", argv, envp, Glib::SPAWN_SEARCH_PATH, sigc::slot<void>(), &std_out, &std_error, &exit_status ) ; } else Glib::spawn_command_line_sync( "sh -c '" + command + "'", &std_out, &std_error, &exit_status ) ; } catch ( Glib::Exception & e ) { error = e .what() ; return -1 ; } output = std_out ; error = std_error ; return exit_status ; }
这里的关键就是调用Glib::spawn_command_line_sync()这个函数,而这个函数是在glibmm中定义的。其中glib::ustring你可以用std::string来代替,这个没什么关系。output是用来接收子进程的输出,error用来接收子进程的错误输出信息,command就是你要执行的命令,例如“ls -l"之类的。
假如你想偷懒,又可以很好的执行你想执行的子进程,那么你就可以直接使用上面的这个函数,其条件是你必须链接面给出的三个库: -lglibmm-2.4 -lglib-2.0 -lsigc-2.0 并且要包含下面这几个头文件路径:/usr/lib/glibmm-2.4/include /usr/include/glibmm-2.4 /usr/include/glib-2.0 /usr/lib/glib-2.0/include /usr/include/sigc++-2.0 /usr/lib/sigc++-2.0/include
据说glibmm是个跨平台的库,我只在linux使用过这个库,windows下面没试过 :)
第二个就比这个简单得多,其要求就是使用QT库,用QT来执行子进程,当然也是跨平台的啦。下面看下这个QT的类怎么写。
externalcommand.h
#if !defined(EXTERNALCOMMAND__H) #define EXTERNALCOMMAND__H #include <QProcess> #include <QStringList> #include <QString> class ExternalCommand : public QProcess { Q_OBJECT Q_DISABLE_COPY(ExternalCommand) public: explicit ExternalCommand(const QString& cmd = QString(), const QStringList& args = QStringList()); public: void setCommand(const QString& cmd) { m_Command = cmd; } /**< @param cmd the command to run */ const QString& command() const { return m_Command; } /**< @return the command to run */ void addArg(const QString& s) { m_Args << s; } /**< @param s the argument to add */ const QStringList& args() const { return m_Args; } /**< @return the arguments */ void setArgs(const QStringList& args) { m_Args = args; } /**< @param args the new arguments */ bool start(int timeout = 30000); bool waitFor(int timeout = 30000); bool run(int timeout = 30000); int exitCode() const { return m_ExitCode; } /**< @return the exit code */ const QString& output() const { return m_Output; } /**< @return the command output */ // Report* report() { return m_Report; } /**< @return pointer to the Report or NULL */ protected: void setExitCode(int i) { m_ExitCode = i; } void setup(); protected slots: void onFinished(int exitCode); void onReadOutput(); private: // Report* m_Report; QString m_Command; QStringList m_Args; int m_ExitCode; QString m_Output; }; #endif
其中Q_DISABLE_COPY的作用就是禁止类的拷贝,其做作用相当于:
class ExternalCommand { private: ExternalCommand(const ExternalCommand & extcmd); ExternalCommand& operator=(const ExternalCommand &extcmd); ......... };
externalcommand.cpp
#include "externalcommand.h" #include <QString> #include <QStringList> #include <stdlib.h> #include <iostream> /** Creates a new ExternalCommand instance without Report. @param cmd the command to run @param args the arguments to pass to the command */ ExternalCommand::ExternalCommand(const QString& cmd, const QStringList& args) : QProcess(), m_Command(cmd), m_Args(args), m_ExitCode(-1), m_Output() { setup(); } void ExternalCommand::setup() { setEnvironment(QStringList() << "LC_ALL=C" << QString("PATH=") + getenv("PATH")); setProcessChannelMode(MergedChannels); connect(this, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(onFinished(int))); connect(this, SIGNAL(readyReadStandardOutput()), SLOT(onReadOutput())); } /** Starts the external command. @param timeout timeout to wait for the process to start @return true on success */ bool ExternalCommand::start(int timeout) { QProcess::start(command(), args()); if (!waitForStarted(timeout)) { std::cout<<"(Command timeout while starting)"<<std::endl; return false; } return true; } /** Waits for the external command to finish. @param timeout timeout to wait until the process finishes. @return true on success */ bool ExternalCommand::waitFor(int timeout) { closeWriteChannel(); if (!waitForFinished(timeout)) { std::cout<<"(Command timeout while starting)"<<std::endl; return false; } onReadOutput(); return true; } /** Runs the command. @param timeout timeout to use for waiting when starting and when waiting for the process to finish @return true on success */ bool ExternalCommand::run(int timeout) { return start(timeout) && waitFor(timeout) && exitStatus() == 0; } void ExternalCommand::onReadOutput() { const QString s = QString(readAllStandardOutput()); m_Output += s; } void ExternalCommand::onFinished(int exitCode) { setExitCode(exitCode); }
这个类怎么使用呢,下面给个例子使用,例如运行“ls -l”命令
ExternalCommand cmd("ntfsresize", QStringList()<<"-l"); if (cmd.run()) { cout<<cmd.output()<<endl; }
cmd.output()就是子进程的输出,想要获得子进程的退出码就应该调用cmd.exitCode()。是不是比自己写fork来得简单肋 :)。
以上这两个库函数的使用真是省心又省力,适合那么在程序设计时经常要调用外部命令的人。
注:以上两个库均来自于linux下的源代码,并略有修改,第一个来自于gparted,第二个来自于partitionmanager。这个两个程序一个是gtk下的分区管理工具,一个是kde下的分区管理工具,但两个的代码的实现方式是差不多的,里面用了许多工厂方法。