Created
January 19, 2010 11:13
-
-
Save jarib/280865 to your computer and use it in GitHub Desktop.
FFI wrapper for CreateProcess()
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | |
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!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.