opal-webassembly 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/.gitignore +14 -0
- data/.rspec +3 -0
- data/.rspec-opal +3 -0
- data/.travis.yml +6 -0
- data/Gemfile +25 -0
- data/LICENSE.adoc +31 -0
- data/LICENSE.txt +21 -0
- data/README.adoc +45 -0
- data/Rakefile +13 -0
- data/bin/console +14 -0
- data/bin/opal-repl-wasm +4 -0
- data/bin/setup +8 -0
- data/examples/experiment/Makefile +2 -0
- data/examples/experiment/experiment-wasm.c +74 -0
- data/examples/experiment/experiment-wasm.wasm +0 -0
- data/examples/experiment/experiment.rb +25 -0
- data/examples/experiment/experiment.sh +2 -0
- data/examples/onigmo/onigmo-wasm.wasm +0 -0
- data/examples/onigmo/onigmo.rb +4 -0
- data/examples/onigmo/onigmo.sh +2 -0
- data/examples/simple_ffi/Rakefile +14 -0
- data/examples/simple_ffi/simple-wasm.wasm +0 -0
- data/examples/simple_ffi/simple-wasm.wat +8 -0
- data/examples/simple_ffi/simple.rb +14 -0
- data/exe/opal-repl-wasm +9 -0
- data/lib/opal/webassembly.rb +9 -0
- data/lib/opal/webassembly/processor.rb +49 -0
- data/lib/opal/webassembly/version.rb +5 -0
- data/opal-webassembly.gemspec +31 -0
- data/opal/ffi.rb +22 -0
- data/opal/ffi/library.rb +67 -0
- data/opal/ffi/pointer.rb +289 -0
- data/opal/ffi/struct.rb +125 -0
- data/opal/ffi/types.rb +226 -0
- data/opal/webassembly.rb +68 -0
- data/opal/webassembly/global.rb +15 -0
- data/opal/webassembly/instance.rb +44 -0
- data/opal/webassembly/memory.rb +18 -0
- data/opal/webassembly/module.rb +10 -0
- data/opal/webassembly/table.rb +35 -0
- data/spec-opal/ffi_spec.rb +139 -0
- data/spec-opal/spec_helper.rb +18 -0
- data/spec-opal/webassembly_spec.rb +12 -0
- metadata +104 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative 'lib/opal/webassembly/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "opal-webassembly"
|
5
|
+
spec.version = Opal::WebAssembly::VERSION
|
6
|
+
spec.authors = ["Ribose Inc."]
|
7
|
+
spec.email = ["open.source@ribose.com"]
|
8
|
+
|
9
|
+
spec.summary = %q{Opal with WebAssembly support}
|
10
|
+
#spec.description = %q{TODO: Write a longer description or delete this line.}
|
11
|
+
spec.homepage = "https://github.com/interscript/opal-webassembly"
|
12
|
+
spec.license = "BSD-2-Clause"
|
13
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
14
|
+
|
15
|
+
#spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/interscript/opal-webassembly"
|
19
|
+
spec.metadata["changelog_uri"] = "https://github.com/interscript/opal-webassembly/commits/master"
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
24
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
25
|
+
end
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_dependency "opal"
|
31
|
+
end
|
data/opal/ffi.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'webassembly'
|
2
|
+
|
3
|
+
# An API trying to be compatible with Ruby-FFI.
|
4
|
+
module FFI
|
5
|
+
def self.contexts
|
6
|
+
(@contexts ||= [])
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.context
|
10
|
+
(@contexts ||= []).last.tap do |lib|
|
11
|
+
unless lib
|
12
|
+
raise RuntimeError, "This call needs to be done in a FFI::Library#context "+
|
13
|
+
"block (Opal-WebAssembly limitation)."
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'ffi/types'
|
20
|
+
require 'ffi/pointer'
|
21
|
+
require 'ffi/struct'
|
22
|
+
require 'ffi/library'
|
data/opal/ffi/library.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
module FFI
|
2
|
+
module Library
|
3
|
+
def self.extended(mod)
|
4
|
+
raise RuntimeError.new("must only be extended by module") unless mod.kind_of?(Module)
|
5
|
+
end
|
6
|
+
|
7
|
+
def ffi_lib(*names)
|
8
|
+
# Expect the library (first one) to be already loaded in "Opal.WebAssembly.modules"
|
9
|
+
@ffi_lib = WebAssembly.libs[names.first]
|
10
|
+
raise LoadError, "Library #{names.first} not loaded" if !@ffi_lib
|
11
|
+
|
12
|
+
begin
|
13
|
+
attach_function :malloc, [:long], :pointer
|
14
|
+
attach_function :free, [:pointer], :void
|
15
|
+
attach_function :realloc, [:pointer, :long], :pointer
|
16
|
+
rescue LoadError
|
17
|
+
# It's ok, a library doesn't really need to provide memory functions.
|
18
|
+
# But we will need those.
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def library
|
23
|
+
@ffi_lib
|
24
|
+
end
|
25
|
+
|
26
|
+
def attach_function(name, func, args = nil, returns = nil, **options)
|
27
|
+
if func.is_a? Array # Don't rename a function
|
28
|
+
func, args, returns = name, func, args
|
29
|
+
end
|
30
|
+
|
31
|
+
raise LoadError, "No library responds to #{func}" unless @ffi_lib.exports.has_key?(func)
|
32
|
+
fun = @ffi_lib.exports[func]
|
33
|
+
|
34
|
+
self.define_singleton_method name do |*as|
|
35
|
+
if as.count != args.count # :varargs?
|
36
|
+
raise ArgumentError, "Provided #{as.count} arguments, expected #{args.count}"
|
37
|
+
end
|
38
|
+
|
39
|
+
as = as.each_with_index.map do |a,ind|
|
40
|
+
type = Type[args[ind]]
|
41
|
+
if type.respond_to? :to_native_mem
|
42
|
+
type.to_native_mem(a, @ffi_lib.memory)
|
43
|
+
else
|
44
|
+
type.to_native(a)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
ret = fun.call(*as)
|
49
|
+
type = Type[returns]
|
50
|
+
if type.respond_to? :from_native_mem
|
51
|
+
type.from_native_mem(ret, @ffi_lib.memory)
|
52
|
+
else
|
53
|
+
type.from_native(ret)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Launch a block in a current context. Needed for memory management.
|
59
|
+
# Think: segmented memory.
|
60
|
+
def context &block
|
61
|
+
FFI.contexts.push self
|
62
|
+
out = yield
|
63
|
+
FFI.contexts.pop
|
64
|
+
out
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/opal/ffi/pointer.rb
ADDED
@@ -0,0 +1,289 @@
|
|
1
|
+
# Large portions of this file are adapted from
|
2
|
+
# https://github.com/ffi/ffi/blob/master/lib/ffi/pointer.rb
|
3
|
+
|
4
|
+
module FFI
|
5
|
+
class Pointer
|
6
|
+
SIZE = 4
|
7
|
+
|
8
|
+
# Return the size of a pointer on the current platform, in bytes
|
9
|
+
# @return [Numeric]
|
10
|
+
def self.size
|
11
|
+
SIZE
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [nil,Numeric] len length of string to return
|
15
|
+
# @return [String]
|
16
|
+
# Read pointer's contents as a string, or the first +len+ bytes of the
|
17
|
+
# equivalent string if +len+ is not +nil+.
|
18
|
+
def read_string(len=nil)
|
19
|
+
if len
|
20
|
+
return ''.b if len == 0
|
21
|
+
get_bytes(0, len)
|
22
|
+
else
|
23
|
+
get_string(0)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param [Numeric] len length of string to return
|
28
|
+
# @return [String]
|
29
|
+
# Read the first +len+ bytes of pointer's contents as a string.
|
30
|
+
#
|
31
|
+
# Same as:
|
32
|
+
# ptr.read_string(len) # with len not nil
|
33
|
+
def read_string_length(len)
|
34
|
+
get_bytes(0, len)
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [String]
|
38
|
+
# Read pointer's contents as a string.
|
39
|
+
#
|
40
|
+
# Same as:
|
41
|
+
# ptr.read_string # with no len
|
42
|
+
def read_string_to_null
|
43
|
+
get_string(0)
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param [String] str string to write
|
47
|
+
# @param [Numeric] len length of string to return
|
48
|
+
# @return [self]
|
49
|
+
# Write +len+ first bytes of +str+ in pointer's contents.
|
50
|
+
#
|
51
|
+
# Same as:
|
52
|
+
# ptr.write_string(str, len) # with len not nil
|
53
|
+
def write_string_length(str, len)
|
54
|
+
put_bytes(0, str, 0, len)
|
55
|
+
end
|
56
|
+
|
57
|
+
# @param [String] str string to write
|
58
|
+
# @param [Numeric] len length of string to return
|
59
|
+
# @return [self]
|
60
|
+
# Write +str+ in pointer's contents, or first +len+ bytes if
|
61
|
+
# +len+ is not +nil+.
|
62
|
+
def write_string(str, len=nil)
|
63
|
+
#len = str.bytesize unless len
|
64
|
+
# Write the string data without NUL termination
|
65
|
+
put_bytes(0, str, 0, len)
|
66
|
+
end
|
67
|
+
|
68
|
+
# @param [Type] type type of data to read from pointer's contents
|
69
|
+
# @param [Symbol] reader method to send to +self+ to read +type+
|
70
|
+
# @param [Numeric] length
|
71
|
+
# @return [Array]
|
72
|
+
# Read an array of +type+ of length +length+.
|
73
|
+
# @example
|
74
|
+
# ptr.read_array_of_type(TYPE_UINT8, :read_uint8, 4) # -> [1, 2, 3, 4]
|
75
|
+
def read_array_of_type(type, reader, length)
|
76
|
+
ary = []
|
77
|
+
size = FFI.type_size(type)
|
78
|
+
tmp = self
|
79
|
+
length.times { |j|
|
80
|
+
ary << tmp.send(reader)
|
81
|
+
tmp += size unless j == length-1 # avoid OOB
|
82
|
+
}
|
83
|
+
ary
|
84
|
+
end
|
85
|
+
|
86
|
+
# @param [Type] type type of data to write to pointer's contents
|
87
|
+
# @param [Symbol] writer method to send to +self+ to write +type+
|
88
|
+
# @param [Array] ary
|
89
|
+
# @return [self]
|
90
|
+
# Write +ary+ in pointer's contents as +type+.
|
91
|
+
# @example
|
92
|
+
# ptr.write_array_of_type(TYPE_UINT8, :put_uint8, [1, 2, 3 ,4])
|
93
|
+
def write_array_of_type(type, writer, ary)
|
94
|
+
size = FFI.type_size(type)
|
95
|
+
ary.each_with_index { |val, i|
|
96
|
+
break unless i < self.size
|
97
|
+
self.send(writer, i * size, val)
|
98
|
+
}
|
99
|
+
self
|
100
|
+
end
|
101
|
+
|
102
|
+
# @return [self]
|
103
|
+
def to_ptr
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
def pointer
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
# @param [Symbol,Type] type of data to read
|
112
|
+
# @return [Object]
|
113
|
+
# Read pointer's contents as +type+
|
114
|
+
#
|
115
|
+
# Same as:
|
116
|
+
# ptr.get(type, 0)
|
117
|
+
def read(type)
|
118
|
+
get(type, 0)
|
119
|
+
end
|
120
|
+
|
121
|
+
# @param [Symbol,Type] type of data to read
|
122
|
+
# @param [Object] value to write
|
123
|
+
# @return [nil]
|
124
|
+
# Write +value+ of type +type+ to pointer's content
|
125
|
+
#
|
126
|
+
# Same as:
|
127
|
+
# ptr.put(type, 0)
|
128
|
+
def write(type, value)
|
129
|
+
put(type, 0, value)
|
130
|
+
end
|
131
|
+
|
132
|
+
def get_bytes(pos, len)
|
133
|
+
ary = @memory.buffer.to_a
|
134
|
+
|
135
|
+
out = []
|
136
|
+
i = 0
|
137
|
+
while i < len
|
138
|
+
out << ary[@address + pos + i]
|
139
|
+
i += 1
|
140
|
+
end
|
141
|
+
|
142
|
+
# We expect a binary string as an output of this function.
|
143
|
+
out.pack("c*").b # TODO upstream: #bytes is wrong!
|
144
|
+
end
|
145
|
+
|
146
|
+
def put_bytes(pos, src, src_pos=0, len=nil)
|
147
|
+
if src.respond_to? :bytes
|
148
|
+
src = if src.encoding == Encoding::BINARY
|
149
|
+
src.chars.map(&:ord)
|
150
|
+
else
|
151
|
+
src.bytes
|
152
|
+
end
|
153
|
+
end
|
154
|
+
len ||= src.length - src_pos
|
155
|
+
|
156
|
+
ary = @memory.buffer.to_a
|
157
|
+
i = 0
|
158
|
+
while i < len
|
159
|
+
ary[@address + i + pos] = src[src_pos + i]
|
160
|
+
i += 1
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def get(type, pos)
|
165
|
+
type = FFI::Type[type]
|
166
|
+
bytes = get_bytes(pos, type.size)
|
167
|
+
if type.respond_to? :from_native_mem
|
168
|
+
type.from_native_mem(type.unpack(bytes), @memory)
|
169
|
+
else
|
170
|
+
type.from_native(type.unpack(bytes))
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def put(type, pos, value)
|
175
|
+
type = FFI::Type[type]
|
176
|
+
value = if type.respond_to? :to_native_mem
|
177
|
+
type.to_native_mem(value, @memory)
|
178
|
+
else
|
179
|
+
type.to_native(value)
|
180
|
+
end
|
181
|
+
value = type.pack(value)
|
182
|
+
put_bytes(pos, value)
|
183
|
+
end
|
184
|
+
|
185
|
+
def get_string(pos)
|
186
|
+
out = []
|
187
|
+
ary = @memory.buffer.to_a
|
188
|
+
pos += @address
|
189
|
+
loop do
|
190
|
+
byte = ary[pos]
|
191
|
+
break if byte == 0
|
192
|
+
out << byte
|
193
|
+
pos += 1
|
194
|
+
end
|
195
|
+
out.pack("c*").b
|
196
|
+
end
|
197
|
+
|
198
|
+
def put_string(pos, string)
|
199
|
+
put_bytes(pos, string)
|
200
|
+
end
|
201
|
+
|
202
|
+
def self.from_string src
|
203
|
+
bs = if src.encoding == Encoding::BINARY
|
204
|
+
src.chars.map(&:ord)
|
205
|
+
else
|
206
|
+
src.bytes
|
207
|
+
end
|
208
|
+
bs = bs + [0, 0]
|
209
|
+
out = new(:uint8, bs.count)
|
210
|
+
out.put_bytes(0, bs)
|
211
|
+
out
|
212
|
+
end
|
213
|
+
|
214
|
+
def self.alloc_out size, count, smh=false
|
215
|
+
new(:uint8, size*count)
|
216
|
+
end
|
217
|
+
|
218
|
+
def self.alloc_in size, count, smh=false
|
219
|
+
new(:uint8, size*count)
|
220
|
+
end
|
221
|
+
|
222
|
+
def method_missing method, *args
|
223
|
+
method = method.to_s
|
224
|
+
super
|
225
|
+
end
|
226
|
+
|
227
|
+
# Pointer in our case is more complex than a regular one: it's
|
228
|
+
# [WebAssembly::Memory instance, uint32 address] since WebAssembly
|
229
|
+
# "processes" run in separate address spaces.
|
230
|
+
#
|
231
|
+
# Alternatively, memory can be deduced from wrapping inside
|
232
|
+
# Library#context.
|
233
|
+
def initialize(address, type=nil, size=nil)
|
234
|
+
if address.respond_to? :address
|
235
|
+
if address.respond_to? :memory
|
236
|
+
@memory = address.memory || FFI.context.library.memory
|
237
|
+
else
|
238
|
+
@memory = FFI.context.library.memory
|
239
|
+
end
|
240
|
+
@address = address.address
|
241
|
+
elsif address.respond_to?(:to_sym) || address.is_a?(Type) || address.is_a?(Class) # Allocation call
|
242
|
+
type, count = address, type
|
243
|
+
@memory = FFI.context.library.memory
|
244
|
+
@address = FFI.context.malloc(FFI::Type[type].size * (count || 1)).address
|
245
|
+
elsif address.respond_to? :to_ary
|
246
|
+
@memory, @address = address.to_ary
|
247
|
+
@size = size
|
248
|
+
elsif address.respond_to? :to_int
|
249
|
+
@memory = FFI.context.library.memory
|
250
|
+
@address = address.to_int
|
251
|
+
@size = size
|
252
|
+
else
|
253
|
+
raise TypeError, "Address has an invalid type"
|
254
|
+
end
|
255
|
+
@type = type ? FFI::Type[type] : FFI::Type[:uint8]
|
256
|
+
end
|
257
|
+
|
258
|
+
attr_accessor :memory, :address, :type
|
259
|
+
|
260
|
+
def == other
|
261
|
+
(self.address == other.address) &&
|
262
|
+
(self.address == 0 || (self.memory == other.memory))
|
263
|
+
end
|
264
|
+
|
265
|
+
def + offset
|
266
|
+
self.dup.tap { |i| i.address += offset * type.size }
|
267
|
+
end
|
268
|
+
|
269
|
+
def [] offset
|
270
|
+
self.get(type, offset * type.size)
|
271
|
+
end
|
272
|
+
|
273
|
+
def []= offset, value
|
274
|
+
self.put(type, offset * type.size, value)
|
275
|
+
end
|
276
|
+
|
277
|
+
def resize(new_size)
|
278
|
+
self.address = FFI.context.realloc(self, new_size).address
|
279
|
+
end
|
280
|
+
|
281
|
+
def inspect
|
282
|
+
out = "#<#{self.class.name}:#{"0x%08x" % self.address} of #{self.type.inspect}>"
|
283
|
+
end
|
284
|
+
|
285
|
+
NULL = new([nil, 0])
|
286
|
+
end
|
287
|
+
|
288
|
+
AutoPointer = MemoryPointer = Buffer = Pointer
|
289
|
+
end
|
data/opal/ffi/struct.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
module FFI
|
2
|
+
class Struct
|
3
|
+
def self.size
|
4
|
+
@size || 4
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.alignment
|
8
|
+
@alignment ||= @layout&.values&.map(&:type)&.map(&:alignment)&.max || 4
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.by_value; self; end
|
12
|
+
def self.by_ptr; FFI::Type::WrappedStruct.new(self); end
|
13
|
+
|
14
|
+
def self.pack x; raise ArgumentError, "Packing Struct directly is forbidden"; end
|
15
|
+
def self.unpack x; raise ArgumentError, "Unpacking Struct directly is forbidden"; end
|
16
|
+
|
17
|
+
def from_native_mem(x, memory)
|
18
|
+
new(FFI::Pointer.new([memory, x], self))
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_native(x)
|
22
|
+
x.pointer.address
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.ptr; by_ptr; end
|
26
|
+
|
27
|
+
def self.layout *fs, **fields
|
28
|
+
if fs.length > 0
|
29
|
+
while (z = fs.shift(2)).length == 2
|
30
|
+
k, v = z
|
31
|
+
fields[k] = v
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
offset = 0
|
36
|
+
@layout = {}
|
37
|
+
fields.each do |name, type|
|
38
|
+
count = nil
|
39
|
+
if type.is_a? Array
|
40
|
+
type, count = type
|
41
|
+
end
|
42
|
+
type = FFI::Type[type, :struct]
|
43
|
+
|
44
|
+
offset += 1 until (offset % type.alignment) == 0
|
45
|
+
@layout[name] = Field.new(self, name, type, offset, count)
|
46
|
+
|
47
|
+
if count
|
48
|
+
offset += type.size * count
|
49
|
+
else
|
50
|
+
offset += type.size
|
51
|
+
end
|
52
|
+
end
|
53
|
+
@size = offset
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.members; @layout; end
|
57
|
+
def members; self.class.members; end
|
58
|
+
|
59
|
+
def [] field
|
60
|
+
members[field].get(self)
|
61
|
+
end
|
62
|
+
|
63
|
+
def []= field, value
|
64
|
+
members[field].set(self, value)
|
65
|
+
end
|
66
|
+
|
67
|
+
def offset_of field
|
68
|
+
members[field].offset
|
69
|
+
end
|
70
|
+
|
71
|
+
attr_reader :pointer
|
72
|
+
|
73
|
+
def address
|
74
|
+
pointer.address
|
75
|
+
end
|
76
|
+
|
77
|
+
def initialize pointer=nil
|
78
|
+
if pointer
|
79
|
+
@pointer = pointer
|
80
|
+
else
|
81
|
+
@pointer = FFI.context.malloc(self.class.size)
|
82
|
+
@pointer.type = FFI::Type[self.class]
|
83
|
+
@pointer
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def inspect
|
88
|
+
out = ["#<#{self.class.name}:#{"0x%08x" % self.address}: "]
|
89
|
+
out << self.members.keys.map do |key|
|
90
|
+
":#{key} => #{self.[](key).inspect}"
|
91
|
+
end.join(", ")
|
92
|
+
out << ">"
|
93
|
+
out.join
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class Field
|
98
|
+
attr_accessor :struct, :name, :type, :offset, :count
|
99
|
+
def initialize struct, name, type, offset, count
|
100
|
+
@struct, @name, @type, @offset, @count = struct, name, type, offset, count
|
101
|
+
end
|
102
|
+
|
103
|
+
def get from
|
104
|
+
if count
|
105
|
+
FFI::Pointer.new([from.pointer.memory, from.address + offset], type)
|
106
|
+
elsif type.is_a?(Class) && type <= FFI::Struct
|
107
|
+
type.new(FFI::Pointer.new([from.pointer.memory, from.address + offset], type))
|
108
|
+
else
|
109
|
+
from.pointer.get(type, offset)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def set from, value
|
114
|
+
if count
|
115
|
+
raise ArgumentError, "Can't set an array #{name} of #{struct}."
|
116
|
+
elsif type.is_a?(Class) && type <= FFI::Struct
|
117
|
+
raise ArgumentError, "Can't set a nested struct #{name}."
|
118
|
+
else
|
119
|
+
from.pointer.put(type, offset, value)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
ManagedStruct = Struct
|
125
|
+
end
|