Skip to content
rdp edited this page Sep 13, 2010 · 57 revisions

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.

/* compile with: * gcc -DWINVER=0x0501 -Wall -o mytime.exe mytime.c -luser32 * cl /DWINVER=0x0501 /Femytime.exe mytime.c /link user32.lib */ #include <stdio.h> #include <windows.h> #define BUF_SIZE 2048 int main() { char *buf = calloc(1, BUF_SIZE * sizeof(char)); SYSTEMTIME *sys_time = calloc(1, sizeof(SYSTEMTIME)); if (buf==NULL || sys_time==NULL) return -1; GetLocalTime(sys_time); sprintf(buf, "Date: %u/%u/%u\nTime: %02u:%02u:%02u", sys_time->wMonth, sys_time->wDay, sys_time->wYear, sys_time->wHour, sys_time->wMinute, sys_time->wSecond); MessageBox(NULL, buf, "Local Time", MB_ICONINFORMATION | MB_OK); free(sys_time); free(buf); return 0; }
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



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.

/* compile with: * gcc -DWINVER=0x0501 -Wall -o enumwin.exe enumwin.c -luser32 * cl /DWINVER=0x0501 /Feenumwin.exe enumwin.c /link user32.lib */ #include <windows.h> #include <stdio.h> #define BUF_SIZE 512 static INT WinCount = 0; static CHAR *buf; static BOOL CALLBACK EnumCallback(HWND hWnd, LPARAM lParam) { memset(buf, 0, BUF_SIZE * sizeof(CHAR)); GetWindowText(hWnd, buf, BUF_SIZE * sizeof(CHAR)); printf("[%03i] Found '%s'\n", ++WinCount, buf); return TRUE; } int main() { buf = calloc(1, BUF_SIZE * sizeof(CHAR)); if (buf==NULL) return -1; if (!EnumDesktopWindows(NULL, EnumCallback, 0)) { printf("Unable to enumerate current desktop's top-level windows"); return -1; } free(buf); return 0; }
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.

Intermediate

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.

/* compile with: * gcc -DWINVER=0x0501 -Wall -o umsgbox.exe umsgbox.c -luser32 * cl /DWINVER=0x0501 umsgbox.c /link user32.lib */ #include <windows.h> int main() { LPCWSTR msg = L"Test with umlaut: \x00e4"; MessageBoxW(NULL, msg, msg, 0); return 0; }
require "ffi" module Win extend FFI::Library ffi_lib "user32" ffi_convention :stdcall attach_function :message_box, :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.message_box(nil, msg, msg, 0)


There are three crucial points shown in the Ruby FFI code:

  1. The Windows API Unicode function version (MessageBoxW) is explicitly specified to FFI’s attach_function.
  2. Rather than using :string parameter types (as would normally be used with the MessageBoxA 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.
  3. 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.

/* compile with: * gcc -DWINVER=0x0501 -Wall -o mvmouse.exe mvmouse.c -luser32 * cl /DWINVER=0x0501 /Femvmouse.exe mvmouse.c /link user32.lib * * usage: mvmouse <delta_x> <delta_y> */ #include <windows.h> #include <stdio.h> int main (int argc, const char **argv) { MOUSEINPUT *mi = calloc(1, sizeof(MOUSEINPUT)); INPUT *event = calloc(1, sizeof(INPUT)); if (mi==NULL || event==NULL) return -1; mi->dx = atoi(argv[1]); mi->dy = atoi(argv[2]); mi->mouseData = 0; mi->dwFlags = MOUSEEVENTF_MOVE; mi->time = 0; mi->dwExtraInfo = 0; event->type = INPUT_MOUSE; event->mi = *mi; SendInput(1, event, sizeof(INPUT)); free(mi); free(event); return 0; }
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 myinput = Win::Input.new myinput[:type] = Win::INPUT_MOUSE in_evt = myinput[: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, myinput, 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 either FindWindowA (ASCII) or FindWindowW (Unicode).

Types

  • DWORD appears to be an :int (32 bits that is, so :int should work well)
  • 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

another list (the VB list at the bottom is especially helpful)

Gems

A few gems use FFI with the windows API.

Clone this wiki locally