Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

question: how to parse a variable length array in a struct? #874

Closed
kojix2 opened this issue Jan 7, 2021 · 6 comments
Closed

question: how to parse a variable length array in a struct? #874

kojix2 opened this issue Jan 7, 2021 · 6 comments

Comments

@kojix2
Copy link
Contributor

kojix2 commented Jan 7, 2021

Hi Ruby-FFI developers!

Thank you for your awesome work.
I want to use ruby-ffi to create Ruby bindings for some bioinformatics tools.

I have a question. How to convert a structure with a variable length array into Ruby code?

typedef struct {
	uint32_t capacity;
	int32_t dp_score, dp_max, dp_max2;
	uint32_t n_ambi:30, trans_strand:2;
	uint32_t n_cigar;
	uint32_t cigar[]; # Here
} mm_extra_t;
@kojix2
Copy link
Contributor Author

kojix2 commented Jan 7, 2021

I have one more question.
I looked at the ruby-ffi wiki, and it says that bit fields are not supported. Is this still the case today?

@larskanis
Copy link
Member

larskanis commented Jan 7, 2021

Bitfields are not supported, but can be emulated by simple integer arithmetic. A variable length array can be used by struct.pointer. Use something like this:

class MmExtra < FFI::Struct
  layout capacity: :uint32,
    dp_score: :int32,
    dp_max: :int32,
    dp_max2: :int32,
    n_ambi_trans_strand: :uint32,
    n_cigar: :uint32
end

n_ambi = 123456
trans_strand = 0x2
cigar = [4,5,6]
s = MmExtra.new(FFI::MemoryPointer.new(MmExtra.size + FFI.type_size(:uint32) * cigar.size))
s[:n_ambi_trans_strand] = n_ambi | (trans_strand << 30)


s[:n_cigar] = cigar.size
s.pointer.put_array_of_uint32(s.size, cigar)

p n_ambi: s[:n_ambi_trans_strand] & ((1 << 30) - 1), trans_strand: (s[:n_ambi_trans_strand] >> 30) & ((1 << 2) - 1) # => {:n_ambi=>123456, :trans_strand=>2}
p s[:n_cigar]  # => 3
p s.pointer.get_array_of_uint32(s.size, 3) # => [4, 5, 6]
p s.pointer.read_bytes(s.pointer.size) # => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\xE2\x01\x80\x03\x00\x00\x00\x04\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00"

You might also add some custom methods to MmExtra to access the fields and do the bitfield arithmetic.

@kojix2
Copy link
Contributor Author

kojix2 commented Jan 7, 2021

Thank you!

@kojix2 kojix2 closed this as completed Jan 7, 2021
@kojix2
Copy link
Contributor Author

kojix2 commented Feb 5, 2021

I write a monkey patch that supports bit fields. (read only)

module FFI
  class BitStruct < Struct
    class << self
      module BitFieldsModule
        def [](name)
          bit_fields = self.class.bit_fields_map
          parent, start, width = bit_fields[name]
          if parent
            (super(parent) >> start) & ((1 << width) - 1)
          else
            super(name)
          end
        end
      end
      private_constant :BitFieldsModule

      attr_reader :bit_fields_map

      def bitfields(*args)
        unless instance_variable_defined?(:@bit_fields_map)
          @bit_fields_map = {}
          prepend BitFieldsModule
        end

        parent = args.shift
        labels = []
        widths = []
        args.each_slice(2) do |l, w|
          labels << l
          widths << w
        end
        starts = widths.inject([0]) do |result, w|
          result << (result.last + w)
        end
        labels.zip(starts, widths).each do |l, s, w|
          @bit_fields_map[l] = [parent, s, w]
        end
      end
    end
  end
end
    class Sample < ::FFI::Struct
      layout \
        :aaaaaaa,            :uint32,
        :bbbbbbb,            :int32,
        :ccccccc,            :int32,
        :ddddddd,            :int32,

      bitfields :aaaaaaa, 
        :a,       2,
        :b,       2,
        :c,       3

      bitfields :bbbbbbb, 
        :d,       2,
        :e,       2,
        :f,       3
    end

It's not a good code, but it seems to work anyway.
I hope bitfields will be supported in Ruby-ffi.

@kojix2
Copy link
Contributor Author

kojix2 commented Mar 17, 2021

I have created a Gem based on the above idea.
However, I am sure that someone else can create a Gem of much higher quality than my code.

https://github.com/kojix2/ffi-bitstruct

@kojix2
Copy link
Contributor Author

kojix2 commented Jul 23, 2021

I further improved ffi-bitstruct and renamed it ffi-bitfield. I think this Gem is useful for many use cases.
https://github.com/kojix2/ffi-bitfield

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants