1require 'dl'
2require 'dl/value'
3require 'dl/pack.rb'
4
5module DL
6  # C struct shell
7  class CStruct
8    # accessor to DL::CStructEntity
9    def CStruct.entity_class()
10      CStructEntity
11    end
12  end
13
14  # C union shell
15  class CUnion
16    # accessor to DL::CUnionEntity
17    def CUnion.entity_class()
18      CUnionEntity
19    end
20  end
21
22  # Used to construct C classes (CUnion, CStruct, etc)
23  #
24  # DL::Importer#struct and DL::Importer#union wrap this functionality in an
25  # easy-to-use manner.
26  module CStructBuilder
27    # Construct a new class given a C:
28    # * class +klass+ (CUnion, CStruct, or other that provide an
29    #   #entity_class)
30    # * +types+ (DL:TYPE_INT, DL::TYPE_SIZE_T, etc., see the C types
31    #   constants)
32    # * corresponding +members+
33    #
34    # DL::Importer#struct and DL::Importer#union wrap this functionality in an
35    # easy-to-use manner.
36    #
37    # Example:
38    #
39    #   require 'dl/struct'
40    #   require 'dl/cparser'
41    #
42    #   include DL::CParser
43    #
44    #   types, members = parse_struct_signature(['int i','char c'])
45    #
46    #   MyStruct = DL::CStructBuilder.create(CUnion, types, members)
47    #
48    #   obj = MyStruct.allocate
49    #
50    def create(klass, types, members)
51      new_class = Class.new(klass){
52        define_method(:initialize){|addr|
53          @entity = klass.entity_class.new(addr, types)
54          @entity.assign_names(members)
55        }
56        define_method(:to_ptr){ @entity }
57        define_method(:to_i){ @entity.to_i }
58        members.each{|name|
59          define_method(name){ @entity[name] }
60          define_method(name + "="){|val| @entity[name] = val }
61        }
62      }
63      size = klass.entity_class.size(types)
64      new_class.module_eval(<<-EOS, __FILE__, __LINE__+1)
65        def new_class.size()
66          #{size}
67        end
68        def new_class.malloc()
69          addr = DL.malloc(#{size})
70          new(addr)
71        end
72      EOS
73      return new_class
74    end
75    module_function :create
76  end
77
78  # A C struct wrapper
79  class CStructEntity < (DL.fiddle? ? Fiddle::Pointer : CPtr)
80    include PackInfo
81    include ValueUtil
82
83    # Allocates a C struct the +types+ provided.  The C function +func+ is
84    # called when the instance is garbage collected.
85    def CStructEntity.malloc(types, func = nil)
86      addr = DL.malloc(CStructEntity.size(types))
87      CStructEntity.new(addr, types, func)
88    end
89
90    # Given +types+, returns the offset for the packed sizes of those types
91    #
92    #   DL::CStructEntity.size([DL::TYPE_DOUBLE, DL::TYPE_INT, DL::TYPE_CHAR,
93    #                           DL::TYPE_VOIDP])
94    #   => 24
95    def CStructEntity.size(types)
96      offset = 0
97
98      max_align = types.map { |type, count = 1|
99        last_offset = offset
100
101        align = PackInfo::ALIGN_MAP[type]
102        offset = PackInfo.align(last_offset, align) +
103                 (PackInfo::SIZE_MAP[type] * count)
104
105        align
106      }.max
107
108      PackInfo.align(offset, max_align)
109    end
110
111    # Wraps the C pointer +addr+ as a C struct with the given +types+.  The C
112    # function +func+ is called when the instance is garbage collected.
113    #
114    # See also DL::CPtr.new
115    def initialize(addr, types, func = nil)
116      set_ctypes(types)
117      super(addr, @size, func)
118    end
119
120    # Set the names of the +members+ in this C struct
121    def assign_names(members)
122      @members = members
123    end
124
125    # Given +types+, calculate the offsets and sizes for the types in the
126    # struct.
127    def set_ctypes(types)
128      @ctypes = types
129      @offset = []
130      offset = 0
131
132      max_align = types.map { |type, count = 1|
133        orig_offset = offset
134        align = ALIGN_MAP[type]
135        offset = PackInfo.align(orig_offset, align)
136
137        @offset << offset
138
139        offset += (SIZE_MAP[type] * count)
140
141        align
142      }.max
143
144      @size = PackInfo.align(offset, max_align)
145    end
146
147    # Fetch struct member +name+
148    def [](name)
149      idx = @members.index(name)
150      if( idx.nil? )
151        raise(ArgumentError, "no such member: #{name}")
152      end
153      ty = @ctypes[idx]
154      if( ty.is_a?(Array) )
155        r = super(@offset[idx], SIZE_MAP[ty[0]] * ty[1])
156      else
157        r = super(@offset[idx], SIZE_MAP[ty.abs])
158      end
159      packer = Packer.new([ty])
160      val = packer.unpack([r])
161      case ty
162      when Array
163        case ty[0]
164        when TYPE_VOIDP
165          val = val.collect{|v| CPtr.new(v)}
166        end
167      when TYPE_VOIDP
168        val = CPtr.new(val[0])
169      else
170        val = val[0]
171      end
172      if( ty.is_a?(Integer) && (ty < 0) )
173        return unsigned_value(val, ty)
174      elsif( ty.is_a?(Array) && (ty[0] < 0) )
175        return val.collect{|v| unsigned_value(v,ty[0])}
176      else
177        return val
178      end
179    end
180
181    # Set struct member +name+, to value +val+
182    def []=(name, val)
183      idx = @members.index(name)
184      if( idx.nil? )
185        raise(ArgumentError, "no such member: #{name}")
186      end
187      ty  = @ctypes[idx]
188      packer = Packer.new([ty])
189      val = wrap_arg(val, ty, [])
190      buff = packer.pack([val].flatten())
191      super(@offset[idx], buff.size, buff)
192      if( ty.is_a?(Integer) && (ty < 0) )
193        return unsigned_value(val, ty)
194      elsif( ty.is_a?(Array) && (ty[0] < 0) )
195        return val.collect{|v| unsigned_value(v,ty[0])}
196      else
197        return val
198      end
199    end
200
201    def to_s() # :nodoc:
202      super(@size)
203    end
204  end
205
206  # A C union wrapper
207  class CUnionEntity < CStructEntity
208    include PackInfo
209
210    # Allocates a C union the +types+ provided.  The C function +func+ is
211    # called when the instance is garbage collected.
212    def CUnionEntity.malloc(types, func=nil)
213      addr = DL.malloc(CUnionEntity.size(types))
214      CUnionEntity.new(addr, types, func)
215    end
216
217    # Given +types+, returns the size needed for the union.
218    #
219    #   DL::CUnionEntity.size([DL::TYPE_DOUBLE, DL::TYPE_INT, DL::TYPE_CHAR,
220    #                          DL::TYPE_VOIDP])
221    #   => 8
222    def CUnionEntity.size(types)
223      types.map { |type, count = 1|
224        PackInfo::SIZE_MAP[type] * count
225      }.max
226    end
227
228    # Given +types+, calculate the necessary offset and for each union member
229    def set_ctypes(types)
230      @ctypes = types
231      @offset = Array.new(types.length, 0)
232      @size   = self.class.size types
233    end
234  end
235end
236
237