In Progress
Unit 1, Lesson 1
In Progress

Fiddle Importer

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=[4], @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 fiddle: the fiddle/import library.

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 fiddle/import library.

require "fiddle/import"

Next we create a new module to hold the C functions that we will be importing.

module LibNotify

end

We extend the module with Fiddle::Importer functionality.

extend Fiddle::Importer

Next we tell fiddle to load the libnotify shared library.

dlload "libnotify.so"

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 notify_init.

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 gboolean type.

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 gboolean type.

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 notify_init.

From the notification.h header file, we add notify_notification_new.

We also grab notify_notification_show.

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.

LibNotify.notify_init("RubyTapas")

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.

LibNotify.notify_notification_show(notification, 0)

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.

GLib.g_object_unref(notification)

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.

LibNotify.notify_uninit()

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!

Responses