-
-
Save jarib/280865 to your computer and use it in GitHub Desktop.
| require "rubygems" | |
| require "ffi" | |
| module WinProcess | |
| extend FFI::Library | |
| ffi_lib "kernel32" | |
| ffi_convention :stdcall | |
| class Error < StandardError | |
| end | |
| # typedef struct _STARTUPINFO { | |
| # DWORD cb; | |
| # LPTSTR lpReserved; | |
| # LPTSTR lpDesktop; | |
| # LPTSTR lpTitle; | |
| # DWORD dwX; | |
| # DWORD dwY; | |
| # DWORD dwXSize; | |
| # DWORD dwYSize; | |
| # DWORD dwXCountChars; | |
| # DWORD dwYCountChars; | |
| # DWORD dwFillAttribute; | |
| # DWORD dwFlags; | |
| # WORD wShowWindow; | |
| # WORD cbReserved2; | |
| # LPBYTE lpReserved2; | |
| # HANDLE hStdInput; | |
| # HANDLE hStdOutput; | |
| # HANDLE hStdError; | |
| # } STARTUPINFO, *LPSTARTUPINFO; | |
| class StartupInfo < FFI::Struct | |
| layout :cb, :ulong, | |
| :lpReserved, :pointer, | |
| :lpDesktop, :pointer, | |
| :lpTitle, :pointer, | |
| :dwX, :ulong, | |
| :dwY, :ulong, | |
| :dwXSize, :ulong, | |
| :dwYSize, :ulong, | |
| :dwXCountChars, :ulong, | |
| :dwYCountChars, :ulong, | |
| :dwFillAttribute, :ulong, | |
| :wShowWindow, :ushort, | |
| :cbReserved2, :ushort, | |
| :lpReserved2, :pointer, | |
| :hStdInput, :pointer, # void ptr | |
| :hStdOutput, :pointer, # void ptr | |
| :hStdError, :pointer # void ptr | |
| end | |
| # typedef struct _PROCESS_INFORMATION { | |
| # HANDLE hProcess; | |
| # HANDLE hThread; | |
| # DWORD dwProcessId; | |
| # DWORD dwThreadId; | |
| # } PROCESS_INFORMATION, *LPPROCESS_INFORMATION; | |
| class ProcessInfo < FFI::Struct | |
| layout :hProcess, :pointer, # void ptr | |
| :hThread, :pointer, # void ptr | |
| :dwProcessId, :ulong, | |
| :dwThreadId, :ulong | |
| end | |
| # BOOL WINAPI CreateProcess( | |
| # __in_opt LPCTSTR lpApplicationName, | |
| # __inout_opt LPTSTR lpCommandLine, | |
| # __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes, | |
| # __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, | |
| # __in BOOL bInheritHandles, | |
| # __in DWORD dwCreationFlags, | |
| # __in_opt LPVOID lpEnvironment, | |
| # __in_opt LPCTSTR lpCurrentDirectory, | |
| # __in LPSTARTUPINFO lpStartupInfo, | |
| # __out LPPROCESS_INFORMATION lpProcessInformation | |
| # ); | |
| attach_function :create_process, :CreateProcessA, | |
| [:pointer, :pointer, :pointer, :pointer, :bool, | |
| :ulong, :pointer, :pointer, :pointer, :pointer], :bool | |
| attach_function :get_last_error, :GetLastError, [], :ulong | |
| # DWORD WINAPI FormatMessage( | |
| # __in DWORD dwFlags, | |
| # __in_opt LPCVOID lpSource, | |
| # __in DWORD dwMessageId, | |
| # __in DWORD dwLanguageId, | |
| # __out LPTSTR lpBuffer, | |
| # __in DWORD nSize, | |
| # __in_opt va_list *Arguments | |
| # ); | |
| attach_function :format_message, :FormatMessageA, [:ulong, :pointer, :ulong, :ulong, | |
| :pointer, :ulong, :pointer], :ulong | |
| attach_function :close_handle, :CloseHandle, [:pointer], :bool | |
| FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000 | |
| FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000 | |
| module_function | |
| def create(cmd, opts = {}) | |
| cmd_ptr = FFI::MemoryPointer.from_string cmd | |
| si = StartupInfo.new | |
| pi = ProcessInfo.new | |
| if create_process(nil, cmd_ptr, nil, nil, !!opts[:inherit], 0, nil, nil, si, pi) | |
| close_handle pi[:hProcess] | |
| close_handle pi[:hThread] | |
| pi[:dwProcessId] # returns the wrong pid?! | |
| else | |
| raise Error, last_error_message | |
| end | |
| end | |
| def last_error_message | |
| errnum = get_last_error() | |
| buf = FFI::MemoryPointer.new :char, 512 | |
| flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY | |
| size = format_message(flags, nil, errnum, 0, buf, buf.size, nil) | |
| buf.read_string(size).strip | |
| end | |
| end | |
| p :pid => WinProcess.create("C:\\Windows\\System32\\regedt32.exe") | |
Thanks. This code has been folded into my ChildProcess gem:
http://github.com/jarib/childprocess
It uses java.lang.ProcessBuilder on JRuby though, so may not do exactly what you want.
a #pid method in the childprocess gem might be kind :)
What would you use it for? It's basically impossible on JRuby the way it's currently implemented, since we use ProcessBuilder et al there.
as a follow-up, 1.6.0RC2 now returns the correct pid's for (non shell-looking commands) in windows, though this might still be useful as something of a Process.spawn for jruby windows 1.8 (and 1.9 until it actually works there--doesn't yet I don't think)
Is the StartupInfo struct missing dwFlags?
@dsjbirch: It's there in the gem. I'm not maintaining the code in this gist ;)
@jarib, that is very cool!
happiness. A process creator for jruby that actually returns the right PID. you rock.
NB that for me I couldn't run regedt32.exe, but running "ruby -e sleep" works well.
>>* p :pid => WinProcess.create("C:\\Windows\\System32\\regedt32.exe") WinProcess::Error: The operation completed successfully. from (irb):116:in `create' from (irb):134 >>* p :pid => WinProcess.create("C:\\Windows\\System32\\regedt32.exe") WinProcess::Error: The requested operation requires elevation. from (irb):116:in `create' from (irb):136