Handling multiple processes on Windows

I started up a ruby group at my university and this weeks 'challenge'
was to take one of your old messy programs from another language and
port it to Ruby, cleaning it up along the way. So I chose a terrible
little Tk based wrapper around mpg123 as my challenge. However, now
I'm thinking I should have picked something that showed how much easier
Ruby is, rather than wander into the gray areas.

The problem with this is, I need to run multiple processes. If I
wanted it to just work on *nix I could just use fork, as I did in the
perl script but a lot of the people in this group are tied down to
Windows (much to my chagrin :-/) so I figured I'd be nice and make my
ported script Windows friendly.

So my question is, how exactly would you go about writing a program
that could fire a process to a command line application, let it run,
give control back to your main program, and kill the process if an
event occurs (Like the pressing of a 'stop' button or even command line
interaction).

I thought about threading but since the threads are done in process I'm
afraid of explosions and complications galore.

So for those of you Windows savvy Rubyist's, what is a good way to do
multi processing in such a scenario?

For the brave, I've included a link to the source I'm porting, which is
in obvious need of cleanup.

http://cvs.sourceforge.net/viewcvs.py/tkmp3/tk-mp3/src/tk-mp3.pl?rev=1.1.1.1&view=auto

If you run into an issue spawning process and getting command prompts
that pop up, Simon Kroger gave me a very nice fix.. Here's the code
for future reference:

Simon Kröger wrote:

[...]
hmmm ... i will read through the mentioned section ....

I did.

Ok, here is the 'solution': one has to use CreateProcess to spawn the
child. Unfortunately this is a bit more complicated but thanks to
Win32API its all achievable in pure ruby.

The stuff in $0 == __FILE__ builds a simple fox gui and starts cmd.exe
if a button is pressed. It uses a pipe to write to the stdin of the
child ('dir\n') and reads back the childs stdout via another pipe and
dumps it to a text control. (The stderror is also mapped but not used)

It's still some work to make it look as easy as popen, but definitaly
possible.

Here is the code:
(some lines are copied from this list and the net)

···

--------------------------------------------------------------------
require 'Win32API'

NORMAL_PRIORITY_CLASS = 0x00000020
STARTUP_INFO_SIZE = 68
PROCESS_INFO_SIZE = 16
SECURITY_ATTRIBUTES_SIZE = 12

ERROR_SUCCESS = 0x00
FORMAT_MESSAGE_FROM_SYSTEM = 0x1000
FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x2000

HANDLE_FLAG_INHERIT = 1
HANDLE_FLAG_PROTECT_FROM_CLOSE =2

STARTF_USESHOWWINDOW = 0x00000001
STARTF_USESTDHANDLES = 0x00000100

def raise_last_win_32_error
  errorCode = Win32API.new("kernel32", "GetLastError", , 'L').call
  if errorCode != ERROR_SUCCESS
    params = [
      'L', # IN DWORD dwFlags,
      'P', # IN LPCVOID lpSource,
      'L', # IN DWORD dwMessageId,
      'L', # IN DWORD dwLanguageId,
      'P', # OUT LPSTR lpBuffer,
      'L', # IN DWORD nSize,
      'P', # IN va_list *Arguments
    ]

    formatMessage = Win32API.new("kernel32", "FormatMessage", params, 'L')
    msg = ' ' * 255
    msgLength = formatMessage.call(FORMAT_MESSAGE_FROM_SYSTEM +
      FORMAT_MESSAGE_ARGUMENT_ARRAY, '', errorCode, 0, msg, 255, '')

    msg.gsub!(/\000/, '')
    msg.strip!
    raise msg
  else
    raise 'GetLastError returned ERROR_SUCCESS'
  end
end

def create_pipe # returns read and write handle
  params = [
    'P', # pointer to read handle
    'P', # pointer to write handle
    'P', # pointer to security attributes
    'L'] # pipe size

  createPipe = Win32API.new("kernel32", "CreatePipe", params, 'I')

  read_handle, write_handle = [0].pack('I'), [0].pack('I')
  sec_attrs = [SECURITY_ATTRIBUTES_SIZE, 0, 1].pack('III')

  raise_last_win_32_error if createPipe.Call(read_handle,
    write_handle, sec_attrs, 0).zero?

  [read_handle.unpack('I')[0], write_handle.unpack('I')[0]]
end

def set_handle_information(handle, flags, value)
  params = [
    'L', # handle to an object
    'L', # specifies flags to change
    'L'] # specifies new values for flags

  setHandleInformation = Win32API.new("kernel32",
    "SetHandleInformation", params, 'I')
  raise_last_win_32_error if setHandleInformation.Call(handle,
    flags, value).zero?
  nil
end

def close_handle(handle)
  closeHandle = Win32API.new("kernel32", "CloseHandle", ['L'], 'I')
  raise_last_win_32_error if closeHandle.call(handle).zero?
end

def create_process(command, stdin, stdout, stderror)
  params = [
    'L', # IN LPCSTR lpApplicationName
    'P', # IN LPSTR lpCommandLine
    'L', # IN LPSECURITY_ATTRIBUTES lpProcessAttributes
    'L', # IN LPSECURITY_ATTRIBUTES lpThreadAttributes
    'L', # IN BOOL bInheritHandles
    'L', # IN DWORD dwCreationFlags
    'L', # IN LPVOID lpEnvironment
    'L', # IN LPCSTR lpCurrentDirectory
    'P', # IN LPSTARTUPINFOA lpStartupInfo
    'P'] # OUT LPPROCESS_INFORMATION lpProcessInformation

  startupInfo = [STARTUP_INFO_SIZE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW, 0,
    0, 0, stdin, stdout, stderror].pack('IIIIIIIIIIIISSIIII')

  processInfo = [0, 0, 0, 0].pack('IIII')
  command << 0

  createProcess = Win32API.new("kernel32", "CreateProcess", params, 'I')
  raise_last_win_32_error if createProcess.call(0,
    command, 0, 0, 1, 0, 0, 0, startupInfo, processInfo).zero?

  hProcess, hThread, dwProcessId, dwThreadId = processInfo.unpack('LLLL')

  close_handle(hProcess)
  close_handle(hThread)

  [dwProcessId, dwThreadId]
end

def write_file(hFile, buffer)
  params = [
    'L', # handle to file to write to
    'P', # pointer to data to write to file
    'L', # number of bytes to write
    'P', # pointer to number of bytes written
    'L'] # pointer to structure for overlapped I/O

  written = [0].pack('I')
  writeFile = Win32API.new("kernel32", "WriteFile", params, 'I')

  raise_last_win_32_error if writeFile.call(hFile, buffer, buffer.size,
    written, 0).zero?

  written.unpack('I')
end

def read_file(hFile)
  params = [
    'L', # handle of file to read
    'P', # pointer to buffer that receives data
    'L', # number of bytes to read
    'P', # pointer to number of bytes read
    'L'] #pointer to structure for data

  number = [0].pack('I')
  buffer = ' ' * 255

  readFile = Win32API.new("kernel32", "ReadFile", params, 'I')

  return '' if readFile.call(hFile, buffer, 255, number, 0).zero?

  buffer[0...number.unpack('I')[0]]
end

if $0 == __FILE__
  require 'fox12'

  include Fox

  application = FXApp.new("popen", "popen")

  main = FXMainWindow.new(application, "popen", nil, nil, DECOR_ALL,
    0, 0, 500, 500, 10, 10, 10, 10, 10, 10)

  button = FXButton.new(main, "&Do it!", nil, nil, 0,
    FRAME_THICK|FRAME_RAISED|LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT,
    0, 0, 0, 0, 10, 10, 5, 5)

  frame = FXHorizontalFrame.new(main,
    FRAME_THICK|FRAME_SUNKEN|LAYOUT_FILL_X|LAYOUT_FILL_Y,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0)

  edit = FXText.new(frame, nil, 0,
    LAYOUT_FILL_X|LAYOUT_FILL_Y|TEXT_READONLY, 0, 0, 0, 0)

  edit.textColor, edit.backColor = 0xFFFFFF, 0

  button.connect(SEL_COMMAND) do
    cmd = 'cmd.exe'
    input = "dir\nexit\n"

    # create 3 pipes
    child_in_r, child_in_w = create_pipe
    child_out_r, child_out_w = create_pipe
    child_error_r, child_error_w = create_pipe

    # Ensure the write handle to the pipe for STDIN is not inherited.
    set_handle_information(child_in_w, HANDLE_FLAG_INHERIT, 0)
    set_handle_information(child_out_r, HANDLE_FLAG_INHERIT, 0)
    set_handle_information(child_error_r, HANDLE_FLAG_INHERIT, 0)

    processId, threadId = create_process(cmd, child_in_r,
      child_out_w, child_error_w)

    # we have to close the handles, so the pipes terminate with the process
    close_handle(child_out_w)
    close_handle(child_error_w)

    write_file(child_in_w, input)

    while !(buffer = read_file(child_out_r)).empty?
      edit.appendText(buffer.gsub("\r", ''))
    end
  end

  application.create()
  main.show(PLACEMENT_SCREEN)
  application.run()
end
----------------------------------------------------------------------

On 9/18/05, Gregory Brown <gregory.t.brown@gmail.com> wrote:

I started up a ruby group at my university and this weeks 'challenge'
was to take one of your old messy programs from another language and
port it to Ruby, cleaning it up along the way. So I chose a terrible
little Tk based wrapper around mpg123 as my challenge. However, now
I'm thinking I should have picked something that showed how much easier
Ruby is, rather than wander into the gray areas.

The problem with this is, I need to run multiple processes. If I
wanted it to just work on *nix I could just use fork, as I did in the
perl script but a lot of the people in this group are tied down to
Windows (much to my chagrin :-/) so I figured I'd be nice and make my
ported script Windows friendly.

So my question is, how exactly would you go about writing a program
that could fire a process to a command line application, let it run,
give control back to your main program, and kill the process if an
event occurs (Like the pressing of a 'stop' button or even command line
interaction).

I thought about threading but since the threads are done in process I'm
afraid of explosions and complications galore.

So for those of you Windows savvy Rubyist's, what is a good way to do
multi processing in such a scenario?

For the brave, I've included a link to the source I'm porting, which is
in obvious need of cleanup.

Tk-mp3 download | SourceForge.net

Gregory Brown wrote:

So my question is, how exactly would you go about writing a program
that could fire a process to a command line application, let it run,
give control back to your main program, and kill the process if an
event occurs (Like the pressing of a 'stop' button or even command line
interaction).

I thought about threading but since the threads are done in process I'm
afraid of explosions and complications galore.

Use a ruby thread to manage the external process. There are some libs
for this (session is one, I think it will give you better access to
in/out/err streams). But ruby itself has some basic support:

cmd_th = Thread.new do
  system "something"
end

if user_presses_stop
  cmd_th.kill
end

···

--
      vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

session could be ported to windows using the win32-popen3 call. volunteers?

-a

···

On Tue, 20 Sep 2005, Joel VanderWerf wrote:

Gregory Brown wrote:

So my question is, how exactly would you go about writing a program
that could fire a process to a command line application, let it run,
give control back to your main program, and kill the process if an
event occurs (Like the pressing of a 'stop' button or even command line
interaction).

I thought about threading but since the threads are done in process I'm
afraid of explosions and complications galore.

Use a ruby thread to manage the external process. There are some libs
for this (session is one, I think it will give you better access to
in/out/err streams). But ruby itself has some basic support:

cmd_th = Thread.new do
system "something"
end

if user_presses_stop
cmd_th.kill
end

--

email :: ara [dot] t [dot] howard [at] noaa [dot] gov
phone :: 303.497.6469
Your life dwells amoung the causes of death
Like a lamp standing in a strong breeze. --Nagarjuna

===============================================================================

Joel VanderWerf wrote:

Use a ruby thread to manage the external process. There are some libs
for this (session is one, I think it will give you better access to
in/out/err streams). But ruby itself has some basic support:

cmd_th = Thread.new do
  system "something"
end

if user_presses_stop
  cmd_th.kill
end

This very well may be exactly what I had in mind. I haven't had a
chance to touch a Windows box but will be able to this afternoon so
I'll let you know how it turns out. Thanks!

Ara.T.Howard wrote:

···

On Tue, 20 Sep 2005, Joel VanderWerf wrote:

> Gregory Brown wrote:
>
>> So my question is, how exactly would you go about writing a program
>> that could fire a process to a command line application, let it run,
>> give control back to your main program, and kill the process if an
>> event occurs (Like the pressing of a 'stop' button or even command line
>> interaction).
>>
>> I thought about threading but since the threads are done in process I'm
>> afraid of explosions and complications galore.
>
> Use a ruby thread to manage the external process. There are some libs
> for this (session is one, I think it will give you better access to
> in/out/err streams). But ruby itself has some basic support:
>
> cmd_th = Thread.new do
> system "something"
> end
>
> if user_presses_stop
> cmd_th.kill
> end

session could be ported to windows using the win32-popen3 call. volunteers?

-a

I think you just did. I'll setup your account. :wink:

Dan