ffi-module 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b5eb3b1aa3f99163ffd15dddc88852709b31677be988ebd0c2d3758866f19d43
4
+ data.tar.gz: 138e20272596fe28a109d49634f3357e7c544fe3b266ccc158dfcfc44906efff
5
+ SHA512:
6
+ metadata.gz: bd1516bf65a1d8d19bd4a972f03e47ef078441f6c5927eb699a79428bb727b740e62193f810cce6c2ebc4d4e1e39d90abcf38a076da04c8d4e628af4e1a3ae50
7
+ data.tar.gz: e194ca5bd89f3c75716e50b9f389803c73b0a5136e7eef766c43a1b31f0d193e2e097bd93373454d458fdd5057d20042b955b4e8c88b81053ce5aac8038d6dcc
data/lib/ffi/module.rb ADDED
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require_relative 'module/version'
24
+ require_relative 'module/library'
25
+ require_relative 'module/loader'
26
+
27
+ module FFI
28
+ module Module
29
+ def self.included(target)
30
+ target.extend(Library)
31
+ target.extend(Loader)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'shellwords'
24
+
25
+ require_relative 'loader'
26
+
27
+ module FFI
28
+ module Module
29
+ module ConfigTool
30
+ extend Loader
31
+
32
+ def ffi_load_using_config_tool(command, search_paths: [], names: [], **options)
33
+ return false unless output = ::IO.popen(command).read
34
+
35
+ arguments = ::Shellwords.split(output)
36
+
37
+ arguments.each do |argument|
38
+ if match = argument.match(/\A(-[lL])(.*)\z/)
39
+ command, value = match.captures
40
+ case command
41
+ when '-L'
42
+ search_paths << value
43
+ when '-l'
44
+ names << value
45
+ end
46
+ else
47
+ # Assume it's a search path:
48
+ search_paths << value
49
+ end
50
+ end
51
+
52
+ # Load all specified libraries:
53
+ names.each do |name|
54
+ ffi_load(name, search_paths: search_paths, **options)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,241 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'ffi'
24
+
25
+ module FFI
26
+ module Module
27
+ module Library
28
+ def self.extended(target)
29
+ raise "Must only be extended by module, got #{target}!" unless target.kind_of?(::Module)
30
+
31
+ target.instance_variable_set(:@ffi_libraries, Array.new)
32
+ target.instance_variable_set(:@ffi_calling_convention, :default)
33
+ target.instance_variable_set(:@ffi_type_map, Hash.new)
34
+ target.instance_variable_set(:@ffi_enumerations, FFI::Enums.new)
35
+ end
36
+
37
+ DEFAULT_FLAGS = FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_LOCAL
38
+
39
+ def ffi_open_library(name = nil, flags: DEFAULT_FLAGS)
40
+ @ffi_libraries << DynamicLibrary.open(name, flags)
41
+
42
+ return true
43
+ rescue LoadError
44
+ return nil
45
+ end
46
+
47
+ def ffi_calling_convention(value = nil)
48
+ if value
49
+ @ffi_calling_convention = value
50
+ end
51
+
52
+ return @ffi_calling_convention
53
+ end
54
+
55
+ def ffi_attach_function(name, cname, argument_types, return_type = :void, **options)
56
+ argument_types = argument_types.map{|type| self.ffi_find_type(type)}
57
+ return_type = self.ffi_find_type(return_type)
58
+
59
+ options[:convention] ||= @ffi_calling_convention
60
+ options[:type_map] ||= @ffi_type_map
61
+ options[:enums] ||= @ffi_enumerations
62
+
63
+ invoker = nil
64
+
65
+ @ffi_libraries.each do |library|
66
+ function = nil
67
+
68
+ ffi_function_names(cname, argument_types).each do |function_name|
69
+ break if function = library.find_function(function_name.to_s)
70
+ end
71
+
72
+ if function
73
+ if argument_types.length > 0 && argument_types.last == FFI::NativeType::VARARGS
74
+ invoker = VariadicInvoker.new(function, argument_types, return_type, options)
75
+ else
76
+ invoker = Function.new(return_type, argument_types, function, options)
77
+ end
78
+
79
+ break
80
+ end
81
+ end
82
+
83
+ if invoker
84
+ invoker.attach(self, name.to_s)
85
+ return true
86
+ else
87
+ raise FFI::NotFoundError.new(cname, @ffi_libraries)
88
+ end
89
+ end
90
+
91
+ def ffi_attach_variable(name, cname, type)
92
+ address = @ffi_libraries.find do |library|
93
+ begin
94
+ library.find_variable(cname)
95
+ rescue LoadError
96
+ end
97
+ end
98
+
99
+ if address.nil? || address.null?
100
+ raise FFI::NotFoundError.new(cname, @ffi_libraries)
101
+ end
102
+
103
+ if type.is_a?(Class) && type < FFI::Struct
104
+ variable = type.new(address)
105
+
106
+ self.define_singleton_method(name) do
107
+ variable
108
+ end
109
+ else
110
+ container_type = Class.new(FFI::Struct)
111
+ container_type.layout :value, self.ffi_find_type(type)
112
+ container = container_type.new(address)
113
+
114
+ self.define_singleton_method(name) do
115
+ container[:value]
116
+ end
117
+
118
+ self.define_singleton_method(:"#{name}=") do |value|
119
+ container[:value] = value
120
+ end
121
+ end
122
+
123
+ return true
124
+ end
125
+
126
+ def ffi_callback(argument_types, return_type, **options)
127
+ argument_types = argument_types.map{|type| self.ffi_find_type(type)}
128
+ return_type = self.ffi_find_type(return_type)
129
+
130
+ if argument_types.include?(FFI::Type::VARARGS)
131
+ raise ArgumentError, "Callbacks cannot have variadic parameters!"
132
+ end
133
+
134
+ options[:convention] ||= @ffi_calling_convention
135
+ options[:enums] ||= @ffi_enumerations
136
+
137
+ if return_type == Type::STRING
138
+ raise TypeError, "String is not allowed as return type of callbacks!"
139
+ end
140
+
141
+ return FFI::CallbackInfo.new(return_type, argument_types, options)
142
+ end
143
+
144
+ def ffi_define_callback(name, *arguments, **options)
145
+ callback = ffi_callback(*arguments, **options)
146
+
147
+ ffi_define_type(name, callback)
148
+
149
+ return callback
150
+ end
151
+
152
+ def ffi_define_type(name, value)
153
+ case type
154
+ when FFI::Type
155
+ @ffi_type_map[name] = type
156
+ when FFI::DataConverter
157
+ @ffi_type_map[name] = FFI::Type::Mapped.new(value)
158
+ else
159
+ @ffi_type_map[name] = self.ffi_find_type(value)
160
+ end
161
+ end
162
+
163
+ def ffi_define_enumeration(name, *arguments)
164
+ native_type = arguments.first.kind_of?(FFI::Type) ? arguments.shift : nil
165
+
166
+ ffi_generic_enumeration(name, FFI::Enum, native_type, arguments)
167
+ end
168
+
169
+ def ffi_define_bitmask(name, *arguments)
170
+ native_type = arguments.first.kind_of?(FFI::Type) ? arguments.shift : nil
171
+
172
+ ffi_generic_enumeration(name, FFI::Bitmask, native_type, arguments)
173
+ end
174
+
175
+ private
176
+
177
+ def ffi_define_generic_enumeration(name, klass, native_type, values)
178
+ enumeration = nil
179
+
180
+ if native_type
181
+ enumeration = klass.new(native_type, values, name)
182
+ else
183
+ enumeration = klass.new(values, name)
184
+ end
185
+
186
+ @ffi_enumerations << enumeration
187
+
188
+ ffi_define_type(name, enumeration)
189
+
190
+ return enumeration
191
+ end
192
+
193
+ def ffi_function_names(name, argument_types)
194
+ result = [name]
195
+
196
+ if @ffi_calling_convention == :stdcall
197
+ # Get the size of each parameter:
198
+ size = argument_types.inject(0) do |total, argument|
199
+ size = argument.size
200
+
201
+ # The size must be a multiple of 4:
202
+ size += (4 - size) % 4
203
+
204
+ total + size
205
+ end
206
+
207
+ # win32 naming convention:
208
+ result << "_#{name.to_s}@#{size}"
209
+
210
+ # win64 naming convention:
211
+ result << "#{name.to_s}@#{size}"
212
+ end
213
+
214
+ return result
215
+ end
216
+
217
+ def ffi_find_type(argument)
218
+ if argument.kind_of?(Type)
219
+ return argument
220
+ end
221
+
222
+ if type = @ffi_type_map[argument]
223
+ return type
224
+ end
225
+
226
+ if argument.is_a?(Class) && argument < Struct
227
+ return Type::POINTER
228
+ end
229
+
230
+ if argument.is_a?(DataConverter)
231
+ # Cache the mapped type:
232
+ return ffi_define_type(argument, Type::Mapped.new(argument))
233
+ end
234
+
235
+ if argument
236
+ return FFI.find_type(argument)
237
+ end
238
+ end
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'ffi'
24
+
25
+ module FFI
26
+ module Module
27
+ module Loader
28
+ def ffi_find_library_path(libname, search_paths)
29
+ search_paths.each do |search_path|
30
+ full_path = File.join(search_path, libname)
31
+ if File.exist?(full_path)
32
+ return full_path
33
+ end
34
+ end
35
+
36
+ return nil
37
+ end
38
+
39
+ def ffi_load(name, search_paths: nil, **options)
40
+ # Try to load the library directly:
41
+ return true if ffi_load_library(name, **options)
42
+
43
+ # If that fails, try to load it from the specified search paths:
44
+ if search_paths&.any?
45
+ name = FFI.map_library_name(name)
46
+
47
+ if path = ffi_find_library_path(library_name, search_paths)
48
+ if library = ffi_load_library(path, **options)
49
+ libraries << library
50
+ end
51
+ end
52
+
53
+ return true if ffi_load_library(name, **options)
54
+ end
55
+
56
+ return nil
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ begin
24
+ require 'ffi'
25
+ rescue LoadError
26
+ # Ignore.
27
+
28
+ # The FFI gem has the following in `ffi.rb`, so we need to be careful about load order:
29
+ # Object.send(:remove_const, :FFI) if defined?(::FFI)
30
+ end
31
+
32
+ module FFI
33
+ module Module
34
+ VERSION = "0.1.0"
35
+ end
36
+ end
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ffi-module
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Samuel Williams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-04-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ffi
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: covered
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.6'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.6'
83
+ description:
84
+ email:
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files: []
88
+ files:
89
+ - lib/ffi/module.rb
90
+ - lib/ffi/module/config_tool.rb
91
+ - lib/ffi/module/library.rb
92
+ - lib/ffi/module/loader.rb
93
+ - lib/ffi/module/version.rb
94
+ homepage: https://github.com/ioquatix/ffi-module
95
+ licenses:
96
+ - MIT
97
+ metadata:
98
+ funding_uri: https://github.com/sponsors/ioquatix/
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: 2.4.0
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubygems_version: 3.2.3
115
+ signing_key:
116
+ specification_version: 4
117
+ summary: Write a short summary, because RubyGems requires one.
118
+ test_files: []