Async system call on Windows?

I need a way to thread a system call to Windows without having the entire
ruby process (all Ruby threads) wait for the system call to return. I need
to call another command-line program that might hang … so I need to be
able to put that system call in a thread and have the main thread timeout in
case the system call did hang. Is there anyway to do this? I dinked a little
with Win32API and _spawnv, but couldn’t get that going as I’m a bit rusty
that low in the OS.

Chris
http://clabs.org

I need a way to thread a system call to Windows without having the entire
ruby process (all Ruby threads) wait for the system call to return.

Following up…

I did get the crtdll _spawn call working, though it sometimes segfaults
ruby. I also got kernel32 CreateProcess going, that will also do what I
need.

Chris
http://clabs.org

I am interested in learning from your Ruby code … if it is available for
public consumption, that is.
TIA,

– shanko

“Chris Morris” chrismo@clabs.org wrote in message
news:003101c27543$f1da0110$22973acc@ntossu2lch…

I need a way to thread a system call to Windows without having the
entire

···

ruby process (all Ruby threads) wait for the system call to return.

Following up…

I did get the crtdll _spawn call working, though it sometimes segfaults
ruby. I also got kernel32 CreateProcess going, that will also do what I
need.

Chris
http://clabs.org

I am interested in learning from your Ruby code … if it is available for
public consumption, that is.

Sure - here you go. Thx to Phil Tomson for posting a version of his
CreateProcess in the archives which helped unstick me (I’d been trying ‘P’
and empty string for nil pointers, which I’ve seen work, but didn’t work
with CreateProcess. He used ‘L’ and 0 for nil pointers, and it worked great)

def async_system(command)

···

wnv.asp

this is working – but frequently Segfaults ruby 1.6.6 mswin32

spawn = Win32API.new(“crtdll”, “_spawnvp”, [‘I’, ‘P’, ‘P’] ,‘L’)

need to try the third param as an ‘L’ and pass it 0, maybe segfault

will go away?

res = spawn.Call(P_NOWAIT, command, ‘’)
if res == -1
Win32API.new(“crtdll”, “perror”, [‘P’], ‘V’).call(‘async_system’)
end
res
end

from WinBase.h in SDK

dwCreationFlag values

NORMAL_PRIORITY_CLASS = 0x00000020

STARTUP_INFO_SIZE = 68
PROCESS_INFO_SIZE = 16

def create_process(command)

from WinBase.h in SDK

Passing nil for a pointer – I’ve seen it work with a P type param and

an empty string … here L with a 0 is the only way that works with

CreateProcess. (see FormatMessage in raise_last_win32_error for an

example of ‘P’ and ‘’ that works)

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
]
returnValue = ‘I’ # BOOL

startupInfo = [STARTUP_INFO_SIZE].pack(‘I’) + ([0].pack(‘I’) *
(STARTUP_INFO_SIZE - 4))
processInfo = [0].pack(‘I’) * PROCESS_INFO_SIZE
createProcess = Win32API.new(“kernel32”, “CreateProcess”, params,
returnValue)
if createProcess.call(0, command, 0, 0, 0, NORMAL_PRIORITY_CLASS, 0, 0,
startupInfo, processInfo) == 0
raise_last_win_32_error
end
processInfo
end

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

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

All this code is BSD license - full code is part of a misc windows utility
unit you can find here:

til/win.rb?rev=1.13&content-type=text/vnd.viewcvs-markup

Chris

Thanks a lot !

“Chris Morris” chrismo@clabs.org wrote in message
news:016701c2764c$68c61800$6401a8c0@3ComC…

I am interested in learning from your Ruby code … if it is available
for
public consumption, that is.

Sure - here you go. Thx to Phil Tomson for posting a version of his
CreateProcess in the archives which helped unstick me (I’d been trying ‘P’
and empty string for nil pointers, which I’ve seen work, but didn’t work
with CreateProcess. He used ‘L’ and 0 for nil pointers, and it worked
great)

def async_system(command)

wnv.asp

this is working – but frequently Segfaults ruby 1.6.6 mswin32

spawn = Win32API.new(“crtdll”, “_spawnvp”, [‘I’, ‘P’, ‘P’] ,‘L’)

need to try the third param as an ‘L’ and pass it 0, maybe segfault

will go away?

res = spawn.Call(P_NOWAIT, command, ‘’)
if res == -1
Win32API.new(“crtdll”, “perror”, [‘P’], ‘V’).call(‘async_system’)
end
res
end

from WinBase.h in SDK

dwCreationFlag values

NORMAL_PRIORITY_CLASS = 0x00000020

STARTUP_INFO_SIZE = 68
PROCESS_INFO_SIZE = 16

def create_process(command)

from WinBase.h in SDK

Passing nil for a pointer – I’ve seen it work with a P type param and

an empty string … here L with a 0 is the only way that works with

CreateProcess. (see FormatMessage in raise_last_win32_error for an

example of ‘P’ and ‘’ that works)

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
]
returnValue = ‘I’ # BOOL

startupInfo = [STARTUP_INFO_SIZE].pack(‘I’) + ([0].pack(‘I’) *
(STARTUP_INFO_SIZE - 4))
processInfo = [0].pack(‘I’) * PROCESS_INFO_SIZE
createProcess = Win32API.new(“kernel32”, “CreateProcess”, params,
returnValue)
if createProcess.call(0, command, 0, 0, 0, NORMAL_PRIORITY_CLASS, 0, 0,
startupInfo, processInfo) == 0
raise_last_win_32_error
end
processInfo
end

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

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

All this code is BSD license - full code is part of a misc windows utility
unit you can find here:

···

til/win.rb?rev=1.13&content-type=text/vnd.viewcvs-markup

Chris