Ruby iterators and C callback functions
Mike Sax wonders what’s the fuss about iterators. Aren’t they just a fancy use of function pointers? Indeed, Mike has hit the nail on the head. Consider the window iterator that’s been built into Windows since 1.0:
This function iterates through all of the top-level windows (children of the desktop window) and calls lpEnumFunc
for each one, passing it the HWND
of each window and the lParam
that you passed to EnumWindows
.
So lParam
is how you get to provide some state that the enumeration function can make use of. Suppose you wanted to write a function that counted the number of visible top-level windows. Your C code might look like this:
This works, but it is rather tedious. So Windows 2.0 added the GetWindow
function, which lets you simply ask for a window’s child or next sibling. That simplifies the overall structure of the code, especially if you use the GetFirstChild
and GetNextSibling
macros defined in windowsx.h
:
That’s it, just one function, no callback function or struct definition needed. We don’t need the struct because the code inside the loop can directly reference the nVisible
variable defined in the function.
But the simplification came at a price: We had to write the loop ourselves, asking explicitly for the first child of the desktop window and then the next sibling of each child window.
Also, it doesn’t work.
What if another application creates or destroys a top-level window, or just changes a window’s Z-order, while you’re in the loop? You’ll either miss a window, count one twice, or crash with an invalid window handle.
To handle these cases, you need a bit more complexity. If you had a way to temporarily lock all window creation and destruction, you could quickly create a list of all the windows and then release the lock, then enumerate from that list, perhaps also doing a last-minute check when you enumerate each window to skip any that get destroyed during enumeration. Or, you might set a Windows hook to notify you of any windows created, destroyed, or moved in the Z-order, so you could deal with them appropriately.
Whatever you did, it would be enough code that you wouldn’t want to duplicate it each time you wanted to write a window loop. The GetFirstChild
/GetNextSibling
style of loop doesn’t really facilitate that kind of code isolation. The EnumWindows
style enumerator completely separates the code that does the iterating (EnumWindows
itself) from the code that receives the iteration (your callback function). But, it makes it harder to share state between the callback function and the code that called EnumWindows
.
If you had a way to use a callback function, but have it more easily share state with the calling function, you’d have a winner. In C# and JavaScript, you can do this by using an anonymous callback function nested inside the surrounding code. Because of lexical scoping, the callback function can access variables in the parent function as easily as it can access its own.
Both those language have enough extra syntactic cruft that when you look at a simple example using nested anonymous functions, it’s easy to be unimpressed. The payoff shows up in more complicated, real-life coding situations.
Code blocks in Ruby simplify this technique down to its essence, making it useful even for simple cases. Assuming a good Rubyesque Windows interface library, our function might be something like:
In this code, the enumWindows
function takes a code block argument and calls that code block for each window, passing it the window as an argument. Because the code block is nested inside the countVisibleWindows
function, it can access the nVisible
variable directly.
This solves both our problems: The logic for iterating through the windows is separated out into the enumWindows
function, and the callback function (code block) can access state variables cleanly and easily.
(In Ruby, a code block is a like a callback function, but it’s not quite a full-fledged function. A code block does not introduce a new scope for variablesit shares the scope of the enclosing function.)
Unfortunately, Ruby does not seem to have a Windows interface library that works like this. Ruby’s standard Win32
module provides a general way to call Windows DLL functions, but it doesn’t have a clean implementation of enumWindows
that uses a code block.
However, MoonWolf has written a Ruby port of Perl’s Win32::GuiTest module that includes this kind of enumWindows
function. It’s implemented in two parts: a low level function written in C that enumerates HWND
values, and a higher level function written in Ruby that constructs Ruby window
objects and enumerates them. The window
object in Win32::GuiTest
is a fairly thin wrapper that encapsulates an HWND
and other window information.
The high-level enumWindows
looks like this:
This code calls the low-level _enumWindows
function, which passes an HWND
to the code block enclosed in curly braces. This code block creates the window
object, appends to the ret
array, and also yields the window object to a code block that was provided by the caller of enumWindows
.
If I were implementing this, I think I would change it a bit. Typically a function like this either yields results to a code block, or it returns a value, but not both. And I would change the confusingly named createWindow
function (which has no relation to the CreateWindow
function in Windows):
Either way, our countVisibleWindows
example ends up pretty much as I’d imagined:
The low-level enumWindows
function that enumerates HWND
values is implemented in C. The initialization code to add the enumWindows
function is simply:
where mGuiTest
is a reference to the Win32::GuiTest
module.
The guitest_enumWindows
function is:
and the EnumWindowsProc
callback is:
This shows how easy it is to extend Ruby with C code, adding functions that work just like ones written in Ruby.
So, how do all the calls and callbacks stack up when we run the countVisibleWindows
function? Something like this:
enumWindows
_enumWindows
EnumWindows
EnumWindowsProc
rb_yield
(code block in enumWindows)
yield
(code block in
countVisibleWindows)
In everyday use, of course, you don’t worry about that whole call stack, just the part of it you’re working with.
Building win32 guitest in May of 2007 is non-trivial. I posted a recipie at https://www.kleinfelter.com/build-ruby-guitest-win32
Just can’t get away from that Ruby code, eh, Mike?
Hi Frank! :-) Yep, I’m stuck with Ruby forever, I guess.
Anyone who wonders what the heck Frank is talking about, here is the full story.