ffi-module 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 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: []