【问题标题】:Ruby FFI: multi-dimensional arrayRuby FFI:多维数组
【发布时间】:2014-03-25 17:45:45
【问题描述】:

我试图从 Ruby 调用的 C 函数是这样的:

void foo(double *in_array, double *out_array)

地点:

  • in_array 是一个数组数组,“foo”将使用它来 计算并返回:
  • out_array 也是一个数组数组,C 函数会改变它的内容。

我的包装看起来像这样:

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

我正在 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)

但我收到以下错误:

:pointer argument is not a valid pointer (ArgumentError)

我可以理解我需要使用指向这些数组的指针而不是数组对象,但我不知道该怎么做。这是否意味着我需要使用 LibC 包装器在 C 中创建这些结构?

【问题讨论】:

    标签: c ruby arrays ffi


    【解决方案1】:

    Per Momer 的回答,看起来您确实需要使用 LibC 包装器。将多维数组转换为正确的指针并不简单,所以我想我会把它放在这里以防它帮助其他人:

    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
    

    【讨论】:

      【解决方案2】:

      当您需要 FFI 的指针时,只需声明一个即可。

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

      这适用于单个变量。不过,我们都需要查阅 FFI 文档以获取数组。 http://rubydoc.info/github/ffi/ffi/FFI 一定有一些关于数组指针的东西。 我的问题类似I am familiar with Ruby /DL but not sure how to use the C function calls that have pointers for return parameters

      【讨论】:

      • 是的,当然。我的意思是更新我的答案,但你确实可以简单地做in_array_ptr = FFI::MemoryPointer.new(FFI.type_size(FFI::TYPE_FLOAT64) * in_array_flattened.size)。这就是我最终做到的方式。
      • 很好。很高兴你找到了答案。感谢大家的投票。
      【解决方案3】:

      FFI documentation about pointers. 中对您发生的事情进行了详尽的解释

      直接来自该文档:

      某些情况需要分配本机内存并将该缓冲区移交给外部库。然后外部库处理该缓冲区的生命周期,包括最终释放它。

      包装 libc 并使用它的 malloc 和 free 函数来分配和释放本机内存。

      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
      

      在 ruby​​ 代码中,对这些函数的调用将返回 FFI::Pointers。使用 FFI::Pointer 上定义的方法将数据从 ruby​​ 内存移动到本机内存。

      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
      

      【讨论】:

      • 谢谢妈妈。我已经看到了这一点(根据我提到 LibC 包装器的问题的结尾)。但是一旦你有了它,你如何将它应用到多维数组就不是直截了当的(我认为根本没有)。我将添加一个解释如何执行此操作的答案。
      猜你喜欢
      • 2011-10-24
      • 2012-12-05
      • 2018-02-25
      • 2011-11-29
      • 2015-09-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多