1

The C function I am trying to call from Ruby is like this:

void foo(double *in_array, double *out_array)

where:

  • in_array is an array of arrays that will be used by "foo" to calculate and return:
  • out_array which is also an array of arrays, and the C function will alter its content.

My wrapper looks like this:

module FooLib
  extend FFI::Library
  ffi_lib "foo.so"
  attach_function :Foo, [:pointer, :pointer], :void
end

And I am doing the following in Ruby:

# Allocate the objects and prepare them    
in_array = Matrix.build(10, 3) { rand }.to_a
out_array = Matrix.build(10, 3) { 0 }.to_a
FooLib.Foo(in_array, out_array)

But I get the following error:

:pointer argument is not a valid pointer (ArgumentError)

I can understand I need to use pointers to these arrays instead of the array objects, but I am not sure how to do this. Does it mean I need to create these structures in C using a LibC wrapper?

PJC
  • 997
  • 7
  • 21

3 Answers3

2

Per Momer's answer, it looks like you do need to use the LibC wrapper. Turning multi-dimensional arrays into the right pointers is not straightforward though, so I thought I'd put it here in case it helps others:

in_array = Matrix.build(10, 3) { rand }.to_a
in_array_flattened = in_array.transpose.flatten # Just flatten your multi-dim array
in_array_ptr = LibC.malloc(FFI.type_size(FFI::TYPE_FLOAT64) * in_array_flattened.size) # Watchout the type you want to use.
in_array_ptr.write_array_of_double(in_array.flatten)

# Same for out_array

FooLib.Foo(in_array_ptr, out_array_ptr)

# Convert back to Ruby
values = in_array_ptr.read_array_of_double(in_array_flattened.length)
values = values.enum_for(:each_slice, 10).to_a.transpose # Might be the C lib I am using but you do need to do this conversion in my case to get the multi-dim array you are expecting
PJC
  • 997
  • 7
  • 21
1

When you need a pointer for FFI just declare one.

# some_pointer = FFI::MemoryPointer.new(:type, size)
some_pointer = FFI::MemoryPointer.new(:double, 8)

That will work for single variables. We both need to consult the FFI docs for arrays though. http://rubydoc.info/github/ffi/ffi/FFI There must be something there about array pointers. My question that was similar I am familiar with Ruby /DL but not sure how to use the C function calls that have pointers for return parameters

Community
  • 1
  • 1
Douglas G. Allen
  • 2,203
  • 21
  • 20
  • Yes, absolutely. I meant to update my answer but you can indeed simply do `in_array_ptr = FFI::MemoryPointer.new(FFI.type_size(FFI::TYPE_FLOAT64) * in_array_flattened.size)`. That's how I ended up doing it. – PJC Apr 16 '14 at 09:27
  • Well good. Glad you found an answer. Thanks for the vote up all. – Douglas G. Allen Apr 29 '14 at 07:34
0

What's happening to you is explained thoroughly in the FFI documentation about pointers.

Directly from that documentation:

Some situations will require allocating native memory and handing off that buffer to an external library. The external library then handles the lifecycle of that buffer including eventually freeing it.

Wrap libc and use its malloc and free functions to allocate and free native memory.

module LibC
  extend FFI::Library
  ffi_lib FFI::Library::LIBC

  # memory allocators
  attach_function :malloc, [:size_t], :pointer
  attach_function :calloc, [:size_t], :pointer
  attach_function :valloc, [:size_t], :pointer
  attach_function :realloc, [:pointer, :size_t], :pointer
  attach_function :free, [:pointer], :void

  # memory movers
  attach_function :memcpy, [:pointer, :pointer, :size_t], :pointer
  attach_function :bcopy, [:pointer, :pointer, :size_t], :void

end # module LibC

In the ruby code, calls to these functions will return FFI::Pointers. Use the methods defined on FFI::Pointer to move data from ruby memory to native memory.

foo = "a ruby string"
bar = 3.14159
baz = [1, 2, 3, 4, 5]

buffer1 = LibC.malloc foo.size
buffer1.write_string foo

buffer2 = LibC.malloc bar.size
buffer2.write_float bar

# all of the array elements need to be the same type
# meaning you can't mix ints, floats, strings, etc.
buffer3 = LibC.malloc(baz.first.size * baz.size)
buffer3.write_array_of_int baz
Momer
  • 3,158
  • 22
  • 23
  • Thanks Momer. I had seen this (per the end of my question where I refer to the LibC wrapper). But once you have that, it is not straightforward (at all, I think) how you apply this to multi-dimensional arrays. I will add an answer explaining how to do this. – PJC Mar 26 '14 at 10:02