We have already started to add callbacks to JRuby's FFI...Wayne can
probably talk more on that, but so far it works pretty nicely (this is a
prototype API...Wayne can update on where it stands right now):
callback :qsort_callback, :int, [:pointer, :pointer]
attach_function :qsort, :int, [:pointer, :int, :int, :qsort_callback]
qsort(some_data, num, elem_size) {|ptr_a, ptr_b| .... }
I believe this is working now on JRuby trunk. Certainly open for
discussion...we obviously want FFI to work across all implementations.
- Charlie
Charles pretty much described the API. I've done a quick and dirty
conversion of the example from http://www.sqlite.org/quickstart.html -
untested, but it should be close.
The main things to note are the callback line in the module - this
defines what arguments the callback will take and what its return type
will be - it is very important that these match the C callback
definition, or *crash*.
Then, you just create a Proc instance and pass that as the callback
parameter to sqlite3_exec()
require 'ffi'
dbname = ARGV[0]
dbcmd = ARGV[1]
module SqLite3
extend FFI::Library
ffi_lib 'sqlite3'
callback :exec_callback, [ :pointer, :int, :pointer, :pointer ], :int
attach_function :open, :sqlite3_open, [ :string, :pointer ], :int
attach_function :exec, :sqlite3_exec, [ :pointer, :string,
:exec_callback, :int, :pointer ], :int
attach_function :close, :sqlite3_close, [ :pointer ], :int
attach_function :free, :sqlite3_free, [ :pointer ], :void
SQLITE_OK = 0
end
dbp = MemoryPointer.new :pointer
puts "Opening db #{dbname}"
rc = SqLite3.open(dbname, dbp)
raise "Could not open #{dbname}" unless rc == 0
db = dbp.get_pointer(0)
callback = Proc.new { |unused, argc, argv, azColName|
0.upto(argc-1) do |i|
col = azColName.get_pointer(i * FFI.type_size(:pointer)).get_string(0)
datap = argv.get_pointer(i * FFI.type_size(:pointer))
data = datap.null? ? "NULL" : datap.get_string(0)
puts "#{col} = #{data}"
end
}
err_ref = MemoryPointer.new :pointer
rc = SqLite3.exec(db, dbcmd, callback, 0, err_ref)
if rc != SqLite3::SQLITE_OK
errp = err_ref.get_pointer(0)
$stderr.puts "SQL error: #{errp.get_string(0)}"
SqLite3.free(errp)
end
SqLite3.close(db)
Well in case you missed it, this is a JRuby trunk feature only, and not
yet in Rubinius. I've talked very briefly with Evan about adding
callbacks and I'm not sure when there will be time to add them to
Rubinius. So for the moment, you'd have to use JRuby trunk and beware
it's not an officially agreed-upon API across FFI implementations.
And for Rubinius folks interested in FFI: we'd love input on this. We
have continued to move forward on FFI and plan to rework a few native
dependencies like readline to use FFI instead of JNI libraries. And
readline, for example, requires callbacks.
- Charlie
Why doesn't it take a block instead?
rc = SqLite3.exec(db, dbcmd, callback, 0, err_ref) { |unused, argc,
I assume you mean this instead:
exec(db, dbcmd, 0, err_ref) { ....
Wayne and I talked about this tonight on #jruby, and I think that would
be cool to do. Since the function attach already says exactly where the
callback goes, we know where to apply the block. And if there's two
callback types, we just error out?
- Charlie
ArgumentError when using a block with multiple callbacks sounds fine
with me.