core/pointer.rb
module Rubinius
module FFI
# We need to define this so we can include it. Since the file is
# auto-generated, we can't pull it in here but we don't want to break the
# alphabetized load order for random cases.
module PointerAccessors; end
##
# Pointer is Rubinius's "fat" pointer class. It represents an actual
# pointer, in C language terms, to an address in memory. They're called
# fat pointers because the Pointer object is an wrapper around
# the actual pointer, the Rubinius runtime doesn't have direct access
# to the raw address.
#
# This class is used extensively in FFI usage to interface with various
# parts of the underlying system. It provides a number of operations
# for operating on the memory that is pointed to. These operations effectively
# give Rubinius the cast/read capabilities available in C, but using
# high level methods.
#
# MemoryPointer objects can be put in autorelease mode. In this mode,
# when the GC cleans up a MemoryPointer object, the memory it points
# to is passed to free(3), releasing the memory back to the OS.
#
# NOTE: MemoryPointer exposes direct, unmanaged operations on any
# memory. It therefore MUST be used carefully. Reading or writing to
# invalid address will cause bus errors and segmentation faults.
#
class Pointer
include PointerAccessors
def initialize(a1, a2=undefined)
if undefined.equal? a2
self.address = a1
else
@type = a1
self.address = a2
end
end
private :initialize
def inspect
# Don't have this print the data at the location. It can crash everything.
addr = address()
if addr < 0
sign = "-"
addr = -addr
else
sign = ""
end
"#<#{self.class.name} address=#{sign}0x#{addr.to_s(16)}>"
end
# Return the address pointed to as an Integer
def address
Rubinius.primitive :pointer_address
raise PrimitiveFailure, "FFI::Pointer#address primitive failed"
end
alias_method :to_i, :address
# Set the address pointed to from an Integer
def address=(address)
Rubinius.primitive :pointer_set_address
raise PrimitiveFailure, "FFI::Pointer#address= primitive failed"
end
def null?
address == 0x0
end
# Add +value+ to the address pointed to and return a new Pointer
def +(value)
Rubinius.primitive :pointer_add
raise PrimitiveFailure, "FFI::Pointer#+ primitive failed"
end
# Indicates if +self+ and +other+ point to the same address
def ==(other)
return false unless other.kind_of? Pointer
return address == other.address
end
def network_order(start, size)
Rubinius.primitive :pointer_network_order
raise PrimitiveFailure, "FFI::Pointer#network_order primitive failed"
end
# Read +len+ bytes from the memory pointed to and return them as
# a String
def read_string_length(len)
Rubinius.primitive :pointer_read_string
raise PrimitiveFailure, "FFI::Pointer#read_string_length primitive failed"
end
# Read bytes from the memory pointed to until a NULL is seen, return
# the bytes as a String
def read_string_to_null
Rubinius.primitive :pointer_read_string_to_null
raise PrimitiveFailure, "FFI::Pointer#read_string_to_null primitive failed"
end
# Read bytes as a String from the memory pointed to
def read_string(len=nil)
if len
read_string_length(len)
else
read_string_to_null
end
end
# FFI compat methods
def get_bytes(offset, length)
(self + offset).read_string_length(length)
end
# Write String +str+ as bytes into the memory pointed to. Only
# write up to +len+ bytes.
def write_string_length(str, len)
Rubinius.primitive :pointer_write_string
raise PrimitiveFailure, "FFI::Pointer#write_string_length primitive failed"
end
# Write a String +str+ as bytes to the memory pointed to.
def write_string(str, len=nil)
len = str.bytesize unless len
write_string_length(str, len);
end
# Read a sequence of types +type+, length +length+, using method +reader+
def read_array_of_type(type, reader, length, signed=nil)
# If signed is not nil and is actually a boolean,
# then use that as an argument to the reader, which
# is then assumed to support signed reading.
args = []
args = [signed] if !signed.nil?
# Build up the array
ary = []
size = FFI.type_size(FFI.find_type type)
tmp = self
length.times {
ary << tmp.send(reader, *args)
tmp += size
}
ary
end
# Write a sequence of types +type+ using method +reader+ from +ary+
def write_array_of_type(type, writer, ary)
size = FFI.type_size(FFI.find_type type)
tmp = self
ary.each do |i|
tmp.send(writer, i)
tmp += size
end
self
end
# Read bytes from +offset+ from the memory pointed to as type +type+
def get_at_offset(offset, type)
Rubinius.primitive :pointer_get_at_offset
raise PrimitiveFailure, "FFI::Pointer#get_at_offset primitive failed"
end
# Write +val+ as type +type+ to bytes from +offset+
def set_at_offset(offset, type, val)
Rubinius.primitive :pointer_set_at_offset
raise PrimitiveFailure, "FFI::Pointer#set_at_offset primitive failed"
end
# Number of bytes taken up by a pointer.
def self.size
Rubinius::WORDSIZE / 8
end
# Primitive methods
def primitive_read_char(signed)
Rubinius.primitive :pointer_read_char
raise PrimitiveFailure, "FFI::Pointer#primitive_read_char primitive failed"
end
def primitive_write_char(obj)
Rubinius.primitive :pointer_write_char
raise PrimitiveFailure, "FFI::Pointer#primitive_write_char primitive failed"
end
def primitive_read_short(signed)
Rubinius.primitive :pointer_read_short
raise PrimitiveFailure, "FFI::Pointer#primitive_read_short primitive failed"
end
def primitive_write_short(obj)
Rubinius.primitive :pointer_write_short
raise PrimitiveFailure, "FFI::Pointer#primitive_write_short primitive failed"
end
def primitive_read_int(signed)
Rubinius.primitive :pointer_read_int
raise PrimitiveFailure, "FFI::Pointer#primitive_read_int primitive failed"
end
def primitive_write_int(obj)
Rubinius.primitive :pointer_write_int
raise PrimitiveFailure, "FFI::Pointer#primitive_write_int primitive failed"
end
def primitive_read_long(signed)
Rubinius.primitive :pointer_read_long
raise PrimitiveFailure, "FFI::Pointer#primitive_read_long primitive failed"
end
def primitive_write_long(obj)
Rubinius.primitive :pointer_write_long
raise PrimitiveFailure, "FFI::Pointer#primitive_write_long primitive failed"
end
def primitive_read_long_long(signed)
Rubinius.primitive :pointer_read_long_long
raise PrimitiveFailure, "FFI::Pointer#primitive_read_long_long primitive failed"
end
def primitive_write_long_long(obj)
Rubinius.primitive :pointer_write_long_long
raise PrimitiveFailure, "FFI::Pointer#primitive_write_long_long primitive failed"
end
def primitive_read_float
Rubinius.primitive :pointer_read_float
raise PrimitiveFailure, "FFI::Pointer#primitive_read_float primitive failed"
end
def primitive_write_float(obj)
Rubinius.primitive :pointer_write_float
raise PrimitiveFailure, "FFI::Pointer#primitive_write_float primitive failed"
end
def primitive_read_double
Rubinius.primitive :pointer_read_double
raise PrimitiveFailure, "FFI::Pointer#primitive_read_double primitive failed"
end
def primitive_write_double(obj)
Rubinius.primitive :pointer_write_double
raise PrimitiveFailure, "FFI::Pointer#primitive_write_double primitive failed"
end
def primitive_read_pointer
Rubinius.primitive :pointer_read_pointer
raise PrimitiveFailure, "FFI::Pointer#primitive_read_pointer primitive failed"
end
def primitive_write_pointer(obj)
Rubinius.primitive :pointer_write_pointer
raise PrimitiveFailure, "FFI::Pointer#primitive_write_pointer primitive failed"
end
##
# If +val+ is true, this Pointer object will call
# free() on it's address when it is garbage collected.
def autorelease=(val)
Rubinius.primitive :pointer_set_autorelease
raise PrimitiveFailure, "FFI::Pointer#autorelease= primitive failed"
end
##
# Returns true if autorelease is enabled, otherwise false.
def autorelease?
Rubinius.primitive :pointer_autorelease_p
raise PrimitiveFailure, "FFI::Pointer#pointer_autorelease_p primitive failed"
end
NULL = Pointer.new(0x0)
end
class MemoryPointer < Pointer
# call-seq:
# MemoryPointer.new(num) => MemoryPointer instance of <i>num</i> bytes
# MemoryPointer.new(sym) => MemoryPointer instance with number
# of bytes need by FFI type <i>sym</i>
# MemoryPointer.new(obj) => MemoryPointer instance with number
# of <i>obj.size</i> bytes
# MemoryPointer.new(sym, count) => MemoryPointer instance with number
# of bytes need by length-<i>count</i> array
# of FFI type <i>sym</i>
# MemoryPointer.new(obj, count) => MemoryPointer instance with number
# of bytes need by length-<i>count</i> array
# of <i>obj.size</i> bytes
# MemoryPointer.new(arg) { |p| ... }
#
# Both forms create a MemoryPointer instance. The number of bytes to
# allocate is either specified directly or by passing an FFI type, which
# specifies the number of bytes needed for that type.
#
# The form without a block returns the MemoryPointer instance. The form
# with a block yields the MemoryPointer instance and frees the memory
# when the block returns. The value returned is the value of the block.
#
def self.new(type, count=nil, clear=true)
if type.kind_of? Fixnum
size = type
elsif type.kind_of? Symbol
type = FFI.find_type type
size = FFI.type_size(type)
else
size = type.size
end
if count
total = size * count
else
total = size
end
return NULL if total < 0
ptr = malloc total
ptr.total = total
ptr.type_size = size
FFI::Platform::POSIX.memset ptr, 0, total if clear
if block_given?
begin
value = yield ptr
ensure
ptr.free
end
return value
else
ptr.autorelease = true
ptr
end
end
def self.malloc(total)
Rubinius.primitive :pointer_malloc
raise PrimitiveFailure, "FFI::MemoryPointer.malloc primitive failed"
end
def self.from_string(str)
ptr = new str.bytesize + 1
ptr.write_string str + "\0"
ptr
end
def copy
other = malloc total
other.total = total
other.type_size = type_size
FFI::Platform::POSIX.memcpy other, self, total
Rubinius.privately do
other.initialize_copy self
end
other
end
# Indicates how many bytes the chunk of memory that is pointed to takes up.
attr_accessor :total
# Indicates how many bytes the type that the pointer is cast as uses.
attr_accessor :type_size
# Access the MemoryPointer like a C array, accessing the +which+ number
# element in memory. The position of the element is calculate from
# +@type_size+ and +which+. A new MemoryPointer object is returned, which
# points to the address of the element.
#
# Example:
# ptr = MemoryPointer.new(:int, 20)
# new_ptr = ptr[9]
#
# c-equiv:
# int *ptr = (int*)malloc(sizeof(int) * 20);
# int *new_ptr;
# new_ptr = &ptr[9];
#
def [](which)
raise ArgumentError, "unknown type size" unless @type_size
self + (which * @type_size)
end
# Release the memory pointed to back to the OS.
def free
Rubinius.primitive :pointer_free
raise PrimitiveFailure, "FFI::MemoryPointer#free primitive failed"
end
end
class DynamicLibrary
class Symbol < Pointer
def initialize(library, ptr, name)
@library = library
@name = name
self.address = ptr.address
end
private :initialize
def inspect
"#<FFI::Library::Symbol name=#{@name} address=#{address.to_s(16)}>"
end
end
end
class Function < Pointer
def initialize(ret_type, arg_types, val=nil, options=nil, &block)
if block
if val or options
raise ArgumentError, "specify a block or a proc/address, not both"
end
val = block
end
args = arg_types.map { |x| FFI.find_type(x) }
ret = FFI.find_type(ret_type)
if val.kind_of? Pointer
@function = FFI.generate_function(val, :func, args, ret)
self.address = val.address
elsif val.respond_to? :call
@function, ptr = FFI.generate_trampoline(val, :func, args, ret)
self.address = ptr.address
else
raise ArgumentError, "value wasn't a FFI::Pointer and didn't respond to call"
end
# Hook the created function into the method_table so that #call goes
# straight to it.
sc = Rubinius::Type.object_singleton_class(self)
Rubinius::VM.reset_method_cache sc, :call
sc.method_table.store :call, nil, @function, nil, 0, :public
end
private :initialize
attr_reader :function
# Hook this Function up to be an instance/class method +name+ on +mod+
def attach(mod, name)
unless mod.kind_of?(Module)
raise TypeError, "mod must be a Module"
end
name = name.to_sym
# Make it available as a method callable directly..
sc = Rubinius::Type.object_singleton_class(mod)
Rubinius::VM.reset_method_cache sc, name
sc.method_table.store name, nil, @function, nil, 0, :public
# and expose it as a private method for people who
# want to include this module.
Rubinius::VM.reset_method_cache mod, name
mod.method_table.store name, nil, @function, nil, 0, :public
end
end
end
end