ruby-FFI-utilities 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,90 @@
1
+ # Ruby FFI::Utilities
2
+
3
+ [![Build Status](https://travis-ci.org/nicb/ruby-FFI-utilities.svg?branch=master)](https://travis-ci.org/nicb/ruby-FFI-utilities)
4
+ [![Code Climate](https://codeclimate.com/github/nicb/ruby-FFI-utilities/badges/gpa.svg)](https://codeclimate.com/github/nicb/ruby-FFI-utilities)
5
+ [![Test Coverage](https://codeclimate.com/github/nicb/ruby-FFI-utilities/badges/coverage.svg)](https://codeclimate.com/github/nicb/ruby-FFI-utilities/coverage)
6
+ [![Issue Count](https://codeclimate.com/github/nicb/ruby-FFI-utilities/badges/issue_count.svg)](https://codeclimate.com/github/nicb/ruby-FFI-utilities)
7
+
8
+ Utilities for the `FFI` (*Foreign Function Interface*) library for Ruby
9
+
10
+ ## Description
11
+
12
+ The `FFI` library is fantastic in many ways. Sometimes we wish to have some
13
+ extra features to be added upon request. We put these features in this
14
+ `FFI::Utilities` library.
15
+
16
+ ## Installation
17
+
18
+ Add this line to your application's Gemfile:
19
+
20
+ ```ruby
21
+ gem 'ruby-FFI-utilities'
22
+ ```
23
+
24
+ And then execute:
25
+
26
+ $ bundle
27
+
28
+ Or install it yourself as:
29
+
30
+ $ gem install ruby-FFI-utilities
31
+
32
+ ## Implemented Utilities
33
+
34
+ Currently implemented utility functions are:
35
+
36
+ * `set_args(array_of_string_arguments)` - transforms a `ruby` string array
37
+ into an array suitable to be passed to a `C` function that accepts a `char *[]` argument
38
+ * `set_string(const char *string)` - transforms a `ruby` string
39
+ into a string pointer suitable to be passed to a `C` function that accepts a
40
+ `const char *` or a `char *` argument
41
+
42
+ There are also two wrappers for `FFI::Struct` and `FFI::ManagedStruct`,
43
+ respectively called `FFI::Utilities::Struct` and
44
+ `FFI::Utilities::ManagedStruct. These two wrappers implement some features,
45
+ such as:
46
+
47
+ * attribute accessors
48
+ * attribute `char` accessors, which implement single `char` type access to
49
+ data structures (separated from usual attribute accessors because `ruby`
50
+ does not make a difference between single-char and multiple-char `String`s
51
+ * private `new` method
52
+ * public `create(*args) [{ |this, *args| ... }]` method, which substitutes `new`
53
+ * `struct_initialize(*args)` private function which mimicks the functionality
54
+ of `initialize` but gets called by `create`
55
+
56
+ ## Development
57
+
58
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
59
+
60
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
61
+
62
+ ## Contributing
63
+
64
+ 1. Fork it ( https://github.com/[my-github-username]/ruby-FFI-utilities/fork )
65
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
66
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
67
+ 4. Push to the branch (`git push origin my-new-feature`)
68
+ 5. Create a new Pull Request
69
+
70
+ ## License
71
+
72
+ GNU GENERAL PUBLIC LICENSE
73
+ Version 2, June 1991
74
+
75
+ Ruby FFI Utilities
76
+ Copyright (C) 2015 Nicola Bernardini
77
+
78
+ This program is free software; you can redistribute it and/or modify
79
+ it under the terms of the GNU General Public License as published by
80
+ the Free Software Foundation; either version 2 of the License, or
81
+ (at your option) any later version.
82
+
83
+ This program is distributed in the hope that it will be useful,
84
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
85
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
86
+ GNU General Public License for more details.
87
+
88
+ You should have received a copy of the GNU General Public License along
89
+ with this program; if not, write to the Free Software Foundation, Inc.,
90
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
@@ -0,0 +1,21 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ begin
4
+ require 'byebug'
5
+ require 'rspec/core/rake_task'
6
+
7
+ #
8
+ # FIXME: we need to set the ruby options to -W0 to silence the warnings connected
9
+ # to the reassignement of the RUBY_PLATFORM constant value for testing purposes
10
+ #
11
+ RSpec::Core::RakeTask.new(:spec => 'fixtures:C:build') { |t| t.ruby_opts = '-W0' }
12
+ rescue LoadError
13
+ # no rspec available
14
+ end
15
+
16
+ task :default => :spec
17
+
18
+ #
19
+ # Load all other rake tasks that reside in lib/tasks
20
+ #
21
+ Dir.glob(File.expand_path(File.join('..', 'lib', 'tasks', '**', '*.rake'), __FILE__)).each { |f| load f }
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'FFI/utilities'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require 'pry'
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,17 @@
1
+ require 'ffi'
2
+
3
+ module FFI
4
+ module Utilities
5
+ PATH = File.expand_path(File.join('..', 'utilities'), __FILE__)
6
+ end
7
+ end
8
+
9
+ %w(
10
+ version
11
+ deprecated
12
+ suffixes
13
+ set_argv
14
+ set_string
15
+ struct_extensions
16
+ struct
17
+ ).each { |f| require File.join(FFI::Utilities::PATH, f) }
@@ -0,0 +1,15 @@
1
+ module FFI
2
+
3
+ module Utilities
4
+
5
+ class << self
6
+
7
+ def deprecated(msg)
8
+ $stderr.puts("WARNING: DEPRECATED: #{msg}. This will soon be removed from sources")
9
+ end
10
+
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,28 @@
1
+ module FFI
2
+
3
+ module Utilities
4
+
5
+ class << self
6
+
7
+ #
8
+ # <tt>set_argv(array_of_strings)</tt>
9
+ #
10
+ # creates the appropriate +FFI::MemoryPointer+ out of a +ruby+ array of
11
+ # strings
12
+ #
13
+ def set_argv(array_of_strings)
14
+ str_array = []
15
+ array_of_strings.each { |str| str_array << FFI::MemoryPointer.from_string(str) }
16
+ str_array << nil
17
+
18
+ res = FFI::MemoryPointer.new(:pointer, str_array.size)
19
+ str_array.each_with_index { |p, i| res[i].put_pointer(0, p) }
20
+
21
+ res
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+
28
+ end
@@ -0,0 +1,21 @@
1
+ module FFI
2
+
3
+ module Utilities
4
+
5
+ class << self
6
+
7
+ #
8
+ # <tt>set_string(str)</tt>
9
+ #
10
+ # creates the appropriate +FFI::MemoryPointer+ from a +ruby+ string
11
+ # passed as argument
12
+ #
13
+ def set_string(str)
14
+ FFI::MemoryPointer.from_string(str)
15
+ end
16
+
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,30 @@
1
+ module FFI
2
+ module Utilities
3
+ #
4
+ # <tt>FFI::Utilities::Struct</tt> and <tt>FFI::Utilities::ManagedStruct</tt>
5
+ # *may not* be called with the +new+ method (because it is +private+ in
6
+ # these classes. Users may use the <tt>create(*args) { |args| ... }</tt>
7
+ # method and block for initialization purposes
8
+ #
9
+ class Struct < FFI::Struct
10
+
11
+ include FFI::Utilities::StructExtensions
12
+
13
+ private_class_method :new
14
+
15
+ private :struct_initialize
16
+
17
+ end
18
+
19
+ class ManagedStruct < FFI::ManagedStruct
20
+
21
+ include FFI::Utilities::StructExtensions
22
+
23
+ private_class_method :new
24
+
25
+ private :struct_initialize
26
+
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,156 @@
1
+ module FFI
2
+ module Utilities
3
+
4
+ module StructExtensions
5
+
6
+ class NoLayout < StandardError; end
7
+ class NotALayoutMember < StandardError; end
8
+
9
+ def self.included(base)
10
+ base.extend ClassMethods
11
+ end
12
+
13
+ module ClassMethods
14
+
15
+ #
16
+ # <tt>create(*args) [{ |this_object, args| ... }]</tt>
17
+ #
18
+ # does all the necessary FFI memory housekeeping for data structures
19
+ # and then passes a pointer to self and its arguments to a block.
20
+ #
21
+ # Also, this method may be overridden in subclasses and then called
22
+ # with <tt>super()</tt> if necessary.
23
+ #
24
+ def create(*args)
25
+ mp = FFI::MemoryPointer.new(self.size, 1)
26
+ p = FFI::Pointer.new(mp)
27
+ this_self = new(p)
28
+ this_self.send(:struct_initialize, *args)
29
+ yield(this_self, *args) if block_given?
30
+ this_self
31
+ end
32
+
33
+ def cast_pointer(p)
34
+ raise ArgumentError, 'bad pointer' unless p.kind_of?(FFI::MemoryPointer)
35
+ new(p)
36
+ end
37
+
38
+ #
39
+ # +attr_reader+, +attr_writer+, +attr_accessor+
40
+ # +attr_char_reader, +attr_char_writer+, +attr_char_accessor+
41
+ #
42
+ # Two basic sets of read/write methods are created, because we have to
43
+ # differentiate whether we want to be able to convert to/from a single
44
+ # +char+ (which +ruby+ will treat as a String object +C+ treats
45
+ # differently)
46
+ #
47
+ def attr_reader(*m)
48
+ method = 'read'
49
+ m.flatten.each { |a| common_read_eval(a, method) }
50
+ end
51
+
52
+ def attr_writer(*m)
53
+ method = 'write'
54
+ m.flatten.each { |a| common_write_eval(a, method) }
55
+ end
56
+
57
+ def attr_accessor(*m)
58
+ attr_reader(*m)
59
+ attr_writer(*m)
60
+ end
61
+
62
+ def attr_char_reader(*m)
63
+ method = 'read_char'
64
+ m.flatten.each { |a| common_read_eval(a, method) }
65
+ end
66
+
67
+ def attr_char_writer(*m)
68
+ method = 'write_char'
69
+ m.flatten.each { |a| common_write_eval(a, method) }
70
+ end
71
+
72
+ def attr_char_accessor(*m)
73
+ attr_char_reader(*m)
74
+ attr_char_writer(*m)
75
+ end
76
+
77
+ private
78
+
79
+ def common_read_eval(m, method)
80
+ rms = "def #{m}(); #{method}_attribute(:#{m}); end"
81
+ class_eval(rms) if exists_in_layout?(m)
82
+ end
83
+
84
+ def common_write_eval(m, method)
85
+ wms = "def #{m}=(val); #{method}_attribute(:#{m}, val); end"
86
+ class_eval(wms) if exists_in_layout?(m)
87
+ end
88
+
89
+ def exists_in_layout?(m)
90
+ raise NoLayout, "No layout for class #{self.name}" unless self.layout
91
+ raise NotALayoutMember, "method #{m} is not a layout member in class #{self.name}" unless self.layout.members.include?(m)
92
+ true
93
+ end
94
+
95
+ end
96
+
97
+ def read_attribute(attr)
98
+ self[attr]
99
+ end
100
+
101
+ #
102
+ # <tt>write_attribute(attr, val)</tt>
103
+ #
104
+ # writes +val+ into property +attr+. Care is taken to manage strings
105
+ # properly, even tough there seems to be a general suggestion that
106
+ # assigning strings is a Bad Thing (tm)
107
+ #
108
+ def write_attribute(attr, val)
109
+ if self.class.layout[attr].type == FFI::Type::Builtin::STRING
110
+ pos = offset_of(attr)
111
+ sp = val.nil? ? FFI::MemoryPointer::NULL : FFI::MemoryPointer.from_string(val)
112
+ self.pointer.put_pointer(pos, sp)
113
+ else
114
+ self[attr] = val
115
+ end
116
+ self[attr]
117
+ end
118
+
119
+ def read_char_attribute(attr)
120
+ self[attr].chr
121
+ end
122
+
123
+ def write_char_attribute(attr, val)
124
+ self[attr] = val.each_byte.map { |b| b }.first.to_i
125
+ read_char_attribute(attr)
126
+ end
127
+
128
+ private
129
+
130
+ #
131
+ # <tt>struct_initialize(*args)</tt>
132
+ #
133
+ # the +struct_initialize+ private method can be overridden by
134
+ # subclasses just like the +initialize+ method can, and it will be
135
+ # called by the <tt>create(*args)</tt> method just +new+ calls
136
+ # the +initialize+ method.
137
+ #
138
+ # *PLEASE NOTE*: the +initialize+ method cannot be used as usual,
139
+ # (just like +new+ cannot be used) because <tt>new()</tt> is used
140
+ # by FFI to memory initialization - so don't use it unless you
141
+ # *really* know what you are doing.
142
+ #
143
+ # The classes using this mixin should make this method +private+,
144
+ # just as +initialize+ is.
145
+ #
146
+ def struct_initialize(*args)
147
+ #
148
+ # this is supposed to be overridden and as such it is empty
149
+ #
150
+ end
151
+
152
+ end
153
+
154
+ end
155
+
156
+ end
@@ -0,0 +1,38 @@
1
+ module FFI
2
+
3
+ module Utilities
4
+
5
+ class UnknownPlatform < StandardError
6
+ def initialize(msg)
7
+ ext_msg = [msg, "unknown platform #{RUBY_PLATFORM}"].join(': ')
8
+ super(ext_msg)
9
+ end
10
+ end
11
+
12
+ class << self
13
+
14
+ def library_suffix
15
+ deprecated("use the FFI.manage_library_name() method instead")
16
+ res = case RUBY_PLATFORM
17
+ when /linux/i then '.so'
18
+ when /darwin/i then '.dylib'
19
+ when /windows/i then '.dll'
20
+ else raise UnknownPlatform.new('library_suffix')
21
+ end
22
+ end
23
+
24
+ def object_suffix
25
+ res = case RUBY_PLATFORM
26
+ when /linux/i then '.o'
27
+ when /darwin/i then '.o'
28
+ when /windows/i then '.obj'
29
+ else raise UnknownPlatform.new('object_suffix')
30
+ end
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+