dirk/C++/glibmm/spawn


Ravensburg, 10-02-21

spawn – Call an External Executable

This a sample to call an external application. It is usually done with a combination of fork and exec on *ix systems. Unfortunately, fork is not available in Microsoft's C API.

The glibmm provides the spawn functions which might be a portable alternative. However, there are some issues which have to be clarified before successful usage in professional application development:

Solving these questions is especially interesting in MS Windows.

Source Code

Text Filespawn.cc
/** @file sample for call of an external executable
 *
 * Last CVS checkin:
 * $Date: $
 * $Author: scheff $
 */

/**************************************************************************/

// standard C/C++ header:
#include <iostream>
#include <string>
#include <vector>

// Gnome header:
#include <glibmm/miscutils.h>
#include <glibmm/spawn.h>

using namespace std;

/**************************************************************************/

/** provides an output operator for vector<string>.
 *
 * @param out the output stream
 * @param strv the string vector
 * @return the output stream
 */
ostream& operator << (ostream &out, const vector<string> &strv)
{
  if (!strv.empty()) {
    vector<string>::const_iterator it;
    for (it = strv.begin();;) {
      out << *it; ++it; if (it != strv.end()) out << ' '; else break;
    }
  }
  return out;
}

/** waits for user confirm.
 *
 * @param action text with next action
 */
static void pause(const char *action)
{
  cerr << "spawn: Press [ENTER] to " << action << ": " << flush;
  string str; getline(cin, str);
}

/** provides the main function of application.
 *
 * @param argc number of command line arguments
 * @param argv pointer to array of pointers to strings with command line
 *        arguments
 * @return 0 ... application exited regularly\n
 *         else ... execution of application aborted
 */
int main(int argc, char *argv[])
{
  cout << "spawn: start" << endl;
  // build file path
#ifdef WIN32
  string file;
  { gchar *dir = g_win32_get_package_installation_directory_of_module(0);
    file = Glib::build_filename(dir, "test-spawn.exe");
    g_free(dir);
  }
#else // WIN32
  string file
    = Glib::build_filename(Glib::get_current_dir(), "test-spawn");
#endif // WIN32
  // spawn regular
  { vector<string> args;
    args.push_back(file);
    // spawn
    cout << "spawn: spawn_sync(" << args << "):" << endl;
    try {
      Glib::spawn_sync(".", args,
        Glib::SPAWN_CHILD_INHERITS_STDIN);
    } catch (const Glib::SpawnError &error) {
      cerr << "spawn: ERROR: " << error.what() << endl;
    }
  }
  pause("continue");
  // spawn with missing app.
  { vector<string> args;
    // spawn
    cout << "spawn: spawn_sync(" << args << "):" << endl;
    try {
      Glib::spawn_sync(".", args,
        Glib::SPAWN_CHILD_INHERITS_STDIN);
    } catch (const Glib::SpawnError &error) {
      cerr << "spawn: ERROR: " << error.what() << endl;
    }
  }
  pause("continue");
  // spawn with error return
  { vector<string> args;
    args.push_back(file); args.push_back("-return"); args.push_back("1");
    // spawn
    cout << "spawn: spawn_sync(" << args << "):" << endl;
    try {
      int exitCode;
      Glib::spawn_sync(".", args,
        Glib::SPAWN_CHILD_INHERITS_STDIN,
        sigc::slot<void>(), 0, 0, &exitCode);
      cout << "spawn: Received exit code: " << exitCode << endl;
    } catch (const Glib::SpawnError &error) {
      cerr << "spawn: ERROR: " << error.what() << endl;
    }
  }
  pause("continue");
  // spawn with exception
  { vector<string> args;
    args.push_back(file); args.push_back("-throw");
    // spawn
    cout << "spawn: spawn_sync(" << args << "):" << endl;
    try {
      int exitCode;
      Glib::spawn_sync(".", args,
        Glib::SPAWN_CHILD_INHERITS_STDIN,
        sigc::slot<void>(), 0, 0, &exitCode);
      cout << "spawn: Received exit code: " << exitCode << endl;
    } catch (const Glib::SpawnError &error) {
      cerr << "spawn: ERROR: " << error.what() << endl;
    }
  }
  pause("continue");
  // spawn with standard channels
  { vector<string> args;
    args.push_back(file); args.push_back("-nopause");
    // spawn
    cout << "spawn: spawn_sync(" << args << "):" << endl;
    try {
      string output, error; int exitCode;
      Glib::spawn_sync(".", args,
        Glib::SpawnFlags(0),
        sigc::slot<void>(), &output, &error, &exitCode);
      cout << "spawn: Received on standard output:" << endl
	<< output << endl;
      cout << "spawn: Received on standard error:" << endl
	<< error << endl;
      cout << "spawn: Received exit code: " << exitCode << endl;
    } catch (const Glib::SpawnError &error) {
      cerr << "spawn: ERROR: " << error.what() << endl;
    }
  }
#if 0 // This problem isn't solved, satisfying!
  pause("continue");
  // spawn with merged standard output and error
  { vector<string> args;
    args.push_back(file);
    try {
      string output; int exitCode = 0;
      /* This doesn't work:
      Glib::spawn_sync(".", args, Glib::SpawnFlags(0),
        sigc::slot<void>(), &output, &output, &exitCode);
       * Var. output receives only error channel!
       * Output channel gets lost.
       */
      /* This doesn't work, also:
      Glib::spawn_sync(".", args, Glib::SpawnFlags(0),
        sigc::slot<void>(), &output, 0, &exitCode);
       * Var. output receives only output channel!
       * Error channel goes to console.
       */
      cout << "spawn: Received on standard output:" << endl
        << output << endl;
      cout << "spawn: Received exit code: " << exitCode << endl;
    } catch (const Glib::SpawnError &error) {
      cerr << "spawn: ERROR: " << error.what() << endl;
    }
  }
#endif // 0
  // done
  pause("finish");
  return 0;
}
Text Filetest-spawn.cc
/** @file sample which is called by spwan
 *
 * Last CVS checkin:
 * $Date: $
 * $Author: scheff $
 */

/**************************************************************************/

// standard C/C++ header:
#include <exception>
#include <iostream>
#include <string>

// Gnome header:
#include <glib/gtimer.h>
#include <glibmm/timer.h>

using namespace std;

/**************************************************************************/

/// provides an exception for abort.
class Abort: public exception {
  /// returns text with exception description.
  public: virtual const char* what(void) const;
};

/**************************************************************************/

const char* Abort::what(void) const
{
  return "Intentional abort thrown.";
}

/**************************************************************************/

/// @name Settings & Options
//@{
static double tWait = 3.0; ///< sleep time
static int ret = 0; ///< main function return value
static bool forceAbort = false; ///< flag: true ... throw Abort()
static bool noPause = false; ///< flag: true ... skip user confirm
//@}

/** waits for user confirm.
 *
 * @param action text with next action
 */
static void pause(const char *action)
{
  if (!noPause) {
    cout << "test-spawn: Press [ENTER] to " << action << ": " << flush;
    string str; getline(cin, str);
  }
}

/** provides the main function of application.
 *
 * @param argc number of command line arguments
 * @param argv pointer to array of pointers to strings with command line
 *        arguments
 * @return 0 ... application exited regularly\n
 *         else ... execution of application aborted
 */
int main(int argc, char *argv[])
{
  cout << "test-spawn: start" << endl;
  // process options
  for (int i = 1; i < argc; ++i) {
    string arg = argv[i];
    /* unused:
    if (arg == "-wait") {
      ++i; tWait = strtod(argv[i], 0);
      cout << "test-spawn: wait = " << tWait << endl;
    } else*/ if (arg == "-return") {
      ++i; ret = atoi(argv[i]);
      cout << "test-spawn: return = " << ret << endl;
    } else if (arg == "-throw") {
      forceAbort = true;
      cout << "test-spawn: will abort" << endl;
    } else if (arg == "-nopause") noPause = true;
  }
  // write to console
  pause("continue");
  cout << "test-spawn: Output to cout" << endl;
  cerr << "test-spawn: Output to cerr" << endl;
  cout << "test-spawn: Another output to cout" << endl;
  cerr << "test-spawn: Another output to cerr" << endl;
  // consume some time
  cout << "test-spawn: Waiting for " << tWait << " s..." << endl;
  Glib::usleep((gulong)(G_USEC_PER_SEC * tWait));
  // abort?
  if (forceAbort) throw Abort();
  // done
  pause("finish");
  return ret;
}

Output

Console:
                                                                                
spawn: start
spawn: spawn_sync(c:\...\glibmm\Debug\test-spawn.exe):
test-spawn: start
test-spawn: Press [ENTER] to continue: ↵
test-spawn: Output to cout
test-spawn: Output to cerr
test-spawn: Another output to cout
test-spawn: Another output to cerr
test-spawn: Waiting for 3 s...
test-spawn: Press [ENTER] to finish: ↵
spawn: Press [ENTER] to continue: ↵
spawn: spawn_sync():
Snapshot
spawn Snapshot 1
Console/Interaction:
click on the Nicht senden button
spawn: ERROR: Lesen aus Weiterleitung zum Kind (EOF) fehlgeschlagen
spawn: Press [ENTER] to continue: ↵
spawn: spawn_sync(c:\Dokumente und Einstellungen\scheff\Eigene Dateien\tests\gli
bmm\Debug\test-spawn.exe -return 1):
test-spawn: start
test-spawn: return = 1
test-spawn: Press [ENTER] to continue: ↵
test-spawn: Output to cout
test-spawn: Output to cerr
test-spawn: Another output to cout
test-spawn: Another output to cerr
test-spawn: Waiting for 3 s...
test-spawn: Press [ENTER] to finish: ↵
spawn: Received exit code: 1
spawn: Press [ENTER] to continue: ↵
spawn: spawn_sync(c:\Dokumente und Einstellungen\scheff\Eigene Dateien\tests\gli
bmm\Debug\test-spawn.exe -throw):
test-spawn: start
test-spawn: will abort
test-spawn: Press [ENTER] to continue: ↵
test-spawn: Output to cout
test-spawn: Output to cerr
test-spawn: Another output to cout
test-spawn: Another output to cerr
test-spawn: Waiting for 3 s...
Snapshot
spawn Snapshot 2
Console/Interaction:
click on the Ignorieren button
spawn: Press [ENTER] to continue: ↵
spawn: spawn_sync(c:\Dokumente und Einstellungen\scheff\Eigene Dateien\tests\gli
bmm\Debug\test-spawn.exe -nopause):
spawn: Received on standard output:
test-spawn: start
test-spawn: Output to cout
test-spawn: Another output to cout
test-spawn: Waiting for 3 s...

spawn: Received on standard error:
test-spawn: Output to cerr
test-spawn: Another output to cerr

spawn: Received exit code: 0
spawn: Press [ENTER] to finish: ↵

Conclusion

This time, I focused on usage of Glib::spawn_sync. Control of external application worked fine even in error situations. I'm not quite satisfied with the handling of standard output/error channnel. I was not able to (chronological) merge both channels in one string. Thus, further investigation is necessary. I'm afraid I have to use Gtk::spawn_async_with_pipes reading the channels "by myself".

Files

Text Filespawn.ccC++ source code
MS Visual C++ 2008 Project Filespawn.vcprojMS Visual C++ 2008 project file
Text Filetest-spawn.ccC++ source code
MS Visual C++ 2008 Project Filetest-spawn.vcprojMS Visual C++ 2008 project file

dirk/C++/glibmm/spawn


Last modified: Wed Feb 24 20:57:00 2010