Create a gist now

Instantly share code, notes, and snippets.

@jarib /file.rb
Created Jan 19, 2010

FFI wrapper for CreateProcess()
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")
@rdp

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
@jarib
Owner

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.

@rdp

a #pid method in the childprocess gem might be kind :)

@jarib
Owner

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.

@rdp

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)

@bluekeys

Is the StartupInfo struct missing dwFlags?

http://msdn.microsoft.com/en-us/library/ms686331.aspx

@jarib
Owner

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

@bluekeys

@jarib, that is very cool!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment