#!/usr/bin/ruby require 'ffi' require 'forwardable' # this is really very crude logging # @private $vips_debug = true # @private def log str if $vips_debug puts str end end def set_debug debug $vips_debug = debug end module Libc extend FFI::Library ffi_lib FFI::Library::LIBC attach_function :malloc, [:size_t], :pointer attach_function :free, [:pointer], :void end module GLib extend FFI::Library ffi_lib 'gobject-2.0' # nil being the default glib_log_domain = nil def self.set_log_domain(domain) glib_log_domain = domain end # we have a set of things we need to inherit in different ways: # # - we want to be able to subclass GObject in a simple way # - the layouts of the nested structs # - casting between structs which share a base # - gobject refcounting # # the solution is to split the class into four areas which we treat # differently: # # - we have a "wrapper" Ruby class to allow easy subclassing ... this has a # @struct member which holds the actual pointer # - we use "forwardable" to forward the various ffi methods on to the # @struct member ... we arrange things so that subclasses do not need to # do the forwarding themselves # - we have two versions of the struct: a plain one which we can use for # casting that will not change the refcounts # - and a managed one with an unref which we just use for .new # - we separate the struct layout into a separate module to avoid repeating # ourselves class GObject extend Forwardable extend SingleForwardable def_instance_delegators :@struct, :[], :to_ptr def_single_delegators :ffi_struct, :ptr # the layout of the GObject struct module GObjectLayout def self.included(base) base.class_eval do layout :g_type_instance, :pointer, :ref_count, :uint, :qdata, :pointer end end end # the struct with unref ... manage object lifetime with this class ManagedStruct < FFI::ManagedStruct include GObjectLayout def initialize(ptr) log "GLib::GObject::ManagedStruct.new: #{ptr}" super end def self.release(ptr) log "GLib::GObject::ManagedStruct.release: unreffing #{ptr}" GLib::g_object_unref(ptr) unless ptr.null? end end # the plain struct ... cast with this class Struct < FFI::Struct include GObjectLayout def initialize(ptr) log "GLib::GObject::Struct.new: #{ptr}" super end end # don't allow ptr == nil, we never want to allocate a GObject struct # ourselves, we just want to wrap GLib-allocated GObjects # # here we use ManagedStruct, not Struct, since this is the ref that will # need the unref def initialize(ptr) log "GLib::GObject.initialize: ptr = #{ptr}" @struct = ffi_managed_struct.new(ptr) end # access to the cast struct for this class def ffi_struct self.class.ffi_struct end class << self def ffi_struct self.const_get(:Struct) end end # access to the lifetime managed struct for this class def ffi_managed_struct self.class.ffi_managed_struct end class << self def ffi_managed_struct self.const_get(:ManagedStruct) end end end # :gtype will usually be 64-bit, but will be 32-bit on 32-bit Windows typedef :ulong, :GType end module Vips extend FFI::Library ffi_lib 'vips' LOG_DOMAIN = "VIPS" GLib::set_log_domain(LOG_DOMAIN) # need to repeat this typedef :ulong, :GType attach_function :vips_init, [:string], :int attach_function :vips_shutdown, [], :void attach_function :vips_error_buffer, [], :string attach_function :vips_error_clear, [], :void def self.get_error errstr = Vips::vips_error_buffer Vips::vips_error_clear errstr end if Vips::vips_init($0) != 0 puts Vips::get_error exit 1 end at_exit { Vips::vips_shutdown } attach_function :vips_object_print_all, [], :void attach_function :vips_leak_set, [:int], :void def self.showall if $vips_debug GC.start vips_object_print_all end end if $vips_debug vips_leak_set 1 end class VipsObject < GLib::GObject # the layout of the VipsObject struct module VipsObjectLayout def self.included(base) base.class_eval do # don't actually need most of these, remove them later layout :parent, GLib::GObject::Struct, :constructed, :int, :static_object, :int, :argument_table, :pointer, :nickname, :string, :description, :string, :preclose, :int, :close, :int, :postclose, :int, :local_memory, :size_t end end end class Struct < GLib::GObject::Struct include VipsObjectLayout def initialize(ptr) log "Vips::VipsObject::Struct.new: #{ptr}" super end end class ManagedStruct < GLib::GObject::ManagedStruct include VipsObjectLayout def initialize(ptr) log "Vips::VipsObject::ManagedStruct.new: #{ptr}" super end end end class VipsImage < VipsObject # the layout of the VipsImage struct module VipsImageLayout def self.included(base) base.class_eval do layout :parent, VipsObject::Struct # rest opaque end end end class Struct < VipsObject::Struct include VipsImageLayout def initialize(ptr) log "Vips::VipsImage::Struct.new: #{ptr}" super end end class ManagedStruct < VipsObject::ManagedStruct include VipsImageLayout def initialize(ptr) log "Vips::VipsImage::ManagedStruct.new: #{ptr}" super end end def self.new_partial VipsImage.new(Vips::vips_image_new) end end attach_function :vips_image_new, [], :pointer end puts "creating image" begin x = Vips::VipsImage.new_partial puts "x = #{x}" puts "" puts "x[:parent] = #{x[:parent]}" puts "" puts "x[:parent][:description] = #{x[:parent][:description]}" puts "" end