PlStream
can be used to get a stream from a Prolog term,
or to lock the stream so that other threads cannot interleave their
output. With either usage, PlStream
is a RAII
class that ensure the matchin PL_release_stream() is done, and
also handles some subtle problems with C++ exceptions.
The methods are:
PlStream
object to an invalid stream (see PlStream::check_stream()).IOSTREAM*
, PlStream
is implicitly converted to IOSTREAM*
.PlStream
object contains a valid stream and throws an
exception if it doesn't. This is used to ensure that PlStream::release()
hasn't been called.
Most of the stream I/O functions have corresponding methods in PlStream
.
For example, Sfprintf() corresponds to
PlStream::printf(). PlStream::seek() and PlStream::tell()
call
Sseek64() and Stell64() instead of long
(they
are also deprecated: PlStream::seek64() and PlStream::tell64()
are preferred).
The C interface to stream I/O doesn't raise a Prolog error when
there's a stream error (typically indicated by a -1 return code).
Instead, the error sets a flag on the stream and
PL_release_stream() creates the error term. The
PlStream
destructor calls PL_release_stream(); but
it's a fatal error in C++ to raise an exception in a destructor if the
destructor is invoked by stack-unwinding due to another exception,
including the pseudo-exceptions PlFail
and
PlExceptionFail
.
To get around this, the various stream I/O functions have wrapper
methods in the PlStream
class that check for an error and
call PlStream::release()
to create the Prolog error, which is thrown as a C++ error.
The destructor calls PlStream::release(), which throws a C++ exception if there is a stream error. This is outside the destructor, so it is safe - the destructor checks if the stream has been released and does nothing in that situation.
The following two code examples do essentially the same thing:
PREDICATE(name_arity, 1) { PlStream strm(Scurrent_output); strm.printf("name = %s, arity = %zd\n", A1.name().as_string().c_str(), A1.arity()); return true; }
PREDICATE(name_arity, 1) { PlStream strm(Scurrent_output); try { strm.printf("name = %s, arity = %zd\n", A1.name().as_string().c_str(), A1.arity()); } PREDICATE_CATCH({strm.release(); return false;}) return true; }
If you write the code as follows, using Sfprintf() directly, it is possible that a fatal exception will be raised on an I/O error:
PREDICATE(name_arity, 1) { PlStream strm(Scurrent_output); Sfprintf(strm, "name = %s, arity = %zd\n", A1.name().as_string().c_str(), A1.arity()); return true; // WARNING: the PlStream destructor might throw a C++ // exception on stack unwinding, giving a fatal // fatal runtime exception. }
If you don't use these, and want to throw an exception if there's an
error, the following code works because PlStream
(and the
underlying PL_acquire_stream()) can be called recursively:
{ PlStream strm(...); strm.release(); }