Does GnuPlot have a temination character option for each output?

  boost, c++, gnuplot

I have created a C++ class which launches gnuplot and runs it in the background using boost::process. I connect async_pipe ‘s to the stdin and stdout to be able to write and read back from gnuplot.

I have an application which draws SVG plots in multiple tabs and the user can select different options for each chart in each tab so I am sending custom plot commands to my gnuplot "server". Each plot command is sent at the user’s request (e.g. button click).

I use boost::asio::write to send commands on the pipe and then I can read back the plot. The problem I have is that boost::asio::read blocks so I tried using read_until to look for the last characters of the svg which is "/svg>rnrn". This works and I get my plot in SVG format, but it only works for plot commands. If I send a command that expects a different response (or no response) then the read blocks again.

I tried using async_read but that also basically does nothing until I shutdown gnuplot with a "quit" command which triggers a broken pipe error causing the read to flag an error. Then I get all my outputs at once which is useless for me.

The problem is that gnuplot does not seem to provide any ‘end of data/output’ trigger on its output stream that could unblock a read.

Is there a way I can setup gnuplot so that when it has finished sending its output to stdout it sends some EOF character or similar?

Perhaps someone already has a solution to this problem.

The only workaround I can see at the moment is to start and stop the gnuplot process everytime I want to generate a chart which seems clunky and wasteful.

Here is my test code which shows the basic functionality of the class.

GnuPlot.h:

#include <boost/asio.hpp>
#include <boost/process.hpp>
#include <thread>

using namespace boost;


class GnuPlot //: std::enable_shared_from_this<GnuPlot>
{
public:
  GnuPlot();
  ~GnuPlot();
  void SendGnuPlotCommand(const std::string& command, bool responseExpected);
  void OnStdOutErr(const system::error_code& ec, std::size_t n);
  void Shutdown();

private:
  // Below order is critical.
  asio::io_context                        ioc_;
  std::unique_ptr<asio::io_context::work> work_;
  process::async_pipe                     pipeIn_;
  process::async_pipe                     pipeOut_;
  process::async_pipe                     pipeErr_;
  process::child                          gpProcess_;
  std::thread                             thrd_;
  asio::streambuf                         chartBuf_;
  std::string                             chartSvg_;
  std::mutex                              strmLock_;
};

and GnuPlot.cpp:

#include "GnuPlot.h"
#include <chrono>
#include <future>
#include <iostream>

using namespace std::literals;

GnuPlot::GnuPlot()
  : pipeIn_(ioc_)
  , pipeOut_(ioc_)
  , pipeErr_(ioc_)
  , gpProcess_(process::search_path("gnuplot.exe"), process::std_out > pipeOut_, process::std_in<pipeIn_, process::std_err> pipeOut_)
{
  work_ = std::make_unique<asio::io_context::work>(ioc_); // Provide work to io_service to keep it running until we are done.
  thrd_ = std::thread(([this]() { ioc_.run(); }));        // Start io_service thread.
}

GnuPlot::~GnuPlot() {}

void GnuPlot::SendGnuPlotCommand(const std::string& command, bool responseExpected)
{
  asio::post(ioc_,
             [&, responseExpected]()
             {
               asio::write(pipeIn_, asio::buffer(command));
               system::error_code ec;

               if (responseExpected)
               {
                 size_t n = asio::read(pipeOut_, chartBuf_, ec, 1);
                 std::istream strm(&chartBuf_);
                 std::cout << strm.rdbuf() << "n" << std::flush;
                 chartBuf_.consume(n);

                 if (ec)
                 {
                   std::cout << "Error code : " << ec.value() << " Msg -> " << (ec.message().empty() ? "No error message." : ec.message()) << "n" << std::flush;
                 }
               }
             });
}

void GnuPlot::OnStdOutErr(const system::error_code& ec, std::size_t n)
{
  std::istream strm(&chartBuf_);
  std::cout << strm.rdbuf() << "n" << std::flush;
  chartBuf_.consume(n);

  if (ec)
  {
    std::cout << "Error code : " << ec.value() << " Msg -> " << (ec.message().empty() ? "No error message." : ec.message()) << "n" << std::flush;
  }
};

void GnuPlot::Shutdown()
{
  asio::post(ioc_,
             [this]()
             {
               pipeIn_.cancel();
               work_.reset(nullptr); // Stop work so io_context can end.
             });

  if (thrd_.joinable())
    thrd_.join(); // Thread will exit once all io_service operations are completed.
}

and my small main.cpp to test it:

#include "GnuPlot.h"
#include <string>

using namespace std::literals;

int main()
{
  GnuPlot plot;

  std::string command = "set terminal svg size 1920 1080 dynamic mouse standalonen"
                        "$data << EODn1 1n2 2n3 3n4 4n5 5nEODn"
                        "plot $data using 1:2 with lines notitlen";

  plot.SendGnuPlotCommand(command, true);
  //std::string s = "plot sin(x)n";
  //plot.SendGnuPlotCommand(s);
  std::string q = "quitn";
  plot.SendGnuPlotCommand(command, true);
  plot.SendGnuPlotCommand(command, true);
  plot.SendGnuPlotCommand(q, false);
  plot.Shutdown();
}

I hope someone can help me with this as it is part of a much bigger project I am working on.

Source: Windows Questions C++

LEAVE A COMMENT