Mailing List Archive

do anonymous pipes normally work on NT?
'''
Hi all,

I seem to be unable to do anonymous pipes right on NT4sp3.
This may be more of a Windows than a Python related
question, but I'll try it here for a start anyway...

The code below was created with the help of an example
posted here some time ago by Bill Tut, a C++ example by
someone else, and after studying the docs to the win32
extensions by Mark Hammond, as well as the documentation
in VisualStudio for VC++5.

To my uninitiated eyes, everything looks as if it would
make sense, and corresponds to similar code for unix,
using fork() and execv().

The problem is, that in the case here, I can write
to the stdin pipe, but reading from stdout or stderr
will get no results at all. All I see is an exception
eventually, telling me:
'(109, 'ReadFile', 'Die Pipe wurde beendet')
'The pipe was terminated'.

When calling a program from cygwin (b19), instead
of listening to lines of input and answering with
its results, it will just die and nothing else
will happen. This is normally the same if I call a
program from windows, except for example with ftp.exe,
which starts an infinite loop and eats all cpu. I don't
know if this is a related problem or just a ftp.exe
weirdness.

An example invocation would be:

d:\somewhere\> python
>>> import ntprocess
>>> p = ntprocess.Coprocess('c:\\winnt\\system32\\ping.exe -t
localhost')
>>> print p.read()
# nothing happens until I kill ping from the task manager:
Traceback (innermost last):
File "<stdin>", line 1, in ?
File "ntprocess.py", line 111, in read
res, str = win32file.ReadFile(self.hStdout, 0)
pywintypes.api_error: (109, 'ReadFile', 'Die Pipe wurde beendet.')

Now it may be that the nt console programs don't
write to stdout but just directly to the console.
But my cygwin stuff certainly *does* write to stdout
and stderr. Are there any problems known when
combining native NT Python with cygwin binaries?
Am I doing aything else wrong?

Since I have *no* idea where to look, I can't help
but just include the complete source below.
If anyone knowing more about this stuff could
take a short look at it and point me to the blatant
error I made, I'd be extremely happy.


Thanks for any insights!

-schorsch

'''
# ntprocess.py

import types
import os
import sys
import string

import msvcrt
import winnt
import win32api
import win32pipe
import win32file
import win32process
import win32event
import win32security


class Coprocess:
def __init__ (self, cmd, env={}):
self.running = 0
self.exitstatus = None

if type(cmd) == types.StringType:
cmd = string.split(cmd)
executable = cmd[0]
self.args = cmd[1:]
self.executable = os.path.normpath(executable)

# security attributes for created pipes
sAttrs = win32security.SECURITY_ATTRIBUTES()
sAttrs.bInheritHandle = 1

# redirecting child STDOUT:
# XXX redirection should actually be checked before
# cmd is split.
# XXX Right now, the '>' must be framed by whitespace
# in a string.
if '>' in self.args:
redirpos = self.args.index('>')
if len(self.args) < redirpos + 2:
raise IOError, 'Empty Redirect.'
if len(self.args) > redirpos + 2:
raise IOError, 'Ambiguous Redirect.'
outfn = self.args[-1]
outfd = os.open(outfn,
os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0664)
hStdout_w = msvcrt.get_osfhandle(outfd)
self.args = self.args[:-2]
hStdout_r = None
self.hStdout = hStdout_r
elif '>>' in self.args:
redirpos = self.args.index('>>')
if len(self.args) < redirpos + 2:
raise IOError, 'Empty Redirect.'
if len(self.args) > redirpos + 2:
raise IOError, 'Ambiguous Redirect.'
outfn = self.args[-1]
outfd = os.open(outfn,
os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0664)
hStdout_w = msvcrt.get_osfhandle(outfd)
self.args = self.args[:-2]
hStdout_r = None
self.hStdout = hStdout_r
else:
hStdout_r, hStdout_w = win32pipe.CreatePipe(sAttrs, 0)
self.hStdout = hStdout_r

# child STDIN:
hStdin_r, hStdin_w = win32pipe.CreatePipe(sAttrs, 0)
self.hStdin = hStdin_w

# child STDERR:
hStderr_r, hStderr_w = win32pipe.CreatePipe(sAttrs, 0)
self.hStderr = hStderr_r

# create environment for new process.
newenv = os.environ.copy() # use parent environment.
newenv.update(env) # add/override from caller data.

# set up the command line.
sCmdLine = self.executable + ' ' + string.join(self.args)

# set the info structure for the new process.
StartupInfo = win32process.STARTUPINFO()
StartupInfo.hStdInput = hStdin_r
StartupInfo.hStdOutput = hStdout_w
StartupInfo.hStdError = hStderr_w
StartupInfo.dwFlags = win32process.STARTF_USESTDHANDLES

# start the process.
hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(
self.executable, sCmdLine, # what to start
None, # process security attributes
None, # thread attributes
1, # inherit handles, or USESTDHANDLES won't work.
# creation flags. Don't access the console.
win32process.DETACHED_PROCESS,
newenv, # new environment
None, # current directory (stay where we are)
StartupInfo)
#print hProcess, hThread, dwPid, dwTid
self.hProcess = hProcess
self.hThread = hThread
self.pid = dwPid
self.tid = dwTid
self.running = 1

def write(self, str):
err, bytes = win32file.WriteFile(self.hStdin, str)
return bytes

def read(self):
if not self.hStdout: raise IOError, 'Error: output redirected.'
res, str = win32file.ReadFile(self.hStdout, 0)
if res == 0: return str
else: return ''

def stderr_read(self):
res, str = win32file.ReadFile(self.hStderr, 0)
if res == 0: return str
else: return ''

#XXX we'd probably need to implement line buffering ourselves
#def readline(self):
# return ''

#def stderr_readline(self):
# return ''

def flush(self):
win32file.FlushFileBuffers(self.hStdin)

def waitpid (self, opt=0):
wres = win32event.WaitForSingleObject(self.hProcess, opt)
# XXX do we want to check the status if we timed out?
status = win32process.GetExitCodeProcess(self.hProcess)
self.exitstatus = status/256
self.running = 0
return (self.pid, status)

def kill (self, signal=9):
self.close()
if self.running:
win32process.TerminateProcess(self.hProcess, -9)
return self.waitpid(0)
else:
return 0

def __del__(self):
'''close all handles when our instance is garbage collected.'''
self.close()

def close(self):
# close handles defensively, in case we fail early...
if hasattr(self, 'self.hStdin'):
win32api.CloseHandle(self.hStdin)
if hasattr(self, 'self.hStdout'):
win32api.CloseHandle(self.hStdout)
if hasattr(self, 'self.hStderr'):
win32api.CloseHandle(self.hStderr)
if hasattr(self, 'self.hProcess'):
win32api.CloseHandle(self.hProcess)
if hasattr(self, 'self.hThread'):
win32api.CloseHandle(self.hThread)


--
Georg Mischler -- simulation developper -- schorsch at schorsch.com
+schorsch.com+ -- lighting design tools -- http://www.schorsch.com/


Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.
do anonymous pipes normally work on NT? [ In reply to ]
Nothing is particularly obvious, although the code is too large to really be
sure.

* Have you seen win32pipe.popen*?

* I can't see where you keep references to the handles for the sub-process.
Is it possible that the handles are being closed by the parent process,
thereby closing the child process handles?

* Personally I would use win32file.CreateFile instead of
os.open/msvcrt.get_osfhandle. One advantage is that a layer of indirection
you are not using is removed. Second advantage is that you deal with
"PyHANDLE" objects, and explicit closing is not necessary (a-la files)

* You may have more luck spotting the problem (or getting someone else to)
if you provide all this in a single, general purpose function.

def CoProc( command_line, infile, outfile ):
...

with all command-line parsing and other app specific stuff that doesnt
relate to the problem removed.

Hope this helps...

Mark.
do anonymous pipes normally work on NT? [ In reply to ]
Mark Hammond wrote:
> Nothing is particularly obvious, although the code is too large
> to really be sure.

Thanks for looking into it.


> * Have you seen win32pipe.popen*?

Yes I did. The trouble is that I need to store the pid
(and tid on NT), since the started process can live
longer than the parent. If I want to regain control
over the child again later (for stopping it etc.) then
I can't use any of the popen*s. Also, in some cases
I need to redirect stdout to a file (which works fine).


> * I can't see where you keep references to the handles
> for the sub-process.
> Is it possible that the handles are being closed by the
> parent process, thereby closing the child process handles?

I was afraid of that too. I save all necessary handles as
self.hStd{Input|Output|Error}, self.hProcess and self.hThread
(assuming that I actually save the right ones...). Those
handles are only closed in self.__del__() long after my
problem arises.


> * Personally I would use win32file.CreateFile instead of
> os.open/msvcrt.get_osfhandle. One advantage is that a
> layer of indirection you are now using is removed.

This has a technical point. Otoh, it's one of the things
that *do* work right now. I'll check seperately, if your
suggestion changes anything.


> Second advantage is that you deal with > "PyHANDLE" objects,
> and explicit closing is not necessary (a-la files)

I seem to remember a post by someone (you?) who stated
that *not* closing Windows handles on could lead to serious
resource leaks. Am I confusing something here or are
not all handles created equal?


> * You may have more luck spotting the problem (or getting
> someone else to) if you provide all this in a single,
> general purpose function.
>
> def CoProc( command_line, infile, outfile ):
> ...
>
> with all command-line parsing and other app specific stuff
> that doesnt relate to the problem removed.

Yes, I guess I'll have to do this. I stripped it down
allready to a certain degree, but there's still some
unrelated stuff in there (In my application context it's
a tiny piece of code the way it is, but that obviuosly
doesn't help an external observer to understand it...)
I'll be back after some more fiddling.


Thanks a lot

-schorsch

--
Georg Mischler -- simulation developper -- schorsch at schorsch.com
+schorsch.com+ -- lighting design tools -- http://www.schorsch.com/


Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.
do anonymous pipes normally work on NT? [ In reply to ]
Georg Mischler wrote in message <7k07tu$veb$1@nnrp1.deja.com>...

>> Second advantage is that you deal with > "PyHANDLE" objects,
>> and explicit closing is not necessary (a-la files)
>
>I seem to remember a post by someone (you?) who stated
>that *not* closing Windows handles on could lead to serious
>resource leaks. Am I confusing something here or are
>not all handles created equal?

Well, not all handles are created equal anyway, but the point I was making
that PyHANDLE object automatically close the handle when it dies - like a
file object. So the handles will be closed even if you do not explicitely
close them.

My comment wasnt intended to throw light on the specific problem - just
point out an advantage that PyHANDLE objects have. OTOH, I am wildly
guessing that using CreateFile could solve the problem - not due to the
"auto-close" behaviour, but simply because they remove the indirection. I
have no basis for believing it will solve the problem, just a guess!

Mark.
do anonymous pipes normally work on NT? [ In reply to ]
Ok, here we go again.

I simplified my class to the absolute minimum I could.
It now does nothing but create three pipes, save the
parent ends to self.xxx, and create a process with
the child ends of the pipes. After that it tries to
write to the stdin pipe of the child, which goes
without error, and tries to read from stdout, which
blocks.

To have a look at the other end, I wrote a little
script that mimicks the unix "cat" command (in a
very verbose logging incarnation...). This script
first tries to write something to stdout as well,
which goes without error, and then enters a loop
reading from stdin and writing to stdout.

This script is called from my class as a child
process. The interesting thing is now, that in this
case the child gets a "Bad file descriptor" when
reading from stdin!

There must be something going fundamentally wrong
with the pipes, but without any Windows programming
background, I have no chance left figuring out what.
This happens on NT4sp3 with Python 1.5.2 (the
binary from python.org) and the win32 extensions
build 125, if that makes any difference.

If this should be caused by a mistake I made
(which may be likely), could anyone recommend a
book where I can learn how to do this stuff
right? And in any case, what is going on here?

To try this out, copy both files together into
a directory, change the path of python in the
last code line of numproc.py to something that
exists on your system, and call "python runproc.py".
You'll have to kill the process with the task
manager (at least I have to do this). The new
file "log.txt" will contain the results of the
child process.


Thanks a lot for any help!

-schorsch

------------------------------

'''cat.py

a "enhanced" version of the good old cat unix filter.
'''

import sys

try:
# write status to a file, in order not to lose it.
f = open('log.txt', 'a')
f.write('before write\n')
sys.stdout.write('huh?')
f.write('after write, before read\n')
data = sys.stdin.read()
f.write('after read\n')
while data:
f.write('in loop, before write\n')
sys.stdout.write(data)
f.write('in loop, after write, before read\n')
data = sys.stdin.read()
f.write('in loop, after read\n')
except:
import traceback, string
tb_strs = traceback.format_exception(
sys.exc_type,
sys.exc_value,
sys.exc_traceback)
f.write(string.join(tb_strs))

# end of cat.py

------------------------------

'''cat.py

an "enhanced" version of the good old cat unix filter.
'''

import sys

try:
# write status to a file, in order not to lose it.
f = open('log.txt', 'a')
f.write('before write\n')
sys.stdout.write('huh?')
f.write('after write, before read\n')
data = sys.stdin.read()
f.write('after read\n')
while data:
f.write('in loop, before write\n')
sys.stdout.write(data)
f.write('in loop, after write, before read\n')
data = sys.stdin.read()
f.write('in loop, after read\n')
except:
import traceback, string
tb_strs = traceback.format_exception(
sys.exc_type,
sys.exc_value,
sys.exc_traceback)
f.write(string.join(tb_strs))

# end of cat.py

------------------------------

'''runproc.py

start a process with three inherited pipes.
Try to write to and read from those.
'''

import win32pipe
import win32file
import win32process
import win32security

class Process:
def run(self, cmdline):

# security attributes for pipes
sAttrs = win32security.SECURITY_ATTRIBUTES()
sAttrs.bInheritHandle = 1

# create pipes
hStdin_r, self.hStdin_w = win32pipe.CreatePipe(sAttrs, 0)
self.hStdout_r, hStdout_w = win32pipe.CreatePipe(sAttrs, 0)
self.hStderr_r, hStderr_w = win32pipe.CreatePipe(sAttrs, 0)

# set the info structure for the new process.
StartupInfo = win32process.STARTUPINFO()
StartupInfo.hStdInput = hStdin_r
StartupInfo.hStdOutput = hStdout_w
StartupInfo.hStdError = hStderr_w
StartupInfo.dwFlags = win32process.STARTF_USESTDHANDLES

# start the process.
hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(
None, # program
cmdline,# command line
None, # process security attributes
None, # thread attributes
1, # inherit handles, or USESTDHANDLES won't work.
# creation flags. Don't access the console.
win32process.DETACHED_PROCESS,
None, # no new environment
None, # current directory (stay where we are)
StartupInfo)
# normally, we would save the pid etc. here...

err, bytes = win32file.WriteFile(self.hStdin_w, 'hmmm')
print 'wrote:', bytes, 'bytes'

res, str = win32file.ReadFile(self.hStdout_r, 1)
if res == 0: print 'read:', str
else: 'read nothing.'

res, str = win32file.ReadFile(self.hStderr_r, 1)
if res == 0: print 'read:', str
else: 'read nothing.'


if __name__ == '__main__':
p = Process()
p.run('c:\programme\python\python.exe cat.py')

# end of runproc.py

--
Georg Mischler -- simulation developper -- schorsch at schorsch.com
+schorsch.com+ -- lighting design tools -- http://www.schorsch.com/


Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.
do anonymous pipes normally work on NT? [ In reply to ]
In article <7k43sk$6vd$1@nnrp1.deja.com>,
Georg Mischler <schorsch@schorsch.com> wrote:
>
>I simplified my class to the absolute minimum I could. It now does
>nothing but create three pipes, save the parent ends to self.xxx, and
>create a process with the child ends of the pipes. After that it tries
>to write to the stdin pipe of the child, which goes without error, and
>tries to read from stdout, which blocks.

How about sockets?
--
--- Aahz (@netcom.com)

Hugs and backrubs -- I break Rule 6 <*> http://www.rahul.net/aahz/
Androgynous poly kinky vanilla queer het

"That doesn't stop me from wanting to work all three of them over with
the clue stick for while, with no safewords allowed." --abostick
do anonymous pipes normally work on NT? [ In reply to ]
Aahz Maruch wrote:
> Georg Mischler wrote:
> >
> >I simplified my class to the absolute minimum I could. It now does
> >nothing but create three pipes, save the parent ends to self.xxx, and
> >create a process with the child ends of the pipes. After that it
tries
> >to write to the stdin pipe of the child, which goes without error,
and
> >tries to read from stdout, which blocks.
>
> How about sockets?

Because I don't feel like rewriting several dozen existing C programs
as provided by Radiance (radsite.lbl.gov/radiance/) just for this
purpose. It's not that I don't know about sockets. I use them in
situations when I want to run processes on remote hosts (distributed
rendering). But once I'm there, I need a simple way to just run
the existing stuff and get the results back by the simplest
means possible. That's exactly what anonymous pipes are there
for, and I'd *love* them to work as documented (or rather as I
understand the documentation... ;).

But thanks for the suggestion anyway :)


Have fun!

-schorsch

--
Georg Mischler -- simulation developper -- schorsch at schorsch.com
+schorsch.com+ -- lighting design tools -- http://www.schorsch.com/


Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.
do anonymous pipes normally work on NT? [ In reply to ]
Im afraid I can't be of much help here. I have had a good play, and nothing
seems obvious.

Interestingly, if you change cat.py to:

f.write("Stdin fileno is %s\n" % (sys.stdin.fileno(), ))
f.write("Stdin handle is %s\n" %
(msvcrt.get_osfhandle(sys.stdin.fileno()), ) )

You will notice that when run "normally", stdin has a fileno of 0, and a
valid handle. However, when run by runproc.py, the fileno is -1, and
attempting to get back the handle fails. If I had more time I would dig
into where fileno() gets its value from, and see if this points to why we
are failing.

Adding
except:
traceback.print_exc(file=f)

Also helps see this stuff. I am afraid I am all out of time with this
stuff. If you are really keen, the next step would probably be to recode
this small sample in C, and see if that works. If it does, Python (or the
win32 extensions) has a problem we should fix as a matter of priority. If
not, then we need to find the magic words to chant to make it work from C,
and move that back to Python...

Mark.
do anonymous pipes normally work on NT? [ In reply to ]
Well, I finally noticed this thread, and I'll chime in with what I found out
so far:
(No, I haven't figured it all out yet, its time for sleep.)

Symptoms:
python -u cat.py < cat.py is allowed to read from stdin.
python cat.py < cat.py isn't allowed to read from stdin.
python -u cat.py > blah is allowed to write to stdout
python cat.py > blah isn't allowed to wrtie to stdout.
python runproc.py (with -u) cat.py isn't allowed to write to stdout
python runproc.py cat.py isn't allowed to write to stdout.

-u does two things:
1) turns stdin/stdout into binary streams
2) turns off stdio buffering

The binary thing shouldn't make any difference. The stdio thing might make a
difference, but how isn't obvious from the read.c source code.

Now the big mystery is: Why does the CRT care? (prolly need to debug read.c
to find out)
The 2nd question is: Why doesn't -u fix the runproc.py case?

I think the answer to the 2nd question may come from the
::InitializeSecurityDescriptor(&m_sd, SECURITY_DESCRIPTOR_REVISION);
line in PySECURITY_ATTRIBUTES's constructor.

I'm guessing (not having debug bits atm) that this causes a security
descriptor that doesn't have the default permissions that the current
process has to become filled in.

(Default permissions are inferred from the current process/thread if
lpSecurityDescriptor is NULL)

This in turn causes reading/writing to the inherited handles to be denied
because the new process doesn't have the correct permissions. Which in turn
turns into errno == EBADF in read.c.


This seems to be the only meaningful difference between the Python code in
runproc.py, and the win32popen.cpp code I wrote...

So the first thing to try might be to try commenting out the
InitializeSecurityDescriptor line, and rebuilding pywintypes15.dll, and then
using -u when running cat.py.

The next thing to do is to debug _read.c (in the MS CRT) and see why its
complaining if -u isn't specified on the command line.

If somebody could help explore where exactly errno is getting set to EBADF
during the non- "runproc.py" cases that'd be a great help.

Bill
Not part of product support, or PR.
do anonymous pipes normally work on NT? [ In reply to ]
Mark Hammond wrote:
> Im afraid I can't be of much help here. I have had a good play,
> and nothing seems obvious.
[...]
> If you are really keen, the next step would probably be to recode
> this small sample in C, and see if that works. If it does, Python
> (or the win32 extensions) has a problem we should fix as a matter of
> priority. If not, then we need to find the magic words to chant to
> make it work from C, and move that back to Python...


Thanks for your efforts.

Based on my own further experiments I allmost expected to hear
this, and I'm starting to get mentally prepared to check with
a few lines of C. In the extreme case, I'd actually have to
rewrite it as an extension module since I really need the
functionality.
But of course, finding a mistake of mine, or seeing python resp.
the win32 extensions fixed in case there should be a real problem,
would be highly preferrable.

I'll be back with my further findings.


Thanks

-schorsch


--
Georg Mischler -- simulation developper -- schorsch at schorsch.com
+schorsch.com+ -- lighting design tools -- http://www.schorsch.com/


Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.
do anonymous pipes normally work on NT? [ In reply to ]
In article <4D0A23B3F74DD111ACCD00805F31D8100DB90DDC@RED-MSG-50>,
Bill Tutt <billtut@microsoft.com> wrote:
> Well, I finally noticed this thread, and I'll chime in with what I
found out
> so far:
> (No, I haven't figured it all out yet, its time for sleep.)
>
> Symptoms:
> python -u cat.py < cat.py is allowed to read from stdin.
> python cat.py < cat.py isn't allowed to read from stdin.
> python -u cat.py > blah is allowed to write to stdout
> python cat.py > blah isn't allowed to wrtie to stdout.
> python runproc.py (with -u) cat.py isn't allowed to write to stdout
> python runproc.py cat.py isn't allowed to write to stdout.
>
> -u does two things:
> 1) turns stdin/stdout into binary streams
> 2) turns off stdio buffering

Hi

maybe I'm missing something about what you are looking for: here are 2
test scripts that work as anonymous pipes:

file runproc.py
---------------

'''runproc.py

start a process with three inherited pipes.
Try to write to and read from those.
'''

import win32api
import win32pipe
import win32file
import win32process
import win32security
import win32event
import os

#Constants for stdandard handles
STD_ERR_HANDLE=-12
STD_OUTPUT_HANDLE=-11
STD_INPUT_HANDLE=-10

class Process:
def run(self, cmdline):

# security attributes for pipes
sAttrs = win32security.SECURITY_ATTRIBUTES()
sAttrs.bInheritHandle = 1

# create pipes
hStdin_r,self.hStdin_w=win32pipe.CreatePipe(sAttrs, 0)
self.hStdout_r,hStdout_w=win32pipe.CreatePipe(sAttrs,0)
self.hStderr_r,hStderr_w=win32pipe.CreatePipe(sAttrs, 0)

#associate Standard output to previously open handle
win32api.SetStdHandle(STD_OUTPUT_HANDLE,hStdout_w)

# set the info structure for the new process.
StartupInfo = win32process.STARTUPINFO()

# start the process.
hProcess, hThread, dwPid, dwTid=win32process.CreateProcess(
None, #program
cmdline,# command line
sAttrs, # process security attributes
sAttrs, # thread attributes
1, # inherit handles,
win32process.NORMAL_PRIORITY_CLASS,
None, # no new environment
None, # current directory (stay where we are)
StartupInfo)
# normally, we would save the pid etc. here...


res, str = win32file.ReadFile(self.hStdout_r,100)
if res == 0: print 'read:', str
else: 'read nothing.'


if __name__ == '__main__':
p = Process()
p.run('..\\python.exe testpipe.py')
-----------------
end of runproc.py

file testpipe.py
----------------
import win32api
import win32file

STD_ERR_HANDLE=-12
STD_OUTPUT_HANDLE=-11
STD_INPUT_HANDLE=-10

std_in=win32api.GetStdHandle(STD_INPUT_HANDLE)
std_err=win32api.GetStdHandle(STD_ERR_HANDLE)
std_out=win32api.GetStdHandle(STD_OUTPUT_HANDLE)

win32file.WriteFile(std_out, 'this method works....')

end of testpipe.py
------------------


Cheers

Florent Heyworth


Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.
do anonymous pipes normally work on NT? [ In reply to ]
> From: radbit@my-deja.com [mailto:radbit@my-deja.com]
>
>
> In article <4D0A23B3F74DD111ACCD00805F31D8100DB90DDC@RED-MSG-50>,
> Bill Tutt <billtut@microsoft.com> wrote:
> > Well, I finally noticed this thread, and I'll chime in with what I
> found out
> > so far:
> > (No, I haven't figured it all out yet, its time for sleep.)
> >
> > Symptoms:
> > python -u cat.py < cat.py is allowed to read from stdin.
> > python cat.py < cat.py isn't allowed to read from stdin.
> > python -u cat.py > blah is allowed to write to stdout
> > python cat.py > blah isn't allowed to wrtie to stdout.
> > python runproc.py (with -u) cat.py isn't allowed to write to stdout
> > python runproc.py cat.py isn't allowed to write to stdout.
> >
> > -u does two things:
> > 1) turns stdin/stdout into binary streams
> > 2) turns off stdio buffering
>
> Hi
>
> maybe I'm missing something about what you are looking for: here are 2
> test scripts that work as anonymous pipes:
>

That's not the question... Anonymous pipes on NT work wonderfully. (just not
the way we're expecting from this particular Python context)
Calling SetStdHandle() is particularly silly as well, you shouldn't need to
overwrite the current stdout just to get this to work.

> win32file.WriteFile(std_out, 'this method works....')
>

Calling GetStdHandle shouldn't be necessary either. The CRT already grabbed
that information for us when Python started up.

The question is why does sys.stdout.write() not seem to work without -u when
the above line works wonderfully.... I'm not worried about a working example
of anonymous pipes in general, thats the easy part.

Bill
do anonymous pipes normally work on NT? [ In reply to ]
Bill Tutt wrote:
>
>
> radbit@my-deja.com wrote:
> >
> > maybe I'm missing something about what you are looking for:
> > here are 2 test scripts that work as anonymous pipes:
> >
>
> That's not the question...
> Anonymous pipes on NT work wonderfully. (just not
> the way we're expecting from this particular Python context)

Well, at least he solved *my* problem! And after fiddling a
little, it even works with my cat.py as well, and should
work with arbitrary child processes. I'll just have to steal
a readline() method somewhere to make it practical...


The Documentaion provided with VC++ about this stuff
it not very helpful, so you have to try and guess.
Here's what I figured by now: The CreateProcess()
function has several ways to pass handles to child
processes. The way I tried it (passing through
StartupInfo.hStdXxx) only seems to work with disk files,
and not with pipes.

The other way is to assign the handles to the stdxxx
of the current process, and inherit them to the
child. This does work with pipes, and is fundamentally
incompatible with the first approach. In fact, as soon
as you just tell it you want to pass any handles in the
StartupInfo, by assigning STARTF_USESTDHANDLES to it's
dwFlags attribute, inheritance is dead. Still, if
you *want* to pass (file) handles through the
StartupInfo object, you nonetheless *need* to set the
bInheritHandles parameter of CreateProcess() to TRUE.
Go figure...

The least obvious thing though, which I only found
by comparing the working code with mine very carefully,
is that for inheritance to work, the dwCreationFlags
must *not* contain win32process.DETACHED_PROCESS.
The VisualStudio docs (and consequently the win32proc
help file) tell us that this would deny acess to the
*console* of the parent process. In fact, it appears
to cut all connections between the two processes.

many thanks for everybody's assistance in figuring
this out!

> Calling SetStdHandle() is particularly silly as well,
> you shouldn't need to overwrite the current stdout just
> to get this to work.

In fact, This is the way I saw it in several C++ examples
by now, and my own observations make me think that it's
just needed if you want to inherit *pipe* handles.


> > win32file.WriteFile(std_out, 'this method works....')
>
> Calling GetStdHandle shouldn't be necessary either. The CRT
> already grabbed that information for us when Python started up.
>
> The question is why does sys.stdout.write() not seem to work
> without -u when

In my latest experiments this *does* work if the parent
process manages to get the handles across. CreateProcess()
has so many mutually exclusive combinations of parameters
that it's just hard to find the right ones for a particular
purpose.

I don't mind setting and resetting the std handles in the
parent, as long as I get the result I want. But then, I'm
also trying to get along with as little knowledge as possible
about Windows programming. I assume you look at it from a
slightly different angle.


Thanks again to everybody!

-schorsch

--
Georg Mischler -- simulation developper -- schorsch at schorsch.com
+schorsch.com+ -- lighting design tools -- http://www.schorsch.com/


Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.
do anonymous pipes normally work on NT? [ In reply to ]
Bill Tutt wrote:

> > From: radbit@my-deja.com [mailto:radbit@my-deja.com]
> >
> >
> > Hi
> >
> > maybe I'm missing something about what you are looking for: here are 2
> > test scripts that work as anonymous pipes:
> >
>
> That's not the question... Anonymous pipes on NT work wonderfully. (just not
> the way we're expecting from this particular Python context)
> Calling SetStdHandle() is particularly silly as well, you shouldn't need to
> overwrite the current stdout just to get this to work.

Agreed - it was just the only way I could get it to work.

>
>
> > win32file.WriteFile(std_out, 'this method works....')
> >
>
> Calling GetStdHandle shouldn't be necessary either. The CRT already grabbed
> that information for us when Python started up.
>

True. That was just for the sake of being pedantic. Of course:
print "this method works..."
works just fine.

>
> The question is why does sys.stdout.write() not seem to work without -u when
> the above line works wonderfully.... I'm not worried about a working example
> of anonymous pipes in general, thats the easy part.

Interestingly enough I couldn't get at the outpost of testpipe.py even when
when passing "..\python -u testpipe1.py" as the command line argument in
win32process.CreateProcess().

The only workaround I found was to use SetStdHandle 8-(. Once that is done the
child process can use sys.stdout.write("this works....") without problems. For
safety I should have saved the parent's Stdout handle first.

Just a question: I'm pretty sure that this leads nowhere but: do you think what
we're seeing might have something to do with the fact that Python is built in
with multithreaded support?

Anyway - off to bed soon 8-)

Cheers
Florent Heyworth
do anonymous pipes normally work on NT? [ In reply to ]
> From: Georg Mischler [mailto:schorsch@schorsch.com]

> The Documentaion provided with VC++ about this stuff
> it not very helpful, so you have to try and guess.
> Here's what I figured by now: The CreateProcess()
> function has several ways to pass handles to child
> processes. The way I tried it (passing through
> StartupInfo.hStdXxx) only seems to work with disk files,
> and not with pipes.
>

It may seem that way, but thats far from the truth.
Because win32popen.cpp uses StartupInfo.hStdxxxx and it works quite well.
The real cause of this problem is that there is a bug in win32process's
startupinfo support. (see info at bottom of email)

> The other way is to assign the handles to the stdxxx
> of the current process, and inherit them to the
> child. This does work with pipes, and is fundamentally
> incompatible with the first approach. In fact, as soon
> as you just tell it you want to pass any handles in the
> StartupInfo, by assigning STARTF_USESTDHANDLES to it's
> dwFlags attribute, inheritance is dead. Still, if
> you *want* to pass (file) handles through the
> StartupInfo object, you nonetheless *need* to set the
> bInheritHandles parameter of CreateProcess() to TRUE.
> Go figure...
>

Well, inheritance of stdin, stdout, stderr is dead, yes. (All other
inhertible handles will be valid in the child process)
They sure like to make life confusing don't they? :)

> The least obvious thing though, which I only found
> by comparing the working code with mine very carefully,
> is that for inheritance to work, the dwCreationFlags
> must *not* contain win32process.DETACHED_PROCESS.
> The VisualStudio docs (and consequently the win32proc
> help file) tell us that this would deny acess to the
> *console* of the parent process. In fact, it appears
> to cut all connections between the two processes.
>

In theory, this should only disallow the new process from acessing the
current program's NT Console (if any). This should have nothing to do with
hStdXxx being anonymous pipe handles. Although after some experimentation it
certainly seems to be doing just a little bit more than revoking access to
the NT console. i.e. passing DETACHED_PROCESS in the example below works
fine, but it completly failed if I tried to fire up Mark's cool debugger.
(It asserted at objcore.cpp:45 in MFC land...)

> In my latest experiments this *does* work if the parent
> process manages to get the handles across. CreateProcess()
> has so many mutually exclusive combinations of parameters
> that it's just hard to find the right ones for a particular
> purpose.
>

Hrm, now when I go to retest what I did last night, I wasn't paying close
enough attention. If i do "cat.py < xxx > yyy" it complains because
something in the file extension association code is screwing things up. :(

python cat.py < xxxx > yyy works just fine.

> I don't mind setting and resetting the std handles in the
> parent, as long as I get the result I want. But then, I'm
> also trying to get along with as little knowledge as possible
> about Windows programming. I assume you look at it from a
> slightly different angle.
>

Heh, if you know more about it you can do more useful things when life
becomes complicated. :) I'm just digging through the docs like you, I don't
work on the NT team and can't go look up the source for CreateProcess().

Attached below is the fix information, along with a Georg's example slightly
tweaked to be useful.

Bill

Patch info:

Alter the PyHANDLE_Check() branch of win32processmodule_win32.cpp:sethandle
to:
*pobHandle = v;
if (PyWinObject_AsHANDLE(v, ph))
{
Py_INCREF(v);
}
else
{
rc = -1;
}
The cast that was there before was setting *ph equal to the Python reference
count on v.

Working sample:

'''cat.py
a "enhanced" version of the good old cat unix filter.
'''

import sys
try:
# write status to a file, in order not to lose it.
f = open('log.txt', 'a')
f.write('before write\n')
sys.stdout.write('huh?')
f.write('after write, before read\n')
data = sys.stdin.read()
f.write('data: ' + data)
f.write('after read\n')
f.write('in loop, before write\n')
sys.stdout.write(data)
except:
import traceback, string
tb_strs = traceback.format_exception(
sys.exc_type,
sys.exc_value,
sys.exc_traceback)
f.write(string.join(tb_strs))

# end of cat.py

'''runproc.py

start a process with three inherited pipes.
Try to write to and read from those.
'''

import win32pipe
import win32file
import win32process
import win32security
import win32con

class Process:
def run(self, cmdline):
# security attributes for pipes
sAttrs = win32security.SECURITY_ATTRIBUTES()
sAttrs.bInheritHandle = 1

# create pipes
hStdin_r, self.hStdin_w = win32pipe.CreatePipe(sAttrs, 0)
self.hStdout_r, hStdout_w = win32pipe.CreatePipe(sAttrs, 0)
self.hStderr_r, hStderr_w = win32pipe.CreatePipe(sAttrs, 0)

# set the info structure for the new process.
StartupInfo = win32process.STARTUPINFO()
StartupInfo.hStdInput = hStdin_r
StartupInfo.hStdOutput = hStdout_w
StartupInfo.hStdError = hStderr_w
StartupInfo.dwFlags = win32process.STARTF_USESTDHANDLES

# start the process.
hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(
None, # program
cmdline,# command line
None, # process security attributes
None, # thread attributes
1, # inherit handles, or USESTDHANDLES won't work.
# creation flags. Don't access the console.
win32process.DETACHED_PROCESS,
None, # no new environment
None, # current directory (stay where we are)
StartupInfo)
# normally, we would save the pid etc. here...

err, bytes = win32file.WriteFile(self.hStdin_w, 'hmmm\n')
print 'wrote:', bytes, 'bytes'

res, str = win32file.ReadFile(self.hStdout_r, 7)
if res == 0: print 'read:', str
else: print 'read nothing on stdin.'

if __name__ == '__main__':
p = Process()
p.run(r'd:\pythondevtree\python\dist\src\pcbuild\python_d.exe
d:\cat.py')

# end of runproc.py
do anonymous pipes normally work on NT? [ In reply to ]
Bill Tutt wrote:
>
> Patch info:
>
> Alter the PyHANDLE_Check() branch of
win32processmodule_win32.cpp:sethandle
> to:
> *pobHandle = v;
> if (PyWinObject_AsHANDLE(v, ph))
> {
> Py_INCREF(v);
> }
> else
> {
> rc = -1;
> }
> The cast that was there before was setting *ph equal to the
> Python reference count on v.


Ok, I'll have to build this stuff myself some day anyway...

But then, I just downloaded win32_src.zip and Pythonwin_src.zip
and didn't find anything resembling a win32processmodule in there.
Guess I am looking in the wrong place. Where is this module defined?

Thanks a lot

-schorsch

--
Georg Mischler -- simulation developper -- schorsch at schorsch.com
+schorsch.com+ -- lighting design tools -- http://www.schorsch.com/


Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.
do anonymous pipes normally work on NT? [ In reply to ]
Bill Tutt wrote in message
<4D0A23B3F74DD111ACCD00805F31D8100DB90DE4@RED-MSG-50>...
>

>Alter the PyHANDLE_Check() branch of win32processmodule_win32.cpp:sethandle
>to:
> *pobHandle = v;
> if (PyWinObject_AsHANDLE(v, ph))
> {
> Py_INCREF(v);
> }
> else
> {
> rc = -1;
> }

Ahh - excellent analysis. I made the mistake of attempting to use some
fancy C++ operators, and, like many C++ features, turned around and bit me
in the bum. I will also seek out and destroy any similar casts.

Good stuff!

Mark.
do anonymous pipes normally work on NT? [ In reply to ]
Inside win32_src.zip you should find win32process.i and
win32processmodule.cpp - the latter is generated by SWIG from the former.

Bill is working from the same archive, so it must be there!

The only thing I can think of is that you didnt grab the archives from my
starship page...

Mark.

Georg Mischler wrote in message <7kbq78$vgm$1@nnrp1.deja.com>...
>But then, I just downloaded win32_src.zip and Pythonwin_src.zip
>and didn't find anything resembling a win32processmodule in there.
>Guess I am looking in the wrong place. Where is this module defined?
do anonymous pipes normally work on NT? [ In reply to ]
Mark Hammond wrote:
> Inside win32_src.zip you should find win32process.i and
> win32processmodule.cpp - the latter is generated by SWIG from
> the former.
>
> Bill is working from the same archive, so it must be there!

Do I need to swig this stuff myself? or...

> The only thing I can think of is that you didnt grab the
> archives from my starship page...

Uh, indeed. Are the source packages on www.python.org so
heavily out of date? Someone might want to update them in
that case. The binary package is obviously build 125, but
a quick search didn't reveal the "build" number of the sources.
I'll get them from the starship in any case to make sure
it's the real thing.

Thanks a lot!

-schorsch

--
Georg Mischler -- simulation developper -- schorsch at schorsch.com
+schorsch.com+ -- lighting design tools -- http://www.schorsch.com/


Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.
do anonymous pipes normally work on NT? [ In reply to ]
> From: Georg Mischler [mailto:schorsch@schorsch.com]

> Do I need to swig this stuff myself? or...

Nope, they already come pre-swigged.
Download, load into VC++, tweak away so that it can find all of the
necessary .h/.libs it needs and build away.

Bill
do anonymous pipes normally work on NT? [ In reply to ]
Bill Tutt wrote in message
<4D0A23B3F74DD111ACCD00805F31D8100DB90DEF@RED-MSG-50>...
>> From: Georg Mischler [mailto:schorsch@schorsch.com]
>
>> Do I need to swig this stuff myself? or...
>
>Nope, they already come pre-swigged.
>Download, load into VC++, tweak away so that it can find all of the
>necessary .h/.libs it needs and build away.

FWIW, I am now recommending that you place the Python directories on your
MSVC global include and lib paths (using View/Options/Directories). This
makes it easier to change versions or locations of Python - simply change
the global settings once, and all the project still work.

This is the way I ship my project files - with no embedded Python etc
paths - as I can't possibly predict your system. So setting these paths
globally also means you can update the sources without re-tweaking the
projects.

If there are wrinkles in this that I can iron out, let me know... [.although
longer term it may make sense to move to DA's compile.py tool, then I can
just ship a setup.in and the projects files are built locally.]

Mark.

Mark.
do anonymous pipes normally work on NT? [ In reply to ]
After digging through this stuff, I went back and looked at the code I had
written for win32popen.cpp. I realized that I had forgotten why I had
written the DuplicateHandle() code, so I went back commented it out from the
KB article that I had originally used, and then modified runproc.py to do
the same thing.

cat.py is now much happier with life with the new and improved runproc.py.
The other important thing is to remember to close the stdin writing handle
only once.
Once you call msvcrt.open_osfhandle, you don't need to call
win32file.CloseHandle on it any more, simply issue a CRT close, and all will
be well.

Notice I also changed the DETACHED_PROCESS to a zero. This is to not do
those wierd things that DETACHED_PROCESS seems to do. Since stdin, etc.. are
pipes, it doesn't matter if the process technically has access to
runproc.py's NT console. (if any)

The other alternative from 0 would be to pass in CREATE_NEW_CONSOLE, and
tweak win32process_win32module.cpp to add:
{"wShowWindow", T_INT, OFF(m_startupinfo.wShowWindow)},
//@prop integer|wShowWindow|

onto the end of PySTARTUPINFO::memberlist, and then do the following:
StartupInfo = win32process.STARTUPINFO()
StartupInfo.hStdInput = hStdin_r
StartupInfo.hStdOutput = hStdout_w
StartupInfo.hStdError = hStderr_w
StartupInfo.dwFlags = win32process.STARTF_USESTDHANDLES |
win32process.STARTF_USESHOWWINDOW
StartupInfo.wShowWindow = win32con.SW_HIDE

Mark: please add that line when you get a chance. :)

Bill

'''runproc.py

start a process with three inherited pipes.
Try to write to and read from those.
'''

import win32api
import win32pipe
import win32file
import win32process
import win32security
import win32con
import msvcrt
import os

class Process:
def run(self, cmdline):
# security attributes for pipes
sAttrs = win32security.SECURITY_ATTRIBUTES()
sAttrs.bInheritHandle = 1

# create pipes
hStdin_r, self.hStdin_w = win32pipe.CreatePipe(sAttrs, 0)
self.hStdout_r, hStdout_w = win32pipe.CreatePipe(sAttrs, 0)
self.hStderr_r, hStderr_w = win32pipe.CreatePipe(sAttrs, 0)

# set the info structure for the new process.
StartupInfo = win32process.STARTUPINFO()
StartupInfo.hStdInput = hStdin_r
StartupInfo.hStdOutput = hStdout_w
StartupInfo.hStdError = hStderr_w
StartupInfo.dwFlags = win32process.STARTF_USESTDHANDLES

# Create new output read handles and the input write handle. Set
# the inheritance properties to FALSE. Otherwise, the child inherits
# the these handles; resulting in non-closeable handles to the pipes
# being created.
pid = win32api.GetCurrentProcess()

tmp = win32api.DuplicateHandle(
pid,
self.hStdin_w,
pid,
0,
0, # non-inheritable!!
win32con.DUPLICATE_SAME_ACCESS)
# Close the inhertible version of the handle
win32file.CloseHandle(self.hStdin_w)
self.hStdin_w = tmp
tmp = win32api.DuplicateHandle(
pid,
self.hStdout_r,
pid,
0,
0, # non-inheritable!
win32con.DUPLICATE_SAME_ACCESS)
# Close the inhertible version of the handle
win32file.CloseHandle(self.hStdout_r)
self.hStdout_r = tmp

# start the process.
hProcess, hThread, dwPid, dwTid = win32process.CreateProcess(
None, # program
cmdline,# command line
None, # process security attributes
None, # thread attributes
1, # inherit handles, or USESTDHANDLES won't work.
# creation flags. Don't access the console.
0,
None, # no new environment
None, # current directory (stay where we are)
StartupInfo)
# normally, we would save the pid etc. here...

# Child is launched. Close the parents copy of those pipe handles
# that only the child should have open.
# You need to make sure that no handles to the write end of the
# output pipe are maintained in this process or else the pipe will
# not close when the child process exits and the ReadFile will hang.
win32file.CloseHandle(hStderr_w)
win32file.CloseHandle(hStdout_w)
win32file.CloseHandle(hStdin_r)

self.stdin = os.fdopen(msvcrt.open_osfhandle(self.hStdin_w, 0),
"wb")
self.stdin.write('hmmmmm\n')
self.stdin.flush()
self.stdin.close()

self.stdout = os.fdopen(msvcrt.open_osfhandle(self.hStdout_r, 0),
"rb")
print "Read on stdout: ", repr(self.stdout.read())

self.stderr = os.fdopen(msvcrt.open_osfhandle(self.hStderr_r, 0),
"rb")
print "Read on stderr: ", repr(self.stderr.read())


if __name__ == '__main__':
p = Process()
p.run(r'd:\pythondevtree\python\dist\src\pcbuild\python_d.exe
d:\cat.py')

# end of runproc.py
do anonymous pipes normally work on NT? [ In reply to ]
Bill Tutt wrote:
> After digging through this stuff, I went back and looked at the code I
had
> written for win32popen.cpp. I realized that I had forgotten why I had
> written the DuplicateHandle() code, so I went back commented it out
from the
> KB article that I had originally used, and then modified runproc.py
to do
> the same thing.
>
> cat.py is now much happier with life with the new and improved
runproc.py.
[more stuff]

Uh oh, Bill...

If you should ever happen to visit the Munich Oktoberfest,
please remind me that I owe you a few beer!

Actually, I stayed with the inheriting version for the moment,
as it will work with existing versions of the win32 extensions.
And I'm not sure if I got the changes in the sources (or the
compilation) right. At least I didn't see any changes after
replacing my win32process.pyd.

The most important thing is of course that my code works now.
I'll have another look at the esthetically more pleasing
changes later, when the dust has settled a little in my brain...


> Notice I also changed the DETACHED_PROCESS to a zero. This is to not
do
> those wierd things that DETACHED_PROCESS seems to do. Since stdin,
etc.. are
> pipes, it doesn't matter if the process technically has access to
> runproc.py's NT console. (if any)

Another thing I noted here, is that with pythonw.exe (instead
python.exe), the second generation of processes gets those
problems again, no matter what you do (my application starts a
"process wrapper" in python, which will call other programs to
do the real work in the background).


> The other alternative from 0 would be to pass in CREATE_NEW_CONSOLE,
> and tweak win32process_win32module.cpp to add:
> {"wShowWindow", T_INT, OFF(m_startupinfo.wShowWindow)},
> //@prop integer|wShowWindow|
>
> onto the end of PySTARTUPINFO::memberlist, and then do the following:
> StartupInfo = win32process.STARTUPINFO()
> StartupInfo.hStdInput = hStdin_r
> StartupInfo.hStdOutput = hStdout_w
> StartupInfo.hStdError = hStderr_w
> StartupInfo.dwFlags = win32process.STARTF_USESTDHANDLES |
> win32process.STARTF_USESHOWWINDOW
> StartupInfo.wShowWindow = win32con.SW_HIDE
>
> Mark: please add that line when you get a chance. :)

Yes, I noticed this attribute missing as well, but wasn't sure
what to insert where... Interestingly, STARTF_USESHOWWINDOW
keeps the console window from opening even without it. The
structure member seems to get initialized to zero somehow.


> '''runproc.py
>
[and more of my code expertively enhanced...]


Thanks a lot for all the tips and bug hunting!

-schorsch

--
Georg Mischler -- simulation developper -- schorsch at schorsch.com
+schorsch.com+ -- lighting design tools -- http://www.schorsch.com/


Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.
do anonymous pipes normally work on NT? [ In reply to ]
In article <7kjgt1$noj$1@nnrp1.deja.com>,
Georg Mischler <schorsch@schorsch.com> wrote:
> Bill Tutt wrote:
> > After digging through this stuff, I went back and looked at the
code I
> had
> > written for win32popen.cpp. I realized that I had forgotten why I
had
>
> Uh oh, Bill...
>
> If you should ever happen to visit the Munich Oktoberfest,
> please remind me that I owe you a few beer!
>
>
> Thanks a lot for all the tips and bug hunting!
>

Well......

(I) never dug up the source - but I still spent 3 hours of my time
trying to get your code to work...... do I get a beer at the Fest 8-)?

Cheers
--
Florent Heyworth


Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.
do anonymous pipes normally work on NT? [ In reply to ]
Florent Heyworth wrote:
> Georg Mischler wrote:
> > Bill Tutt wrote:
> > > After digging through this stuff, I went back and looked at
> > > the code I had written for win32popen.cpp.
> > > I realized that I had forgotten why I had
> >
> > Uh oh, Bill...
> >
> > If you should ever happen to visit the Munich Oktoberfest,
> > please remind me that I owe you a few beer!
> >
> >
> > Thanks a lot for all the tips and bug hunting!
> >
>
> Well......
>
> (I) never dug up the source - but I still spent 3 hours of my time
> trying to get your code to work...... do I get a beer at the Fest 8-)?


Oops! I knew this usenet thing was going to be expensive...
Just warn me beforehand when you turn up there. And remember,
as the name suggests, the Munich Oktoberfest is in September. ;)


Have fun!

-schorsch

--
Georg Mischler -- simulation developper -- schorsch at schorsch.com
+schorsch.com+ -- lighting design tools -- http://www.schorsch.com/


Sent via Deja.com http://www.deja.com/
Share what you know. Learn what you don't.