FFI support for callbacks

15 views
Skip to first unread message

Klaas Prause

unread,
Sep 2, 2008, 9:58:32 AM9/2/08
to rubinius-dev
Hi,

I was trying to figure out how to add support for JRuby/Rubinius for
the sqlite3-ruby gem using the FFI API as explained here
http://pluskid.lifegoo.com/?p=370. My problem is that sqlite3 provides
callbacks that must be implemented for a few tasks. The sqlite3-ruby
implementation provides this callbacks via the standard extension API.
Is it planed to add support for callbacks, so that C APIs that need
the implementation of callbacks can be used via FFI?

Regards Klaas

Charles Oliver Nutter

unread,
Sep 2, 2008, 10:40:21 AM9/2/08
to rubini...@googlegroups.com

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

Wayne Meissner

unread,
Sep 2, 2008, 7:45:19 PM9/2/08
to rubini...@googlegroups.com
2008/9/2 Klaas Prause <klaas....@googlemail.com>:

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)

Klaas Prause

unread,
Sep 3, 2008, 1:27:38 AM9/3/08
to rubinius-dev
wow, thank you for the example, you are really fast! I'll going to
check this out and see if I can get this to work.

Charles Oliver Nutter

unread,
Sep 3, 2008, 3:18:58 AM9/3/08
to rubini...@googlegroups.com
Klaas Prause wrote:
> wow, thank you for the example, you are really fast! I'll going to
> check this out and see if I can get this to work.

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

Eric Hodel

unread,
Sep 6, 2008, 12:34:48 AM9/6/08
to rubini...@googlegroups.com
On Sep 2, 2008, at 16:45 PM, Wayne Meissner wrote:
> 2008/9/2 Klaas Prause <klaas....@googlemail.com>:

>> I was trying to figure out how to add support for JRuby/Rubinius for
>> the sqlite3-ruby gem using the FFI API as explained here
>> http://pluskid.lifegoo.com/?p=370. My problem is that sqlite3
>> provides
>> callbacks that must be implemented for a few tasks. The sqlite3-ruby
>> implementation provides this callbacks via the standard extension
>> API.
>> Is it planed to add support for callbacks, so that C APIs that need
>> the implementation of callbacks can be used via FFI?
>
> 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()

Why doesn't it take a block instead?

rc = SqLite3.exec(db, dbcmd, callback, 0, err_ref) { |unused, argc,

Charles Oliver Nutter

unread,
Sep 6, 2008, 2:50:26 AM9/6/08
to rubini...@googlegroups.com

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

Eric Hodel

unread,
Sep 16, 2008, 2:08:30 PM9/16/08
to rubini...@googlegroups.com
On Sep 5, 2008, at 23:50 PM, Charles Oliver Nutter wrote:

> Eric Hodel wrote:
>> Why doesn't it take a block instead?
>>
>> rc = SqLite3.exec(db, dbcmd, callback, 0, err_ref) { |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
>> }
>
> 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?

ArgumentError when using a block with multiple callbacks sounds fine
with me.

Reply all
Reply to author
Forward
0 new messages