David Unric
David Unric

Reputation: 7719

How to interface more complex shared library?

I'm trying to dynamically call functions of external library on Linux/Unix system.

I have some success with dl library but only when primitive C types are used and arguments are passed by value:

require 'dl/import'
module LibM
  extend DL::Importer
  dlload 'libm.so'
  extern 'double sin(double)'
end

puts LibM.sin(3.14159265358979323846 / 2)    # 1.0

However how can be interfaced functions using more complex types like C structs or if arguments are pointers where results of the call are stored ?

module LibX11
  extend DL::Importer
  dlload 'libX11.so.6'
  extern 'Display *XkbOpenDisplay (char *display_name, int *event_rtrn, int *error_rtrn, int *major_in_out, int *minor_in_out, int *reason_rtrn)'
end

Display is a big struct, at event_rtrn is stored some result etc.

I've taken a look at DL::CStructBuilder and it looks can do the job, but as the documentation is very brief and no working examples found I'm lost here how it has to be properly used.

I have to add standard Ruby 1.9 modules have to be used (if possible) as installation of additional gems on the target machine is prohibited.

Upvotes: 3

Views: 844

Answers (1)

mdesantis
mdesantis

Reputation: 8517

I am facing with interfacing with C libraries in this period (I am writing a wrapper for libgtop), and I choosed to use ffi, which is quite well documented (although the documentation sometimes is a bit outdated) and above all its mailing list is full of people who give you an helping hand when you are in trouble.

So I propose you a solution which uses ffi:

require 'ffi'

module LibX11
  extend FFI::Library
  ffi_lib 'libX11.so.6'

  # Display *XkbOpenDisplay (char *display_name, int *event_rtrn, int *error_rtrn, int *major_in_out, int *minor_in_out, int *reason_rtrn)
  attach_function :XkbOpenDisplay, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :pointer

end

Then you have to describe the Display struct layout, like written here:

class Display < FFI::Struct
  layout  :value,       :double,
          :other_value, :char,
          ...
end

And then you do something like this:

p1 = FFI::MemoryPointer.new(:char)
p2 = FFI::MemoryPointer.new(:int)
p3 = FFI::MemoryPointer.new(:int)
p4 = FFI::MemoryPointer.new(:int)
p5 = FFI::MemoryPointer.new(:int)
p6 = FFI::MemoryPointer.new(:int)

# write to pointer if needed, otherwise it is a null pointer
# p1.write_char('a')
# p2.write_int(1)
# ...

struct_pointer = LibX11.XkbOpenDisplay(p1, p2, p3, p4, p5, p6)

# read the struct
struct = Display.new(display_struct_pointer)
p Hash[ s.members.map { |m| [ m, s[m] ] } ]

I didn't tested the code, but it should be roughly correct. Let me now if it isn't.


After some research about DL on ruby 2.0 I found that it is deprecated and replaced with fiddle, you would consider to use it rather than DL if you can't use FFI. Fiddle seems to be available for ruby 1.9.3 and 1.9.2 too

Upvotes: 2

Related Questions