From 0713f89944ecfff3aeefda1edcf44c0ea3e98de0 Mon Sep 17 00:00:00 2001 From: tenderlove Date: Wed, 28 Nov 2012 00:02:49 +0000 Subject: [PATCH] * ext/fiddle/fiddle.c: adding alignment constants for compatibility with DL. * ext/fiddle/fiddle.h: ditto * ext/fiddle/lib/fiddle/cparser.rb: importing the C parser for DL backwards compatibility. * ext/fiddle/lib/fiddle/import.rb: importing the import DSL for DL backwards compatibility. * ext/fiddle/lib/fiddle/pack.rb: importing structure pack for DL backwards compatibility. * ext/fiddle/lib/fiddle/value.rb: ditto * ext/fiddle/lib/fiddle/struct.rb: importing struct DSL for DL backwards compatibility. * test/dl/test_c_struct_entry.rb: importing tests * test/dl/test_c_union_entity.rb: ditto * test/dl/test_cparser.rb: ditto * test/dl/test_import.rb: ditto * test/fiddle/test_c_struct_entry.rb: ditto * test/fiddle/test_c_union_entity.rb: ditto * test/fiddle/test_cparser.rb: ditto * test/fiddle/test_import.rb: ditto git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@37914 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 23 +++ ext/fiddle/fiddle.c | 88 +++++++++++ ext/fiddle/fiddle.h | 13 ++ ext/fiddle/lib/fiddle/cparser.rb | 156 +++++++++++++++++++ ext/fiddle/lib/fiddle/import.rb | 241 +++++++++++++++++++++++++++++ ext/fiddle/lib/fiddle/pack.rb | 128 +++++++++++++++ ext/fiddle/lib/fiddle/struct.rb | 236 ++++++++++++++++++++++++++++ ext/fiddle/lib/fiddle/value.rb | 114 ++++++++++++++ test/dl/test_c_struct_entry.rb | 65 ++++---- test/dl/test_c_union_entity.rb | 37 ++--- test/dl/test_cparser.rb | 14 +- test/dl/test_import.rb | 8 +- test/fiddle/test_c_struct_entry.rb | 54 +++++++ test/fiddle/test_c_union_entity.rb | 31 ++++ test/fiddle/test_cparser.rb | 33 ++++ test/fiddle/test_import.rb | 136 ++++++++++++++++ 16 files changed, 1316 insertions(+), 61 deletions(-) create mode 100644 ext/fiddle/lib/fiddle/cparser.rb create mode 100644 ext/fiddle/lib/fiddle/import.rb create mode 100644 ext/fiddle/lib/fiddle/pack.rb create mode 100644 ext/fiddle/lib/fiddle/struct.rb create mode 100644 ext/fiddle/lib/fiddle/value.rb create mode 100644 test/fiddle/test_c_struct_entry.rb create mode 100644 test/fiddle/test_c_union_entity.rb create mode 100644 test/fiddle/test_cparser.rb create mode 100644 test/fiddle/test_import.rb diff --git a/ChangeLog b/ChangeLog index d039b4490e..03b4494777 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,26 @@ +Wed Nov 28 09:00:34 2012 Aaron Patterson + + * ext/fiddle/fiddle.c: adding alignment constants for compatibility + with DL. + * ext/fiddle/fiddle.h: ditto + * ext/fiddle/lib/fiddle/cparser.rb: importing the C parser for DL + backwards compatibility. + * ext/fiddle/lib/fiddle/import.rb: importing the import DSL for DL + backwards compatibility. + * ext/fiddle/lib/fiddle/pack.rb: importing structure pack for DL + backwards compatibility. + * ext/fiddle/lib/fiddle/value.rb: ditto + * ext/fiddle/lib/fiddle/struct.rb: importing struct DSL for DL backwards + compatibility. + * test/dl/test_c_struct_entry.rb: importing tests + * test/dl/test_c_union_entity.rb: ditto + * test/dl/test_cparser.rb: ditto + * test/dl/test_import.rb: ditto + * test/fiddle/test_c_struct_entry.rb: ditto + * test/fiddle/test_c_union_entity.rb: ditto + * test/fiddle/test_cparser.rb: ditto + * test/fiddle/test_import.rb: ditto + Wed Nov 28 08:56:00 2012 Zachary Scott * doc/globals.rdoc: Add documentation file for magic globals diff --git a/ext/fiddle/fiddle.c b/ext/fiddle/fiddle.c index c4b590ca84..89644301aa 100644 --- a/ext/fiddle/fiddle.c +++ b/ext/fiddle/fiddle.c @@ -260,6 +260,86 @@ Init_fiddle(void) */ rb_define_const(mFiddle, "TYPE_UINTPTR_T", INT2NUM(TYPE_UINTPTR_T)); + /* Document-const: ALIGN_VOIDP + * + * The alignment size of a void* + */ + rb_define_const(mFiddle, "ALIGN_VOIDP", INT2NUM(ALIGN_VOIDP)); + + /* Document-const: ALIGN_CHAR + * + * The alignment size of a char + */ + rb_define_const(mFiddle, "ALIGN_CHAR", INT2NUM(ALIGN_CHAR)); + + /* Document-const: ALIGN_SHORT + * + * The alignment size of a short + */ + rb_define_const(mFiddle, "ALIGN_SHORT", INT2NUM(ALIGN_SHORT)); + + /* Document-const: ALIGN_INT + * + * The alignment size of an int + */ + rb_define_const(mFiddle, "ALIGN_INT", INT2NUM(ALIGN_INT)); + + /* Document-const: ALIGN_LONG + * + * The alignment size of a long + */ + rb_define_const(mFiddle, "ALIGN_LONG", INT2NUM(ALIGN_LONG)); + +#if HAVE_LONG_LONG + /* Document-const: ALIGN_LONG_LONG + * + * The alignment size of a long long + */ + rb_define_const(mFiddle, "ALIGN_LONG_LONG", INT2NUM(ALIGN_LONG_LONG)); +#endif + + /* Document-const: ALIGN_FLOAT + * + * The alignment size of a float + */ + rb_define_const(mFiddle, "ALIGN_FLOAT", INT2NUM(ALIGN_FLOAT)); + + /* Document-const: ALIGN_DOUBLE + * + * The alignment size of a double + */ + rb_define_const(mFiddle, "ALIGN_DOUBLE",INT2NUM(ALIGN_DOUBLE)); + + /* Document-const: ALIGN_SIZE_T + * + * The alignment size of a size_t + */ + rb_define_const(mFiddle, "ALIGN_SIZE_T", INT2NUM(ALIGN_OF(size_t))); + + /* Document-const: ALIGN_SSIZE_T + * + * The alignment size of a ssize_t + */ + rb_define_const(mFiddle, "ALIGN_SSIZE_T", INT2NUM(ALIGN_OF(size_t))); /* same as size_t */ + + /* Document-const: ALIGN_PTRDIFF_T + * + * The alignment size of a ptrdiff_t + */ + rb_define_const(mFiddle, "ALIGN_PTRDIFF_T", INT2NUM(ALIGN_OF(ptrdiff_t))); + + /* Document-const: ALIGN_INTPTR_T + * + * The alignment size of a intptr_t + */ + rb_define_const(mFiddle, "ALIGN_INTPTR_T", INT2NUM(ALIGN_OF(intptr_t))); + + /* Document-const: ALIGN_UINTPTR_T + * + * The alignment size of a uintptr_t + */ + rb_define_const(mFiddle, "ALIGN_UINTPTR_T", INT2NUM(ALIGN_OF(uintptr_t))); + /* Document-const: WINDOWS * * Returns a boolean regarding whether the host is WIN32 @@ -356,6 +436,14 @@ Init_fiddle(void) */ rb_define_const(mFiddle, "RUBY_FREE", PTR2NUM(ruby_xfree)); + /* Document-const: BUILD_RUBY_PLATFORM + * + * Platform built against (i.e. "x86_64-linux", etc.) + * + * See also RUBY_PLATFORM + */ + rb_define_const(mFiddle, "BUILD_RUBY_PLATFORM", rb_str_new2(RUBY_PLATFORM)); + rb_define_module_function(mFiddle, "dlwrap", rb_fiddle_value2ptr, 1); rb_define_module_function(mFiddle, "dlunwrap", rb_fiddle_ptr2value, 1); rb_define_module_function(mFiddle, "malloc", rb_fiddle_malloc, 1); diff --git a/ext/fiddle/fiddle.h b/ext/fiddle/fiddle.h index 8c8a93425b..b37c37bc65 100644 --- a/ext/fiddle/fiddle.h +++ b/ext/fiddle/fiddle.h @@ -121,6 +121,19 @@ #define TYPE_FLOAT 7 #define TYPE_DOUBLE 8 +#define ALIGN_OF(type) offsetof(struct {char align_c; type align_x;}, align_x) + +#define ALIGN_VOIDP ALIGN_OF(void*) +#define ALIGN_SHORT ALIGN_OF(short) +#define ALIGN_CHAR ALIGN_OF(char) +#define ALIGN_INT ALIGN_OF(int) +#define ALIGN_LONG ALIGN_OF(long) +#if HAVE_LONG_LONG +#define ALIGN_LONG_LONG ALIGN_OF(LONG_LONG) +#endif +#define ALIGN_FLOAT ALIGN_OF(float) +#define ALIGN_DOUBLE ALIGN_OF(double) + extern VALUE mFiddle; extern VALUE rb_eFiddleError; diff --git a/ext/fiddle/lib/fiddle/cparser.rb b/ext/fiddle/lib/fiddle/cparser.rb new file mode 100644 index 0000000000..ede88ae668 --- /dev/null +++ b/ext/fiddle/lib/fiddle/cparser.rb @@ -0,0 +1,156 @@ +module Fiddle + # Methods for parsing C struct and C prototype signatures. + module CParser + # Parses a C struct's members + # + # Example: + # + # parse_struct_signature(['int i', 'char c']) + # => [[Fiddle::TYPE_INT, Fiddle::TYPE_CHAR], ["i", "c"]] + # + def parse_struct_signature(signature, tymap=nil) + if( signature.is_a?(String) ) + signature = signature.split(/\s*,\s*/) + end + mems = [] + tys = [] + signature.each{|msig| + tks = msig.split(/\s+(\*)?/) + ty = tks[0..-2].join(" ") + member = tks[-1] + + case ty + when /\[(\d+)\]/ + n = $1.to_i + ty.gsub!(/\s*\[\d+\]/,"") + ty = [ty, n] + when /\[\]/ + ty.gsub!(/\s*\[\]/, "*") + end + + case member + when /\[(\d+)\]/ + ty = [ty, $1.to_i] + member.gsub!(/\s*\[\d+\]/,"") + when /\[\]/ + ty = ty + "*" + member.gsub!(/\s*\[\]/, "") + end + + mems.push(member) + tys.push(parse_ctype(ty,tymap)) + } + return tys, mems + end + + # Parses a C prototype signature + # + # Example: + # + # include Fiddle::CParser + # => Object + # + # parse_signature('double sum(double, double)') + # => ["sum", Fiddle::TYPE_DOUBLE, [Fiddle::TYPE_DOUBLE, Fiddle::TYPE_DOUBLE]] + # + def parse_signature(signature, tymap=nil) + tymap ||= {} + signature = signature.gsub(/\s+/, " ").strip + case signature + when /^([\w@\*\s]+)\(([\w\*\s\,\[\]]*)\)$/ + ret = $1 + (args = $2).strip! + ret = ret.split(/\s+/) + args = args.split(/\s*,\s*/) + func = ret.pop + if( func =~ /^\*/ ) + func.gsub!(/^\*+/,"") + ret.push("*") + end + ret = ret.join(" ") + return [func, parse_ctype(ret, tymap), args.collect{|arg| parse_ctype(arg, tymap)}] + else + raise(RuntimeError,"can't parse the function prototype: #{signature}") + end + end + + # Given a String of C type +ty+, return the corresponding DL constant. + # + # +ty+ can also accept an Array of C type Strings, and will returned in a + # corresponding Array. + # + # If Hash +tymap+ is provided, +ty+ is expected to be the key, and the + # value will be the C type to be looked up. + # + # Example: + # + # parse_ctype('int') + # => Fiddle::TYPE_INT + # + # parse_ctype('double') + # => Fiddle::TYPE_DOUBLE + # + # parse_ctype('unsigned char') + # => -Fiddle::TYPE_CHAR + # + def parse_ctype(ty, tymap=nil) + tymap ||= {} + case ty + when Array + return [parse_ctype(ty[0], tymap), ty[1]] + when "void" + return TYPE_VOID + when "char" + return TYPE_CHAR + when "unsigned char" + return -TYPE_CHAR + when "short" + return TYPE_SHORT + when "unsigned short" + return -TYPE_SHORT + when "int" + return TYPE_INT + when "unsigned int", 'uint' + return -TYPE_INT + when "long" + return TYPE_LONG + when "unsigned long" + return -TYPE_LONG + when "long long" + if( defined?(TYPE_LONG_LONG) ) + return TYPE_LONG_LONG + else + raise(RuntimeError, "unsupported type: #{ty}") + end + when "unsigned long long" + if( defined?(TYPE_LONG_LONG) ) + return -TYPE_LONG_LONG + else + raise(RuntimeError, "unsupported type: #{ty}") + end + when "float" + return TYPE_FLOAT + when "double" + return TYPE_DOUBLE + when "size_t" + return TYPE_SIZE_T + when "ssize_t" + return TYPE_SSIZE_T + when "ptrdiff_t" + return TYPE_PTRDIFF_T + when "intptr_t" + return TYPE_INTPTR_T + when "uintptr_t" + return TYPE_UINTPTR_T + when /\*/, /\[\s*\]/ + return TYPE_VOIDP + else + if( tymap[ty] ) + return parse_ctype(tymap[ty], tymap) + else + raise(DLError, "unknown type: #{ty}") + end + end + end + end +end diff --git a/ext/fiddle/lib/fiddle/import.rb b/ext/fiddle/lib/fiddle/import.rb new file mode 100644 index 0000000000..10b4fad2b5 --- /dev/null +++ b/ext/fiddle/lib/fiddle/import.rb @@ -0,0 +1,241 @@ +require 'fiddle' +require 'fiddle/struct' +require 'fiddle/cparser' + +module Fiddle + class CompositeHandler + def initialize(handlers) + @handlers = handlers + end + + def handlers() + @handlers + end + + def sym(symbol) + @handlers.each{|handle| + if( handle ) + begin + addr = handle.sym(symbol) + return addr + rescue DLError + end + end + } + return nil + end + + def [](symbol) + sym(symbol) + end + end + + # DL::Importer includes the means to dynamically load libraries and build + # modules around them including calling extern functions within the C + # library that has been loaded. + # + # == Example + # + # require 'dl' + # require 'dl/import' + # + # module LibSum + # extend DL::Importer + # dlload './libsum.so' + # extern 'double sum(double*, int)' + # extern 'double split(double)' + # end + # + module Importer + include Fiddle + include CParser + extend Importer + + def dlload(*libs) + handles = libs.collect{|lib| + case lib + when nil + nil + when Handle + lib + when Importer + lib.handlers + else + begin + Fiddle.dlopen(lib) + rescue DLError + raise(DLError, "can't load #{lib}") + end + end + }.flatten() + @handler = CompositeHandler.new(handles) + @func_map = {} + @type_alias = {} + end + + def typealias(alias_type, orig_type) + @type_alias[alias_type] = orig_type + end + + def sizeof(ty) + case ty + when String + ty = parse_ctype(ty, @type_alias).abs() + case ty + when TYPE_CHAR + return SIZEOF_CHAR + when TYPE_SHORT + return SIZEOF_SHORT + when TYPE_INT + return SIZEOF_INT + when TYPE_LONG + return SIZEOF_LONG + when TYPE_LONG_LONG + return SIZEOF_LONG_LON + when TYPE_FLOAT + return SIZEOF_FLOAT + when TYPE_DOUBLE + return SIZEOF_DOUBLE + when TYPE_VOIDP + return SIZEOF_VOIDP + else + raise(DLError, "unknown type: #{ty}") + end + when Class + if( ty.instance_methods().include?(:to_ptr) ) + return ty.size() + end + end + return CPtr[ty].size() + end + + def parse_bind_options(opts) + h = {} + while( opt = opts.shift() ) + case opt + when :stdcall, :cdecl + h[:call_type] = opt + when :carried, :temp, :temporal, :bind + h[:callback_type] = opt + h[:carrier] = opts.shift() + else + h[opt] = true + end + end + h + end + private :parse_bind_options + + def extern(signature, *opts) + symname, ctype, argtype = parse_signature(signature, @type_alias) + opt = parse_bind_options(opts) + f = import_function(symname, ctype, argtype, opt[:call_type]) + name = symname.gsub(/@.+/,'') + @func_map[name] = f + # define_method(name){|*args,&block| f.call(*args,&block)} + begin + /^(.+?):(\d+)/ =~ caller.first + file, line = $1, $2.to_i + rescue + file, line = __FILE__, __LINE__+3 + end + module_eval(<<-EOS, file, line) + def #{name}(*args, &block) + @func_map['#{name}'].call(*args,&block) + end + EOS + module_function(name) + f + end + + def bind(signature, *opts, &blk) + name, ctype, argtype = parse_signature(signature, @type_alias) + h = parse_bind_options(opts) + case h[:callback_type] + when :bind, nil + f = bind_function(name, ctype, argtype, h[:call_type], &blk) + else + raise(RuntimeError, "unknown callback type: #{h[:callback_type]}") + end + @func_map[name] = f + #define_method(name){|*args,&block| f.call(*args,&block)} + begin + /^(.+?):(\d+)/ =~ caller.first + file, line = $1, $2.to_i + rescue + file, line = __FILE__, __LINE__+3 + end + module_eval(<<-EOS, file, line) + def #{name}(*args,&block) + @func_map['#{name}'].call(*args,&block) + end + EOS + module_function(name) + f + end + + # Creates a class to wrap the C struct described by +signature+. + # + # MyStruct = struct ['int i', 'char c'] + def struct(signature) + tys, mems = parse_struct_signature(signature, @type_alias) + Fiddle::CStructBuilder.create(CStruct, tys, mems) + end + + # Creates a class to wrap the C union described by +signature+. + # + # MyUnion = union ['int i', 'char c'] + def union(signature) + tys, mems = parse_struct_signature(signature, @type_alias) + Fiddle::CStructBuilder.create(CUnion, tys, mems) + end + + def [](name) + @func_map[name] + end + + def create_value(ty, val=nil) + s = struct([ty + " value"]) + ptr = s.malloc() + if( val ) + ptr.value = val + end + return ptr + end + alias value create_value + + def import_value(ty, addr) + s = struct([ty + " value"]) + ptr = s.new(addr) + return ptr + end + + def handler + @handler or raise "call dlload before importing symbols and functions" + end + + def import_symbol(name) + addr = handler.sym(name) + if( !addr ) + raise(DLError, "cannot find the symbol: #{name}") + end + Pointer.new(addr) + end + + def import_function(name, ctype, argtype, call_type = nil) + addr = handler.sym(name) + if( !addr ) + raise(DLError, "cannot find the function: #{name}()") + end + Function.new(addr, argtype, ctype, call_type) + end + + def bind_function(name, ctype, argtype, call_type = nil, &block) + closure = Class.new(Fiddle::Closure) { + define_method(:call, block) + }.new(ctype, argtype) + + Function.new(closure, argtype, ctype) + end + end +end diff --git a/ext/fiddle/lib/fiddle/pack.rb b/ext/fiddle/lib/fiddle/pack.rb new file mode 100644 index 0000000000..f406d374f9 --- /dev/null +++ b/ext/fiddle/lib/fiddle/pack.rb @@ -0,0 +1,128 @@ +require 'fiddle' + +module Fiddle + module PackInfo + ALIGN_MAP = { + TYPE_VOIDP => ALIGN_VOIDP, + TYPE_CHAR => ALIGN_CHAR, + TYPE_SHORT => ALIGN_SHORT, + TYPE_INT => ALIGN_INT, + TYPE_LONG => ALIGN_LONG, + TYPE_FLOAT => ALIGN_FLOAT, + TYPE_DOUBLE => ALIGN_DOUBLE, + -TYPE_CHAR => ALIGN_CHAR, + -TYPE_SHORT => ALIGN_SHORT, + -TYPE_INT => ALIGN_INT, + -TYPE_LONG => ALIGN_LONG, + } + + PACK_MAP = { + TYPE_VOIDP => ((SIZEOF_VOIDP == SIZEOF_LONG_LONG) ? "q" : "l!"), + TYPE_CHAR => "c", + TYPE_SHORT => "s!", + TYPE_INT => "i!", + TYPE_LONG => "l!", + TYPE_FLOAT => "f", + TYPE_DOUBLE => "d", + -TYPE_CHAR => "c", + -TYPE_SHORT => "s!", + -TYPE_INT => "i!", + -TYPE_LONG => "l!", + } + + SIZE_MAP = { + TYPE_VOIDP => SIZEOF_VOIDP, + TYPE_CHAR => SIZEOF_CHAR, + TYPE_SHORT => SIZEOF_SHORT, + TYPE_INT => SIZEOF_INT, + TYPE_LONG => SIZEOF_LONG, + TYPE_FLOAT => SIZEOF_FLOAT, + TYPE_DOUBLE => SIZEOF_DOUBLE, + -TYPE_CHAR => SIZEOF_CHAR, + -TYPE_SHORT => SIZEOF_SHORT, + -TYPE_INT => SIZEOF_INT, + -TYPE_LONG => SIZEOF_LONG, + } + if defined?(TYPE_LONG_LONG) + ALIGN_MAP[TYPE_LONG_LONG] = ALIGN_MAP[-TYPE_LONG_LONG] = ALIGN_LONG_LONG + PACK_MAP[TYPE_LONG_LONG] = PACK_MAP[-TYPE_LONG_LONG] = "q" + SIZE_MAP[TYPE_LONG_LONG] = SIZE_MAP[-TYPE_LONG_LONG] = SIZEOF_LONG_LONG + end + + def align(addr, align) + d = addr % align + if( d == 0 ) + addr + else + addr + (align - d) + end + end + module_function :align + end + + class Packer + include PackInfo + + def self.[](*types) + new(types) + end + + def initialize(types) + parse_types(types) + end + + def size() + @size + end + + def pack(ary) + case SIZEOF_VOIDP + when SIZEOF_LONG + ary.pack(@template) + when SIZEOF_LONG_LONG + ary.pack(@template) + else + raise(RuntimeError, "sizeof(void*)?") + end + end + + def unpack(ary) + case SIZEOF_VOIDP + when SIZEOF_LONG + ary.join().unpack(@template) + when SIZEOF_LONG_LONG + ary.join().unpack(@template) + else + raise(RuntimeError, "sizeof(void*)?") + end + end + + private + + def parse_types(types) + @template = "" + addr = 0 + types.each{|t| + orig_addr = addr + if( t.is_a?(Array) ) + addr = align(orig_addr, ALIGN_MAP[TYPE_VOIDP]) + else + addr = align(orig_addr, ALIGN_MAP[t]) + end + d = addr - orig_addr + if( d > 0 ) + @template << "x#{d}" + end + if( t.is_a?(Array) ) + @template << (PACK_MAP[t[0]] * t[1]) + addr += (SIZE_MAP[t[0]] * t[1]) + else + @template << PACK_MAP[t] + addr += SIZE_MAP[t] + end + } + addr = align(addr, ALIGN_MAP[TYPE_VOIDP]) + @size = addr + end + end +end diff --git a/ext/fiddle/lib/fiddle/struct.rb b/ext/fiddle/lib/fiddle/struct.rb new file mode 100644 index 0000000000..043b3885b3 --- /dev/null +++ b/ext/fiddle/lib/fiddle/struct.rb @@ -0,0 +1,236 @@ +require 'fiddle' +require 'fiddle/value' +require 'fiddle/pack' + +module Fiddle + # C struct shell + class CStruct + # accessor to Fiddle::CStructEntity + def CStruct.entity_class + CStructEntity + end + end + + # C union shell + class CUnion + # accessor to Fiddle::CUnionEntity + def CUnion.entity_class + CUnionEntity + end + end + + # Used to construct C classes (CUnion, CStruct, etc) + # + # Fiddle::Importer#struct and Fiddle::Importer#union wrap this functionality in an + # easy-to-use manner. + module CStructBuilder + # Construct a new class given a C: + # * class +klass+ (CUnion, CStruct, or other that provide an + # #entity_class) + # * +types+ (Fiddle::TYPE_INT, Fiddle::TYPE_SIZE_T, etc., see the C types + # constants) + # * corresponding +members+ + # + # Fiddle::Importer#struct and Fiddle::Importer#union wrap this functionality in an + # easy-to-use manner. + # + # Example: + # + # require 'dl/struct' + # require 'dl/cparser' + # + # include Fiddle::CParser + # + # types, members = parse_struct_signature(['int i','char c']) + # + # MyStruct = Fiddle::CStructBuilder.create(CUnion, types, members) + # + # obj = MyStruct.allocate + # + def create(klass, types, members) + new_class = Class.new(klass){ + define_method(:initialize){|addr| + @entity = klass.entity_class.new(addr, types) + @entity.assign_names(members) + } + define_method(:to_ptr){ @entity } + define_method(:to_i){ @entity.to_i } + members.each{|name| + define_method(name){ @entity[name] } + define_method(name + "="){|val| @entity[name] = val } + } + } + size = klass.entity_class.size(types) + new_class.module_eval(<<-EOS, __FILE__, __LINE__+1) + def new_class.size() + #{size} + end + def new_class.malloc() + addr = Fiddle.malloc(#{size}) + new(addr) + end + EOS + return new_class + end + module_function :create + end + + # A C struct wrapper + class CStructEntity < Fiddle::Pointer + include PackInfo + include ValueUtil + + # Allocates a C struct the +types+ provided. The C function +func+ is + # called when the instance is garbage collected. + def CStructEntity.malloc(types, func = nil) + addr = Fiddle.malloc(CStructEntity.size(types)) + CStructEntity.new(addr, types, func) + end + + # Given +types+, returns the offset for the packed sizes of those types + # + # Fiddle::CStructEntity.size([Fiddle::TYPE_DOUBLE, Fiddle::TYPE_INT, Fiddle::TYPE_CHAR, + # Fiddle::TYPE_VOIDP]) + # => 24 + def CStructEntity.size(types) + offset = 0 + + max_align = types.map { |type, count = 1| + last_offset = offset + + align = PackInfo::ALIGN_MAP[type] + offset = PackInfo.align(last_offset, align) + + (PackInfo::SIZE_MAP[type] * count) + + align + }.max + + PackInfo.align(offset, max_align) + end + + # Wraps the C pointer +addr+ as a C struct with the given +types+. The C + # function +func+ is called when the instance is garbage collected. + # + # See also Fiddle::CPtr.new + def initialize(addr, types, func = nil) + set_ctypes(types) + super(addr, @size, func) + end + + # Set the names of the +members+ in this C struct + def assign_names(members) + @members = members + end + + # Given +types+, calculate the offsets and sizes for the types in the + # struct. + def set_ctypes(types) + @ctypes = types + @offset = [] + offset = 0 + + max_align = types.map { |type, count = 1| + orig_offset = offset + align = ALIGN_MAP[type] + offset = PackInfo.align(orig_offset, align) + + @offset << offset + + offset += (SIZE_MAP[type] * count) + + align + }.max + + @size = PackInfo.align(offset, max_align) + end + + # Fetch struct member +name+ + def [](name) + idx = @members.index(name) + if( idx.nil? ) + raise(ArgumentError, "no such member: #{name}") + end + ty = @ctypes[idx] + if( ty.is_a?(Array) ) + r = super(@offset[idx], SIZE_MAP[ty[0]] * ty[1]) + else + r = super(@offset[idx], SIZE_MAP[ty.abs]) + end + packer = Packer.new([ty]) + val = packer.unpack([r]) + case ty + when Array + case ty[0] + when TYPE_VOIDP + val = val.collect{|v| CPtr.new(v)} + end + when TYPE_VOIDP + val = CPtr.new(val[0]) + else + val = val[0] + end + if( ty.is_a?(Integer) && (ty < 0) ) + return unsigned_value(val, ty) + elsif( ty.is_a?(Array) && (ty[0] < 0) ) + return val.collect{|v| unsigned_value(v,ty[0])} + else + return val + end + end + + # Set struct member +name+, to value +val+ + def []=(name, val) + idx = @members.index(name) + if( idx.nil? ) + raise(ArgumentError, "no such member: #{name}") + end + ty = @ctypes[idx] + packer = Packer.new([ty]) + val = wrap_arg(val, ty, []) + buff = packer.pack([val].flatten()) + super(@offset[idx], buff.size, buff) + if( ty.is_a?(Integer) && (ty < 0) ) + return unsigned_value(val, ty) + elsif( ty.is_a?(Array) && (ty[0] < 0) ) + return val.collect{|v| unsigned_value(v,ty[0])} + else + return val + end + end + + def to_s() # :nodoc: + super(@size) + end + end + + # A C union wrapper + class CUnionEntity < CStructEntity + include PackInfo + + # Allocates a C union the +types+ provided. The C function +func+ is + # called when the instance is garbage collected. + def CUnionEntity.malloc(types, func=nil) + addr = Fiddle.malloc(CUnionEntity.size(types)) + CUnionEntity.new(addr, types, func) + end + + # Given +types+, returns the size needed for the union. + # + # Fiddle::CUnionEntity.size([Fiddle::TYPE_DOUBLE, Fiddle::TYPE_INT, Fiddle::TYPE_CHAR, + # Fiddle::TYPE_VOIDP]) + # => 8 + def CUnionEntity.size(types) + types.map { |type, count = 1| + PackInfo::SIZE_MAP[type] * count + }.max + end + + # Given +types+, calculate the necessary offset and for each union member + def set_ctypes(types) + @ctypes = types + @offset = Array.new(types.length, 0) + @size = self.class.size types + end + end +end + diff --git a/ext/fiddle/lib/fiddle/value.rb b/ext/fiddle/lib/fiddle/value.rb new file mode 100644 index 0000000000..0849e4ac35 --- /dev/null +++ b/ext/fiddle/lib/fiddle/value.rb @@ -0,0 +1,114 @@ +require 'fiddle' + +module Fiddle + module ValueUtil + def unsigned_value(val, ty) + case ty.abs + when TYPE_CHAR + [val].pack("c").unpack("C")[0] + when TYPE_SHORT + [val].pack("s!").unpack("S!")[0] + when TYPE_INT + [val].pack("i!").unpack("I!")[0] + when TYPE_LONG + [val].pack("l!").unpack("L!")[0] + when TYPE_LONG_LONG + [val].pack("q").unpack("Q")[0] + else + val + end + end + + def signed_value(val, ty) + case ty.abs + when TYPE_CHAR + [val].pack("C").unpack("c")[0] + when TYPE_SHORT + [val].pack("S!").unpack("s!")[0] + when TYPE_INT + [val].pack("I!").unpack("i!")[0] + when TYPE_LONG + [val].pack("L!").unpack("l!")[0] + when TYPE_LONG_LONG + [val].pack("Q").unpack("q")[0] + else + val + end + end + + def wrap_args(args, tys, funcs, &block) + result = [] + tys ||= [] + args.each_with_index{|arg, idx| + result.push(wrap_arg(arg, tys[idx], funcs, &block)) + } + result + end + + def wrap_arg(arg, ty, funcs = [], &block) + require 'dl/func' + + funcs ||= [] + case arg + when nil + return 0 + when Pointer + return arg.to_i + when IO + case ty + when TYPE_VOIDP + return Pointer[arg].to_i + else + return arg.to_i + end + when Function + if( block ) + arg.bind_at_call(&block) + funcs.push(arg) + elsif !arg.bound? + raise(RuntimeError, "block must be given.") + end + return arg.to_i + when String + if( ty.is_a?(Array) ) + return arg.unpack('C*') + else + case SIZEOF_VOIDP + when SIZEOF_LONG + return [arg].pack("p").unpack("l!")[0] + when SIZEOF_LONG_LONG + return [arg].pack("p").unpack("q")[0] + else + raise(RuntimeError, "sizeof(void*)?") + end + end + when Float, Integer + return arg + when Array + if( ty.is_a?(Array) ) # used only by struct + case ty[0] + when TYPE_VOIDP + return arg.collect{|v| Integer(v)} + when TYPE_CHAR + if( arg.is_a?(String) ) + return val.unpack('C*') + end + end + return arg + else + return arg + end + else + if( arg.respond_to?(:to_ptr) ) + return arg.to_ptr.to_i + else + begin + return Integer(arg) + rescue + raise(ArgumentError, "unknown argument type: #{arg.class}") + end + end + end + end + end +end diff --git a/test/dl/test_c_struct_entry.rb b/test/dl/test_c_struct_entry.rb index 414f17ba53..a7a0e34644 100644 --- a/test/dl/test_c_struct_entry.rb +++ b/test/dl/test_c_struct_entry.rb @@ -2,52 +2,53 @@ require_relative 'test_base' require 'dl/struct' -class DL::TestCStructEntity < DL::TestBase - def test_class_size - types = [DL::TYPE_DOUBLE, DL::TYPE_CHAR] +module DL + class TestCStructEntity < TestBase + def test_class_size + types = [TYPE_DOUBLE, TYPE_CHAR] - size = DL::CStructEntity.size types + size = CStructEntity.size types - alignments = types.map { |type| DL::PackInfo::ALIGN_MAP[type] } + alignments = types.map { |type| PackInfo::ALIGN_MAP[type] } - expected = DL::PackInfo.align 0, alignments[0] - expected += DL::PackInfo::SIZE_MAP[DL::TYPE_DOUBLE] + expected = PackInfo.align 0, alignments[0] + expected += PackInfo::SIZE_MAP[TYPE_DOUBLE] - expected = DL::PackInfo.align expected, alignments[1] - expected += DL::PackInfo::SIZE_MAP[DL::TYPE_CHAR] + expected = PackInfo.align expected, alignments[1] + expected += PackInfo::SIZE_MAP[TYPE_CHAR] - expected = DL::PackInfo.align expected, alignments.max + expected = PackInfo.align expected, alignments.max - assert_equal expected, size - end + assert_equal expected, size + end - def test_class_size_with_count - size = DL::CStructEntity.size([[DL::TYPE_DOUBLE, 2], [DL::TYPE_CHAR, 20]]) + def test_class_size_with_count + size = CStructEntity.size([[TYPE_DOUBLE, 2], [TYPE_CHAR, 20]]) - types = [DL::TYPE_DOUBLE, DL::TYPE_CHAR] - alignments = types.map { |type| DL::PackInfo::ALIGN_MAP[type] } + types = [TYPE_DOUBLE, TYPE_CHAR] + alignments = types.map { |type| PackInfo::ALIGN_MAP[type] } - expected = DL::PackInfo.align 0, alignments[0] - expected += DL::PackInfo::SIZE_MAP[DL::TYPE_DOUBLE] * 2 + expected = PackInfo.align 0, alignments[0] + expected += PackInfo::SIZE_MAP[TYPE_DOUBLE] * 2 - expected = DL::PackInfo.align expected, alignments[1] - expected += DL::PackInfo::SIZE_MAP[DL::TYPE_CHAR] * 20 + expected = PackInfo.align expected, alignments[1] + expected += PackInfo::SIZE_MAP[TYPE_CHAR] * 20 - expected = DL::PackInfo.align expected, alignments.max + expected = PackInfo.align expected, alignments.max - assert_equal expected, size - end + assert_equal expected, size + end - def test_set_ctypes - union = DL::CStructEntity.malloc [DL::TYPE_INT, DL::TYPE_LONG] - union.assign_names %w[int long] + def test_set_ctypes + union = CStructEntity.malloc [TYPE_INT, TYPE_LONG] + union.assign_names %w[int long] - # this test is roundabout because the stored ctypes are not accessible - union['long'] = 1 - union['int'] = 2 + # this test is roundabout because the stored ctypes are not accessible + union['long'] = 1 + union['int'] = 2 - assert_equal 1, union['long'] - assert_equal 2, union['int'] + assert_equal 1, union['long'] + assert_equal 2, union['int'] + end end end - diff --git a/test/dl/test_c_union_entity.rb b/test/dl/test_c_union_entity.rb index 29b9e1054a..09f7c60e4c 100644 --- a/test/dl/test_c_union_entity.rb +++ b/test/dl/test_c_union_entity.rb @@ -2,29 +2,30 @@ require_relative 'test_base' require 'dl/struct' -class DL::TestCUnionEntity < DL::TestBase - def test_class_size - size = DL::CUnionEntity.size([DL::TYPE_DOUBLE, DL::TYPE_CHAR]) +module DL + class TestCUnionEntity < TestBase + def test_class_size + size = CUnionEntity.size([TYPE_DOUBLE, TYPE_CHAR]) - assert_equal DL::SIZEOF_DOUBLE, size - end + assert_equal SIZEOF_DOUBLE, size + end - def test_class_size_with_count - size = DL::CUnionEntity.size([[DL::TYPE_DOUBLE, 2], [DL::TYPE_CHAR, 20]]) + def test_class_size_with_count + size = CUnionEntity.size([[TYPE_DOUBLE, 2], [TYPE_CHAR, 20]]) - assert_equal DL::SIZEOF_CHAR * 20, size - end + assert_equal SIZEOF_CHAR * 20, size + end - def test_set_ctypes - union = DL::CUnionEntity.malloc [DL::TYPE_INT, DL::TYPE_LONG] - union.assign_names %w[int long] + def test_set_ctypes + union = CUnionEntity.malloc [TYPE_INT, TYPE_LONG] + union.assign_names %w[int long] - # this test is roundabout because the stored ctypes are not accessible - union['long'] = 1 - assert_equal 1, union['long'] + # this test is roundabout because the stored ctypes are not accessible + union['long'] = 1 + assert_equal 1, union['long'] - union['int'] = 1 - assert_equal 1, union['int'] + union['int'] = 1 + assert_equal 1, union['int'] + end end end - diff --git a/test/dl/test_cparser.rb b/test/dl/test_cparser.rb index cea9ac73b0..a4deb6733e 100644 --- a/test/dl/test_cparser.rb +++ b/test/dl/test_cparser.rb @@ -4,30 +4,30 @@ require 'dl/cparser' module DL class TestCParser < TestBase - include DL::CParser + include CParser def test_uint_ctype - assert_equal(-DL::TYPE_INT, parse_ctype('uint')) + assert_equal(-TYPE_INT, parse_ctype('uint')) end def test_size_t_ctype - assert_equal(DL::TYPE_SIZE_T, parse_ctype("size_t")) + assert_equal(TYPE_SIZE_T, parse_ctype("size_t")) end def test_ssize_t_ctype - assert_equal(DL::TYPE_SSIZE_T, parse_ctype("ssize_t")) + assert_equal(TYPE_SSIZE_T, parse_ctype("ssize_t")) end def test_ptrdiff_t_ctype - assert_equal(DL::TYPE_PTRDIFF_T, parse_ctype("ptrdiff_t")) + assert_equal(TYPE_PTRDIFF_T, parse_ctype("ptrdiff_t")) end def test_intptr_t_ctype - assert_equal(DL::TYPE_INTPTR_T, parse_ctype("intptr_t")) + assert_equal(TYPE_INTPTR_T, parse_ctype("intptr_t")) end def test_uintptr_t_ctype - assert_equal(DL::TYPE_UINTPTR_T, parse_ctype("uintptr_t")) + assert_equal(TYPE_UINTPTR_T, parse_ctype("uintptr_t")) end end end diff --git a/test/dl/test_import.rb b/test/dl/test_import.rb index 41def7c913..8b3f39b851 100644 --- a/test/dl/test_import.rb +++ b/test/dl/test_import.rb @@ -35,7 +35,7 @@ module DL ] CallCallback = bind("void call_callback(void*, void*)"){|ptr1, ptr2| - f = Function.new(CFunc.new(ptr1.to_i, DL::TYPE_VOID, ""), [TYPE_VOIDP]) + f = Function.new(CFunc.new(ptr1.to_i, TYPE_VOID, ""), [TYPE_VOIDP]) f.call(ptr2) } CarriedFunction = bind("void callback_function(void*)", :carried, 0) @@ -45,7 +45,7 @@ module DL def test_ensure_call_dlload err = assert_raises(RuntimeError) do Class.new do - extend DL::Importer + extend Importer extern "void *strcpy(char*, char*)" end end @@ -59,7 +59,7 @@ module DL end def test_sizeof() - assert_equal(DL::SIZEOF_VOIDP, LIBC.sizeof("FILE*")) + assert_equal(SIZEOF_VOIDP, LIBC.sizeof("FILE*")) assert_equal(LIBC::MyStruct.size(), LIBC.sizeof(LIBC::MyStruct)) end @@ -71,7 +71,7 @@ module DL end def test_io() - if( RUBY_PLATFORM != DL::BUILD_RUBY_PLATFORM ) + if( RUBY_PLATFORM != BUILD_RUBY_PLATFORM ) return end io_in,io_out = IO.pipe() diff --git a/test/fiddle/test_c_struct_entry.rb b/test/fiddle/test_c_struct_entry.rb new file mode 100644 index 0000000000..43cf28e493 --- /dev/null +++ b/test/fiddle/test_c_struct_entry.rb @@ -0,0 +1,54 @@ +require_relative 'helper' + +require 'fiddle/struct' + +module Fiddle + class TestCStructEntity < TestCase + def test_class_size + types = [TYPE_DOUBLE, TYPE_CHAR] + + size = CStructEntity.size types + + alignments = types.map { |type| PackInfo::ALIGN_MAP[type] } + + expected = PackInfo.align 0, alignments[0] + expected += PackInfo::SIZE_MAP[TYPE_DOUBLE] + + expected = PackInfo.align expected, alignments[1] + expected += PackInfo::SIZE_MAP[TYPE_CHAR] + + expected = PackInfo.align expected, alignments.max + + assert_equal expected, size + end + + def test_class_size_with_count + size = CStructEntity.size([[TYPE_DOUBLE, 2], [TYPE_CHAR, 20]]) + + types = [TYPE_DOUBLE, TYPE_CHAR] + alignments = types.map { |type| PackInfo::ALIGN_MAP[type] } + + expected = PackInfo.align 0, alignments[0] + expected += PackInfo::SIZE_MAP[TYPE_DOUBLE] * 2 + + expected = PackInfo.align expected, alignments[1] + expected += PackInfo::SIZE_MAP[TYPE_CHAR] * 20 + + expected = PackInfo.align expected, alignments.max + + assert_equal expected, size + end + + def test_set_ctypes + union = CStructEntity.malloc [TYPE_INT, TYPE_LONG] + union.assign_names %w[int long] + + # this test is roundabout because the stored ctypes are not accessible + union['long'] = 1 + union['int'] = 2 + + assert_equal 1, union['long'] + assert_equal 2, union['int'] + end + end +end diff --git a/test/fiddle/test_c_union_entity.rb b/test/fiddle/test_c_union_entity.rb new file mode 100644 index 0000000000..d27c9edb52 --- /dev/null +++ b/test/fiddle/test_c_union_entity.rb @@ -0,0 +1,31 @@ +require_relative 'helper' + +require 'fiddle/struct' + +module Fiddle + class TestCUnionEntity < TestCase + def test_class_size + size = CUnionEntity.size([TYPE_DOUBLE, TYPE_CHAR]) + + assert_equal SIZEOF_DOUBLE, size + end + + def test_class_size_with_count + size = CUnionEntity.size([[TYPE_DOUBLE, 2], [TYPE_CHAR, 20]]) + + assert_equal SIZEOF_CHAR * 20, size + end + + def test_set_ctypes + union = CUnionEntity.malloc [TYPE_INT, TYPE_LONG] + union.assign_names %w[int long] + + # this test is roundabout because the stored ctypes are not accessible + union['long'] = 1 + assert_equal 1, union['long'] + + union['int'] = 1 + assert_equal 1, union['int'] + end + end +end diff --git a/test/fiddle/test_cparser.rb b/test/fiddle/test_cparser.rb new file mode 100644 index 0000000000..32ef82fc6e --- /dev/null +++ b/test/fiddle/test_cparser.rb @@ -0,0 +1,33 @@ +require_relative 'helper' + +require 'fiddle/cparser' + +module Fiddle + class TestCParser < TestCase + include CParser + + def test_uint_ctype + assert_equal(-TYPE_INT, parse_ctype('uint')) + end + + def test_size_t_ctype + assert_equal(TYPE_SIZE_T, parse_ctype("size_t")) + end + + def test_ssize_t_ctype + assert_equal(TYPE_SSIZE_T, parse_ctype("ssize_t")) + end + + def test_ptrdiff_t_ctype + assert_equal(TYPE_PTRDIFF_T, parse_ctype("ptrdiff_t")) + end + + def test_intptr_t_ctype + assert_equal(TYPE_INTPTR_T, parse_ctype("intptr_t")) + end + + def test_uintptr_t_ctype + assert_equal(TYPE_UINTPTR_T, parse_ctype("uintptr_t")) + end + end +end diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb new file mode 100644 index 0000000000..a2e31abf0b --- /dev/null +++ b/test/fiddle/test_import.rb @@ -0,0 +1,136 @@ +# coding: US-ASCII +require_relative 'helper' +require 'fiddle/import' + +module Fiddle + module LIBC + extend Importer + dlload LIBC_SO, LIBM_SO + + typealias 'string', 'char*' + typealias 'FILE*', 'void*' + + extern "void *strcpy(char*, char*)" + extern "int isdigit(int)" + extern "double atof(string)" + extern "unsigned long strtoul(char*, char **, int)" + extern "int qsort(void*, unsigned long, unsigned long, void*)" + extern "int fprintf(FILE*, char*)" + extern "int gettimeofday(timeval*, timezone*)" rescue nil + + BoundQsortCallback = bind("void *bound_qsort_callback(void*, void*)"){|ptr1,ptr2| ptr1[0] <=> ptr2[0]} + Timeval = struct [ + "long tv_sec", + "long tv_usec", + ] + Timezone = struct [ + "int tz_minuteswest", + "int tz_dsttime", + ] + MyStruct = struct [ + "short num[5]", + "char c", + "unsigned char buff[7]", + ] + + CallCallback = bind("void call_callback(void*, void*)"){ | ptr1, ptr2| + f = Function.new(ptr1.to_i, [TYPE_VOIDP], TYPE_VOID) + f.call(ptr2) + } + end + + class TestImport < TestCase + def test_ensure_call_dlload + err = assert_raises(RuntimeError) do + Class.new do + extend Importer + extern "void *strcpy(char*, char*)" + end + end + assert_match(/call dlload before/, err.message) + end + + def test_malloc() + s1 = LIBC::Timeval.malloc() + s2 = LIBC::Timeval.malloc() + refute_equal(s1.to_ptr.to_i, s2.to_ptr.to_i) + end + + def test_sizeof() + assert_equal(SIZEOF_VOIDP, LIBC.sizeof("FILE*")) + assert_equal(LIBC::MyStruct.size(), LIBC.sizeof(LIBC::MyStruct)) + end + + def test_unsigned_result() + d = (2 ** 31) + 1 + + r = LIBC.strtoul(d.to_s, 0, 0) + assert_equal(d, r) + end + + def test_io() + if( RUBY_PLATFORM != BUILD_RUBY_PLATFORM ) + return + end + io_in,io_out = IO.pipe() + LIBC.fprintf(io_out, "hello") + io_out.flush() + io_out.close() + str = io_in.read() + io_in.close() + assert_equal("hello", str) + end + + def test_value() + i = LIBC.value('int', 2) + assert_equal(2, i.value) + + d = LIBC.value('double', 2.0) + assert_equal(2.0, d.value) + + ary = LIBC.value('int[3]', [0,1,2]) + assert_equal([0,1,2], ary.value) + end + + def test_struct() + s = LIBC::MyStruct.malloc() + s.num = [0,1,2,3,4] + s.c = ?a.ord + s.buff = "012345\377" + assert_equal([0,1,2,3,4], s.num) + assert_equal(?a.ord, s.c) + assert_equal([?0.ord,?1.ord,?2.ord,?3.ord,?4.ord,?5.ord,?\377.ord], s.buff) + end + + def test_gettimeofday() + if( defined?(LIBC.gettimeofday) ) + timeval = LIBC::Timeval.malloc() + timezone = LIBC::Timezone.malloc() + LIBC.gettimeofday(timeval, timezone) + cur = Time.now() + assert(cur.to_i - 2 <= timeval.tv_sec && timeval.tv_sec <= cur.to_i) + end + end + + def test_strcpy() + buff = "000" + str = LIBC.strcpy(buff, "123") + assert_equal("123", buff) + assert_equal("123", str.to_s) + end + + def test_isdigit + r1 = LIBC.isdigit(?1.ord) + r2 = LIBC.isdigit(?2.ord) + rr = LIBC.isdigit(?r.ord) + assert_operator(r1, :>, 0) + assert_operator(r2, :>, 0) + assert_equal(0, rr) + end + + def test_atof + r = LIBC.atof("12.34") + assert_includes(12.00..13.00, r) + end + end +end