Novice
Send a keystroke
(use the keybd_event method).require 'ffi'
module Win
VK_VOLUME_DOWN = 0xAE; VK_VOLUME_UP = 0xAF; VK_VOLUME_MUTE = 0xAD; KEYEVENTF_KEYUP = 2
extend FFI::Library
ffi_lib 'user32'
ffi_convention :stdcall
attach_function :keybd_event, [ :uchar, :uchar, :int, :pointer ], :void
# simulate pressing the mute key on the keyboard
keybd_event(VK_VOLUME_MUTE, 0, 0, nil);
keybd_event(VK_VOLUME_MUTE, 0, KEYEVENTF_KEYUP, nil);
end
System Local Time
This example shows the common task of calling a native function with a pointer to a new struct, then using that same struct once it has been populated and returned by the native function.
require 'ffi'
module Win
extend FFI::Library
class SystemTime < FFI::Struct
layout :year, :ushort,
:month, :ushort,
:day_of_week, :ushort,
:day, :ushort,
:hour, :ushort,
:minute, :ushort,
:second, :ushort,
:millis, :ushort
end
ffi_lib 'kernel32'
ffi_convention :stdcall
attach_function :GetLocalTime, [ :pointer ], :void
end
mytime = Win::SystemTime.new
Win.GetLocalTime(mytime)
args = [
mytime[:month], mytime[:day], mytime[:year],
mytime[:hour], mytime[:minute], mytime[:second]
]
puts "Date: %u/%u/%u\nTime: %02u:%02u:%02u" % args
Intermediate
Convert a path to 8.3 style pathname
<notextile>
module Win
extend FFI::Library
ffi_lib 'kernel32'
ffi_convention :stdcall
=begin
DWORD WINAPI GetShortPathName(
__in LPCTSTR lpszLongPath,
__out LPTSTR lpszShortPath,
__in DWORD cchBuffer
);
=end
attach_function :path_to_8_3, :GetShortPathNameA, [:pointer, :pointer, :uint], :uint
end
out = FFI::MemoryPointer.new 256 # bytes
Win.path_to_8_3("c:\\program files", out, out.size)
p out.get_string # be careful, the path/file you convert to 8.3 must exist or this will be empty
Enumerate Top Level Windows
This example shows how to use a native function that takes a callback specifying simple input parameters. The callback itself calls a native function which returns a string via its pointer to a character buffer parameter.
require 'ffi'
module Win
extend FFI::Library
ffi_lib 'user32'
ffi_convention :stdcall
# BOOL CALLBACK EnumWindowProc(HWND hwnd, LPARAM lParam)
callback :enum_callback, [ :pointer, :long ], :bool
# BOOL WINAPI EnumDesktopWindows(HDESK hDesktop, WNDENUMPROC lpfn, LPARAM lParam)
attach_function :enum_desktop_windows, :EnumDesktopWindows,
[ :pointer, :enum_callback, :long ], :bool
# int GetWindowTextA(HWND hWnd, LPTSTR lpString, int nMaxCount)
attach_function :get_window_text, :GetWindowTextA,
[ :pointer, :pointer, :int ], :int
end
win_count = 0
title = FFI::MemoryPointer.new :char, 512
Win::EnumWindowCallback = Proc.new do |wnd, param|
title.clear
Win.get_window_text(wnd, title, title.size)
puts "[%03i] Found '%s'" % [ win_count += 1, title.get_string(0) ]
true
end
if not Win.enum_desktop_windows(nil, Win::EnumWindowCallback, 0)
puts 'Unable to enumerate current desktop\'s top-level windows'
end
Note that the param you can pass “through” to the enum call is basically a pointer. An example of using this pointer for multiple objects can be found in the win32screenshot gem.
Unicode Popup Dialog (MRI 1.9)
This example shows how to use Unicode text from Ruby with the Unicode version of the MessageBox
Windows API function.
require "ffi"
module Win
extend FFI::Library
ffi_lib "user32"
ffi_convention :stdcall
# use MessageBoxA if you want to pass it strings with ASCII encoding
attach_function :, :MessageBoxW,
[ :pointer, :buffer_in, :buffer_in, :int ], :int
end
# MSFT uses UTF-16 little endian and expects double NULL
# at the end of Unicode strings.
msg = "Test with umlaut: \u00e4\0".encode("UTF-16LE")
Win. (nil, msg, msg, 0)
There are three crucial points shown in the Ruby FFI code:
- The Windows API Unicode function version (
MessageBoxW
) is explicitly specified to FFI’sattach_function
. - Rather than using
:string
parameter types (as would normally be used with theMessageBoxA
ASCII version):buffer_in
parameter types are used for Unicode strings. In Ruby-FFI:string
means “null terminated C string” where UTF-16 is closer to a binary blob of data that can contain NULL bytes. NOTE: currently Ruby-FFI checks for NULL chars in:string
parameters to help avoid NULL byte poisoning attacks from outside string sources. - The Ruby string needs to be encoded to UTF-16LE and have a Unicode string terminator (double NULL)
Move the Mouse
This example shows how to interface with a native function that takes a pointer to a struct which contains an embedded union, both of which are populated before being provided to the native function.
<notextile>
require 'ffi'
module Win
extend FFI::Library
ffi_lib 'user32'
ffi_convention :stdcall
MOUSEEVENTF_MOVE = 1
INPUT_MOUSE = 0
class MouseInput < FFI::Struct
layout :dx, :long,
:dy, :long,
:mouse_data, :ulong,
:flags, :ulong,
:time, :ulong,
:extra, :ulong
end
class InputEvent < FFI::Union
layout :mi, MouseInput
end
class Input < FFI::Struct
layout :type, :ulong,
:evt, InputEvent
end
# UINT SendInput(UINT nInputs, LPINPUT pInputs, int cbSize);
attach_function :SendInput, [ :uint, :pointer, :int ], :uint
end
= Win::Input.new
[:type] = Win::INPUT_MOUSE
in_evt = [:evt][:mi]
in_evt[:dx] = ARGV[0].to_i
in_evt[:dy] = ARGV[1].to_i
in_evt[:mouse_data] = 0
in_evt[:flags] = Win::MOUSEEVENTF_MOVE
in_evt[:time] = 0
in_evt[:extra] = 0
Win.SendInput(1, , Win::Input.size)
The above FFI code takes a shortcut in the name of saving wiki page space. The actual Windows INPUT
struct contains an anonymous union member with MOUSEINPUT
, KEYBDINPUT
, and HARDWAREINPUT
members. As the MOUSEINPUT
struct is the largest of these members, we can get away with the hacky InputEvent
FFI union definition for example purposes.
TODO explain FFI syntax for embedded struct members which is opposite of typical C usage.
Advanced
Bringing windows to the foreground is tricky, since no single call seems to always force it to the foreground. http://betterlogic.com/roger/?p=2950 describes how.
Note that for windows’ core working you use a @ ffi_convention :stdcall@ but with normal DLL’s it appears you do not see here
Gotcha’s
- For all functions that take string arguments, the Windows API provides “short name” macros that expand to function names with a suffix indicating ASCII or Unicode. ASCII versions are suffixed with a “A”, and Unicode versions are suffixed with a “W”. For example, the Windows API
FindWindow
function gets defined as eitherFindWindowA
(ASCII) orFindWindowW
(Unicode).
Types
- DWORD appears to be an :uint (32 bits that is, so :int should work well
- NB that MSDN says it is a long-but for MS, long is 32 bits, even in 64 bit architecture) - HWND appears to be a :pointer see this thread for why you should not actually read from the value it points to. You may as well just use a :long or :ulong.
- LPDWORD is a pointer (the P standing for pointer)
- LPARAM appears to be a long (hence the L)
- WPARAM appears to be a long (i.e. 64 bits on 64bit OS).
- BOOL is an :int
- HANDLE is a :pointer apparently
another good list (the VB list at the bottom is especially helpful)
Gems
A few gems use FFI with the windows API.
- [[http://github.com/jarmo/win32screenshot]] Actually has a lot of windows-examination/use/enumeration/minimize etc. methods fleshed out.
- [[http://github.com/arvicco/win]] A ruby-esque wrapper for many windows API’s (general use).
- [[http://github.com/rdp/sensible-cinema]] Uses ffi for a few things like moving mouse.
More examples
class ScreenTracker
extend FFI::Library
ffi_lib 'user32'
# second parameter, pointer, LPRECT is FFI::MemoryPointer.new(:long, 4)
# read it like rect.read_array_of_long(4)
attach_function :GetWindowRect, [:long, :pointer], :int # returns a BOOL
end
def get_coords_of_window_on_display
out = FFI::MemoryPointer.new(:long, 4)
ScreenTracker.GetWindowRect @hwnd, out
out.read_array_of_long(4)
end
end