public
Last active

FFI wrapper for CreateProcess()

  • Download Gist
file.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
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")

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

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)

@dsjbirch: It's there in the gem. I'm not maintaining the code in this gist ;)

@jarib, that is very cool!

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.