# # Copyright (C) 2008-2010 Wayne Meissner # # This file is part of ruby-ffi. # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of the Ruby FFI project nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # module FFI # Build a {StructLayout struct layout}. class StructLayoutBuilder attr_reader :size attr_reader :alignment def initialize @size = 0 @alignment = 1 @min_alignment = 1 @packed = false @union = false @fields = Array.new end # Set size attribute with +size+ only if +size+ is greater than attribute value. # @param [Numeric] size def size=(size) @size = size if size > @size end # Set alignment attribute with +align+ only if it is greater than attribute value. # @param [Numeric] align def alignment=(align) @alignment = align if align > @alignment @min_alignment = align end # Set union attribute. # Set to +true+ to build a {Union} instead of a {Struct}. # @param [Boolean] is_union # @return [is_union] def union=(is_union) @union = is_union end # Building a {Union} or a {Struct} ? # # @return [Boolean] # def union? @union end # Set packed attribute # @overload packed=(packed) Set alignment and packed attributes to # +packed+. # # @param [Fixnum] packed # # @return [packed] # @overload packed=(packed) Set packed attribute. # @param packed # # @return [0,1] # def packed=(packed) if packed.is_a?(0.class) @alignment = packed @packed = packed else @packed = packed ? 1 : 0 end end # List of number types NUMBER_TYPES = [ Type::INT8, Type::UINT8, Type::INT16, Type::UINT16, Type::INT32, Type::UINT32, Type::LONG, Type::ULONG, Type::INT64, Type::UINT64, Type::FLOAT32, Type::FLOAT64, Type::LONGDOUBLE, Type::BOOL, ] # @param [String, Symbol] name name of the field # @param [Array, DataConverter, Struct, StructLayout::Field, Symbol, Type] type type of the field # @param [Numeric, nil] offset # @return [self] # Add a field to the builder. # @note Setting +offset+ to +nil+ or +-1+ is equivalent to +0+. def add(name, type, offset = nil) if offset.nil? || offset == -1 offset = @union ? 0 : align(@size, @packed ? [ @packed, type.alignment ].min : [ @min_alignment, type.alignment ].max) end # # If a FFI::Type type was passed in as the field arg, try and convert to a StructLayout::Field instance # field = type.is_a?(StructLayout::Field) ? type : field_for_type(name, offset, type) @fields << field @alignment = [ @alignment, field.alignment ].max unless @packed @size = [ @size, field.size + (@union ? 0 : field.offset) ].max return self end # @param (see #add) # @return (see #add) # Same as {#add}. # @see #add def add_field(name, type, offset = nil) add(name, type, offset) end # @param (see #add) # @return (see #add) # Add a struct as a field to the builder. def add_struct(name, type, offset = nil) add(name, Type::Struct.new(type), offset) end # @param name (see #add) # @param type (see #add) # @param [Numeric] count array length # @param offset (see #add) # @return (see #add) # Add an array as a field to the builder. def add_array(name, type, count, offset = nil) add(name, Type::Array.new(type, count), offset) end # @return [StructLayout] # Build and return the struct layout. def build # Add tail padding if the struct is not packed size = @packed ? @size : align(@size, @alignment) layout = StructLayout.new(@fields, size, @alignment) layout.__union! if @union layout end private # @param [Numeric] offset # @param [Numeric] align # @return [Numeric] def align(offset, align) align + ((offset - 1) & ~(align - 1)); end # @param (see #add) # @return [StructLayout::Field] def field_for_type(name, offset, type) field_class = case when type.is_a?(Type::Function) StructLayout::Function when type.is_a?(Type::Struct) StructLayout::InnerStruct when type.is_a?(Type::Array) StructLayout::Array when type.is_a?(FFI::Enum) StructLayout::Enum when NUMBER_TYPES.include?(type) StructLayout::Number when type == Type::POINTER StructLayout::Pointer when type == Type::STRING StructLayout::String when type.is_a?(Class) && type < StructLayout::Field type when type.is_a?(DataConverter) return StructLayout::Mapped.new(name, offset, Type::Mapped.new(type), field_for_type(name, offset, type.native_type)) when type.is_a?(Type::Mapped) return StructLayout::Mapped.new(name, offset, type, field_for_type(name, offset, type.native_type)) else raise TypeError, "invalid struct field type #{type.inspect}" end field_class.new(name, offset, type) end end end