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.
Files changed (162) hide show
  1. data/.gitignore +2 -1
  2. data/Gemfile +1 -1
  3. data/Rakefile +1 -1
  4. data/Readme.md +5 -21
  5. data/VERSION +1 -1
  6. data/docs/WhatsNew.md +9 -47
  7. data/lib/virtualbox.rb +7 -30
  8. data/lib/virtualbox/abstract_model.rb +25 -5
  9. data/lib/virtualbox/abstract_model/attributable.rb +5 -1
  10. data/lib/virtualbox/abstract_model/dirty.rb +2 -0
  11. data/lib/virtualbox/abstract_model/interface_attributes.rb +96 -0
  12. data/lib/virtualbox/abstract_model/relatable.rb +19 -8
  13. data/lib/virtualbox/appliance.rb +59 -0
  14. data/lib/virtualbox/audio_adapter.rb +44 -0
  15. data/lib/virtualbox/bios.rb +44 -0
  16. data/lib/virtualbox/com.rb +23 -0
  17. data/lib/virtualbox/com/abstract_enum.rb +42 -0
  18. data/lib/virtualbox/com/abstract_implementer.rb +43 -0
  19. data/lib/virtualbox/com/abstract_interface.rb +165 -0
  20. data/lib/virtualbox/com/ffi/interface.rb +141 -0
  21. data/lib/virtualbox/com/ffi/interfaces.rb +42 -0
  22. data/lib/virtualbox/com/ffi/util.rb +101 -0
  23. data/lib/virtualbox/com/ffi/vboxxpcomc.rb +31 -0
  24. data/lib/virtualbox/com/ffi_interface.rb +65 -0
  25. data/lib/virtualbox/com/implementer/base.rb +52 -0
  26. data/lib/virtualbox/com/implementer/ffi.rb +350 -0
  27. data/lib/virtualbox/com/implementer/mscom.rb +165 -0
  28. data/lib/virtualbox/com/implementer/nil.rb +10 -0
  29. data/lib/virtualbox/com/interface/appliance.rb +20 -0
  30. data/lib/virtualbox/com/interface/audio_adapter.rb +13 -0
  31. data/lib/virtualbox/com/interface/audio_controller_type.rb +9 -0
  32. data/lib/virtualbox/com/interface/audio_driver_type.rb +9 -0
  33. data/lib/virtualbox/com/interface/bios_boot_menu_mode.rb +9 -0
  34. data/lib/virtualbox/com/interface/bios_settings.rb +19 -0
  35. data/lib/virtualbox/com/interface/clipboard_mode.rb +9 -0
  36. data/lib/virtualbox/com/interface/console.rb +48 -0
  37. data/lib/virtualbox/com/interface/cpu_property_type.rb +9 -0
  38. data/lib/virtualbox/com/interface/device_type.rb +9 -0
  39. data/lib/virtualbox/com/interface/dhcp_server.rb +20 -0
  40. data/lib/virtualbox/com/interface/firmware_type.rb +9 -0
  41. data/lib/virtualbox/com/interface/guest_os_type.rb +21 -0
  42. data/lib/virtualbox/com/interface/host.rb +40 -0
  43. data/lib/virtualbox/com/interface/host_network_interface.rb +28 -0
  44. data/lib/virtualbox/com/interface/host_network_interface_medium_type.rb +9 -0
  45. data/lib/virtualbox/com/interface/host_network_interface_status.rb +9 -0
  46. data/lib/virtualbox/com/interface/host_network_interface_type.rb +9 -0
  47. data/lib/virtualbox/com/interface/host_usb_device.rb +11 -0
  48. data/lib/virtualbox/com/interface/host_usb_device_filter.rb +11 -0
  49. data/lib/virtualbox/com/interface/hw_virt_ex_property_type.rb +9 -0
  50. data/lib/virtualbox/com/interface/machine.rb +103 -0
  51. data/lib/virtualbox/com/interface/machine_state.rb +12 -0
  52. data/lib/virtualbox/com/interface/medium.rb +48 -0
  53. data/lib/virtualbox/com/interface/medium_attachment.rb +16 -0
  54. data/lib/virtualbox/com/interface/medium_format.rb +16 -0
  55. data/lib/virtualbox/com/interface/medium_state.rb +9 -0
  56. data/lib/virtualbox/com/interface/medium_type.rb +9 -0
  57. data/lib/virtualbox/com/interface/medium_variant.rb +9 -0
  58. data/lib/virtualbox/com/interface/network_adapter.rb +28 -0
  59. data/lib/virtualbox/com/interface/network_adapter_type.rb +9 -0
  60. data/lib/virtualbox/com/interface/network_attachment_type.rb +9 -0
  61. data/lib/virtualbox/com/interface/nsiexception.rb +21 -0
  62. data/lib/virtualbox/com/interface/nsisupports.rb +13 -0
  63. data/lib/virtualbox/com/interface/parallel_port.rb +15 -0
  64. data/lib/virtualbox/com/interface/port_mode.rb +9 -0
  65. data/lib/virtualbox/com/interface/progress.rb +58 -0
  66. data/lib/virtualbox/com/interface/serial_port.rb +17 -0
  67. data/lib/virtualbox/com/interface/session.rb +16 -0
  68. data/lib/virtualbox/com/interface/session_state.rb +9 -0
  69. data/lib/virtualbox/com/interface/session_type.rb +9 -0
  70. data/lib/virtualbox/com/interface/shared_folder.rb +15 -0
  71. data/lib/virtualbox/com/interface/snapshot.rb +18 -0
  72. data/lib/virtualbox/com/interface/storage_bus.rb +9 -0
  73. data/lib/virtualbox/com/interface/storage_controller.rb +21 -0
  74. data/lib/virtualbox/com/interface/storage_controller_type.rb +9 -0
  75. data/lib/virtualbox/com/interface/system_properties.rb +35 -0
  76. data/lib/virtualbox/com/interface/usb_controller.rb +18 -0
  77. data/lib/virtualbox/com/interface/usb_device.rb +22 -0
  78. data/lib/virtualbox/com/interface/usb_device_filter.rb +21 -0
  79. data/lib/virtualbox/com/interface/usb_device_filter_action.rb +9 -0
  80. data/lib/virtualbox/com/interface/usb_device_state.rb +9 -0
  81. data/lib/virtualbox/com/interface/virtual_box_error_info.rb +15 -0
  82. data/lib/virtualbox/com/interface/virtual_system_description.rb +17 -0
  83. data/lib/virtualbox/com/interface/virtual_system_description_type.rb +12 -0
  84. data/lib/virtualbox/com/interface/virtual_system_description_value_type.rb +9 -0
  85. data/lib/virtualbox/com/interface/virtualbox.rb +54 -0
  86. data/lib/virtualbox/com/interface/vrdp_auth_type.rb +9 -0
  87. data/lib/virtualbox/com/interface/vrdp_server.rb +17 -0
  88. data/lib/virtualbox/com/mscom_interface.rb +22 -0
  89. data/lib/virtualbox/com/util.rb +18 -0
  90. data/lib/virtualbox/dvd.rb +7 -94
  91. data/lib/virtualbox/exceptions.rb +24 -0
  92. data/lib/virtualbox/ext/glob_loader.rb +22 -0
  93. data/lib/virtualbox/ext/logger.rb +38 -0
  94. data/lib/virtualbox/ext/platform.rb +1 -1
  95. data/lib/virtualbox/extra_data.rb +25 -37
  96. data/lib/virtualbox/forwarded_port.rb +35 -13
  97. data/lib/virtualbox/global.rb +22 -80
  98. data/lib/virtualbox/hard_drive.rb +30 -97
  99. data/lib/virtualbox/lib.rb +82 -0
  100. data/lib/virtualbox/media.rb +7 -6
  101. data/lib/virtualbox/medium.rb +138 -0
  102. data/lib/virtualbox/medium_attachment.rb +61 -0
  103. data/lib/virtualbox/network_adapter.rb +134 -0
  104. data/lib/virtualbox/shared_folder.rb +53 -78
  105. data/lib/virtualbox/storage_controller.rb +76 -20
  106. data/lib/virtualbox/system_properties.rb +74 -0
  107. data/lib/virtualbox/usb_controller.rb +55 -0
  108. data/lib/virtualbox/version.rb +15 -0
  109. data/lib/virtualbox/virtual_system_description.rb +47 -0
  110. data/lib/virtualbox/vm.rb +160 -272
  111. data/test/test_helper.rb +0 -108
  112. data/test/virtualbox/abstract_model/attributable_test.rb +7 -1
  113. data/test/virtualbox/abstract_model/dirty_test.rb +1 -1
  114. data/test/virtualbox/abstract_model/interface_attributes_test.rb +169 -0
  115. data/test/virtualbox/abstract_model/relatable_test.rb +20 -0
  116. data/test/virtualbox/abstract_model_test.rb +40 -5
  117. data/test/virtualbox/appliance_test.rb +152 -0
  118. data/test/virtualbox/audio_adapter_test.rb +83 -0
  119. data/test/virtualbox/bios_test.rb +83 -0
  120. data/test/virtualbox/com/abstract_enum_test.rb +48 -0
  121. data/test/virtualbox/com/abstract_implementer_test.rb +39 -0
  122. data/test/virtualbox/com/abstract_interface_test.rb +139 -0
  123. data/test/virtualbox/com/ffi/interface_test.rb +249 -0
  124. data/test/virtualbox/com/ffi/util_test.rb +86 -0
  125. data/test/virtualbox/com/ffi_interface_test.rb +42 -0
  126. data/test/virtualbox/com/implementer/base_test.rb +37 -0
  127. data/test/virtualbox/com/implementer/ffi_test.rb +519 -0
  128. data/test/virtualbox/com/implementer/mscom_test.rb +208 -0
  129. data/test/virtualbox/com/mscom_interface_test.rb +17 -0
  130. data/test/virtualbox/com/util_test.rb +17 -0
  131. data/test/virtualbox/dvd_test.rb +4 -95
  132. data/test/virtualbox/ext/platform_test.rb +8 -0
  133. data/test/virtualbox/extra_data_test.rb +78 -102
  134. data/test/virtualbox/forwarded_port_test.rb +57 -7
  135. data/test/virtualbox/global_test.rb +25 -115
  136. data/test/virtualbox/hard_drive_test.rb +49 -212
  137. data/test/virtualbox/lib_test.rb +93 -0
  138. data/test/virtualbox/medium_attachment_test.rb +147 -0
  139. data/test/virtualbox/medium_test.rb +192 -0
  140. data/test/virtualbox/network_adapter_test.rb +160 -0
  141. data/test/virtualbox/shared_folder_test.rb +144 -160
  142. data/test/virtualbox/storage_controller_test.rb +166 -45
  143. data/test/virtualbox/system_properties_test.rb +87 -0
  144. data/test/virtualbox/usb_controller_test.rb +104 -0
  145. data/test/virtualbox/version_test.rb +34 -0
  146. data/test/virtualbox/virtual_system_description_test.rb +61 -0
  147. data/test/virtualbox/vm_test.rb +288 -322
  148. data/test/virtualbox_test.rb +1 -9
  149. data/virtualbox.gemspec +139 -23
  150. metadata +143 -27
  151. data/lib/virtualbox/attached_device.rb +0 -249
  152. data/lib/virtualbox/command.rb +0 -109
  153. data/lib/virtualbox/image.rb +0 -137
  154. data/lib/virtualbox/nic.rb +0 -111
  155. data/lib/virtualbox/system_property.rb +0 -55
  156. data/lib/virtualbox/usb.rb +0 -72
  157. data/test/virtualbox/attached_device_test.rb +0 -303
  158. data/test/virtualbox/command_test.rb +0 -152
  159. data/test/virtualbox/image_test.rb +0 -190
  160. data/test/virtualbox/nic_test.rb +0 -76
  161. data/test/virtualbox/system_property_test.rb +0 -71
  162. 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