A ruby gem for getting direct access to cfunc method function pointers.
In one sentence: it retrieves function pointers that got passed in to rb_define_method.
E.g. consider this call:
VALUE my_method(VALUE self) { /* ... */ }
void define_function(VALUE klass) {
rb_define_method(klass, "my_method", my_method, 0);
}direct-bind allows you to retrieve the my_method pointer again:
void call_function_directly(VALUE klass, VALUE instance) {
direct_bind_cfunc_result result = direct_bind_get_cfunc_with_arity(klass, rb_intern("my_method"), 0, true);
// At this point result.func == my_method, and you can call it without dispatching through the VM
result.func(instance);
}Why? Its main use-case is on very low-level Ruby tools.
Often Ruby VM APIs are not exposed as public functions but we would like to call them without dispatching through the VM. direct-bind enables such use-cases.
One example is finding out if a given thread is alive:
VALUE (*is_thread_alive)(VALUE thread);
is_thread_alive = direct_bind_get_cfunc_with_arity(rb_cThread, rb_intern("alive?"), 0, true).func;
is_thread_alive(rb_thread_current());Looking for a realistic example? direct-bind was integrated into gvl-tracing in this commit which provides a full example and use-case on how to use it.
direct-bind is intended to be vendored by other gems, not used directly. Here’s what you need to do to integrate direct-bind into your project:
-
Add
direct-bindas a development dependency -
Use the built-in rake task to vendor the
direct-bindsources into your own extension by adding to yourRakefile:require "direct_bind/rake" DirectBind::Rake::InstallTask.new("your_extension_name") # This should be the same as you set up in `Rake::ExtensionTask.new` # Extecute the install task by e.g. adding it before your compile step task default: [:"direct-bind:install", :compile]
-
Use
direct-bindin your native extension:#include "direct-bind.h" // Example: Directly bind access to Thread#alive? static VALUE (*is_thread_alive)(VALUE thread); void Init_your_extension(void) { VALUE your_extension_module = rb_define_module("YourExtension"); direct_bind_initialize(your_extension_module, true); is_thread_alive = direct_bind_get_cfunc_with_arity(rb_cThread, rb_intern("alive?"), 0, true).func; // Function is ready to be called! }
-
Use the RSpec helper in your tests to make sure the vendoring is working correctly:
require "direct_bind/rspec_helper" # Add this test-case it "uses the correct direct-bind gem version" do DirectBind::RSpecHelper.expect_direct_bind_version_to_be_up_to_date_in(YourExtension) # Same module/class as used in `direct_bind_initialize` end
-
git addthe newdirect-bind.handdirect-bind.cfiles that showed up in your extension folder
Install the gem and add to the application’s Gemfile or gems.rb file by executing:
$ bundle add direct-bindIf bundler is not being used to manage dependencies, install the gem by executing:
$ gem install direct-binddirect-bind has been tested on Ruby 2.5+. It can probably run on older Rubies too, but I have no use-case for it. PRs to extend support for older Rubies (and what’s your use-case) are welcome :)
To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org. To run specs, run bundle exec rake spec.
To run all actions (build the extension, check linting, and run specs), run bundle exec rake.
Bug reports and pull requests are welcome on GitHub at https://github.com/ivoanjo/direct-bind. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
Everyone interacting in the direct-bind project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.