Video transcript & code
In episode #387, we learned about Ruby's standard "Fiddle" library. We used it to invoke a C system call straight from Ruby code, without having to compile a C extension first, and without having to install any gems.
Just as a quick refresher, here's the code we wrote in that episode.
require "fiddle" libc = Fiddle.dlopen(nil) # => #<Fiddle::Handle:0x005589f24c6310> strerror = Fiddle::Function.new( libc['strerror'], [Fiddle::TYPE_INT], Fiddle::TYPE_VOIDP) # => #<Fiddle::Function:0x005589f24c5d70 @ptr=139627743982768, @args=, @re... strerror.(2).to_s # => "Interrupted system call"
Remember, this is just the code to wrap up a single C function and make it available from within Ruby. As useful as this library is, it seems like if we want to use it to make bindings to a more complex C library, it's going to involve quite a lot of translation effort.
Fortunately, this isn't the only way to use the
fiddle library. In fact, so far we've just used the low-level
fiddle API. Today we're going to meet the other half of
As our example C library to wrap, we're going to use the linux
libnotify library. This is the library which handles popping up little transient notifications. Just to show an example, lets pop up a notification from the command-line:
$ notify-send "Hello!"
You can see this pops up a notification window in the upper-right corner of the screen. It eventually fades out.
We could use this command-line utility from Ruby, of course. But we want the lower overhead and greater control that calling directly into the library affords.
We start out by requiring the
Next we create a new module to hold the C functions that we will be importing.
module LibNotify end
We extend the module with
Next we tell
fiddle to load the
libnotify shared library.
Now we can start wrapping C functions. Let's take a look at the declarations for some of the
libnotify functions we'll need to call, in the
notify.h header file.
The very first function we have to call before we use any others is called
gboolean notify_init (const char *app_name);
Now, here's where we get to see the magic of
fiddle/import. Instead of translating this declaration to a Ruby form, we're just going to copy the whole line. Then we're going to go over to our Ruby module, and type
extern, followed by a string argument. Inside the string, we paste the
notify_init declaration as-is.
The only change we make is to remove the semicolon at the end.
extern "gboolean notify_init (const char *app_name)"
Let's try and execute what we have so far.
require "fiddle/import" module LibNotify extend Fiddle::Importer dlload "libnotify.so" extern "gboolean notify_init (const char *app_name)" end # ~> Fiddle::DLError # ~> unknown type: gboolean # ~> # ~> /home/avdi/.rubies/ruby-2.3.0/lib/ruby/2.3.0/fiddle/cparser.rb:177:in `p... # ~> /home/avdi/.rubies/ruby-2.3.0/lib/ruby/2.3.0/fiddle/cparser.rb:90:in `pa... # ~> /home/avdi/.rubies/ruby-2.3.0/lib/ruby/2.3.0/fiddle/import.rb:163:in `ex... # ~> xmptmp-in3105foD.rb:7:in `<module:LibNotify>' # ~> xmptmp-in3105foD.rb:3:in `<main>'
Uh-oh. Looks like we have an error. Fiddle says that it doesn't recognize the
What is this type? Well, as it turns out, like many libraries on modern linux desktop systems,
libnotify is built on the GNOME project's
Glib framework. GLib which functions as a kind of standard library, providing many generally useful type definitions, functions, and data-types.
As it happens,
gboolean is just a Glib alias for an integer type. We could change the
extern declaration to say
int rather than
gboolean. But instead, let's keep it as-is, and instead introduce a type alias informing Fiddle of how it should treat the
typealias "gboolean", "int"
This time when we execute, we see not output, meaning that everything is A-OK so far.
Let's quickly add several more imported functions. We add
notify_uninit, which is the counterpart to
notification.h header file, we add
We also grab
extern "void notify_uninit(void)" extern "NotifyNotification *notify_notification_new(const char *summary, const char *body, const char *icon)" extern "gboolean notify_notification_show(NotifyNotification *notification, GError **error)"
You might notice that there are now some more undefined data types in our list of declarations. For instance, there's a
NotifyNotification data type referenced a couple of times.
You might think that these would also trigger errors from Fiddle. But these type references are different in one vital respect. In these declarations, these new types are always used as pointers to that type, indicated in C by the asterisk symbol.
No matter what data type it points to, a pointer in C is always just a numeric memory address. Because Fiddle discards pointer type information and treats all pointers generically, it knows that it doesn't have to worry about what pointer arguments or pointer return values are pointing to. As far as it is concerned a pointer is a pointer is a pointer, so it doesn't bother nagging us to define those types.
To complete our imports, there is one more function we need. But it isn't found within the
libnotify library. Rather, it's a standard
Glib function. To import this function, we define a new module, extend it with Fiddle::Importer functionality, load the
libgobject library, and import the
g_object_unref function. We also have to define another type alias to make this import work, letting Fiddle know that a
gpointer type is simply another name for a generic void pointer.
module GLib extend Fiddle::Importer dlload "libgobject-2.0.so" typealias "gpointer", "void*" extern "void g_object_unref(gpointer object)" end
We'll talk about why we need this function in a minute.
At this point, we've imported all the functions we need. So what has all this accomplished?
Well, now we get to use these C functions directly from Ruby code.
We start with an invocation to
LibNotify.notify_init, to initialize the C library. It expects the name of the application that is using it, so we give it one.
Next we create a new "notification" object, by calling the
notify_notification_new function. We pass it a title, some body text, and a special name indicating the standardized icon that the notification should show.
This function returns a pointer to a C structure, which we capture as a pointer object in a local variable.
notification = LibNotify.notify_notification_new( "Greeting", "Hello from Ruby", "dialog-information")
Then we tell
libnotify to display the notification with the
notify_notification_show function, passing it the
notification pointer. The second argument is an optional pointer to a place to put error information. We opt-out of using it by passing zero.
Now, unlike with Ruby objects, Ruby can't automatically garbage-collect memory that C libraries allocate, because it can't know when the C code is finished with them. Fortunately, all Glib objects, of which our notification structure is one, have a simple reference-counting memory management scheme. We just have to let the Glib object system know that now that we've displayed our message, we will not longer be using that notification object.
That's where the
g_object_unref function comes in. We pass the notification pointer to it.
Past this point, we must no longer do anything with that pointer, because we have no idea when
libnotify will finish up with it and release its associated memory.
Finally, we should di-initialize the
libnotify library, just as we first initialized it.
In order to test this out, we need to leave full-screen mode so the desktop will permit notifications. When we execute the code, we see an desktop notification pop up, and then eventually fade.
That almost seems almost anti-climactic after all the setup we did. But let's take another look at that setup.
Instead of translating the C declarations into Ruby code, we simply pasted them in as strings, unchanged. And it worked. We got Ruby-callable methods that exactly mimic the C functions. What sorcery is this, anyway?
This seeming magic is what the
fiddle/import library provides. It is built around a simple C-language parser. It's not a complete parser, so we can't just feed it the full text of a header file. But it's enough to recognize C function declarations and translate them automatically into Fiddle function wrapper definitions.
This is a huge time-saver if we are wrapping up a C library for use from Ruby. And it means we can get access to C libraries even if we have an incomplete understanding of C code.
Fiddle makes lets us access C libraries straight from Ruby, with very little effort, and no C extension compilation. As such, it's a great tool to have in our toolbox. Happy hacking!