virtualbox-com 0.9.6
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.
- data/.gitignore +8 -0
- data/Gemfile +9 -0
- data/LICENSE +19 -0
- data/Rakefile +8 -0
- data/Readme.md +59 -0
- data/examples/simple.rb +43 -0
- data/lib/virtualbox-com.rb +1 -0
- data/lib/virtualbox/com.rb +51 -0
- data/lib/virtualbox/com/abstract_enum.rb +51 -0
- data/lib/virtualbox/com/abstract_interface.rb +144 -0
- data/lib/virtualbox/com/abstract_model.rb +14 -0
- data/lib/virtualbox/com/exceptions.rb +32 -0
- data/lib/virtualbox/com/iid.rb +43 -0
- data/lib/virtualbox/com/model/4.2-gen.rb +2720 -0
- data/lib/virtualbox/com/model/4.2.rb +97 -0
- data/lib/virtualbox/com/util.rb +119 -0
- data/lib/virtualbox/com/version.rb +5 -0
- data/lib/virtualbox/com/xpcomc-ffi.rb +76 -0
- data/lib/virtualbox/com/xpcomc-ffi/binding.rb +87 -0
- data/lib/virtualbox/com/xpcomc-ffi/implementer.rb +86 -0
- data/lib/virtualbox/com/xpcomc-ffi/lib.rb +90 -0
- data/lib/virtualbox/com/xpcomc-ffi/model-types.rb +15 -0
- data/lib/virtualbox/com/xpcomc-ffi/sig.rb +342 -0
- data/lib/virtualbox/com/xpcomc-ffi/spec.rb +58 -0
- data/lib/virtualbox/com/xpcomc-ffi/xpcomc-vbox.rb +54 -0
- data/scripts/xidl-conv.rb +124 -0
- data/virtualbox-com.gemspec +27 -0
- metadata +123 -0
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'virtualbox/com/util'
|
2
|
+
|
3
|
+
module VirtualBox
|
4
|
+
module COM
|
5
|
+
module XPCOMC
|
6
|
+
|
7
|
+
|
8
|
+
module Lib
|
9
|
+
extend ::FFI::Library
|
10
|
+
|
11
|
+
# Constant with default library path and name
|
12
|
+
# according to running plateform
|
13
|
+
PATH = case Util.platform
|
14
|
+
when :mac
|
15
|
+
[ "/Applications/VirtualBox.app/Contents/MacOS",
|
16
|
+
"/Applications/MacPorts/VirtualBox.app/Contents/MacOS" ]
|
17
|
+
when :linux
|
18
|
+
[ "/opt/VirtualBox",
|
19
|
+
"/usr/lib/virtualbox",
|
20
|
+
"/usr/lib64/virtualbox" ]
|
21
|
+
when :solaris
|
22
|
+
[ "/opt/VirtualBox/amd64",
|
23
|
+
"/opt/VirtualBox/i386" ]
|
24
|
+
when :freebsd
|
25
|
+
[ "/usr/local/lib/virtualbox" ]
|
26
|
+
else
|
27
|
+
[ ]
|
28
|
+
end
|
29
|
+
|
30
|
+
NAME = case Util.platform
|
31
|
+
when :max then "VBoxXPCOMC.dylib"
|
32
|
+
else "VBoxXPCOMC.so"
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def self.init
|
37
|
+
# Only once
|
38
|
+
return if respond_to?(:VBoxGetXPCOMCFunctions)
|
39
|
+
|
40
|
+
# Initialize lib and attach main function
|
41
|
+
ffi_lib_flags(:now, :local)
|
42
|
+
ffi_lib(ENV['VBOX_APP_HOME'] || PATH.map {|path| "#{path}/#{NAME}"})
|
43
|
+
attach_function :VBoxGetXPCOMCFunctions, [:uint], :pointer
|
44
|
+
|
45
|
+
|
46
|
+
# Get the pointer to the XPCOMC struct which contains the functions
|
47
|
+
# to initialize
|
48
|
+
xpcom_ptr = self.VBoxGetXPCOMCFunctions(XPCOMC::VERSION)
|
49
|
+
@@xpcom = XPCOMC::VBox.new(xpcom_ptr)
|
50
|
+
|
51
|
+
|
52
|
+
# Initializes the VirtualBox and Session interfaces.
|
53
|
+
# It goes through the various supported interfaces.
|
54
|
+
SUPPORTED_VERSIONS.each {|version, iids|
|
55
|
+
virtualbox_iid_str, session_iid_str = iids
|
56
|
+
|
57
|
+
# Setup the OUT pointers
|
58
|
+
virtualbox_ptr = ::FFI::MemoryPointer.new(:pointer)
|
59
|
+
session_ptr = ::FFI::MemoryPointer.new(:pointer)
|
60
|
+
|
61
|
+
# Call the initialization functions
|
62
|
+
@@xpcom[:pfnComInitialize].call(virtualbox_iid_str, virtualbox_ptr,
|
63
|
+
session_iid_str, session_ptr)
|
64
|
+
|
65
|
+
# Read the pointers from the results
|
66
|
+
virtualbox_ptr = virtualbox_ptr.read_pointer
|
67
|
+
session_ptr = session_ptr.read_pointer
|
68
|
+
|
69
|
+
# If either pointers are null it means that
|
70
|
+
# the initialization was not successful
|
71
|
+
next if virtualbox_ptr.null? || session_ptr.null?
|
72
|
+
|
73
|
+
# Load the interface description
|
74
|
+
require "virtualbox/com/model/#{version}"
|
75
|
+
|
76
|
+
@@virtualbox = Model.create(:VirtualBox, virtualbox_ptr)
|
77
|
+
@@session = Model.create(:Session, session_ptr)
|
78
|
+
|
79
|
+
break
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.xpcom ; @@xpcom ; end
|
84
|
+
def self.virtualbox ; @@virtualbox ; end
|
85
|
+
def self.session ; @@session ; end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,342 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
module COM
|
3
|
+
module XPCOMC
|
4
|
+
|
5
|
+
class Sig
|
6
|
+
def initialize(sig)
|
7
|
+
@sig = sig.dup.freeze
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
# Converts a function spec from {AbstractInterface} to an FFI
|
12
|
+
# function spec. This handles custom types (unicode strings,
|
13
|
+
# arrays, and out-parameters) and will return a perfectly valid
|
14
|
+
# array ready to be passed into `callback`.
|
15
|
+
#
|
16
|
+
# @param [Array] spec The function spec
|
17
|
+
# @return [Array]
|
18
|
+
def to_ffi
|
19
|
+
spec = @sig.map do |item|
|
20
|
+
if item.is_a?(Array) && item[0] == :out
|
21
|
+
if item[1].is_a?(Array)
|
22
|
+
# The out is an array of items, so we add in
|
23
|
+
# two pointers: one for size and one for the array
|
24
|
+
[ :pointer, :pointer ]
|
25
|
+
else
|
26
|
+
# A regular out parameter is just a single pointer
|
27
|
+
:pointer
|
28
|
+
end
|
29
|
+
elsif item.is_a?(Array) && item.length == 1
|
30
|
+
# The parameter is an array of somethings
|
31
|
+
[ UINT32, :pointer ]
|
32
|
+
elsif item == WSTRING
|
33
|
+
# Unicode strings are simply pointers
|
34
|
+
:pointer
|
35
|
+
elsif item.to_s[0,1] == item.to_s[0,1].upcase
|
36
|
+
# Try to get the class from the interfaces
|
37
|
+
Model.get(item).to_ffi
|
38
|
+
else
|
39
|
+
# Unknown items are simply passed as-is, hopefully FFI
|
40
|
+
# will catch any problems
|
41
|
+
item
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Prepend a :pointer to represent the `this` parameter required
|
46
|
+
# for the FFI parameter lists
|
47
|
+
spec.unshift(:pointer).flatten
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
def prepare_args(args=[])
|
52
|
+
args = args.dup
|
53
|
+
|
54
|
+
results = @sig.inject([]) do |results, item|
|
55
|
+
single_type_to_arg(args, item, results)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
# Takes a spec and a formal parameter list and returns the output from
|
63
|
+
# a function, properly dereferencing any output pointers.
|
64
|
+
#
|
65
|
+
# @param [Array] specs The parameter spec for the function
|
66
|
+
# @param [Array] formal The formal parameter list
|
67
|
+
def retrieve_values(formal)
|
68
|
+
return_values = []
|
69
|
+
i = 0
|
70
|
+
@sig.each do |spec|
|
71
|
+
# Output parameters are all we care about
|
72
|
+
if spec.is_a?(Array) && spec[0] == :out
|
73
|
+
if spec[1].is_a?(Array)
|
74
|
+
# We are dealing with formal[i] and formal[i+1]
|
75
|
+
# here, where the first has the size and the
|
76
|
+
# second has the contents
|
77
|
+
return_values << dereference_pointer_array(formal[i+1], spec[1][0], dereference_pointer(formal[i], UINT32))
|
78
|
+
|
79
|
+
# Skip 2: size param + pointer
|
80
|
+
i += 2
|
81
|
+
else
|
82
|
+
return_values << dereference_pointer(formal[i], spec[1])
|
83
|
+
|
84
|
+
# Skip 1: Pointer
|
85
|
+
i += 1
|
86
|
+
end
|
87
|
+
elsif spec.is_a?(Array) && spec.length == 1
|
88
|
+
# This is an array argument, meaning it takes two
|
89
|
+
# arguments: one for size and one is a pointer to the
|
90
|
+
# array items. Therefore, skip two items.
|
91
|
+
i += 2
|
92
|
+
else
|
93
|
+
i += 1
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
if return_values.empty? then nil
|
98
|
+
elsif return_values.length == 1 then return_values.first
|
99
|
+
else return_values
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
|
105
|
+
|
106
|
+
|
107
|
+
|
108
|
+
|
109
|
+
|
110
|
+
|
111
|
+
|
112
|
+
|
113
|
+
|
114
|
+
|
115
|
+
|
116
|
+
# Dereferences a pointer with a given type into a proper Ruby object.
|
117
|
+
# If the type is a standard primitive of Ruby-FFI, it simply calls the
|
118
|
+
# proper `get_*` method on the pointer. Otherwise, it calls a
|
119
|
+
# `read_*` on the Binding class.
|
120
|
+
#
|
121
|
+
# @param [FFI::MemoryPointer] pointer
|
122
|
+
# @param [Symbol] type The type of the pointer
|
123
|
+
# @return [Object] The value of the dereferenced pointer
|
124
|
+
def dereference_pointer(pointer, type)
|
125
|
+
c_type, inferred_type = infer_type(type)
|
126
|
+
method = :"read_#{inferred_type}"
|
127
|
+
|
128
|
+
if pointer.respond_to?(method)
|
129
|
+
result = pointer.send(method)
|
130
|
+
result = result != 0 if type == BOOL
|
131
|
+
result
|
132
|
+
elsif respond_to?(method)
|
133
|
+
send(method, pointer, type)
|
134
|
+
else
|
135
|
+
raise "don't know how to dereference pointer of type #{type}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Dereferences an array out of a pointer into an array of proper Ruby
|
140
|
+
# objects.
|
141
|
+
#
|
142
|
+
# @param [FFI::MemoryPointer] pointer
|
143
|
+
# @param [Symbol] type The type of the pointer
|
144
|
+
# @param [Fixnum] length The length of the array
|
145
|
+
# @return [Array<Object>]
|
146
|
+
def dereference_pointer_array(pointer, type, length)
|
147
|
+
return [] if length == 0
|
148
|
+
|
149
|
+
c_type, inferred_type = infer_type(type)
|
150
|
+
method = :"get_array_of_#{inferred_type}"
|
151
|
+
|
152
|
+
array_pointer = pointer.get_pointer(0)
|
153
|
+
|
154
|
+
if array_pointer.respond_to?(method)
|
155
|
+
array_pointer.send(method, 0, length)
|
156
|
+
elsif respond_to?(method)
|
157
|
+
send(method, array_pointer, type, length)
|
158
|
+
else
|
159
|
+
raise "don't know how to dereference array of type #{type}"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
|
165
|
+
def pointer_for_type(type)
|
166
|
+
c_type, type = infer_type(type)
|
167
|
+
pointer = ::FFI::MemoryPointer.new(c_type)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Gives the C type and inferred type of a parameter type. Quite confusing
|
171
|
+
# since the terminology is not consistent, but hopefully these examples
|
172
|
+
# will help:
|
173
|
+
#
|
174
|
+
# type => [pointer_type, internal_type]
|
175
|
+
# :int => [:int, :int]
|
176
|
+
# :MyStruct => [:pointer, :struct]
|
177
|
+
# :unicode_string => [:pointer, :unicode_string]
|
178
|
+
#
|
179
|
+
def infer_type(type)
|
180
|
+
c_type = type
|
181
|
+
|
182
|
+
begin
|
183
|
+
if type == WSTRING
|
184
|
+
# Handle strings as pointer types
|
185
|
+
c_type = :pointer
|
186
|
+
else
|
187
|
+
# Try to get the class from the interfaces
|
188
|
+
interface = Model.get(type)
|
189
|
+
|
190
|
+
c_type = :pointer
|
191
|
+
|
192
|
+
# Depending on the class type, we're either dealing with an
|
193
|
+
# interface or an enum
|
194
|
+
if interface.superclass == COM::AbstractInterface
|
195
|
+
c_type, type = :pointer, :interface
|
196
|
+
elsif interface.superclass == COM::AbstractEnum
|
197
|
+
c_type, type = :uint32, :enum
|
198
|
+
end
|
199
|
+
end
|
200
|
+
rescue ModelNotFoundException
|
201
|
+
# Do nothing
|
202
|
+
end
|
203
|
+
|
204
|
+
[c_type, type]
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
# Converts a single type and args list to the proper formal args list
|
209
|
+
def single_type_to_arg(args, item, results)
|
210
|
+
if item.is_a?(Array) && item[0] == :out
|
211
|
+
if item[1].is_a?(Array)
|
212
|
+
# For arrays we need two pointers: one for size, and
|
213
|
+
# one for the actual array
|
214
|
+
results << pointer_for_type(UINT32)
|
215
|
+
results << pointer_for_type(item[1][0])
|
216
|
+
else
|
217
|
+
results << pointer_for_type(item[1])
|
218
|
+
end
|
219
|
+
elsif item.is_a?(Array) && item.length == 1
|
220
|
+
# Array argument
|
221
|
+
data = args.shift
|
222
|
+
|
223
|
+
# First add the length of the array
|
224
|
+
results << data.length
|
225
|
+
|
226
|
+
# Create the array
|
227
|
+
c_type, type = infer_type(item.first)
|
228
|
+
|
229
|
+
# If its a regular type (int, bool, etc.) then just make it an
|
230
|
+
# array of that
|
231
|
+
if type != :interface
|
232
|
+
# Build a pointer to an array of values
|
233
|
+
result = ::FFI::MemoryPointer.new(c_type, data.length)
|
234
|
+
adder = result.method("put_#{c_type}")
|
235
|
+
data.each_with_index do |single, index|
|
236
|
+
value = []
|
237
|
+
single_type_to_arg([single], item[0], value)
|
238
|
+
adder.call(index, value.first)
|
239
|
+
end
|
240
|
+
|
241
|
+
results << result
|
242
|
+
else
|
243
|
+
# Then convert the rest into a raw MemoryPointer
|
244
|
+
array = ::FFI::MemoryPointer.new(:pointer, data.length)
|
245
|
+
data.each_with_index do |datum, i|
|
246
|
+
converted = []
|
247
|
+
single_type_to_arg([datum], item.first, converted)
|
248
|
+
array[i].put_pointer(0, converted.first)
|
249
|
+
end
|
250
|
+
|
251
|
+
results << array
|
252
|
+
end
|
253
|
+
elsif item == WSTRING
|
254
|
+
# We have to convert the arg to a unicode string
|
255
|
+
results << XPCOMC::Lib.xpcom.string_to_utf16(args.shift)
|
256
|
+
elsif item == BOOL
|
257
|
+
results << (args.shift ? 1 : 0)
|
258
|
+
elsif item.to_s[0,1] == item.to_s[0,1].upcase
|
259
|
+
# Try to get the class from the interfaces
|
260
|
+
interface = Model.get(item)
|
261
|
+
val = args.shift
|
262
|
+
|
263
|
+
results << if interface.superclass == COM::AbstractInterface
|
264
|
+
# For interfaces, get the instance, then
|
265
|
+
# dig deep to get the pointer to the
|
266
|
+
# VtblParent, which is what the API expects
|
267
|
+
val && val.implementer.binding.object
|
268
|
+
elsif interface.superclass == COM::AbstractEnum
|
269
|
+
# For enums, we need the value of the enum
|
270
|
+
interface.index(val)
|
271
|
+
end
|
272
|
+
else
|
273
|
+
# Simply replace spec item with next item in args
|
274
|
+
# list
|
275
|
+
results << args.shift
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
|
280
|
+
|
281
|
+
|
282
|
+
|
283
|
+
|
284
|
+
|
285
|
+
# Reads a unicode string value from a pointer to that value.
|
286
|
+
#
|
287
|
+
# @return [String]
|
288
|
+
def read_unicode_string(ptr, original_type=nil)
|
289
|
+
XPCOMC::Lib.xpcom.utf16_to_string(ptr.read_pointer) || ''
|
290
|
+
end
|
291
|
+
|
292
|
+
# Reads an interface from the pointer
|
293
|
+
#
|
294
|
+
# @return [::FFI::Struct]
|
295
|
+
def read_interface(ptr, original_type)
|
296
|
+
ptr = ptr.read_pointer
|
297
|
+
return nil if ptr.null?
|
298
|
+
Model.create(original_type, ptr)
|
299
|
+
end
|
300
|
+
|
301
|
+
# Reads an enum
|
302
|
+
#
|
303
|
+
# @return [Symbol]
|
304
|
+
def read_enum(ptr, original_type)
|
305
|
+
Model.get(original_type)[ptr.get_uint(0)]
|
306
|
+
end
|
307
|
+
|
308
|
+
# Reads an array of enums
|
309
|
+
#
|
310
|
+
# @return [Array<Symbol>]
|
311
|
+
def get_array_of_enum(ptr, type, length)
|
312
|
+
klass = Model.get(type)
|
313
|
+
ptr.get_array_of_uint(0, length).map do |value|
|
314
|
+
klass[value]
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
# Reads an array of structs from a pointer
|
319
|
+
#
|
320
|
+
# @return [Array<::FFI::Struct>]
|
321
|
+
def get_array_of_interface(ptr, type, length)
|
322
|
+
klass = Model.get(type)
|
323
|
+
ptr.get_array_of_pointer(0, length).map do |pointer|
|
324
|
+
klass.new(pointer)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
# Reads an array of strings from a pointer
|
329
|
+
#
|
330
|
+
# @return [Array<String>]
|
331
|
+
def get_array_of_unicode_string(ptr, type, length)
|
332
|
+
ptr.get_array_of_pointer(0, length).map do |pointer|
|
333
|
+
XPCOMC::Lib.xpcom.utf16_to_string(pointer)
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
|
338
|
+
end
|
339
|
+
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require_relative 'sig'
|
2
|
+
|
3
|
+
module VirtualBox
|
4
|
+
module COM
|
5
|
+
module XPCOMC
|
6
|
+
|
7
|
+
class Spec
|
8
|
+
attr_reader :name
|
9
|
+
|
10
|
+
def hide?
|
11
|
+
@opts[:hide]
|
12
|
+
end
|
13
|
+
|
14
|
+
class Function < Spec
|
15
|
+
def initialize(name, type, args, opts)
|
16
|
+
@name, @type, @args, @opts = name, type, args, opts
|
17
|
+
end
|
18
|
+
|
19
|
+
def signatures
|
20
|
+
{ name => to_call }
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_call
|
24
|
+
Sig.new( if @type.nil?
|
25
|
+
then @args
|
26
|
+
else @args + [ [ :out, @type ] ]
|
27
|
+
end)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Property < Spec
|
32
|
+
attr_reader :getter, :setter
|
33
|
+
|
34
|
+
def initialize(name, type, opts)
|
35
|
+
@name, @type, @opts = name, type, opts
|
36
|
+
end
|
37
|
+
|
38
|
+
def signatures
|
39
|
+
r = {}
|
40
|
+
r[getter] = to_read
|
41
|
+
r[setter] = to_write unless self.readonly?
|
42
|
+
r
|
43
|
+
end
|
44
|
+
|
45
|
+
def readonly?
|
46
|
+
@opts[:readonly]
|
47
|
+
end
|
48
|
+
|
49
|
+
def getter ; :"get_#{@name}" ; end
|
50
|
+
def setter ; :"set_#{@name}" ; end
|
51
|
+
def to_read ; Sig.new([[:out, @type]]) ; end
|
52
|
+
def to_write ; Sig.new([ @type ]) ; end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|