Archive for June 2011

Thoughts on Process Invocation with Qt and PoCo

June 9, 2011

Since late 2010, I’ve enjoyed developing C++-based applications with the Qt framework. Overall, I find it to be intuitive, fairly well-designed, and well-documented.

However, one area of the framework that I’ve found unsatisfactory (or at least struggled with, despite my best efforts) is the QProcess class. Ideally, I’d like to be able to instantiate it once, on a per-method (or per-class) basis with the appropriate executable path and CLI arguments, and then dump its output directly into a QString for use elsewhere.

I vaguely recall successfully resolving ~90% of the problem in Stroma (although that project’s architecture is currently rather monolithic) – but I never managed to solve the rest of it (actually dumping the output of the invoked process into a QString/multi-line text box widget).

After taking a break from that project for a while, I found myself encountering a similar problem in another project; and after reading various forum/mailing list posts and pieces of documentation, sought to find alternative means of resolution.

The most promising initial candidate was Boost::Process, although it appears that development is still ongoing – therefore, it isn’t an “official” Boost component yet.

With that I mind, I decided to install and investigate Poco::Process, after seeing a mention of it on StackOverflow, last night.

Installation under Fedora was trivial (involved running “sudo yum -y install poco-devel poco-doc” from a shell), and the appropriate header files were conveniently located in /usr/include/Poco for later perusal.

That said, I was sceptical about the quality and usability of the PoCo library itself, since the documentation and sample code felt relatively terse and incomplete; not to mention that guesswork (aided by this post) was necessary, when it came to actually integrating it into the build system.

However, once those hurdles are dealt with, basic integration with the Qt project build system simply involves appending the following to the project file:

#Headers for PoCo
HEADERS += /usr/include/Poco/Pipe.h /usr/include/Poco/Process.h \
/usr/include/Poco/PipeStream.h \
/usr/include/Poco/StreamCopier.h

#Libraries for PoCo
LIBS += -lPocoFoundation

Once the project file is configured, adding the following to your class header file (modulo existing references, of course) should work :

#include <QApplication>
#include <QString>
#include <QDebug>

#include <string>
#include <cstring>
#include <cstdio>
#include <iostream>
#include <Poco/Process.h>
#include <Poco/Pipe.h>
#include <Poco/PipeStream.h>
#include <Poco/StreamCopier.h>

using namespace Poco;
using Poco::Process;
using Poco::Pipe;
using Poco::PipeInputStream;
using Poco::PipeOutputStream;
using Poco::ProcessHandle;

Some of the aforementioned header files are unnecessary – although their presence doesn’t seem to cause any obvious problems at compilation or application execution time.

My method in question (a rather rudimentary/brute-force mechanism for returning the MIME type of a file as a QString object, based upon sample code from a presentation slide), looks like: 
QString FileTypeHandler::GetMimeType(QString aFileName) {

    qDebug() << "Inside FileTypeHandler::GetMimeType";
    qDebug() << "Have text: " << aFileName;

    std::string mimeCommand("/usr/bin/file");
    std::vector<std::string> mimeArgs;
    mimeArgs.push_back("-i");
    mimeArgs.push_back(aFileName.toStdString());
    Poco::Pipe mimeOutput;
    ProcessHandle mimeHandle = Process::launch(mimeCommand, mimeArgs, 0, &mimeOutput, 0);
    Poco::PipeInputStream mimeInput(mimeOutput);

    std::string mimeData;

    mimeInput >> mimeData;

    qDebug() << QString::fromStdString(mimeData);

    return QString::fromStdString(mimeData);

}

Theoretically/in essence, it accepts a file name as its argument, invokes the file -i command to (hopefully) look up its MIME type, stores it in a standard ANSI C++ string, and finally returns it as a QString for consumption by UI widgets.

In reality, it pretty much does just that – with the caveat that it doesn’t quite return the correct output. (It returns “/dev/null:” instead of “/dev/null: application/x-character-device; charset=binary“, for example).

Still, I hope that these notes are useful for others who are facing the same problem…

Maybe others can chime in with a better alternative, or other suggestions?