Search code examples
rubystructruby-ffi

Assigning to nested struct members in Ruby FFI


Consider the following two FFI structs:

class A < FFI::Struct
layout :data, :int
end 

class B < FFI::Struct
layout :nested, A
end

To instantiate them:

a = A.new
b = B.new

Now when I try to assign a to b.nested like this:

b[:nested] = a

I get the following error:

ArgumentError: put not supported for FFI::StructByValue

It seems FFI doesn't allow you to assign using the [] syntax if the nested struct is "nested by value", that is it is not a pointer. If so, how do I then assign a to b.nested?


Solution

  • When you use FFI to nest it can work like this:

    b = B.new
    b[:nested][:data] = 42
    b[:nested][:data] #=> 42
    

    The FFI "b" object has created its own "a" object; you don't need to create your own.

    What it looks like you're trying to do is create your own "a" object then store it:

    a = A.new
    b = B.new
    b[:nested] = a  #=> fails because "a" is a Ruby object, not a nested value
    

    A solution is to store "a" as a pointer:

    require 'ffi'
    
    class A < FFI::Struct
      layout :data, :int
    end
    
    class B < FFI::Struct
      layout :nested, :pointer  # we use a pointer, not a class
    end
    
    a = A.new
    b = B.new
    
    # Set some arbitrary data
    a[:data] = 42
    
    # Set :nested to the pointer to the "a" object
    b[:nested] = a.pointer
    
    # To prove it works, create a new object with the pointer
    c = A.new(b[:nested])
    
    # And prove we can get the arbitrary data    
    puts c[:data]  #=> 42