On Ruby C Extensions
Eric Hodel | Tue, 18 Aug 2009 02:07:40 GMT
I’m not a great C coder, but I have implemented, cleaned up and read a few C extensions for Ruby and I’ve got some tips for writing C extensions
C is Bad
The C itself is your biggest enemy when writing C extensions. The more you write the harder it is for you to maintain and the harder it will be for anyone else to improve or fix your code in the future. The closer your C extension code is to the library’s API the better you can use Ruby to glue all the pieces together into a friendly interface.
Ruby is Good
With less C code and more Ruby code you end up a library that’s easier to refactor, adapt and extend. Generic data manipulation, simple math, convenience functions, etc. are all easier to write, test and debug when written in Ruby. There’s no need to recompile between changes, worry about compiler warnings, or fix type errors.
If you’re returning results from an operation, do as much of the work in Ruby as possible. If there are objects that can be created in Ruby create them in Ruby. If you get a struct sockaddr, unpack it on the Ruby side rather than calling the Socket methods from C using rb_funcall. If you’ve got an abstract representation of a flag bitfield, create the object on the Ruby side rather than calling rb_class_new_instance.
Check for what you need
mkmf.rb has loads of functions (dir_config, have_library, have_macro, have_func, have_type, have_struct_member, etc.) for determining whether or not you have everything you need to build your extension. When using these functions be sure to check the result and fail when what you need is missing.
Keep Up-to-date on the Ruby/C API
There’s tons of functions and macros for doing most of you need already built-in to Ruby. README.EXT has the overview, but there’s also ruby.h and intern.h which give you a list of functions you can use.
Use the friendly macros RSTRING_LEN, RSTRING_PTR and friends when playing with String, Array, etc. Use rb_define_alloc_func() with Data_Wrap_Struct(). Use RTEST and NIL_P for checking ruby results. Convert numbers with NUM2ULONG or NUM2INT, etc.
Play Nice with Threads
If you’re performing a blocking IO operation use rb_thread_wait_fd, rb_thread_fd_writable, etc. to keep other threads running. rb_thread_wait_for can be used for polling.
Don’t Repeat Yourself
While it is safe to repeatedly call rb_intern() if you have to invoke rb_funcall or rb_iv_set, it’s easier and prettier to use a static variable set from your Init function. Same for looking up classes using rb_path2class() or rb_define_class_under().
C is bad
It’s worth repeating. Writing C code is hard. Refactoring C code is hard. Debugging C code is hard. Do as little as possible in C and you’ll thank yourself down the road.
dnssd 1.2
Eric Hodel | Thu, 13 Aug 2009 05:45:00 GMT
DNS Service Discovery (aka Bonjour, MDNS) API for Ruby. Implements browsing, resolving, registration and domain enumeration.
Changes:
- 4 major enhancements
- DNSSD::Service is now directly instantiable
- DNSSD.announce which registers a server socket you’ve created
- DNSSD::Reply.connect which connects to a browsed service
- Fix asynchronous service shutdown crash
- 8 minor enhancements
- DNSSD.resolve now optionally accepts a DNSSD::Reply from DNSSD.browse
- Use rb_thread_wait_fd instead of custom rb_thread_select code
- DNSSD::Reply#protocol and DNSSD::Reply#service_name
- Added missing error classes
- Added missing InterfaceUnicast constant
- Improved Documentation
- Use C constants in ext/dnssd/errors.c
- Reduced C code in ext/dnssd/service.c for greater control. See DNSSD::Service
- 4 bug fixes
- Don’t invoke block on callback if none was provided
- Remove ext/dnssd/dns_sd.h so the correct header is used
- DNSSD::NoMemoryError is now raised instead of NoMemError
- DNSSD::ReferenceUsedError is now correctly named DNSSD::RefusedError
vi bindings for irb on OS X
Eric Hodel | Tue, 11 Aug 2009 20:56:22 GMT
OS X uses editline(3) instead of readline(3) so ~/.inputrc doesn’t do anything for irb or other tools using readline via the editline wrapper.
Instead, use ~/.editrc:
bind -v
bind \\t rl_complete
Which gives you vi bindings in irb.
Update
Now with tab completion thanks to Curt Sampson from an ancient netbsd-users email!
Converting from REXML to Nokogiri
Eric Hodel | Thu, 18 Jun 2009 01:02:02 GMT
Nokogiri is pretty darn cool, certainly far cooler than REXML. I switched UPnP to Nokogiri, and here’s a handy guide.
require 'rexml/document'
Becomes
require 'nokogiri'
REXML::Document.new
Becomes
Nokogiri::XML
Both accept String or IO objects
my_element.element['element/path']
Becomes
my_element.at 'element > path'
You can use CSS in #at
my_element.each_element 'element/path' do |sub_element|
Becomes
my_element.xpath('./xmlns:element/xmlns:path').each do |sub_element|
”.” is used to select sub-elements of this one
See also Nokogiri’s Node documentation.
UPnP 1.2.0
Eric Hodel | Wed, 17 Jun 2009 00:06:07 GMT
An implementation of the UPnP protocol
Changes
- 2 minor enhancements
- Workaround for missing socket constants on Windows. Reported by Yuri.
- upnp_discover now shows action argument and return value names.
- 4 bug fixes
- Method name must not include entire URI. Reported by Ian Macdonald.
- Step in allowedValueRange is optional. Reported by Ian Macdonald.
- upnp_listen works with all notification types. Reported by Ian Macdonald.
- upnp_discover now warns when a device failed to instantiate. Reported by Ian Macdonald.
A Project Naming Recommendation
Eric Hodel | Wed, 20 May 2009 23:08:02 GMT
I’ve gone through many different styles of project names, but I think I’ve finally found a naming scheme I like.
In the past, I’ve used class names with uppercase letters, dashed names and underscored names, and I think the underscores are the best for projects I’m packaging in ruby.
Rails solidified the convention of mapping CamelCase class names to underscored file names (class IMAPProcesor is defined in imap_processor.rb). Using underscored gem names makes it easy for people to figure out what file to require (same as the project name) or what class name to look for in ri.
If I have a plugin gem or an extension I’ll tack on the sub-project’s name with a dash. If I wanted to add a new handler for imap_to_rss for Chase bank email, the gem would be named imap_to_rss-chase.
This makes it easy to find in gem list -p and it conveniently namespaces the extension. It also would look nice with gems from github which break up author and project with a dash (drbrain-imap_to_rss-chase, if I used github). As an added bonus, when double-clicking any underscored part OS X only highlights a part of the name making cut-and-paste a little more convenient for picking apart gem names.
Currently almost 95% of gem names start with lowercase letters, 6% contain uppercase letters anywhere in the name, a little over 20% use dashes, 16% use underscore. Sixteen use both underscores and dashes (my favorite name being what_does_this_error_mean-merb).
Fat binary gems
Eric Hodel | Tue, 12 May 2009 23:10:58 GMT
Right now people who publish native gems targeting the windows platform have a problem. Our problem is supporting ruby 1.8 and 1.9 at the same time. Right now, we can’t build one gem targeting 1.8 and one gem targeting 1.9, and have rubygems differentiate the two. I have a solution: fat binary gems. We can build a gem that contains dynamic libraries that target ruby 1.8 and ruby 1.9 on windows, with no changes to rubygems whatsoever. I’ve put together a proof of concept that I want to share. I will walk through the steps for building a fat binary gem with the tools we have today. The steps I am going to present are not necessarily the best steps, they are just the steps I took to get this idea working.
— Fat binary gems make the rockin’ world go round via Tender Love Making
gmail_contacts 1.1
Eric Hodel | Fri, 01 May 2009 21:08:11 GMT
- <a href=”http://seattlerb.rubyforge.org/gmail_contacts”>Documentation
Simple Gmail contacts extraction using GData.
gmail_contacts development was sponsored by AT&T Interactive.
Changes
- 1 minor enhancement
- Allow for saved session tokens
- 1 bug fix
- Fix stubs for gdata 1.1
Binding#remove_local_variable
Eric Hodel | Fri, 24 Apr 2009 00:29:07 GMT
require 'rubygems'
require 'inline'
class Binding
inline do |builder|
builder.include '"node.h"'
builder.include '"env.h"'
##
# struct BLOCK isn't in any header, so include it here.
builder.prefix <<-C
struct BLOCK {
NODE *var;
NODE *body;
VALUE self;
struct FRAME frame;
struct SCOPE *scope;
VALUE klass;
NODE *cref;
int iter;
int vmode;
int flags;
int uniq;
struct RVarmap *dyna_vars;
VALUE orig_thread;
VALUE wrapper;
VALUE block_obj;
struct BLOCK *outer;
struct BLOCK *prev;
};
C
##
# :method: remove_local_variable
#
# Removes the local variable +name+ and replaces it with nil.
#
# Ordinarily if a local variable didn't exist it would raise
# NoMethodError, but currently after #remove_local_variable it doesn't, it
# just returns nil.
#
# In order to make this behave correctly, block->body would need to be
# walked, duping nodes (so as not to affect future invocations of this
# method) and replacing the LVAR nodes with VCALL nodes.
#
# That's just too much work for 17:15, though. Maybe tomorrow.
builder.c <<-C
VALUE remove_local_variable(VALUE name) {
struct BLOCK *block;
struct SCOPE *scope;
ID name_id;
ID *local_table;
int i, n;
VALUE entry;
name_id = SYM2ID(name);
Data_Get_Struct(self, struct BLOCK, block);
scope = block->scope;
local_table = scope->local_tbl;
if (local_table) {
n = *local_table++;
for (i = 2; i < n; i++) { /* skip $_ and $~ */
if (!rb_is_local_id(local_table[i])) continue; /* skip flip states */
if (local_table[i] == name_id) {
entry = scope->local_vars[i];
local_table[i] = (ID)NULL;
scope->local_vars[i] = Qnil;
return entry;
}
}
}
return Qnil;
}
C
end
end
a = :my_value
p :lvar_a => a
b = binding
p :remove_local_variable_says => b.remove_local_variable(:a)
p :lvar_a => a # TODO raise exception
InfoQ on RubyGems Plugins
Eric Hodel | Thu, 23 Apr 2009 00:05:39 GMT
RubyGems 1.3.2 introduced a new feature: plugins that can hook into the install process and provide new commands. An example is Ryan Davis’ graph that visualizes dependencies between installed Gems. We talked to RubyGems maintainer Eric Hodel to learn more. By Mirko Stocker
— RubyGems Gets Plugins via InfoQ

Articles