virtualbox 0.5.4 → 0.6.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.
- data/.gitignore +2 -1
- data/Gemfile +1 -1
- data/Rakefile +1 -1
- data/Readme.md +5 -21
- data/VERSION +1 -1
- data/docs/WhatsNew.md +9 -47
- data/lib/virtualbox.rb +7 -30
- data/lib/virtualbox/abstract_model.rb +25 -5
- data/lib/virtualbox/abstract_model/attributable.rb +5 -1
- data/lib/virtualbox/abstract_model/dirty.rb +2 -0
- data/lib/virtualbox/abstract_model/interface_attributes.rb +96 -0
- data/lib/virtualbox/abstract_model/relatable.rb +19 -8
- data/lib/virtualbox/appliance.rb +59 -0
- data/lib/virtualbox/audio_adapter.rb +44 -0
- data/lib/virtualbox/bios.rb +44 -0
- data/lib/virtualbox/com.rb +23 -0
- data/lib/virtualbox/com/abstract_enum.rb +42 -0
- data/lib/virtualbox/com/abstract_implementer.rb +43 -0
- data/lib/virtualbox/com/abstract_interface.rb +165 -0
- data/lib/virtualbox/com/ffi/interface.rb +141 -0
- data/lib/virtualbox/com/ffi/interfaces.rb +42 -0
- data/lib/virtualbox/com/ffi/util.rb +101 -0
- data/lib/virtualbox/com/ffi/vboxxpcomc.rb +31 -0
- data/lib/virtualbox/com/ffi_interface.rb +65 -0
- data/lib/virtualbox/com/implementer/base.rb +52 -0
- data/lib/virtualbox/com/implementer/ffi.rb +350 -0
- data/lib/virtualbox/com/implementer/mscom.rb +165 -0
- data/lib/virtualbox/com/implementer/nil.rb +10 -0
- data/lib/virtualbox/com/interface/appliance.rb +20 -0
- data/lib/virtualbox/com/interface/audio_adapter.rb +13 -0
- data/lib/virtualbox/com/interface/audio_controller_type.rb +9 -0
- data/lib/virtualbox/com/interface/audio_driver_type.rb +9 -0
- data/lib/virtualbox/com/interface/bios_boot_menu_mode.rb +9 -0
- data/lib/virtualbox/com/interface/bios_settings.rb +19 -0
- data/lib/virtualbox/com/interface/clipboard_mode.rb +9 -0
- data/lib/virtualbox/com/interface/console.rb +48 -0
- data/lib/virtualbox/com/interface/cpu_property_type.rb +9 -0
- data/lib/virtualbox/com/interface/device_type.rb +9 -0
- data/lib/virtualbox/com/interface/dhcp_server.rb +20 -0
- data/lib/virtualbox/com/interface/firmware_type.rb +9 -0
- data/lib/virtualbox/com/interface/guest_os_type.rb +21 -0
- data/lib/virtualbox/com/interface/host.rb +40 -0
- data/lib/virtualbox/com/interface/host_network_interface.rb +28 -0
- data/lib/virtualbox/com/interface/host_network_interface_medium_type.rb +9 -0
- data/lib/virtualbox/com/interface/host_network_interface_status.rb +9 -0
- data/lib/virtualbox/com/interface/host_network_interface_type.rb +9 -0
- data/lib/virtualbox/com/interface/host_usb_device.rb +11 -0
- data/lib/virtualbox/com/interface/host_usb_device_filter.rb +11 -0
- data/lib/virtualbox/com/interface/hw_virt_ex_property_type.rb +9 -0
- data/lib/virtualbox/com/interface/machine.rb +103 -0
- data/lib/virtualbox/com/interface/machine_state.rb +12 -0
- data/lib/virtualbox/com/interface/medium.rb +48 -0
- data/lib/virtualbox/com/interface/medium_attachment.rb +16 -0
- data/lib/virtualbox/com/interface/medium_format.rb +16 -0
- data/lib/virtualbox/com/interface/medium_state.rb +9 -0
- data/lib/virtualbox/com/interface/medium_type.rb +9 -0
- data/lib/virtualbox/com/interface/medium_variant.rb +9 -0
- data/lib/virtualbox/com/interface/network_adapter.rb +28 -0
- data/lib/virtualbox/com/interface/network_adapter_type.rb +9 -0
- data/lib/virtualbox/com/interface/network_attachment_type.rb +9 -0
- data/lib/virtualbox/com/interface/nsiexception.rb +21 -0
- data/lib/virtualbox/com/interface/nsisupports.rb +13 -0
- data/lib/virtualbox/com/interface/parallel_port.rb +15 -0
- data/lib/virtualbox/com/interface/port_mode.rb +9 -0
- data/lib/virtualbox/com/interface/progress.rb +58 -0
- data/lib/virtualbox/com/interface/serial_port.rb +17 -0
- data/lib/virtualbox/com/interface/session.rb +16 -0
- data/lib/virtualbox/com/interface/session_state.rb +9 -0
- data/lib/virtualbox/com/interface/session_type.rb +9 -0
- data/lib/virtualbox/com/interface/shared_folder.rb +15 -0
- data/lib/virtualbox/com/interface/snapshot.rb +18 -0
- data/lib/virtualbox/com/interface/storage_bus.rb +9 -0
- data/lib/virtualbox/com/interface/storage_controller.rb +21 -0
- data/lib/virtualbox/com/interface/storage_controller_type.rb +9 -0
- data/lib/virtualbox/com/interface/system_properties.rb +35 -0
- data/lib/virtualbox/com/interface/usb_controller.rb +18 -0
- data/lib/virtualbox/com/interface/usb_device.rb +22 -0
- data/lib/virtualbox/com/interface/usb_device_filter.rb +21 -0
- data/lib/virtualbox/com/interface/usb_device_filter_action.rb +9 -0
- data/lib/virtualbox/com/interface/usb_device_state.rb +9 -0
- data/lib/virtualbox/com/interface/virtual_box_error_info.rb +15 -0
- data/lib/virtualbox/com/interface/virtual_system_description.rb +17 -0
- data/lib/virtualbox/com/interface/virtual_system_description_type.rb +12 -0
- data/lib/virtualbox/com/interface/virtual_system_description_value_type.rb +9 -0
- data/lib/virtualbox/com/interface/virtualbox.rb +54 -0
- data/lib/virtualbox/com/interface/vrdp_auth_type.rb +9 -0
- data/lib/virtualbox/com/interface/vrdp_server.rb +17 -0
- data/lib/virtualbox/com/mscom_interface.rb +22 -0
- data/lib/virtualbox/com/util.rb +18 -0
- data/lib/virtualbox/dvd.rb +7 -94
- data/lib/virtualbox/exceptions.rb +24 -0
- data/lib/virtualbox/ext/glob_loader.rb +22 -0
- data/lib/virtualbox/ext/logger.rb +38 -0
- data/lib/virtualbox/ext/platform.rb +1 -1
- data/lib/virtualbox/extra_data.rb +25 -37
- data/lib/virtualbox/forwarded_port.rb +35 -13
- data/lib/virtualbox/global.rb +22 -80
- data/lib/virtualbox/hard_drive.rb +30 -97
- data/lib/virtualbox/lib.rb +82 -0
- data/lib/virtualbox/media.rb +7 -6
- data/lib/virtualbox/medium.rb +138 -0
- data/lib/virtualbox/medium_attachment.rb +61 -0
- data/lib/virtualbox/network_adapter.rb +134 -0
- data/lib/virtualbox/shared_folder.rb +53 -78
- data/lib/virtualbox/storage_controller.rb +76 -20
- data/lib/virtualbox/system_properties.rb +74 -0
- data/lib/virtualbox/usb_controller.rb +55 -0
- data/lib/virtualbox/version.rb +15 -0
- data/lib/virtualbox/virtual_system_description.rb +47 -0
- data/lib/virtualbox/vm.rb +160 -272
- data/test/test_helper.rb +0 -108
- data/test/virtualbox/abstract_model/attributable_test.rb +7 -1
- data/test/virtualbox/abstract_model/dirty_test.rb +1 -1
- data/test/virtualbox/abstract_model/interface_attributes_test.rb +169 -0
- data/test/virtualbox/abstract_model/relatable_test.rb +20 -0
- data/test/virtualbox/abstract_model_test.rb +40 -5
- data/test/virtualbox/appliance_test.rb +152 -0
- data/test/virtualbox/audio_adapter_test.rb +83 -0
- data/test/virtualbox/bios_test.rb +83 -0
- data/test/virtualbox/com/abstract_enum_test.rb +48 -0
- data/test/virtualbox/com/abstract_implementer_test.rb +39 -0
- data/test/virtualbox/com/abstract_interface_test.rb +139 -0
- data/test/virtualbox/com/ffi/interface_test.rb +249 -0
- data/test/virtualbox/com/ffi/util_test.rb +86 -0
- data/test/virtualbox/com/ffi_interface_test.rb +42 -0
- data/test/virtualbox/com/implementer/base_test.rb +37 -0
- data/test/virtualbox/com/implementer/ffi_test.rb +519 -0
- data/test/virtualbox/com/implementer/mscom_test.rb +208 -0
- data/test/virtualbox/com/mscom_interface_test.rb +17 -0
- data/test/virtualbox/com/util_test.rb +17 -0
- data/test/virtualbox/dvd_test.rb +4 -95
- data/test/virtualbox/ext/platform_test.rb +8 -0
- data/test/virtualbox/extra_data_test.rb +78 -102
- data/test/virtualbox/forwarded_port_test.rb +57 -7
- data/test/virtualbox/global_test.rb +25 -115
- data/test/virtualbox/hard_drive_test.rb +49 -212
- data/test/virtualbox/lib_test.rb +93 -0
- data/test/virtualbox/medium_attachment_test.rb +147 -0
- data/test/virtualbox/medium_test.rb +192 -0
- data/test/virtualbox/network_adapter_test.rb +160 -0
- data/test/virtualbox/shared_folder_test.rb +144 -160
- data/test/virtualbox/storage_controller_test.rb +166 -45
- data/test/virtualbox/system_properties_test.rb +87 -0
- data/test/virtualbox/usb_controller_test.rb +104 -0
- data/test/virtualbox/version_test.rb +34 -0
- data/test/virtualbox/virtual_system_description_test.rb +61 -0
- data/test/virtualbox/vm_test.rb +288 -322
- data/test/virtualbox_test.rb +1 -9
- data/virtualbox.gemspec +139 -23
- metadata +143 -27
- data/lib/virtualbox/attached_device.rb +0 -249
- data/lib/virtualbox/command.rb +0 -109
- data/lib/virtualbox/image.rb +0 -137
- data/lib/virtualbox/nic.rb +0 -111
- data/lib/virtualbox/system_property.rb +0 -55
- data/lib/virtualbox/usb.rb +0 -72
- data/test/virtualbox/attached_device_test.rb +0 -303
- data/test/virtualbox/command_test.rb +0 -152
- data/test/virtualbox/image_test.rb +0 -190
- data/test/virtualbox/nic_test.rb +0 -76
- data/test/virtualbox/system_property_test.rb +0 -71
- data/test/virtualbox/usb_test.rb +0 -35
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module VirtualBox
|
|
2
|
+
module COM
|
|
3
|
+
module FFI
|
|
4
|
+
# Creates all the interfaces for the FFI implementation. Eventually this
|
|
5
|
+
# file should be conditionally loaded based on OS, so that Windows users
|
|
6
|
+
# don't have to wait for all this translation to occur.
|
|
7
|
+
create_interface(:NSISupports)
|
|
8
|
+
create_interface(:NSIException, :NSISupports)
|
|
9
|
+
create_interface(:Session, :NSISupports)
|
|
10
|
+
create_interface(:VirtualBox, :NSISupports)
|
|
11
|
+
create_interface(:Appliance, :NSISupports)
|
|
12
|
+
create_interface(:AudioAdapter, :NSISupports)
|
|
13
|
+
create_interface(:BIOSSettings, :NSISupports)
|
|
14
|
+
create_interface(:Console, :NSISupports)
|
|
15
|
+
create_interface(:DHCPServer, :NSISupports)
|
|
16
|
+
create_interface(:GuestOSType, :NSISupports)
|
|
17
|
+
create_interface(:Host, :NSISupports)
|
|
18
|
+
create_interface(:HostNetworkInterface, :NSISupports)
|
|
19
|
+
create_interface(:Machine, :NSISupports)
|
|
20
|
+
create_interface(:Medium, :NSISupports)
|
|
21
|
+
create_interface(:MediumAttachment, :NSISupports)
|
|
22
|
+
create_interface(:MediumFormat, :NSISupports)
|
|
23
|
+
create_interface(:NetworkAdapter, :NSISupports)
|
|
24
|
+
create_interface(:ParallelPort, :NSISupports)
|
|
25
|
+
create_interface(:Progress, :NSISupports)
|
|
26
|
+
create_interface(:SerialPort, :NSISupports)
|
|
27
|
+
create_interface(:SharedFolder, :NSISupports)
|
|
28
|
+
create_interface(:Snapshot, :NSISupports)
|
|
29
|
+
create_interface(:StorageController, :NSISupports)
|
|
30
|
+
create_interface(:SystemProperties, :NSISupports)
|
|
31
|
+
create_interface(:USBController, :NSISupports)
|
|
32
|
+
create_interface(:USBDevice, :NSISupports)
|
|
33
|
+
create_interface(:USBDeviceFilter, :NSISupports)
|
|
34
|
+
create_interface(:VirtualBoxErrorInfo, :NSIException)
|
|
35
|
+
create_interface(:VirtualSystemDescription, :NSISupports)
|
|
36
|
+
create_interface(:VRDPServer, :NSISupports)
|
|
37
|
+
|
|
38
|
+
create_interface(:HostUSBDevice, :USBDevice)
|
|
39
|
+
create_interface(:HostUSBDeviceFilter, :USBDeviceFilter)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
module VirtualBox
|
|
2
|
+
module COM
|
|
3
|
+
module FFI
|
|
4
|
+
# Class which contains many class-level utility methods to assist
|
|
5
|
+
# with the FFI interface. These functions range from converting a
|
|
6
|
+
# function spec to a FFI parameter list to dereferencing pointers.
|
|
7
|
+
class Util
|
|
8
|
+
class <<self
|
|
9
|
+
# Converts a function spec from {AbstractInterface} to an FFI
|
|
10
|
+
# function spec. This handles custom types (unicode strings,
|
|
11
|
+
# arrays, and out-parameters) and will return a perfectly valid
|
|
12
|
+
# array ready to be passed into `callback`.
|
|
13
|
+
#
|
|
14
|
+
# @param [Array] spec The function spec
|
|
15
|
+
# @return [Array]
|
|
16
|
+
def spec_to_ffi(spec)
|
|
17
|
+
spec = spec.collect do |item|
|
|
18
|
+
if item.is_a?(Array) && item[0] == :out
|
|
19
|
+
if item[1].is_a?(Array)
|
|
20
|
+
# The out is an array of items, so we add in two pointers:
|
|
21
|
+
# one for size and one for the array
|
|
22
|
+
[:pointer, :pointer]
|
|
23
|
+
else
|
|
24
|
+
# A regular out parameter is just a single pointer
|
|
25
|
+
:pointer
|
|
26
|
+
end
|
|
27
|
+
elsif item.is_a?(Array) && item.length == 1
|
|
28
|
+
# The parameter is an array of somethings
|
|
29
|
+
[T_UINT32, :pointer]
|
|
30
|
+
elsif item == WSTRING
|
|
31
|
+
# Unicode strings are simply pointers
|
|
32
|
+
:pointer
|
|
33
|
+
elsif item.to_s[0,1] == item.to_s[0,1].upcase
|
|
34
|
+
begin
|
|
35
|
+
# Try to get the class from the interfaces
|
|
36
|
+
interface = COM::Interface.const_get(item.to_sym)
|
|
37
|
+
|
|
38
|
+
if interface.superclass == COM::AbstractInterface
|
|
39
|
+
:pointer
|
|
40
|
+
elsif interface.superclass == COM::AbstractEnum
|
|
41
|
+
T_UINT32
|
|
42
|
+
end
|
|
43
|
+
rescue NameError
|
|
44
|
+
# Default to a pointer, since not all interfaces are implemented
|
|
45
|
+
:pointer
|
|
46
|
+
end
|
|
47
|
+
else
|
|
48
|
+
# Unknown items are simply passed as-is, hopefully FFI
|
|
49
|
+
# will catch any problems
|
|
50
|
+
item
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Prepend a :pointer to represent the `this` parameter required
|
|
55
|
+
# for the FFI parameter lists
|
|
56
|
+
spec.unshift(:pointer).flatten
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# An "almost complete" camel-caser. Camel cases a string with a few
|
|
60
|
+
# exceptions. For example: `get_foo` becomes `GetFoo`, but `get_os_type`
|
|
61
|
+
# becomes `GetOSType` since `os` is a special case.
|
|
62
|
+
#
|
|
63
|
+
# @param [String] string The string to camel case
|
|
64
|
+
# @return [String]
|
|
65
|
+
def camelize(string)
|
|
66
|
+
special_cases = {
|
|
67
|
+
"os" => "OS",
|
|
68
|
+
"dhcp" => "DHCP",
|
|
69
|
+
"dvd" => "DVD",
|
|
70
|
+
"usb" => "USB",
|
|
71
|
+
"vram" => "VRAM",
|
|
72
|
+
"3d" => "3D",
|
|
73
|
+
"bios" => "BIOS",
|
|
74
|
+
"vrdp" => "VRDP",
|
|
75
|
+
"hw" => "HW",
|
|
76
|
+
"png" => "PNG",
|
|
77
|
+
"io" => "IO",
|
|
78
|
+
"apic" => "APIC",
|
|
79
|
+
"acpi" => "ACPI",
|
|
80
|
+
"pxe" => "PXE",
|
|
81
|
+
"nat" => "NAT",
|
|
82
|
+
"ide" => "IDE",
|
|
83
|
+
"vfs" => "VFS",
|
|
84
|
+
"ip" => "IP",
|
|
85
|
+
"vdi" => "VDI",
|
|
86
|
+
"cpu" => "CPU",
|
|
87
|
+
"ram" => "RAM",
|
|
88
|
+
"hdd" => "HDD"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
parts = string.to_s.split(/_/).collect do |part|
|
|
92
|
+
special_cases[part] || part.capitalize
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
parts.join("")
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module VirtualBox
|
|
2
|
+
module COM
|
|
3
|
+
module FFI
|
|
4
|
+
# Callback types for VBOXXPCOMC
|
|
5
|
+
callback :pfnGetVersion, [], :uint
|
|
6
|
+
callback :pfnComInitialize, [:string, :pointer, :string, :pointer], :void
|
|
7
|
+
callback :pfnComUninitialize, [], :void
|
|
8
|
+
callback :pfnComUnallocMem, [:void], :void
|
|
9
|
+
callback :pfnUtf16Free, [:pointer], :void
|
|
10
|
+
callback :pfnUtf8Free, [:string], :void
|
|
11
|
+
callback :pfnUtf16ToUtf8, [:pointer, :pointer], :int
|
|
12
|
+
callback :pfnUtf8ToUtf16, [:string, :pointer], :int
|
|
13
|
+
callback :pfnGetEventQueue, [:pointer], :void
|
|
14
|
+
|
|
15
|
+
class VBOXXPCOMC < ::FFI::Struct
|
|
16
|
+
layout :cb, :uint,
|
|
17
|
+
:uVersion, :uint,
|
|
18
|
+
:pfnGetVersion, :pfnGetVersion,
|
|
19
|
+
:pfnComInitialize, :pfnComInitialize,
|
|
20
|
+
:pfnComUninitialize, :pfnComUninitialize,
|
|
21
|
+
:pfnComUnallocMem, :pfnComUnallocMem,
|
|
22
|
+
:pfnUtf16Free, :pfnUtf16Free,
|
|
23
|
+
:pfnUtf8Free, :pfnUtf8Free,
|
|
24
|
+
:pfnUtf16ToUtf8, :pfnUtf16ToUtf8,
|
|
25
|
+
:pfnUtf8ToUtf16, :pfnUtf8ToUtf16,
|
|
26
|
+
:pfnGetEventQueue, :pfnGetEventQueue,
|
|
27
|
+
:uEndVersion, :uint
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module VirtualBox
|
|
2
|
+
module COM
|
|
3
|
+
class FFIInterface
|
|
4
|
+
extend ::FFI::Library
|
|
5
|
+
|
|
6
|
+
# Constant used to initialize the XPCOM C interface
|
|
7
|
+
XPCOMC_VERSION = 0x00020000
|
|
8
|
+
|
|
9
|
+
# VBOXXPCOMC struct. This typically won't be used.
|
|
10
|
+
attr_reader :xpcom
|
|
11
|
+
|
|
12
|
+
# The VirtualBox and Session interfaces, both of which are extremely
|
|
13
|
+
# important in interfacing with the VirtualBox API. Once these have been
|
|
14
|
+
# initialized, all other parts of the API can be accessed via these
|
|
15
|
+
# instances.
|
|
16
|
+
attr_reader :virtualbox
|
|
17
|
+
attr_reader :session
|
|
18
|
+
|
|
19
|
+
class <<self
|
|
20
|
+
# Sets up the FFI interface and also initializes the interface,
|
|
21
|
+
# returning an instance of {FFIInterface}.
|
|
22
|
+
def create(lib_path=nil)
|
|
23
|
+
setup(lib_path)
|
|
24
|
+
new
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Sets up the FFI interface by specifying the FFI library path
|
|
28
|
+
# and attaching the initial function (which can't be done until
|
|
29
|
+
# the FFI library is specified).
|
|
30
|
+
#
|
|
31
|
+
# @param [String] lib_path
|
|
32
|
+
def setup(lib_path=nil)
|
|
33
|
+
# Setup the path to the C library
|
|
34
|
+
lib_path ||= "/Applications/VirtualBox.app/Contents/MacOS/VBoxXPCOMC.dylib"
|
|
35
|
+
|
|
36
|
+
# Attach to the interface
|
|
37
|
+
ffi_lib lib_path
|
|
38
|
+
attach_function :VBoxGetXPCOMCFunctions, [:uint], :pointer
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def initialize
|
|
43
|
+
initialize_com
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Initializes the COM interface with XPCOM. This sets up the `virtualbox`,
|
|
47
|
+
# `session`, and `xpcom` attributes. This should only be called once.
|
|
48
|
+
def initialize_com
|
|
49
|
+
# Get the pointer to the XPCOMC struct which contains the functions
|
|
50
|
+
# to initialize
|
|
51
|
+
xpcom_pointer = self.class.VBoxGetXPCOMCFunctions(XPCOMC_VERSION)
|
|
52
|
+
@xpcom = FFI::VBOXXPCOMC.new(xpcom_pointer)
|
|
53
|
+
|
|
54
|
+
virtualbox_ptr = ::FFI::MemoryPointer.new(:pointer)
|
|
55
|
+
session_ptr = ::FFI::MemoryPointer.new(:pointer)
|
|
56
|
+
|
|
57
|
+
# Initialize the virtualbox API and get the global VirtualBox
|
|
58
|
+
# interface and a session interface
|
|
59
|
+
@xpcom[:pfnComInitialize].call(COM::Interface::VirtualBox::IID_STR, virtualbox_ptr, COM::Interface::Session::IID_STR, session_ptr)
|
|
60
|
+
@virtualbox = Interface::VirtualBox.new(Implementer::FFI, self, virtualbox_ptr.get_pointer(0))
|
|
61
|
+
@session = Interface::Session.new(Implementer::FFI, self, session_ptr.get_pointer(0))
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module VirtualBox
|
|
2
|
+
module COM
|
|
3
|
+
module Implementer
|
|
4
|
+
class Base < AbstractImplementer
|
|
5
|
+
include Logger
|
|
6
|
+
|
|
7
|
+
# Finds and returns the `COM::Interface` class associated with the type.
|
|
8
|
+
# If the class does not exist, a `NameError` will be raised.
|
|
9
|
+
#
|
|
10
|
+
# @return [Class]
|
|
11
|
+
def interface_klass(type)
|
|
12
|
+
COM::Interface.const_get(type)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Gives the C type and inferred type of a parameter type. Quite confusing
|
|
16
|
+
# since the terminology is not consistent, but hopefully these examples
|
|
17
|
+
# will help:
|
|
18
|
+
#
|
|
19
|
+
# type => [pointer_type, internal_type]
|
|
20
|
+
# :int => [:int, :int]
|
|
21
|
+
# :MyStruct => [:pointer, :struct]
|
|
22
|
+
# :unicode_string => [:pointer, :unicode_string]
|
|
23
|
+
#
|
|
24
|
+
def infer_type(type)
|
|
25
|
+
c_type = type
|
|
26
|
+
|
|
27
|
+
begin
|
|
28
|
+
if type == WSTRING
|
|
29
|
+
# Handle strings as pointer types
|
|
30
|
+
c_type = :pointer
|
|
31
|
+
else
|
|
32
|
+
# Try to get the class from the interfaces
|
|
33
|
+
interface = COM::Interface.const_get(type)
|
|
34
|
+
|
|
35
|
+
c_type = :pointer
|
|
36
|
+
|
|
37
|
+
# Depending on the class type, we're either dealing with an interface
|
|
38
|
+
# or an enum
|
|
39
|
+
type = :interface if interface.superclass == COM::AbstractInterface
|
|
40
|
+
type = :enum if interface.superclass == COM::AbstractEnum
|
|
41
|
+
end
|
|
42
|
+
rescue NameError
|
|
43
|
+
# Do nothing
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
[c_type, type]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
module VirtualBox
|
|
2
|
+
module COM
|
|
3
|
+
module Implementer
|
|
4
|
+
class FFI < Base
|
|
5
|
+
attr_reader :ffi_interface
|
|
6
|
+
attr_reader :lib
|
|
7
|
+
|
|
8
|
+
# Initializes the FFI implementer which takes an {VirtualBox::COM::AbstractInterface AbstractInterface}
|
|
9
|
+
# instant and FFI pointer and initializes everything required to
|
|
10
|
+
# communicate with that interface via FFI.
|
|
11
|
+
#
|
|
12
|
+
# @param [VirtualBox::COM::AbstractInteface] inteface
|
|
13
|
+
# @param [FFI::Pointer] pointer
|
|
14
|
+
def initialize(interface, lib_base, pointer)
|
|
15
|
+
super(interface)
|
|
16
|
+
|
|
17
|
+
@lib = lib_base
|
|
18
|
+
@ffi_interface = ffi_class.new(pointer)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Gets the FFI struct class associated with the interface. This works
|
|
22
|
+
# by stripping the namespace off of the interface class and finding that
|
|
23
|
+
# same class within the `COM::FFI` namespace. For example:
|
|
24
|
+
# `VirtualBox::COM::Interface::Session` becomes `VirtualBox::COM::FFI::Session`
|
|
25
|
+
#
|
|
26
|
+
# @return [Class]
|
|
27
|
+
def ffi_class
|
|
28
|
+
# Take off the last part of the class, so `Foo::Bar::Baz` becomes
|
|
29
|
+
# just `Baz`
|
|
30
|
+
klass_name = interface.class.to_s.split("::").last
|
|
31
|
+
|
|
32
|
+
# Get the associated FFI class
|
|
33
|
+
COM::FFI.const_get(klass_name)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Reads a property from the interface with the given name.
|
|
37
|
+
def read_property(name, opts)
|
|
38
|
+
call_vtbl_function("get_#{name}".to_sym, [[:out, opts[:value_type]]])
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Writes a property to the interface with the given name and value.
|
|
42
|
+
def write_property(name, value, opts)
|
|
43
|
+
call_vtbl_function("set_#{name}".to_sym, [opts[:value_type]], [value])
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Calls a function from the interface with the given name and args. This
|
|
47
|
+
# method is called from the {AbstractInterface}.
|
|
48
|
+
def call_function(name, args, opts)
|
|
49
|
+
spec = opts[:spec].dup
|
|
50
|
+
spec << [:out, opts[:value_type]] if !opts[:value_type].nil?
|
|
51
|
+
|
|
52
|
+
call_vtbl_function(name.to_sym, spec, args)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Calls a function on the vtbl of the FFI struct. This function handles
|
|
56
|
+
# converting the spec to proper arguments and also handles reading out
|
|
57
|
+
# the arguments, dereferencing pointers, setting up objects, etc. so that
|
|
58
|
+
# the return value is filled with nicely formatted Ruby objects.
|
|
59
|
+
#
|
|
60
|
+
# If the vtbl function being called only has one out parameter, then the
|
|
61
|
+
# return value will be that single object. If it has multiple, then it will
|
|
62
|
+
# be an array of objects.
|
|
63
|
+
def call_vtbl_function(name, spec, args=[])
|
|
64
|
+
# Get the "formal argument" list. This is the list of arguments to send
|
|
65
|
+
# to the actual function based on the spec. This contains pointers, some
|
|
66
|
+
# arguments from `args`, etc.
|
|
67
|
+
formal_args = spec_to_args(spec, args)
|
|
68
|
+
|
|
69
|
+
# Call the function.
|
|
70
|
+
logger.debug("FFI call: #{name} #{args.inspect}")
|
|
71
|
+
call_and_check(ffi_interface.vtbl[name], ffi_interface.vtbl_parent, *formal_args)
|
|
72
|
+
|
|
73
|
+
# Extract the values from the formal args array, again based on the
|
|
74
|
+
# spec (and the various :out parameters)
|
|
75
|
+
result = values_from_formal_args(spec, formal_args)
|
|
76
|
+
logger.debug(" = #{result.inspect}")
|
|
77
|
+
result
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
#############################################################
|
|
81
|
+
# Internal Methods, a.k.a. unless you're hacking on the code of this
|
|
82
|
+
# library, you should do well to leave these alone =]
|
|
83
|
+
#############################################################
|
|
84
|
+
|
|
85
|
+
# Checks the result of a method call for an error, and if an error
|
|
86
|
+
# occurs, then raises an exception.
|
|
87
|
+
def call_and_check(function, *args)
|
|
88
|
+
result = function.call(*args)
|
|
89
|
+
|
|
90
|
+
# Ignore NS_ERROR_NOT_IMPLEMENTED, since it seems to be raised for
|
|
91
|
+
# things which aren't really exceptional
|
|
92
|
+
if result != 2147500033 && (result & 0x8000_0000) != 0
|
|
93
|
+
# Failure, raise exception with details of the error
|
|
94
|
+
raise exception_map(result).new({
|
|
95
|
+
:function => function.to_s,
|
|
96
|
+
:result_code => result
|
|
97
|
+
})
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Maps a result code to an exception. If no mapping currently exists,
|
|
102
|
+
# then a regular {Exceptions::FFIException} is returned.
|
|
103
|
+
#
|
|
104
|
+
# @param [Fixnum] code Result code
|
|
105
|
+
# @return [Class]
|
|
106
|
+
def exception_map(code)
|
|
107
|
+
map = {
|
|
108
|
+
0x80BB_0001 => Exceptions::ObjectNotFoundException,
|
|
109
|
+
0x80BB_0002 => Exceptions::InvalidVMStateException,
|
|
110
|
+
0x80BB_0003 => Exceptions::VMErrorException,
|
|
111
|
+
0x80BB_0004 => Exceptions::FileErrorException,
|
|
112
|
+
0x80BB_0005 => Exceptions::SubsystemException,
|
|
113
|
+
0x80BB_0006 => Exceptions::PDMException,
|
|
114
|
+
0x80BB_0007 => Exceptions::InvalidObjectStateException,
|
|
115
|
+
0x80BB_0008 => Exceptions::HostErrorException,
|
|
116
|
+
0x80BB_0009 => Exceptions::NotSupportedException,
|
|
117
|
+
0x80BB_000A => Exceptions::XMLErrorException,
|
|
118
|
+
0x80BB_000B => Exceptions::InvalidSessionStateException,
|
|
119
|
+
0x80BB_000C => Exceptions::ObjectInUseException
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
map[code] || Exceptions::FFIException
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Converts a function spec to a proper argument list with the given
|
|
126
|
+
# arguments.
|
|
127
|
+
#
|
|
128
|
+
# @return [Array]
|
|
129
|
+
def spec_to_args(spec, args=[])
|
|
130
|
+
args = args.dup
|
|
131
|
+
|
|
132
|
+
spec = spec.collect do |item|
|
|
133
|
+
if item.is_a?(Array) && item[0] == :out
|
|
134
|
+
if item[1].is_a?(Array)
|
|
135
|
+
# For arrays we need two pointers: one for size, and one for the
|
|
136
|
+
# actual array
|
|
137
|
+
[pointer_for_type(T_UINT32), pointer_for_type(item[1][0])]
|
|
138
|
+
else
|
|
139
|
+
pointer_for_type(item[1])
|
|
140
|
+
end
|
|
141
|
+
elsif item == WSTRING
|
|
142
|
+
# We have to convert the arg to a unicode string
|
|
143
|
+
string_to_utf16(args.shift)
|
|
144
|
+
elsif item == T_BOOL
|
|
145
|
+
args.shift ? 1 : 0
|
|
146
|
+
elsif item.to_s[0,1] == item.to_s[0,1].upcase
|
|
147
|
+
# Try to get the class from the interfaces
|
|
148
|
+
interface = interface_klass(item.to_sym)
|
|
149
|
+
|
|
150
|
+
if interface.superclass == COM::AbstractInterface
|
|
151
|
+
# For interfaces, get the instance, then dig deep to get the pointer
|
|
152
|
+
# to the VtblParent, which is what the API expects
|
|
153
|
+
instance = args.shift
|
|
154
|
+
|
|
155
|
+
if !instance.nil?
|
|
156
|
+
instance.implementer.ffi_interface.vtbl_parent
|
|
157
|
+
else
|
|
158
|
+
# If the argument was nil, just pass a nil pointer as the argument
|
|
159
|
+
nil
|
|
160
|
+
end
|
|
161
|
+
elsif interface.superclass == COM::AbstractEnum
|
|
162
|
+
# For enums, we need the value of the enum
|
|
163
|
+
interface.index(args.shift.to_sym)
|
|
164
|
+
end
|
|
165
|
+
else
|
|
166
|
+
# Simply replace spec item with next item in args
|
|
167
|
+
# list
|
|
168
|
+
args.shift
|
|
169
|
+
end
|
|
170
|
+
end.flatten
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Takes a spec and a formal parameter list and returns the output from
|
|
174
|
+
# a function, properly dereferencing any output pointers.
|
|
175
|
+
#
|
|
176
|
+
# @param [Array] specs The parameter spec for the function
|
|
177
|
+
# @param [Array] formal The formal parameter list
|
|
178
|
+
def values_from_formal_args(specs, formal)
|
|
179
|
+
return_values = []
|
|
180
|
+
i = 0
|
|
181
|
+
specs.each do |spec|
|
|
182
|
+
# Output parameters are all we care about
|
|
183
|
+
if spec.is_a?(Array) && spec[0] == :out
|
|
184
|
+
if spec[1].is_a?(Array)
|
|
185
|
+
# We are dealing with formal[i] and formal[i+1] here, where
|
|
186
|
+
# the first has the size and the second has the contents
|
|
187
|
+
return_values << dereference_pointer_array(formal[i+1], spec[1][0], dereference_pointer(formal[i], T_UINT32))
|
|
188
|
+
|
|
189
|
+
# Increment once more to skip the size param
|
|
190
|
+
i += 1
|
|
191
|
+
else
|
|
192
|
+
return_values << dereference_pointer(formal[i], spec[1])
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
i += 1
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
if return_values.empty?
|
|
200
|
+
nil
|
|
201
|
+
elsif return_values.length == 1
|
|
202
|
+
return_values.first
|
|
203
|
+
else
|
|
204
|
+
return_values
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Dereferences a pointer with a given type into a proper Ruby object.
|
|
209
|
+
# If the type is a standard primitive of Ruby-FFI, it simply calls the
|
|
210
|
+
# proper `get_*` method on the pointer. Otherwise, it calls a
|
|
211
|
+
# `read_*` on the Util class.
|
|
212
|
+
#
|
|
213
|
+
# @param [FFI::MemoryPointer] pointer
|
|
214
|
+
# @param [Symbol] type The type of the pointer
|
|
215
|
+
# @return [Object] The value of the dereferenced pointer
|
|
216
|
+
def dereference_pointer(pointer, type)
|
|
217
|
+
c_type, inferred_type = infer_type(type)
|
|
218
|
+
|
|
219
|
+
if pointer.respond_to?("get_#{inferred_type}".to_sym)
|
|
220
|
+
# This handles reading the typical times such as :uint, :int, etc.
|
|
221
|
+
result = pointer.send("get_#{inferred_type}".to_sym, 0)
|
|
222
|
+
result = !(result == 0) if type == T_BOOL
|
|
223
|
+
result
|
|
224
|
+
else
|
|
225
|
+
send("read_#{inferred_type}".to_sym, pointer, type)
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Dereferences an array out of a pointer into an array of proper Ruby
|
|
230
|
+
# objects.
|
|
231
|
+
#
|
|
232
|
+
# @param [FFI::MemoryPointer] pointer
|
|
233
|
+
# @param [Symbol] type The type of the pointer
|
|
234
|
+
# @param [Fixnum] length The length of the array
|
|
235
|
+
# @return [Array<Object>]
|
|
236
|
+
def dereference_pointer_array(pointer, type, length)
|
|
237
|
+
# If there are no items in the pointer, just return an empty array
|
|
238
|
+
return [] if length == 0
|
|
239
|
+
|
|
240
|
+
c_type, inferred_type = infer_type(type)
|
|
241
|
+
|
|
242
|
+
array_pointer = pointer.get_pointer(0)
|
|
243
|
+
if array_pointer.respond_to?("get_array_of_#{inferred_type}".to_sym)
|
|
244
|
+
# This handles reading the typical times such as :uint, :int, etc.
|
|
245
|
+
array_pointer.send("get_array_of_#{inferred_type}".to_sym, 0, length)
|
|
246
|
+
else
|
|
247
|
+
send("read_array_of_#{inferred_type}".to_sym, array_pointer, type, length)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Converts a symbol type into a MemoryPointer and yield a block
|
|
252
|
+
# with the pointer, the C type, and the FFI type
|
|
253
|
+
def pointer_for_type(type)
|
|
254
|
+
c_type, type = infer_type(type)
|
|
255
|
+
|
|
256
|
+
# Create the pointer, yield, returning the result of the block
|
|
257
|
+
# if a block is given, or otherwise just returning the pointer
|
|
258
|
+
# and inferred type
|
|
259
|
+
pointer = ::FFI::MemoryPointer.new(c_type)
|
|
260
|
+
if block_given?
|
|
261
|
+
yield pointer, type
|
|
262
|
+
else
|
|
263
|
+
pointer
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Converts a ruby string to a UTF16 string
|
|
268
|
+
#
|
|
269
|
+
# @param [String] Ruby String object
|
|
270
|
+
# @return [::FFI::Pointer]
|
|
271
|
+
def string_to_utf16(string)
|
|
272
|
+
return nil if string.nil?
|
|
273
|
+
|
|
274
|
+
ptr = pointer_for_type(:pointer)
|
|
275
|
+
lib.xpcom[:pfnUtf8ToUtf16].call(string, ptr)
|
|
276
|
+
ptr.read_pointer()
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Converts a UTF16 string to UTF8
|
|
280
|
+
def utf16_to_string(pointer)
|
|
281
|
+
result_pointer = pointer_for_type(:pointer)
|
|
282
|
+
lib.xpcom[:pfnUtf16ToUtf8].call(pointer, result_pointer)
|
|
283
|
+
lib.xpcom[:pfnUtf16Free].call(pointer)
|
|
284
|
+
result_pointer.read_pointer().read_string().to_s
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Reads a unicode string value from a pointer to that value.
|
|
288
|
+
#
|
|
289
|
+
# @return [String]
|
|
290
|
+
def read_unicode_string(ptr, original_type=nil)
|
|
291
|
+
address = ptr.get_pointer(0)
|
|
292
|
+
return "" if address.null?
|
|
293
|
+
utf16_to_string(address)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# Reads an interface from the pointer
|
|
297
|
+
#
|
|
298
|
+
# @return [::FFI::Struct]
|
|
299
|
+
def read_interface(ptr, original_type)
|
|
300
|
+
ptr = ptr.get_pointer(0)
|
|
301
|
+
return nil if ptr.null?
|
|
302
|
+
|
|
303
|
+
klass = interface_klass(original_type)
|
|
304
|
+
klass.new(self.class, lib, ptr)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Reads an enum
|
|
308
|
+
#
|
|
309
|
+
# @return [Symbol]
|
|
310
|
+
def read_enum(ptr, original_type)
|
|
311
|
+
klass = interface_klass(original_type)
|
|
312
|
+
klass[ptr.get_uint(0)]
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# Reads an array of enums
|
|
316
|
+
#
|
|
317
|
+
# @return [Array<Symbol>]
|
|
318
|
+
def read_array_of_enum(ptr, type, length)
|
|
319
|
+
klass = interface_klass(type)
|
|
320
|
+
ptr.get_array_of_uint(0, length).collect do |value|
|
|
321
|
+
klass[value]
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# Reads an array of structs from a pointer
|
|
326
|
+
#
|
|
327
|
+
# @return [Array<::FFI::Struct>]
|
|
328
|
+
def read_array_of_interface(ptr, type, length)
|
|
329
|
+
klass = interface_klass(type)
|
|
330
|
+
ptr.get_array_of_pointer(0, length).collect do |single_pointer|
|
|
331
|
+
klass.new(self.class, lib, single_pointer)
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Reads an array of strings from a pointer
|
|
336
|
+
#
|
|
337
|
+
# @return [Array<String>]
|
|
338
|
+
def read_array_of_unicode_string(ptr, type, length)
|
|
339
|
+
ptr.get_array_of_pointer(0, length).collect do |single_pointer|
|
|
340
|
+
if single_pointer.null?
|
|
341
|
+
nil
|
|
342
|
+
else
|
|
343
|
+
utf16_to_string(single_pointer)
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|