On Fri, May 09, 2003 at 09:55:32AM +0900, Simon Strandgaard wrote:
> Problem is almost solved, thanks for pointing me in the right direction.
> 
> I have been experimenting a little with a pipe between 2 threads :-)
> TODO: this has to be integrated into my embedding code.
> This might have others interest.

Is this planned as part of "Embedding Ruby in C++ tutorial"? If so, I hope
you don't mind if I argue that it doesn't belong there.

I had a bit of a think about this, and setting Ruby aside for the moment,
why not consider the following question similar to your original one:

  If I write a subroutine (function) in C++, how can I wrap it so that
  any output it generates does not go to stdout, but is discarded or
  captured in a string?

Now, I'm not a C++ programmer, but I imagine the answer goes something along
the lines of: "change cout to point to a new object, so that cout << "foo"
writes to this object rather than what cout normally points to". Or you
could close fd 1 and fd 2 and reopen them pointing to /tmp/result or
/dev/null.

This is all fine so far. However there are a number of ways in which the
subroutine could bypass this restriction if it chose to:

  (1) it explicitly opens /dev/stdout to a new fd, and writes to it
  (2) it explicitly write()s to fd 1
  (3) it forks and execs a child, and the child writes to stdout
  (4) probably a load of other things

(2) and (3) won't be an issue if you had reopened fd 1, because they will
write to whatever you reopened it as (say /tmp/result, which you can read
later)

However none of this is a problem. A C++ subroutine, after all, is just a
part of your main C++ program. If it explicitly writes to stdout or stderr,
then so be it; and if you don't want it to do these things, then you code
your subroutine so that it doesn't. If you need to do point (2), that is
fork and exec a child, you can explicitly set up fd 1 and 2 for the child,
e.g. as pipes which the parent process reads, before the exec.

Now, the point I'm trying to make is, embedded Ruby is no different to a C++
subroutine. They are part of one and the same program. They are called from
your main() function or elsewhere, do some work, and return. The C++
subroutine sits inside the same C++ execution environment as the main
program, as does the embedded Ruby - they share the same memory space, file
descriptors, and so on.

Therefore the same thing applies for capturing output. If a C++ subroutine
decides to fork off a child and needs to capture its output, it is the
responsibility of the C++ subroutine itself to do that. The same would apply
to embedded Ruby doing this sort of thing.

Hence I do not believe that it is necessary or even desirable to try to set
up some sort of 'sand box' execution environment for the Ruby interpreter,
when it is *part of your program*. In fact it could be a positive
disadvantage, because I for one would definitely want the ability for my
Ruby code to report debug messages to stderr, in the same way as my C(++)
code can. I cannot see any real-world application where such a sandbox is
needed, because it probably just means that the logic is being done in the
wrong place: if the Ruby part of your program needs to redirect any output
it generates, or capture output of a child, then just do it in Ruby.

Just to be clear, I think writing up a tutorial on this embedding is a
really worthwhile thing to do, and I applaud you for doing so. But I think
it's important not to make it seem like this sort of sandboxing is
necessary, because it makes embedding Ruby look far harder than it actually
is, and might put people off. Material for an appendix perhaps.

One final point: you posted some code showing native threads communicating
over a pipe. I think it's a very bad idea to encourage use of threads in
embedded Ruby applications, because Ruby really does not play well with
them. If you made sure that *all* Ruby calls occured in *one* thread it
would probably work, but a safer rule (especially for a tutorial) is: just
don't do it. You'll see recent postings to this list of people who have
tried it, with good reason to do so, and have achieved little more than some
spectacular crashes.

So, if I needed to call some Ruby from C++ and have it as a totally separate
entity which I communicated with down a pipe, then I would fork a new
process. However this loses much of the benefit of embedding because the
_only_ way you could communicate with it would be down the pipe... no direct
method calls or rb_eval_string or whatever.

Regards,

Brian.