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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +14 -0
- data/.gitignore +54 -0
- data/.rspec +2 -0
- data/.rubocop.yml +1171 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +29 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +48 -0
- data/LICENSE +340 -0
- data/README.md +90 -0
- data/Rakefile +21 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/FFI/utilities.rb +17 -0
- data/lib/FFI/utilities/deprecated.rb +15 -0
- data/lib/FFI/utilities/set_argv.rb +28 -0
- data/lib/FFI/utilities/set_string.rb +21 -0
- data/lib/FFI/utilities/struct.rb +30 -0
- data/lib/FFI/utilities/struct_extensions.rb +156 -0
- data/lib/FFI/utilities/suffixes.rb +38 -0
- data/lib/FFI/utilities/version.rb +5 -0
- data/lib/tasks/fixtures.rake +29 -0
- data/lib/tasks/rdoc.rake +6 -0
- data/ruby-FFI-utilities.gemspec +33 -0
- metadata +111 -0
data/README.md
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# Ruby FFI::Utilities
|
2
|
+
|
3
|
+
[](https://travis-ci.org/nicb/ruby-FFI-utilities)
|
4
|
+
[](https://codeclimate.com/github/nicb/ruby-FFI-utilities)
|
5
|
+
[](https://codeclimate.com/github/nicb/ruby-FFI-utilities/coverage)
|
6
|
+
[](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.
|
data/Rakefile
ADDED
@@ -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 }
|
data/bin/console
ADDED
@@ -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
|
data/bin/setup
ADDED
@@ -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,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
|
+
|