class Inline::C
Inline::C
is the default builder used and the only one provided by Inline
. It can be used as a template to write builders for other languages. It understands type-conversions for the basic types and can be extended as needed using add_type_converter
, alias_type_converter
and remove_type_converter
.
Constants
Attributes
Public Class Methods
# File lib/inline.rb, line 397 def initialize(mod) raise ArgumentError, "Class/Module arg is required" unless Module === mod # new (but not on some 1.8s) -> inline -> real_caller|eval stack = caller meth = stack.shift until meth =~ /in .(inline|test_|setup)/ or stack.empty? raise "Couldn't discover caller" if stack.empty? real_caller = stack.first real_caller = stack[3] if real_caller =~ /\(eval\)/ real_caller =~ /(.*):(\d+)/ real_caller = $1 @rb_file = File.expand_path real_caller @mod = mod @pre = [] @src = [] @inc = [] @sig = {} @flags = [] @libs = [] @init_extra = [] @include_ruby_first = true @inherited_methods = {} @struct_name = nil @type_map = TYPE_MAP.dup end
Public Instance Methods
Adds a reader
and writer
for a C
struct member wrapped via Data_Wrap_Struct. method
is the ruby name to give the accessor, type
is the C
type. Unless the C
member name is overridden with member
, the method name is used as the struct member.
builder.struct_name = 'MyStruct' builder.accessor :title, 'char *' builder.accessor :stream_index, 'int', :index
The latter accesses MyStruct->index via the stream_index method.
# File lib/inline.rb, line 436 def accessor(method, type, member = method) reader method, type, member writer method, type, member end
Adds compiler options to the compiler command line. No preprocessing is done, so you must have all your dashes and everything.
# File lib/inline.rb, line 669 def add_compile_flags(*flags) @flags.push(*flags) end
Registers a static id_name for the symbol :name.
# File lib/inline.rb, line 676 def add_id name self.add_static "id_#{name}", "rb_intern(\"#{name}\")" end
Adds linker flags to the link command line. No preprocessing is done, so you must have all your dashes and everything.
# File lib/inline.rb, line 684 def add_link_flags(*flags) @libs.push(*flags) end
Create a static variable and initialize it to a value.
# File lib/inline.rb, line 691 def add_static name, init, type = "VALUE" prefix "static #{type} #{name};" add_to_init "#{name} = #{init};" end
Adds custom content to the end of the init function.
# File lib/inline.rb, line 699 def add_to_init(*src) @init_extra.push(*src) end
Registers C
type-casts r2c
and c2r
for type
.
# File lib/inline.rb, line 706 def add_type_converter(type, r2c, c2r) warn "WAR\NING: overridding #{type} on #{caller[0]}" if @type_map.has_key? type @type_map[type] = [r2c, c2r] end
Registers C
type alias_type
as an alias of existing_type
# File lib/inline.rb, line 714 def alias_type_converter(existing_type, alias_type) warn "WAR\NING: overridding #{type} on #{caller[0]}" if @type_map.has_key? alias_type @type_map[alias_type] = @type_map[existing_type] end
Builds the source file, if needed, and attempts to compile it.
# File lib/inline.rb, line 527 def build so_name = self.so_name so_exists = File.file? so_name unless so_exists and File.mtime(rb_file) < File.mtime(so_name) then unless File.directory? Inline.directory then warn "NOTE: creating #{Inline.directory} for RubyInline" if $DEBUG FileUtils.mkdir_p Inline.directory, :mode => 0700 end src_name = "#{Inline.directory}/#{module_name}.c" old_src_name = File.write_with_backup src_name, generate_ext # recompile only if the files are different recompile = true if so_exists and old_src_name and FileUtils.compare_file(old_src_name, src_name) then recompile = false # Updates the timestamps on all the generated/compiled files. # Prevents us from entering this conditional unless the source # file changes again. t = Time.now File.utime(t, t, src_name, old_src_name, so_name) end if recompile then unless RbConfig::CONFIG["ENABLE_SHARED"] == "yes" then raise "this ruby isn't configured for dynamic linking" end hdrdir = %w(srcdir includedir archdir rubyhdrdir).map { |name| RbConfig::CONFIG[name] }.find { |dir| dir and File.exist? File.join(dir, "ruby.h") } or abort "ERROR: Can't find header dir for ruby. Exiting..." flags = @flags.join(' ') @libs << RbConfig::CONFIG['LIBRUBYARG_SHARED'] libs = @libs.join(' ') config_hdrdir = if RbConfig::CONFIG['rubyarchhdrdir'] then "-I #{RbConfig::CONFIG['rubyarchhdrdir']}" elsif RUBY_VERSION > '1.9' then "-I #{File.join hdrdir, RbConfig::CONFIG['arch']}" else nil end windoze = WINDOZE and RUBY_PLATFORM =~ /mswin/ sane = ! windoze cmd = [ RbConfig::CONFIG['LDSHARED'], flags, (RbConfig::CONFIG['DLDFLAGS'] if sane), (RbConfig::CONFIG['CCDLFLAGS'] if sane), RbConfig::CONFIG['CFLAGS'], (RbConfig::CONFIG['LDFLAGS'] if sane), '-I', hdrdir, config_hdrdir, '-I', RbConfig::CONFIG['includedir'], ("-L#{RbConfig::CONFIG['libdir']}" if sane), (['-o', so_name.inspect] if sane), File.expand_path(src_name).inspect, libs, crap_for_windoze, (RbConfig::CONFIG['LDFLAGS'] if windoze), (RbConfig::CONFIG['CCDLFLAGS'] if windoze), ].compact.join(' ') # odd compilation error on clang + freebsd 10. Ruby built w/ rbenv. cmd = cmd.gsub(/-Wl,-soname,\$@/, "-Wl,-soname,#{File.basename so_name}") # strip off some makefile macros for mingw 1.9 cmd = cmd.gsub(/\$\(.*\)/, '') if RUBY_PLATFORM =~ /mingw/ cmd += " 2> #{DEV_NULL}" if $TESTING and not $DEBUG warn "Building #{so_name} with '#{cmd}'" if $DEBUG result = if WINDOZE Dir.chdir(Inline.directory) { `#{cmd}` } else `#{cmd}` end warn "Output:\n#{result}" if $DEBUG if $? != 0 then bad_src_name = src_name + ".bad" File.rename src_name, bad_src_name raise CompilationError, "error executing #{cmd.inspect}: #{$?}\nRenamed #{src_name} to #{bad_src_name}" end # NOTE: manifest embedding is only required when using VC8 ruby # build or compiler. # Errors from this point should be ignored if RbConfig::CONFIG['arch'] # (RUBY_PLATFORM) matches 'i386-mswin32_80' if WINDOZE and RUBY_PLATFORM =~ /_80$/ then Dir.chdir Inline.directory do cmd = "mt /manifest lib.so.manifest /outputresource:so.dll;#2" warn "Embedding manifest with '#{cmd}'" if $DEBUG result = `#{cmd}` warn "Output:\n#{result}" if $DEBUG if $? != 0 then raise CompilationError, "error executing #{cmd}: #{$?}" end end end warn "Built successfully" if $DEBUG end else warn "#{so_name} is up to date" if $DEBUG end # unless (file is out of date) end
Adds a C
function to the source, including performing automatic type conversion to arguments and the return value. The Ruby method name can be overridden by providing method_name. Unknown type conversions can be extended by using add_type_converter
.
# File lib/inline.rb, line 784 def c src, options = {} options = { :expand_types => true, }.merge options self.generate src, options end
Converts C
type type
to a ruby type
# File lib/inline.rb, line 496 def c2ruby(type) raise ArgumentError, "Unknown type #{type.inspect}" unless @type_map.has_key? type @type_map[type].last end
Adds a raw C
function to the source. This version does not perform any type conversion and must conform to the ruby/C coding conventions. The Ruby method name can be overridden by providing method_name.
# File lib/inline.rb, line 808 def c_raw src, options = {} self.generate src, options end
Same as c_raw
, but adds a class function.
# File lib/inline.rb, line 815 def c_raw_singleton src, options = {} options = { :singleton => true, }.merge options self.generate src, options end
Same as c
, but adds a class function.
# File lib/inline.rb, line 794 def c_singleton src, options = {} options = { :expand_types => true, :singleton => true, }.merge options self.generate src, options end
Returns extra compilation flags for windoze platforms. Ugh.
# File lib/inline.rb, line 649 def crap_for_windoze # gawd windoze land sucks case RUBY_PLATFORM when /mswin32/ then " -link /OUT:\"#{self.so_name}\" /LIBPATH:\"#{RbConfig::CONFIG['libdir']}\" /DEFAULTLIB:\"#{RbConfig::CONFIG['LIBRUBY']}\" /INCREMENTAL:no /EXPORT:Init_#{module_name}" when /mingw32/ then c = RbConfig::CONFIG " -Wl,--enable-auto-import -L#{c['libdir']} -l#{c['RUBY_SO_NAME']} -o #{so_name.inspect}" when /i386-cygwin/ then ' -L/usr/local/lib -lruby.dll' else '' end end
# File lib/inline.rb, line 237 def generate(src, options={}) options = {:expand_types=>options} unless Hash === options expand_types = options[:expand_types] singleton = options[:singleton] result = self.strip_comments(src) signature = parse_signature(src, !expand_types) function_name = signature['name'] method_name = options[:method_name] method_name ||= test_to_normal function_name return_type = signature['return'] arity = options[:arity] || signature['arity'] raise ArgumentError, "too many arguments" if arity > MAGIC_ARITY_THRESHOLD if expand_types then prefix = "static VALUE #{function_name}(" if arity <= MAGIC_ARITY then prefix += "int argc, VALUE *argv, VALUE self" else prefix += "VALUE self" prefix += signature['args'].map { |arg, type| ", VALUE _#{arg}"}.join end prefix += ") {\n" prefix += signature['args'].map { |arg, type| " #{type} #{arg} = #{ruby2c(type)}(_#{arg});\n" }.join # replace the function signature (hopefully) with new sig (prefix) result.sub!(/[^;\/\"\>]+#{function_name}\s*\([^\{]+\{/, "\n" + prefix) result.sub!(/\A\n/, '') # strip off the \n in front in case we added it unless return_type == "void" then raise SyntaxError, "Couldn't find return statement for #{function_name}" unless result =~ /return/ result.gsub!(/return\s+([^\;\}]+)/) do "return #{c2ruby(return_type)}(#{$1})" end else result.sub!(/\s*\}\s*\Z/, "\nreturn Qnil;\n}") end else prefix = "static #{return_type} #{function_name}(" result.sub!(/[^;\/\"\>]+#{function_name}\s*\(/, prefix) result.sub!(/\A\n/, '') # strip off the \n in front in case we added it end delta = if result =~ /\A(static.*?\{)/m then $1.split(/\n/).size else msg = "WAR\NING: Can't find signature in #{result.inspect}\n" warn msg unless $TESTING 0 end file, line = $1, $2 if caller[1] =~ /(.*?):(\d+)/ result = "# line #{line.to_i + delta} \"#{file}\"\n" + result unless $DEBUG and not $TESTING @src << result @sig[function_name] = [arity,singleton,method_name] return result if $TESTING end
Builds a complete C
extension suitable for writing to a file and compiling.
# File lib/inline.rb, line 307 def generate_ext ext = [] if @include_ruby_first @inc.unshift "#include \"ruby.h\"" else @inc.push "#include \"ruby.h\"" end ext << @inc ext << nil unless @pre.empty? then ext << @pre.join("\n\n") ext << nil end ext << @src.join("\n\n") ext << nil ext << nil ext << "#ifdef __cplusplus" ext << "extern \"C\" {" ext << "#endif" ext << " __declspec(dllexport)" if WINDOZE ext << " void Init_#{module_name}(void) {" ext << " VALUE c = rb_cObject;" # TODO: use rb_class2path # ext << " VALUE c = rb_path2class(#{@mod.name.inspect});" ext << @mod.name.split("::").map { |n| " c = rb_const_get(c, rb_intern(\"#{n}\"));" }.join("\n") ext << nil @sig.keys.sort.each do |name| method = [] arity, singleton, method_name = @sig[name] if singleton then if method_name == 'allocate' then raise "#{@mod}::allocate must have an arity of zero" if arity > 0 ext << " rb_define_alloc_func(c, (VALUE(*)(VALUE))#{name});" next end method << " rb_define_singleton_method(c, \"#{method_name}\", " else method << " rb_define_method(c, \"#{method_name}\", " end method << "(VALUE(*)(ANYARGS))#{name}, #{arity});" ext << method.join end ext << @init_extra.join("\n") unless @init_extra.empty? ext << nil ext << " }" ext << "#ifdef __cplusplus" ext << "}" ext << "#endif" ext << nil ext.join "\n" end
Adds an include to the top of the file. Don’t forget to use quotes or angle brackets.
# File lib/inline.rb, line 759 def include(header) @inc << "#include #{header}" end
Specifies that the the ruby.h header should be included after custom header(s) instead of before them.
# File lib/inline.rb, line 767 def include_ruby_last @include_ruby_first = false end
Loads the generated code back into ruby
# File lib/inline.rb, line 520 def load require "#{so_name}" or raise LoadError, "require on #{so_name} failed" end
Attempts to load pre-generated code returning true if it succeeds.
# File lib/inline.rb, line 504 def load_cache begin file = File.join("inline", File.basename(so_name)) if require file then dir = Inline.directory warn "WAR\NING: #{dir} exists but is not being used" if test ?d, dir and $VERBOSE return true end rescue LoadError end return false end
Maps a C
constant to ruby. names_and_types
is a hash that maps the name of the constant to its C
type.
builder.map_c_const :C_NAME => :int
If you wish to give the constant a different ruby name:
builder.map_c_const :C_NAME => [:int, :RUBY_NAME]
# File lib/inline.rb, line 748 def map_c_const(names_and_types) names_and_types.each do |name, typ| typ, ruby_name = Array === typ ? typ : [typ, name] self.add_to_init " rb_define_const(c, #{ruby_name.to_s.inspect}, #{c2ruby(typ.to_s)}(#{name}));" end end
Maps RubyConstants to cRubyConstants.
# File lib/inline.rb, line 731 def map_ruby_const(*names) names.each do |name| self.prefix "static VALUE c#{name};" self.add_to_init " c#{name} = rb_const_get(c, rb_intern(#{name.to_s.inspect}));" end end
# File lib/inline.rb, line 369 def module_name unless defined? @module_name then module_name = @mod.name.gsub('::','__') md5 = Digest::MD5.new @pre.each { |m| md5 << m.to_s } @sig.keys.sort_by { |x| x.to_s }.each { |m| md5 << m.to_s } @module_name = "Inline_#{module_name}_#{md5}" end @module_name end
# File lib/inline.rb, line 194 def parse_signature(src, raw=false) sig = self.strip_comments(src) # strip preprocessor directives sig.gsub!(/^\s*\#.*(\\\n.*)*/, '') # strip {}s sig.gsub!(/\{[^\}]*\}/, '{ }') # clean and collapse whitespace sig.gsub!(/\s+/, ' ') unless defined? @types then @types = 'void|' + @type_map.keys.map{|x| Regexp.escape(x)}.join('|') end if /(#{@types})\s*(\w+)\s*\(([^)]*)\)/ =~ sig then return_type, function_name, arg_string = $1, $2, $3 args = [] arg_string.split(',').each do |arg| # helps normalize into 'char * varname' form arg = arg.gsub(/\s*\*\s*/, ' * ').strip if /(((#{@types})\s*\*?)+)\s+(\w+)\s*$/ =~ arg then args.push([$4, $1]) elsif arg != "void" then warn "WAR\NING: '#{arg}' not understood" end end arity = args.size arity = MAGIC_ARITY if raw return { 'return' => return_type, 'name' => function_name, 'args' => args, 'arity' => arity } end raise SyntaxError, "Can't parse signature: #{sig}" end
Adds any amount of text/code to the source
# File lib/inline.rb, line 774 def prefix(code) @pre << code end
Adds a reader for a C
struct member wrapped via Data_Wrap_Struct. method
is the ruby name to give the reader, type
is the C
type. Unless the C
member name is overridden with member
, the method name is used as the struct member. See accessor
for an example.
# File lib/inline.rb, line 447 def reader(method, type, member = method) raise "struct name not set for reader #{method} #{type}" unless @struct_name c <<-C VALUE #{method}(void) { #{@struct_name} *pointer; Data_Get_Struct(self, #{@struct_name}, pointer); return #{c2ruby type}(pointer->#{member}); } C end
Unregisters C
type-casts for type
.
# File lib/inline.rb, line 724 def remove_type_converter(type) @type_map.delete type end
Converts ruby type type
to a C
type
# File lib/inline.rb, line 488 def ruby2c(type) raise ArgumentError, "Unknown type #{type.inspect}" unless @type_map.has_key? type @type_map[type].first end
# File lib/inline.rb, line 380 def so_name unless defined? @so_name then @so_name = "#{Inline.directory}/#{module_name}.#{RbConfig::CONFIG["DLEXT"]}" end @so_name end
# File lib/inline.rb, line 185 def strip_comments(src) # strip c-comments src = src.gsub(%r%\s*/\*.*?\*/%m, '') # strip cpp-comments src = src.gsub(%r%^\s*//.*?\n%, '') src = src.gsub(%r%[ \t]*//[^\n]*%, '') src end
Adds a writer for a C
struct member wrapped via Data_Get_Struct. method
is the ruby name to give the writer, type
is the C
type. Unless the C
member name is overridden with member
, the method name is used as the struct member. See accessor
for an example.
# File lib/inline.rb, line 468 def writer(method, type, member = method) raise "struct name not set for writer #{method} #{type}" unless @struct_name c <<-C VALUE #{method}_equals(VALUE value) { #{@struct_name} *pointer; Data_Get_Struct(self, #{@struct_name}, pointer); pointer->#{member} = #{ruby2c type}(value); return value; } C end