Browse files

Merge in the native interfacing branch. Gem now uses the native inter…

…face VirtualBox provides.
  • Loading branch information...
1 parent 24323e9 commit 9eda9d8b2874e616c0459d5dbb040851d659e3df @mitchellh committed Apr 8, 2010
Showing with 7,083 additions and 3,554 deletions.
  1. +2 −1 .gitignore
  2. +1 −1 Gemfile
  3. +1 −1 Rakefile
  4. +5 −21 Readme.md
  5. +1 −1 VERSION
  6. +9 −47 docs/WhatsNew.md
  7. +7 −30 lib/virtualbox.rb
  8. +23 −5 lib/virtualbox/abstract_model.rb
  9. +5 −1 lib/virtualbox/abstract_model/attributable.rb
  10. +2 −0 lib/virtualbox/abstract_model/dirty.rb
  11. +96 −0 lib/virtualbox/abstract_model/interface_attributes.rb
  12. +19 −8 lib/virtualbox/abstract_model/relatable.rb
  13. +55 −0 lib/virtualbox/appliance.rb
  14. +0 −249 lib/virtualbox/attached_device.rb
  15. +47 −0 lib/virtualbox/bios.rb
  16. +23 −0 lib/virtualbox/com.rb
  17. +42 −0 lib/virtualbox/com/abstract_enum.rb
  18. +43 −0 lib/virtualbox/com/abstract_implementer.rb
  19. +165 −0 lib/virtualbox/com/abstract_interface.rb
  20. +141 −0 lib/virtualbox/com/ffi/interface.rb
  21. +42 −0 lib/virtualbox/com/ffi/interfaces.rb
  22. +101 −0 lib/virtualbox/com/ffi/util.rb
  23. +31 −0 lib/virtualbox/com/ffi/vboxxpcomc.rb
  24. +65 −0 lib/virtualbox/com/ffi_interface.rb
  25. +50 −0 lib/virtualbox/com/implementer/base.rb
  26. +347 −0 lib/virtualbox/com/implementer/ffi.rb
  27. +165 −0 lib/virtualbox/com/implementer/mscom.rb
  28. +10 −0 lib/virtualbox/com/implementer/nil.rb
  29. +20 −0 lib/virtualbox/com/interface/appliance.rb
  30. +13 −0 lib/virtualbox/com/interface/audio_adapter.rb
  31. +9 −0 lib/virtualbox/com/interface/audio_controller_type.rb
  32. +9 −0 lib/virtualbox/com/interface/audio_driver_type.rb
  33. +9 −0 lib/virtualbox/com/interface/bios_boot_menu_mode.rb
  34. +19 −0 lib/virtualbox/com/interface/bios_settings.rb
  35. +9 −0 lib/virtualbox/com/interface/clipboard_mode.rb
  36. +48 −0 lib/virtualbox/com/interface/console.rb
  37. +9 −0 lib/virtualbox/com/interface/cpu_property_type.rb
  38. +9 −0 lib/virtualbox/com/interface/device_type.rb
  39. +20 −0 lib/virtualbox/com/interface/dhcp_server.rb
  40. +9 −0 lib/virtualbox/com/interface/firmware_type.rb
  41. +21 −0 lib/virtualbox/com/interface/guest_os_type.rb
  42. +40 −0 lib/virtualbox/com/interface/host.rb
  43. +28 −0 lib/virtualbox/com/interface/host_network_interface.rb
  44. +9 −0 lib/virtualbox/com/interface/host_network_interface_medium_type.rb
  45. +9 −0 lib/virtualbox/com/interface/host_network_interface_status.rb
  46. +9 −0 lib/virtualbox/com/interface/host_network_interface_type.rb
  47. +11 −0 lib/virtualbox/com/interface/host_usb_device.rb
  48. +11 −0 lib/virtualbox/com/interface/host_usb_device_filter.rb
  49. +9 −0 lib/virtualbox/com/interface/hw_virt_ex_property_type.rb
  50. +103 −0 lib/virtualbox/com/interface/machine.rb
  51. +12 −0 lib/virtualbox/com/interface/machine_state.rb
  52. +48 −0 lib/virtualbox/com/interface/medium.rb
  53. +16 −0 lib/virtualbox/com/interface/medium_attachment.rb
  54. +16 −0 lib/virtualbox/com/interface/medium_format.rb
  55. +9 −0 lib/virtualbox/com/interface/medium_state.rb
  56. +9 −0 lib/virtualbox/com/interface/medium_type.rb
  57. +9 −0 lib/virtualbox/com/interface/medium_variant.rb
  58. +28 −0 lib/virtualbox/com/interface/network_adapter.rb
  59. +9 −0 lib/virtualbox/com/interface/network_adapter_type.rb
  60. +9 −0 lib/virtualbox/com/interface/network_attachment_type.rb
  61. +21 −0 lib/virtualbox/com/interface/nsiexception.rb
  62. +13 −0 lib/virtualbox/com/interface/nsisupports.rb
  63. +15 −0 lib/virtualbox/com/interface/parallel_port.rb
  64. +9 −0 lib/virtualbox/com/interface/port_mode.rb
  65. +31 −0 lib/virtualbox/com/interface/progress.rb
  66. +17 −0 lib/virtualbox/com/interface/serial_port.rb
  67. +16 −0 lib/virtualbox/com/interface/session.rb
  68. +9 −0 lib/virtualbox/com/interface/session_state.rb
  69. +9 −0 lib/virtualbox/com/interface/session_type.rb
  70. +15 −0 lib/virtualbox/com/interface/shared_folder.rb
  71. +18 −0 lib/virtualbox/com/interface/snapshot.rb
  72. +9 −0 lib/virtualbox/com/interface/storage_bus.rb
  73. +21 −0 lib/virtualbox/com/interface/storage_controller.rb
  74. +9 −0 lib/virtualbox/com/interface/storage_controller_type.rb
  75. +35 −0 lib/virtualbox/com/interface/system_properties.rb
  76. +18 −0 lib/virtualbox/com/interface/usb_controller.rb
  77. +22 −0 lib/virtualbox/com/interface/usb_device.rb
  78. +21 −0 lib/virtualbox/com/interface/usb_device_filter.rb
  79. +9 −0 lib/virtualbox/com/interface/usb_device_filter_action.rb
  80. +9 −0 lib/virtualbox/com/interface/usb_device_state.rb
  81. +15 −0 lib/virtualbox/com/interface/virtual_box_error_info.rb
  82. +17 −0 lib/virtualbox/com/interface/virtual_system_description.rb
  83. +12 −0 lib/virtualbox/com/interface/virtual_system_description_type.rb
  84. +9 −0 lib/virtualbox/com/interface/virtual_system_description_value_type.rb
  85. +54 −0 lib/virtualbox/com/interface/virtualbox.rb
  86. +9 −0 lib/virtualbox/com/interface/vrdp_auth_type.rb
  87. +17 −0 lib/virtualbox/com/interface/vrdp_server.rb
  88. +22 −0 lib/virtualbox/com/mscom_interface.rb
  89. +18 −0 lib/virtualbox/com/util.rb
  90. +0 −109 lib/virtualbox/command.rb
  91. +7 −94 lib/virtualbox/dvd.rb
  92. +24 −0 lib/virtualbox/exceptions.rb
  93. +22 −0 lib/virtualbox/ext/glob_loader.rb
  94. +25 −37 lib/virtualbox/extra_data.rb
  95. +6 −6 lib/virtualbox/forwarded_port.rb
  96. +22 −80 lib/virtualbox/global.rb
  97. +30 −97 lib/virtualbox/hard_drive.rb
  98. +0 −137 lib/virtualbox/image.rb
  99. +82 −0 lib/virtualbox/lib.rb
  100. +7 −6 lib/virtualbox/media.rb
  101. +138 −0 lib/virtualbox/medium.rb
  102. +62 −0 lib/virtualbox/medium_attachment.rb
  103. +135 −0 lib/virtualbox/network_adapter.rb
  104. +0 −111 lib/virtualbox/nic.rb
  105. +55 −78 lib/virtualbox/shared_folder.rb
  106. +77 −20 lib/virtualbox/storage_controller.rb
  107. +74 −0 lib/virtualbox/system_properties.rb
  108. +0 −55 lib/virtualbox/system_property.rb
  109. +0 −72 lib/virtualbox/usb.rb
  110. +55 −0 lib/virtualbox/usb_controller.rb
  111. +13 −0 lib/virtualbox/version.rb
  112. +47 −0 lib/virtualbox/virtual_system_description.rb
  113. +152 −272 lib/virtualbox/vm.rb
  114. +0 −108 test/test_helper.rb
  115. +7 −1 test/virtualbox/abstract_model/attributable_test.rb
  116. +1 −1 test/virtualbox/abstract_model/dirty_test.rb
  117. +169 −0 test/virtualbox/abstract_model/interface_attributes_test.rb
  118. +20 −0 test/virtualbox/abstract_model/relatable_test.rb
  119. +40 −5 test/virtualbox/abstract_model_test.rb
  120. +129 −0 test/virtualbox/appliance_test.rb
  121. +0 −303 test/virtualbox/attached_device_test.rb
  122. +84 −0 test/virtualbox/bios_test.rb
  123. +48 −0 test/virtualbox/com/abstract_enum_test.rb
  124. +39 −0 test/virtualbox/com/abstract_implementer_test.rb
  125. +139 −0 test/virtualbox/com/abstract_interface_test.rb
  126. +249 −0 test/virtualbox/com/ffi/interface_test.rb
  127. +86 −0 test/virtualbox/com/ffi/util_test.rb
  128. +42 −0 test/virtualbox/com/ffi_interface_test.rb
  129. +37 −0 test/virtualbox/com/implementer/base_test.rb
  130. +519 −0 test/virtualbox/com/implementer/ffi_test.rb
  131. +206 −0 test/virtualbox/com/implementer/mscom_test.rb
  132. +17 −0 test/virtualbox/com/mscom_interface_test.rb
  133. +17 −0 test/virtualbox/com/util_test.rb
  134. +0 −152 test/virtualbox/command_test.rb
  135. +4 −95 test/virtualbox/dvd_test.rb
  136. +70 −102 test/virtualbox/extra_data_test.rb
  137. +10 −8 test/virtualbox/forwarded_port_test.rb
  138. +25 −115 test/virtualbox/global_test.rb
  139. +49 −212 test/virtualbox/hard_drive_test.rb
  140. +0 −190 test/virtualbox/image_test.rb
  141. +93 −0 test/virtualbox/lib_test.rb
  142. +148 −0 test/virtualbox/medium_attachment_test.rb
  143. +192 −0 test/virtualbox/medium_test.rb
  144. +161 −0 test/virtualbox/network_adapter_test.rb
  145. +0 −76 test/virtualbox/nic_test.rb
  146. +148 −160 test/virtualbox/shared_folder_test.rb
  147. +168 −45 test/virtualbox/storage_controller_test.rb
  148. +87 −0 test/virtualbox/system_properties_test.rb
  149. +0 −71 test/virtualbox/system_property_test.rb
  150. +104 −0 test/virtualbox/usb_controller_test.rb
  151. +0 −35 test/virtualbox/usb_test.rb
  152. +29 −0 test/virtualbox/version_test.rb
  153. +61 −0 test/virtualbox/virtual_system_description_test.rb
  154. +256 −327 test/virtualbox/vm_test.rb
  155. +1 −9 test/virtualbox_test.rb
View
3 .gitignore
@@ -3,4 +3,5 @@ doc/*
pkg/*
test/coverage/*
.bundle/*
-Gemfile.lock
+Gemfile.lock
+test.rb
View
2 Gemfile
@@ -1,7 +1,7 @@
source :gemcutter
# External Dependencies
-gem "nokogiri", "1.4.1"
+gem "ffi"
# Gems required for testing only.
group :test do
View
2 Rakefile
@@ -9,7 +9,7 @@ begin
gemspec.authors = ["Mitchell Hashimoto"]
gemspec.executables = []
- gemspec.add_dependency('nokogiri', '>= 1.4.1')
+ gemspec.add_dependency('ffi', '>= 0.6.3')
end
Jeweler::GemcutterTasks.new
rescue LoadError
View
26 Readme.md
@@ -11,10 +11,9 @@ Windows, Linux, and OS X. After installation, install the gem:
sudo gem install virtualbox
-The gem assumes that `VBoxManage` will be available on the `PATH`. If not, before using
-the gem, you must set the path to your `VBoxManage` binary:
-
- VirtualBox::Command.vboxmanage = "/path/to/my/VBoxManage"
+The gem uses the native COM interface with VirtualBox provides to communicate with
+VirtualBox. On Windows, this is globally available. On Linux-based machines, the gem
+uses Ruby-FFI to talk to a dynamic library. No configuration should be necessary.
## Basic Usage
@@ -33,30 +32,15 @@ Below are some examples:
vm = VirtualBox::VM.find("my-vm")
# Let's first print out some basic info about the VM
- puts "Memory: #{vm.memory}"
-
- vm.storage_controllers.each do |sc|
- sc.attached_devices.each do |device|
- puts "Attached Device: #{device.uuid}"
- end
- end
+ puts "Memory: #{vm.memory_size}"
# Let's modify the memory and name...
- vm.memory = 360
+ vm.memory_size = 360
vm.name = "my-renamed-vm"
# Save it!
vm.save
-Or here is an example of creating a hard drive:
-
- require 'virtualbox'
-
- hd = VirtualBox::HardDrive.new
- hd.location = "foo.vdi"
- hd.size = 2000 # megabytes
- hd.save
-
## Known Issues or Uncompleted Features
VirtualBox has a _ton_ of features! As such, this gem is still not totally complete.
View
2 VERSION
@@ -1 +1 @@
-0.5.4
+0.6.0.pre
View
56 docs/WhatsNew.md
@@ -1,50 +1,12 @@
-# What's New in 0.5.x?
+# What's New in 0.6.x?
-## HUGE Speed Boost! Very few system calls!
+## Native Interface
-Most of the data retrieved by the virtualbox library now comes via XML parsing, rather
-than making calls to `VBoxManage`. This results in a drastic speedup. The few relationships or
-attributes which require a system call are typically _lazy loaded_ (covered below), so they
-don't incur a performance penalty unless they're used.
+The VirtualBox gem no longer piggy-backs on top of `VBoxManage` or XML configuration files.
+The gem now uses the native interface provided by VirtualBox to communicate. The result of
+this is a _huge_ speed increase which simply would not have been possible otherwise, and
+stability across versions. In addition to that, the entire VirtualBox API is now at your
+disposal. While the gem itself doesn't yet support creating VMs and so on, the API is available
+for you to do it manually (if you really wanted to!).
-The one caveat is that you now need to set the path to the global VirtualBox configuration
-XML. The virtualbox library will do its best to guess this path based on the operating
-system, but this is hardly foolproof. To set the virtualbox config path, it is a simple
-one-liner:
-
- # Remember, this won't be necessary MOST of the time
- VirtualBox::Global.vboxconfig = "~/path/to/VirtualBox.xml"
-
-## Lazy Loading of Attributes and Relationships
-
-Although still not widely used (will be in future patch releases), some attributes and
-relationships are now _lazy loaded_. This means that since they're probably expensive
-to load (many system calls, heavy parsing, etc.) they aren't loaded initially. Instead,
-they are only loaded if they're used. This means that you don't incur the penalty cost
-of loading them unless you use it! Fantastic!
-
-There is no real "example code" for this feature since to the casual user, it happens
-transparently in the background and generally "just works." If you're _really_ curious,
-then feel free to check out any class which derives from {VirtualBox::AbstractModel}
-and any attribute or relationship with the `:lazy => true` option is lazy loaded!
-
-## System Properties
-
-A small but meaningful update is the ability to view the system properties for the
-host system which VirtualBox is running. This is done via the {VirtualBox::SystemProperty}
-class, which is simply a `Hash`. System properties are immutable properties defined
-by the host system, which typically are limits imposed upon VirtualBox, such as
-maximum RAM size or default path to machine files. Retrieving the system properties
-is quite easy:
-
- properties = VirtualBox::SystemProperty.all
- properties.each do |key, value|
- puts "#{key} = #{value}"
- end
-
-## USB Device Relationship on VMs
-
-Previously, {VirtualBox::VM VM} object would only be able to tell you if there
-were USB devices enabled or not. Now, `usbs` is a full-fledged relationship
-on VM. This relationship is access just like any other. For more information
-view the {VirtualBox::USB USB} class.
+Future versions will support more and more of the API.
View
37 lib/virtualbox.rb
@@ -1,34 +1,11 @@
-$:.unshift(File.expand_path(File.dirname(__FILE__)))
+# Load the glob loader, which will handle the loading of all the other files
+libdir = File.join(File.dirname(__FILE__), "virtualbox")
+require File.expand_path("ext/glob_loader", libdir)
-# External Dependencies
-require 'nokogiri'
-
-# Internal Dependencies
-require 'virtualbox/ext/platform'
-require 'virtualbox/exceptions'
-require 'virtualbox/command'
-require 'virtualbox/abstract_model'
-require 'virtualbox/proxies/collection'
-require 'virtualbox/image'
-require 'virtualbox/attached_device'
-require 'virtualbox/dvd'
-require 'virtualbox/extra_data'
-require 'virtualbox/forwarded_port'
-require 'virtualbox/hard_drive'
-require 'virtualbox/nic'
-require 'virtualbox/usb'
-require 'virtualbox/shared_folder'
-require 'virtualbox/storage_controller'
-require 'virtualbox/vm'
-require 'virtualbox/media'
-require 'virtualbox/global'
-require 'virtualbox/system_property'
+# Load them up
+VirtualBox::GlobLoader.glob_require(libdir, %w{ext/platform ext/subclass_listing com abstract_model medium})
+# Setup the top-level module methods
module VirtualBox
- class <<self
- # Returns installed VirtualBox version like '3.1.2r56127'.
- def version
- Command.vboxmanage("-v").chomp.strip
- end
- end
+ extend Version
end
View
28 lib/virtualbox/abstract_model.rb
@@ -1,7 +1,10 @@
-require 'virtualbox/abstract_model/attributable'
-require 'virtualbox/abstract_model/dirty'
-require 'virtualbox/abstract_model/relatable'
-require 'virtualbox/abstract_model/validatable'
+['abstract_model/attributable',
+ 'abstract_model/interface_attributes',
+ 'abstract_model/dirty',
+ 'abstract_model/relatable',
+ 'abstract_model/validatable'].each do |lib|
+ require File.expand_path(lib, File.dirname(__FILE__))
+end
module VirtualBox
# AbstractModel is the base class used for most of virtualbox's classes.
@@ -10,6 +13,7 @@ module VirtualBox
# @abstract
class AbstractModel
include Attributable
+ include InterfaceAttributes
include Dirty
include Relatable
include Validatable
@@ -122,6 +126,20 @@ def save_attribute(key, value, *args)
clear_dirty!(key)
end
+ # Saves only changed interface attributes.
+ def save_changed_interface_attributes(interface)
+ changes.each do |key, values|
+ save_interface_attribute(key, interface)
+ end
+ end
+
+ # Overrides {InterfaceAttributes.save_interface_attribute} to clear the
+ # dirty state of the attribute.
+ def save_interface_attribute(key, interface)
+ super
+ clear_dirty!(key)
+ end
+
# Overriding {Attributable#lazy_attribute?} to always return false for
# new records, since new records shouldn't load lazy data.
def lazy_attribute?(*args)
@@ -217,7 +235,7 @@ def inspect
self.class.attributes.each do |name, options|
value = read_attribute(name)
- value = if value.is_a?(AbstractModel)
+ value = if value.is_a?(AbstractModel) || value.is_a?(COM::AbstractInterface)
"#<#{value.class.name}>"
else
value.inspect
View
6 lib/virtualbox/abstract_model/attributable.rb
@@ -214,7 +214,11 @@ def read_attribute(name)
load_attribute(name.to_sym)
end
- attributes[name] || self.class.attributes[name][:default]
+ if attributes[name].nil?
+ self.class.attributes[name][:default]
+ else
+ attributes[name]
+ end
end
end
View
2 lib/virtualbox/abstract_model/dirty.rb
@@ -93,6 +93,8 @@ module Dirty
# @param [Object] value The new value being set
def set_dirty!(name, current, value)
if current != value
+ name = name.to_sym
+
# If its the first time this attribute has changed, store the
# original value in the first field
changes[name] ||= [current, nil]
View
96 lib/virtualbox/abstract_model/interface_attributes.rb
@@ -0,0 +1,96 @@
+module VirtualBox
+ class AbstractModel
+ # Module which can be included which defines helper methods to DRY out the
+ # code which handles attributes with {VirtualBox::COM} interfaces. This
+ # module works _alongside_ the {Attributable} module, so **both are required**.
+ module InterfaceAttributes
+ # Loads the attributes which have an interface getter and writes
+ # their values.
+ #
+ # @param [VirtualBox::COM::Interface] interface
+ def load_interface_attributes(interface)
+ self.class.attributes.each do |key, options|
+ load_interface_attribute(key, interface)
+ end
+ end
+
+ # Loads a single interface attribute.
+ #
+ # @param [Symbol] key The attribute to load
+ # @param [VirtualBox::COM::Interface] interface The interface
+ def load_interface_attribute(key, interface)
+ # Return unless we have a valid interface attribute with a getter
+ return unless has_attribute?(key)
+ options = self.class.attributes[key.to_sym]
+ return if options.has_key?(:property) && !options[:property]
+ getter = options[:property] || options[:property_getter] || key.to_sym
+ return unless getter
+
+ # Convert the getter to a proc and call it
+ getter = spec_to_proc(getter)
+ write_attribute(key, getter.call(interface))
+ end
+
+ # Saves all the attributes which have an interface setter.
+ def save_interface_attributes(interface)
+ self.class.attributes.each do |key, options|
+ save_interface_attribute(key, interface)
+ end
+ end
+
+ # Saves a single interface attribute
+ #
+ # @param [Symbol] key The attribute to write
+ # @param [VirtualBox::COM::Interface] interface The interface
+ # @param [Object] value The value to write
+ def save_interface_attribute(key, interface)
+ # Return unless we have a valid interface attribute with a setter
+ return unless has_attribute?(key)
+ options = self.class.attributes[key.to_sym]
+ return if options[:readonly]
+ return if options.has_key?(:property) && !options[:property]
+
+ setter = options[:property] || options[:property_setter] || "#{key}=".to_sym
+ return unless setter
+
+ # Convert the setter to a proc and call it
+ setter = spec_to_proc(setter)
+ setter.call(interface, read_attribute(key))
+ end
+
+ # Converts a getter/setter specification to a Proc which can be called
+ # to obtain or set a value. There are multiple ways to specify the getter
+ # and/or setter of an interface attribute:
+ #
+ # ## Symbol
+ #
+ # A symbol represents a method to call on the interface. An example of the
+ # declaration and resulting method call are shown below:
+ #
+ # attribute :foo, :property_getter => :get_foo
+ #
+ # Converts to:
+ #
+ # interface.get_foo
+ #
+ # ## Proc
+ #
+ # A proc is called with the interface and it is expected to return the value
+ # for a getter. For a setter, the interface and the value is sent in as
+ # parameters to the Proc.
+ #
+ # attribute :foo, :property_getter => Proc.new { |i| i.get_foo }
+ #
+ def spec_to_proc(spec)
+ # Return the spec as-is if its a proc
+ return spec if spec.is_a?(Proc)
+
+ if spec.is_a?(Symbol)
+ # For symbols, wrap up a method send in a Proc and return
+ # that
+ return Proc.new { |m, *args| m.send(spec, *args) }
+ end
+ end
+ end
+ end
+end
View
27 lib/virtualbox/abstract_model/relatable.rb
@@ -209,8 +209,8 @@ def save_relationships(*args)
# calls `save_relationship` on the relationship class.
def save_relationship(name, *args)
options = self.class.relationships_hash[name]
- return unless options[:klass].respond_to?(:save_relationship)
- options[:klass].save_relationship(self, relationship_data[name], *args)
+ return unless relationship_class(name).respond_to?(:save_relationship)
+ relationship_class(name).save_relationship(self, relationship_data[name], *args)
end
# The equivalent to {Attributable#populate_attributes}, but with
@@ -224,8 +224,8 @@ def populate_relationships(data)
# Populate a single relationship.
def populate_relationship(name, data)
options = self.class.relationships_hash[name]
- return unless options[:klass].respond_to?(:populate_relationship)
- relationship_data[name] = options[:klass].populate_relationship(self, data)
+ return unless relationship_class(name).respond_to?(:populate_relationship)
+ relationship_data[name] = relationship_class(name).populate_relationship(self, data)
end
# Calls `destroy_relationship` on each of the relationships. Any
@@ -244,13 +244,13 @@ def destroy_relationships(*args)
# @param [Symbol] name The name of the relationship
def destroy_relationship(name, *args)
options = self.class.relationships_hash[name]
- return unless options && options[:klass].respond_to?(:destroy_relationship)
+ return unless options && relationship_class(name).respond_to?(:destroy_relationship)
# Read relationship, which forces lazy relationships to load, which is
# probably necessary for destroying
read_relationship(name)
- options[:klass].destroy_relationship(self, relationship_data[name], *args)
+ relationship_class(name).destroy_relationship(self, relationship_data[name], *args)
end
# Hash to data associated with relationships. You should instead
@@ -281,6 +281,17 @@ def loaded_relationship?(key)
relationship_data.has_key?(key)
end
+ # Returns the class for a given relationship. This method handles converting
+ # a string/symbol into the proper class.
+ #
+ # @return [Class]
+ def relationship_class(key)
+ options = self.class.relationships_hash[key.to_sym]
+ klass = options[:klass]
+ klass = Object.module_eval("#{klass}") unless klass.is_a?(Class)
+ klass
+ end
+
# Sets a relationship to the given value. This is not guaranteed to
# do anything, since "set_relationship" will be called on the class
# that the relationship is associated with and its expected to return
@@ -299,8 +310,8 @@ def set_relationship(key, value)
relationship = self.class.relationships_hash[key]
return unless relationship
- raise Exceptions::NonSettableRelationshipException.new unless relationship[:klass].respond_to?(:set_relationship)
- relationship_data[key] = relationship[:klass].set_relationship(self, relationship_data[key], value)
+ raise Exceptions::NonSettableRelationshipException.new unless relationship_class(key).respond_to?(:set_relationship)
+ relationship_data[key] = relationship_class(key).set_relationship(self, relationship_data[key], value)
end
end
end
View
55 lib/virtualbox/appliance.rb
@@ -0,0 +1,55 @@
+module VirtualBox
+ # Represents an VirtualBox "appliance" which is an exported virtual machine or
+ # virtual machines. Appliances typically come with an OVF file and one or more
+ # compressed hard disks, and can be used to import directly into other VirtualBox
+ # installations. Appliances allow for virtual machine portability.
+ class Appliance < AbstractModel
+ attribute :path
+ attribute :interface, :readonly => true, :property => false
+ relationship :virtual_systems, :VirtualSystemDescription
+
+ def initialize(*args)
+ write_attribute(:interface, Lib.lib.virtualbox.create_appliance)
+
+ initialize_from_path(*args) if args.length == 1
+
+ clear_dirty!
+ end
+
+ # Initializes this Appliance instance from a path to an OVF file. This sets
+ # up the relationships and so on.
+ #
+ # @param [String] path Path to the OVF file.
+ def initialize_from_path(path)
+ # Read in the data from the path
+ interface.read(path).wait_for_completion(-1)
+
+ # Interpret the data to fill in the interface properties
+ interface.interpret
+
+ # Load the interface attributes
+ load_interface_attributes(interface)
+
+ # Fill in the virtual systems
+ populate_relationship(:virtual_systems, interface.virtual_system_descriptions)
+
+ # Should be an existing record
+ existing_record!
+ end
+
+ # Imports the machines associated with this appliance.
+ def import
+ interface.import_machines.wait_for_completion(-1)
+ end
+
+ # Exports the machines to the given path
+ def export
+ interface.write("ovf-1.0", path).wait_for_completion(-1)
+ end
+
+ # Adds a VM to the appliance
+ def add_machine(vm)
+ vm.interface.export(interface)
+ end
+ end
+end
View
249 lib/virtualbox/attached_device.rb
@@ -1,249 +0,0 @@
-module VirtualBox
- # Represents an device which is attached to a storage controller. An example
- # of such a device would be a CD or hard drive attached to an IDE controller.
- #
- # # Creating a New Attached Device
- #
- # Creating a new attached device is simple. The following is a simple example
- # of creating a DVD with an empty drive:
- #
- # ad = VirtualBox::AttachedDevice.new
- # ad.port = 0
- # ad.image = VirtualBox::DVD.empty_drive
- # storage_controller.devices << ad
- # ad.save
- #
- # The only quirk is that the attached device **must** be attached to a
- # storage controller. The above assumes that `storage_controller` exists,
- # which adds the device.
- #
- # Any {Image} subclass can be set to the `image` relationship.
- #
- # The following is an example using {VM.find}:
- #
- # # First creating the new device...
- # ad = VirtualBox::AttachedDevice.new
- # ad.port = 0
- # ad.image = VirtualBox::DVD.empty_drive
- #
- # # Now attaching to existing VM
- # vm = VirtualBox::VM.find("FooVM")
- # vm.storage_controllers[0].devices << ad
- # vm.save
- #
- # The interesting thing in this example is that the `save` method is called on
- # the virtual machine rather than the AttachedDevice. This will actually work
- # as expected! Saving a virtual machine automatically saves all it's relationships
- # as well.
- #
- # # Attributes and Relationships
- #
- # Properties of the model are exposed using standard ruby instance
- # methods which are generated on the fly. Because of this, they are not listed
- # below as available instance methods.
- #
- # These attributes can be accessed and modified via standard ruby-style
- # `instance.attribute` and `instance.attribute=` methods. The attributes are
- # listed below.
- #
- # Relationships are also accessed like attributes but can't be set. Instead,
- # they are typically references to other objects such as an {AttachedDevice} which
- # in turn have their own attributes which can be modified.
- #
- # ## Attributes
- #
- # This is copied directly from the class header, but lists all available
- # attributes. If you don't understand what this means, read {Attributable}.
- #
- # attribute :parent, :readonly => true
- # attribute :uuid
- # attribute :medium
- # attribute :port
- #
- # ## Relationships
- #
- # In addition to the basic attributes, a virtual machine is related
- # to other things. The relationships are listed below. If you don't
- # understand this, read {Relatable}.
- #
- # relationship :image, Image
- #
- class AttachedDevice < AbstractModel
- attribute :parent, :readonly => true
- attribute :uuid, :readonly => true
- attribute :port
- attribute :type, :readonly => true
- relationship :image, Image
-
- class <<self
- # Populate relationship with another model.
- #
- # **This method typically won't be used except internally.**
- #
- # @return [Array<AttachedDevice>]
- def populate_relationship(caller, data)
- relation = Proxies::Collection.new(caller)
-
- counter = 0
- data.css("AttachedDevice").each do |ad|
- relation << new(counter, caller, ad)
- counter += 1
- end
-
- relation
- end
-
- # Destroy attached devices associated with another model.
- #
- # **This method typically won't be used except internally.**
- def destroy_relationship(caller, data, *args)
- data.each { |v| v.destroy(*args) }
- end
-
- # Saves the relationship. This simply calls {#save} on every
- # member of the relationship.
- #
- # **This method typically won't be used except internally.**
- def save_relationship(caller, data)
- # Just call save on each nic with the VM
- data.each do |ad|
- ad.save
- end
- end
- end
-
- # @overload initialize(data={})
- # Creates a new AttachedDevice which is a new record. This
- # should be attached to a storage controller and saved.
- # @param [Hash] data (optional) A hash which contains initial attribute
- # values for the AttachedDevice.
- # @overload initialize(index, caller, data)
- # Creates an AttachedDevice for a relationship. **This should
- # never be called except internally.**
- # @param [Integer] index Index of the port
- # @param [Object] caller The parent
- # @param [Hash] data A hash of data which must be used
- # to extract the relationship data.
- def initialize(*args)
- super()
-
- if args.length == 3
- populate_from_data(*args)
- elsif args.length == 1
- populate_attributes(*args)
- new_record!
- elsif args.empty?
- return
- else
- raise NoMethodError.new
- end
- end
-
- # Validates an attached device.
- def validate
- super
-
- validates_presence_of :parent
- validates_presence_of :image
- validates_presence_of :port
- end
-
- # Saves or creates an attached device.
- #
- # @param [Boolean] raise_errors If true, {Exceptions::CommandFailedException}
- # will be raised if the command failed.
- # @return [Boolean] True if command was successful, false otherwise.
- def save(raise_errors=false)
- return true unless changed?
-
- if !valid?
- raise Exceptions::ValidationFailedException.new(errors) if raise_errors
- return false
- end
-
- # If the port changed, we have to destroy the old one, then create
- # a new one
- destroy({:port => port_was}, raise_errors) if port_changed? && !port_was.nil?
-
- Command.vboxmanage("storageattach", parent.parent.name, "--storagectl", parent.name, "--port", port, "--device", "0", "--type", image.image_type, "--medium", medium)
- existing_record!
- clear_dirty!
-
- true
- rescue Exceptions::CommandFailedException
- raise if raise_errors
- false
- end
-
- # Medium of the attached image. This attribute will be dependent
- # on the attached image and will return one of the following values:
- #
- # * **none** - There is no attached image
- # * **emptydrive** - An image with an empty drive is attached (see
- # {DVD.empty_drive})
- # * **image uuid** - The image's UUID
- #
- # @return [String]
- def medium
- if image.nil?
- "none"
- elsif image.empty_drive?
- "emptydrive"
- else
- image.uuid
- end
- end
-
- # Destroys the attached device. By default, this only removes any
- # media inserted within the device, but does not destroy it. This
- # option can be specified, however, through the `destroy_image`
- # option.
- #
- # @option options [Boolean] :destroy_image (false) If true, will also
- # destroy the image associated with device.
- # @param [Boolean] raise_errors If true, {Exceptions::CommandFailedException}
- # will be raised if the command failed.
- # @return [Boolean] True if command was successful, false otherwise.
- def destroy(options={}, raise_errors=false)
- # parent = storagecontroller
- # parent.parent = vm
- destroy_port = options[:port] || port
- Command.vboxmanage("storageattach", parent.parent.name, "--storagectl", parent.name, "--port", destroy_port, "--device", "0", "--medium", "none")
- image.destroy(raise_errors) if options[:destroy_image] && image
- rescue Exceptions::CommandFailedException
- raise if raise_errors
- false
- end
-
- # Relationship callback when added to a collection. This is automatically
- # called by any relationship collection when this object is added.
- def added_to_relationship(parent)
- write_attribute(:parent, parent)
- end
-
- protected
-
- # Populates the model based on data from a parsed vminfo. This
- # method is used to create a model which already exists and is
- # part of a relationship.
- #
- # **This method should never be called except internally.**
- def populate_from_data(index, caller, data)
- # Get the regular attributes
- attrs = {}
- data.attributes.each do |key, value|
- attrs[key.downcase.to_sym] = value.to_s
- end
-
- # Get the Image UUID
- image = data.css("Image")
- if image.empty?
- attrs[:uuid] = nil
- else
- attrs[:uuid] = image[0]["uuid"][1..-2]
- end
-
- populate_attributes(attrs.merge({ :parent => caller }))
- end
- end
-end
View
47 lib/virtualbox/bios.rb
@@ -0,0 +1,47 @@
+module VirtualBox
+ # Represents the BIOS settings of a {VM}.
+ class BIOS < AbstractModel
+ attribute :parent, :readonly => true, :property => false
+ attribute :acpi_enabled
+ attribute :io_apic_enabled
+
+ class <<self
+ # Populates a relationship with another model.
+ #
+ # **This method typically won't be used except internally.**
+ #
+ # @return [BIOS]
+ def populate_relationship(caller, imachine)
+ data = new(caller, imachine.bios_settings)
+ end
+
+ # Saves the relationship.
+ #
+ # **This method typically won't be used except internally.**
+ def save_relationship(caller, instance)
+ instance.save
+ end
+ end
+
+ def initialize(parent, bios_settings)
+ write_attribute(:parent, parent)
+
+ # Load the attributes and mark the whole thing as existing
+ load_interface_attributes(bios_settings)
+ clear_dirty!
+ existing_record!
+ end
+
+ def save
+ parent.with_open_session do |session|
+ machine = session.machine
+
+ # Save them
+ save_changed_interface_attributes(machine.bios_settings)
+
+ # Persist settings on the machine
+ machine.save_settings
+ end
+ end
+ end
+end
View
23 lib/virtualbox/com.rb
@@ -0,0 +1,23 @@
+module VirtualBox
+ module COM
+ WSTRING = :unicode_string
+ T_INT32 = :int
+ T_INT64 = :long
+ T_ULONG = :ulong
+ T_UINT8 = :uchar
+ T_UINT16 = :ushort
+ T_UINT32 = :uint
+ T_UINT64 = :ulong
+ T_BOOL = :char
+ end
+end
+
+# The com directory of the gem
+comdir = File.join(File.dirname(__FILE__), 'com')
+
+# Require the abstract interface first then glob load all
+# of the interfaces
+require File.expand_path("abstract_interface", comdir)
+require File.expand_path("abstract_enum", comdir)
+VirtualBox::GlobLoader.glob_require(File.join(comdir, "interface"))
+VirtualBox::GlobLoader.glob_require(comdir, %w{abstract_interface abstract_implementer util ffi/interface ffi/util})
View
42 lib/virtualbox/com/abstract_enum.rb
@@ -0,0 +1,42 @@
+module VirtualBox
+ module COM
+ # Represents a C enum type. Provides functionality to easily convert
+ # an int value to its proper symbol within the enum.
+ class AbstractEnum
+ extend Enumerable
+
+ class <<self
+ # Defines the mapping of int => symbol for the given Enum.
+ # The parameter to this can be an Array or Hash or anything which
+ # can be indexed with `[]` and an integer and returns a value of
+ # some sort.
+ def map(value)
+ @map = value
+ end
+
+ # Returns the symbol associatd with the given key
+ def [](key)
+ @map[key]
+ end
+
+ # Returns the index associated with a value
+ def index(key)
+ @map.index(key)
+ end
+
+ # Iterate over the enum, yielding each item to a block.
+ def each
+ @map.each do |key|
+ yield key
+ end
+ end
+
+ # Provided mostly for testing purposes only, but resets the mapping
+ # to nil.
+ def reset!
+ @map = nil
+ end
+ end
+ end
+ end
+end
View
43 lib/virtualbox/com/abstract_implementer.rb
@@ -0,0 +1,43 @@
+module VirtualBox
+ module COM
+ # Base class for a COM interface implementer. Any child of this class is
+ # responsible for properly handling the various method and propery calls
+ # of a given {AbstractInterface} and making them do actual work.
+ #
+ # This abstraction is necessary to change the behavior of calls between
+ # Windows (COM) and Unix (XPCOM), which have different calling conventions.
+ class AbstractImplementer
+ attr_reader :interface
+
+ # Initializes an implementer for the given {AbstractInterface}. The
+ # implementor's other methods, such as {read_property} or
+ # {call_function} are responsible for executing the said action on
+ # the interface.
+ #
+ # @param [AbstractInterface] interface
+ def initialize(interface)
+ @interface = interface
+ end
+
+ # Read a property of the interface.
+ #
+ # @param [Symbol] name The propery name
+ def read_property(name, opts)
+ end
+
+ # Writes a property of the interface.
+ #
+ # @param [Symbol] name The property name
+ # @param [Object] value The value to set
+ def write_property(name, value, opts)
+ end
+
+ # Calls a function on the interface.
+ #
+ # @param [Symbol] name The function name
+ # @param [Array] args The arguments to the function
+ def call_function(name, args, opts)
+ end
+ end
+ end
+end
View
165 lib/virtualbox/com/abstract_interface.rb
@@ -0,0 +1,165 @@
+module VirtualBox
+ module COM
+ # Base class for a COM (component object model) interface class. This
+ # abstraction is necessary to maintain a common ground between
+ # Windows COM usage and the VirtualBox C API for unix based systems.
+ #
+ # # Defining an Interface
+ #
+ # Defining an interface is done by subclassing AbstractInterface and
+ # using the provided class methods to define the COM methods and
+ # properties. A small example class is shown below:
+ #
+ # class Time < AbstractInterface
+ # function :now, [[:out, :uint]]
+ # property :hour, :uint
+ # end
+ #
+ # # Accessing an Interface
+ #
+ # Interfaces are never accessed directly. Instead, an {InterfaceRunner}
+ # should be used. Depending on the OS of the running system, the VirtualBox
+ # gem will automatically either load the MSCOM interface (on Windows)
+ # or the XPCOM interface (on Unix). One loaded, interfaces can simply be
+ # accessed:
+ #
+ # # Assume `time` was retrieved already
+ # puts time.foo.to_s
+ # time.hour = 20
+ # x = time.now
+ #
+ # The above example shows how the properties and functions can be used
+ # with a given interface.
+ #
+ class AbstractInterface
+ attr_reader :implementer
+
+ class <<self
+ # Adds a function to the interface with the given name and function
+ # spec. The spec determines the arguments required, the order they
+ # are required in, and any out-arguments.
+ def function(name, type, spec, opts={})
+ members << [name, {
+ :type => :function,
+ :value_type => type,
+ :spec => spec,
+ :opts => opts
+ }]
+
+ # Define the method to call the function
+ define_method(name) { |*args| call_function(name, *args) }
+ end
+
+ # Adds a property to the interface with the given name, type, and
+ # options.
+ def property(name, type, opts={})
+ members << [name, {
+ :type => :property,
+ :value_type => type,
+ :opts => opts
+ }]
+
+ # Define the method to read the property
+ define_method(name) { read_property(name) }
+
+ # Define method to write the property
+ define_method("#{name}=".to_sym) { |value| write_property(name, value) } unless opts[:readonly]
+ end
+
+ # Returns the information for a given member
+ #
+ # @return [Hash]
+ def member(name)
+ members.each do |current_name, opts|
+ if name == current_name
+ return opts
+ end
+ end
+
+ nil
+ end
+
+ # Returns the members of the interface as an array.
+ #
+ # @return [Array]
+ def members
+ @members ||= []
+ end
+
+ # Returns the functions of the interface as an array in the order they
+ # were defined.
+ #
+ # @return [Array]
+ def functions
+ members.find_all do |data|
+ data[1][:type] == :function
+ end
+ end
+
+ # Returns the properties of the interface as an array in the order they
+ # were defined.
+ #
+ # @return [Array]
+ def properties
+ members.find_all do |data|
+ data[1][:type] == :property
+ end
+ end
+ end
+
+ # Initializes the interface with the given implementer
+ def initialize(implementer, *args)
+ # Instantiate the implementer and set it
+ @implementer = implementer.new(self, *args)
+ end
+
+ # Reads a property with the given name by calling the read_property
+ # method on the implementer.
+ def read_property(name)
+ # Just call it on the implementer
+ @implementer.read_property(name, member(name))
+ end
+
+ # Writes a property with the given name and value by calling the
+ # `write_property` method on the implementer.
+ def write_property(name, value)
+ @implementer.write_property(name, value, member(name))
+ end
+
+ # Calls a function with the given name by calling call_function on the
+ # implementer.
+ def call_function(name, *args)
+ @implementer.call_function(name, args, member(name))
+ end
+
+ # Returns a boolean if a given function exists or not
+ def has_function?(name)
+ info = member(name)
+ !info.nil? && info[:type] == :function
+ end
+
+ # Returns a boolean if a given property exists or not.
+ def has_property?(name)
+ info = member(name)
+ !info.nil? && info[:type] == :property
+ end
+
+ # Returns the member of the interface specified by name. This simply
+ # calls {AbstractInterface.member}
+ def member(name)
+ self.class.member(name)
+ end
+
+ # Returns the members of the interface as an array. This simply calls
+ # {AbstractInterface.members}.
+ def members
+ self.class.members
+ end
+
+ # Concise inspect
+ def inspect
+ "#<#{self.class.name}>"
+ end
+ end
+ end
+end
View
141 lib/virtualbox/com/ffi/interface.rb
@@ -0,0 +1,141 @@
+require 'ffi'
+
+module VirtualBox
+ module COM
+ module FFI
+ extend ::FFI::Library
+
+ # FFI specific types
+ NSRESULT_TYPE = :uint
+
+ # Returns a Class which creates an FFI interface to the specified
+ # com interface and potentially a parent class as well.
+ def self.create_interface(interface, parent=nil)
+ klass = Class.new(Interface)
+ const_set(interface, klass)
+ klass.com_interface(interface, parent)
+ klass
+ end
+
+ # Represents a VirtualBox XPCOM C interface, which is a C struct
+ # which emulates an object (a struct with function pointers
+ # and getters/setters). This class does **a lot** of magic which pretty
+ # much represents everything wrong about ruby programmers, but keep
+ # in mind it is well tested and well commented, and the meta-programming
+ # was done out of a need to keep things DRY between Windows and Unix
+ # operating systems.
+ class Interface
+ extend ::FFI::Library
+
+ attr_reader :vtbl_parent
+ attr_reader :vtbl
+
+ class <<self
+ # Sets up the args to the FFI::Struct `layout` method. This
+ # method defines all the callbacks necessary for working with
+ # FFI and also sets up any layout args to send in. The way the
+ # XPCOM C structs are setup, the properties are first, in
+ # `GetFoo` and `SetFoo` format. And the functions are next. They are
+ # put into the struct in the order defined in the {AbstractInterface}.
+ def com_interface(interface, parent=nil)
+ # Create the parent class and vtbl class
+ interface = ::VirtualBox::COM::Interface.const_get(interface)
+ define_vtbl_parent_for_interface(interface)
+ define_vtbl_for_interface(interface, parent)
+ end
+
+ # Creates the parent of the vtbl class associated with a given
+ # interface.
+ def define_vtbl_parent_for_interface(interface)
+ @vtbl_parent_klass = Class.new(::FFI::Struct)
+ @vtbl_parent_klass.layout(:vtbl, :pointer)
+
+ # Set the constant
+ const_set("VtblParent", @vtbl_parent_klass)
+ end
+
+ # Creates the vtbl class associated with a given interface.
+ def define_vtbl_for_interface(interface, parent=nil)
+ # Define the properties, then the functions, since thats the order
+ # the FFI structs are in
+ layout_args.clear
+ define_interface_parent(parent)
+ define_interface_properties(interface)
+ define_interface_functions(interface)
+
+ # Finally create the classes (the struct and the structs vtbl)
+ @vtbl_klass = Class.new(::FFI::Struct)
+
+ # Set the constant within this class
+ const_set("Vtbl", @vtbl_klass).layout(*layout_args.flatten)
+ end
+
+ # Defines the parent item of the layout. Since the VirtualBox XPCOM C
+ # library emulates an object-oriented environment using structs, the parent
+ # instance is pointed to by the first member of the struct. This method
+ # sets up that member.
+ #
+ # @param [Symbol] parent The name of the parent represented by a symbol
+ def define_interface_parent(parent)
+ return if parent.nil?
+
+ parent_klass = Object.module_eval("::VirtualBox::COM::FFI::#{parent}::Vtbl")
+ layout_args << [:superklass, parent_klass]
+ end
+
+ # Defines all the properties on a com interface.
+ def define_interface_properties(interface)
+ interface.properties.each do |name, opts|
+ # Define the getter
+ define_interface_function("get_#{name}".to_sym, opts[:value_type])
+
+ # Define the setter unless the property is readonly
+ define_interface_function("set_#{name}".to_sym, nil, [opts[:value_type]]) unless opts[:opts] && opts[:opts][:readonly]
+ end
+ end
+
+ # Defines all the functions on a com interface.
+ def define_interface_functions(interface)
+ interface.functions.each do |name, opts|
+ # Define the function
+ define_interface_function(name, opts[:value_type], opts[:spec].dup)
+ end
+ end
+
+ # Defines a single function of a com interface
+ def define_interface_function(name, return_type, spec=[])
+ # Append the return type to the spec as an out parameter (this is how
+ # the C API handles it)
+ spec << [:out, return_type] unless return_type.nil?
+
+ # Define the "callback" type for the FFI module
+ callback(name, Util.spec_to_ffi(spec), NSRESULT_TYPE)
+
+ # Add to the layout args
+ layout_args << [name, name]
+ end
+
+ # Returns an array of the layout args to send to `layout` eventually.
+ #
+ # @return [Array]
+ def layout_args
+ @_layout_args ||= []
+ end
+ end
+
+ # Initializes the interface to the FFI struct with the given pointer. The
+ # pointer is used to initialize the VtblParent which is used to initialize
+ # the Vtbl itself.
+ def initialize(pointer)
+ initialize_vtbl(pointer)
+ end
+
+ def initialize_vtbl(pointer)
+ klass = self.class
+ @vtbl_parent = klass::VtblParent.new(pointer)
+ @vtbl = klass::Vtbl.new(vtbl_parent[:vtbl])
+ end
+ end
+ end
+ end
+end
View
42 lib/virtualbox/com/ffi/interfaces.rb
@@ -0,0 +1,42 @@
+module VirtualBox
+ module COM
+ module FFI
+ # Creates all the interfaces for the FFI implementation. Eventually this
+ # file should be conditionally loaded based on OS, so that Windows users
+ # don't have to wait for all this translation to occur.
+ create_interface(:NSISupports)
+ create_interface(:NSIException, :NSISupports)
+ create_interface(:Session, :NSISupports)
+ create_interface(:VirtualBox, :NSISupports)
+ create_interface(:Appliance, :NSISupports)
+ create_interface(:AudioAdapter, :NSISupports)
+ create_interface(:BIOSSettings, :NSISupports)
+ create_interface(:Console, :NSISupports)
+ create_interface(:DHCPServer, :NSISupports)
+ create_interface(:GuestOSType, :NSISupports)
+ create_interface(:Host, :NSISupports)
+ create_interface(:HostNetworkInterface, :NSISupports)
+ create_interface(:Machine, :NSISupports)
+ create_interface(:Medium, :NSISupports)
+ create_interface(:MediumAttachment, :NSISupports)
+ create_interface(:MediumFormat, :NSISupports)
+ create_interface(:NetworkAdapter, :NSISupports)
+ create_interface(:ParallelPort, :NSISupports)
+ create_interface(:Progress, :NSISupports)
+ create_interface(:SerialPort, :NSISupports)
+ create_interface(:SharedFolder, :NSISupports)
+ create_interface(:Snapshot, :NSISupports)
+ create_interface(:StorageController, :NSISupports)
+ create_interface(:SystemProperties, :NSISupports)
+ create_interface(:USBController, :NSISupports)
+ create_interface(:USBDevice, :NSISupports)
+ create_interface(:USBDeviceFilter, :NSISupports)
+ create_interface(:VirtualBoxErrorInfo, :NSIException)
+ create_interface(:VirtualSystemDescription, :NSISupports)
+ create_interface(:VRDPServer, :NSISupports)
+
+ create_interface(:HostUSBDevice, :USBDevice)
+ create_interface(:HostUSBDeviceFilter, :USBDeviceFilter)
+ end
+ end
+end
View
101 lib/virtualbox/com/ffi/util.rb
@@ -0,0 +1,101 @@
+module VirtualBox
+ module COM
+ module FFI
+ # Class which contains many class-level utility methods to assist
+ # with the FFI interface. These functions range from converting a
+ # function spec to a FFI parameter list to dereferencing pointers.
+ class Util
+ class <<self
+ # Converts a function spec from {AbstractInterface} to an FFI
+ # function spec. This handles custom types (unicode strings,
+ # arrays, and out-parameters) and will return a perfectly valid
+ # array ready to be passed into `callback`.
+ #
+ # @param [Array] spec The function spec
+ # @return [Array]
+ def spec_to_ffi(spec)
+ spec = spec.collect do |item|
+ if item.is_a?(Array) && item[0] == :out
+ if item[1].is_a?(Array)
+ # The out is an array of items, so we add in two pointers:
+ # one for size and one for the array
+ [:pointer, :pointer]
+ else
+ # A regular out parameter is just a single pointer
+ :pointer
+ end
+ elsif item.is_a?(Array) && item.length == 1
+ # The parameter is an array of somethings
+ [T_UINT32, :pointer]
+ elsif item == WSTRING
+ # Unicode strings are simply pointers
+ :pointer
+ elsif item.to_s[0,1] == item.to_s[0,1].upcase
+ begin
+ # Try to get the class from the interfaces
+ interface = COM::Interface.const_get(item.to_sym)
+
+ if interface.superclass == COM::AbstractInterface
+ :pointer
+ elsif interface.superclass == COM::AbstractEnum
+ T_UINT32
+ end
+ rescue NameError
+ # Default to a pointer, since not all interfaces are implemented
+ :pointer
+ end
+ else
+ # Unknown items are simply passed as-is, hopefully FFI
+ # will catch any problems
+ item
+ end
+ end
+
+ # Prepend a :pointer to represent the `this` parameter required
+ # for the FFI parameter lists
+ spec.unshift(:pointer).flatten
+ end
+
+ # An "almost complete" camel-caser. Camel cases a string with a few
+ # exceptions. For example: `get_foo` becomes `GetFoo`, but `get_os_type`
+ # becomes `GetOSType` since `os` is a special case.
+ #
+ # @param [String] string The string to camel case
+ # @return [String]
+ def camelize(string)
+ special_cases = {
+ "os" => "OS",
+ "dhcp" => "DHCP",
+ "dvd" => "DVD",
+ "usb" => "USB",
+ "vram" => "VRAM",
+ "3d" => "3D",
+ "bios" => "BIOS",
+ "vrdp" => "VRDP",
+ "hw" => "HW",
+ "png" => "PNG",
+ "io" => "IO",
+ "apic" => "APIC",
+ "acpi" => "ACPI",
+ "pxe" => "PXE",
+ "nat" => "NAT",
+ "ide" => "IDE",
+ "vfs" => "VFS",
+ "ip" => "IP",
+ "vdi" => "VDI",
+ "cpu" => "CPU",
+ "ram" => "RAM",
+ "hdd" => "HDD"
+ }
+
+ parts = string.to_s.split(/_/).collect do |part|
+ special_cases[part] || part.capitalize
+ end
+
+ parts.join("")
+ end
+ end
+ end
+ end
+ end
+end
View
31 lib/virtualbox/com/ffi/vboxxpcomc.rb
@@ -0,0 +1,31 @@
+module VirtualBox
+ module COM
+ module FFI
+ # Callback types for VBOXXPCOMC
+ callback :pfnGetVersion, [], :uint
+ callback :pfnComInitialize, [:string, :pointer, :string, :pointer], :void
+ callback :pfnComUninitialize, [], :void
+ callback :pfnComUnallocMem, [:void], :void
+ callback :pfnUtf16Free, [:pointer], :void
+ callback :pfnUtf8Free, [:string], :void
+ callback :pfnUtf16ToUtf8, [:pointer, :pointer], :int
+ callback :pfnUtf8ToUtf16, [:string, :pointer], :int
+ callback :pfnGetEventQueue, [:pointer], :void
+
+ class VBOXXPCOMC < ::FFI::Struct
+ layout :cb, :uint,
+ :uVersion, :uint,
+ :pfnGetVersion, :pfnGetVersion,
+ :pfnComInitialize, :pfnComInitialize,
+ :pfnComUninitialize, :pfnComUninitialize,
+ :pfnComUnallocMem, :pfnComUnallocMem,
+ :pfnUtf16Free, :pfnUtf16Free,
+ :pfnUtf8Free, :pfnUtf8Free,
+ :pfnUtf16ToUtf8, :pfnUtf16ToUtf8,
+ :pfnUtf8ToUtf16, :pfnUtf8ToUtf16,
+ :pfnGetEventQueue, :pfnGetEventQueue,
+ :uEndVersion, :uint
+ end
+ end
+ end
+end
View
65 lib/virtualbox/com/ffi_interface.rb
@@ -0,0 +1,65 @@
+module VirtualBox
+ module COM
+ class FFIInterface
+ extend ::FFI::Library
+
+ # Constant used to initialize the XPCOM C interface
+ XPCOMC_VERSION = 0x00020000
+
+ # VBOXXPCOMC struct. This typically won't be used.
+ attr_reader :xpcom
+
+ # The VirtualBox and Session interfaces, both of which are extremely
+ # important in interfacing with the VirtualBox API. Once these have been
+ # initialized, all other parts of the API can be accessed via these
+ # instances.
+ attr_reader :virtualbox
+ attr_reader :session
+
+ class <<self
+ # Sets up the FFI interface and also initializes the interface,
+ # returning an instance of {FFIInterface}.
+ def create(lib_path=nil)
+ setup(lib_path)
+ new
+ end
+
+ # Sets up the FFI interface by specifying the FFI library path
+ # and attaching the initial function (which can't be done until
+ # the FFI library is specified).
+ #
+ # @param [String] lib_path
+ def setup(lib_path=nil)
+ # Setup the path to the C library
+ lib_path ||= "/Applications/VirtualBox.app/Contents/MacOS/VBoxXPCOMC.dylib"
+
+ # Attach to the interface
+ ffi_lib lib_path
+ attach_function :VBoxGetXPCOMCFunctions, [:uint], :pointer
+ end
+ end
+
+ def initialize
+ initialize_com
+ end
+
+ # Initializes the COM interface with XPCOM. This sets up the `virtualbox`,
+ # `session`, and `xpcom` attributes. This should only be called once.
+ def initialize_com
+ # Get the pointer to the XPCOMC struct which contains the functions
+ # to initialize
+ xpcom_pointer = self.class.VBoxGetXPCOMCFunctions(XPCOMC_VERSION)
+ @xpcom = FFI::VBOXXPCOMC.new(xpcom_pointer)
+
+ virtualbox_ptr = ::FFI::MemoryPointer.new(:pointer)
+ session_ptr = ::FFI::MemoryPointer.new(:pointer)
+
+ # Initialize the virtualbox API and get the global VirtualBox
+ # interface and a session interface
+ @xpcom[:pfnComInitialize].call(COM::Interface::VirtualBox::IID_STR, virtualbox_ptr, COM::Interface::Session::IID_STR, session_ptr)
+ @virtualbox = Interface::VirtualBox.new(Implementer::FFI, self, virtualbox_ptr.get_pointer(0))
+ @session = Interface::Session.new(Implementer::FFI, self, session_ptr.get_pointer(0))
+ end
+ end
+ end
+end
View
50 lib/virtualbox/com/implementer/base.rb
@@ -0,0 +1,50 @@
+module VirtualBox
+ module COM
+ module Implementer
+ class Base < AbstractImplementer
+ # Finds and returns the `COM::Interface` class associated with the type.
+ # If the class does not exist, a `NameError` will be raised.
+ #
+ # @return [Class]
+ def interface_klass(type)
+ COM::Interface.const_get(type)
+ end
+
+ # Gives the C type and inferred type of a parameter type. Quite confusing
+ # since the terminology is not consistent, but hopefully these examples
+ # will help:
+ #
+ # type => [pointer_type, internal_type]
+ # :int => [:int, :int]
+ # :MyStruct => [:pointer, :struct]
+ # :unicode_string => [:pointer, :unicode_string]
+ #
+ def infer_type(type)
+ c_type = type
+
+ begin
+ if type == WSTRING
+ # Handle strings as pointer types
+ c_type = :pointer
+ else
+ # Try to get the class from the interfaces
+ interface = COM::Interface.const_get(type)
+
+ c_type = :pointer
+
+ # Depending on the class type, we're either dealing with an interface
+ # or an enum
+ type = :interface if interface.superclass == COM::AbstractInterface
+ type = :enum if interface.superclass == COM::AbstractEnum
+ end
+ rescue NameError
+ # Do nothing
+ end
+
+ [c_type, type]
+ end
+
+ end
+ end
+ end
+end
View
347 lib/virtualbox/com/implementer/ffi.rb
@@ -0,0 +1,347 @@
+module VirtualBox
+ module COM
+ module Implementer
+ class FFI < Base
+ attr_reader :ffi_interface
+ attr_reader :lib
+
+ # Initializes the FFI implementer which takes an {VirtualBox::COM::AbstractInterface AbstractInterface}
+ # instant and FFI pointer and initializes everything required to
+ # communicate with that interface via FFI.
+ #
+ # @param [VirtualBox::COM::AbstractInteface] inteface
+ # @param [FFI::Pointer] pointer
+ def initialize(interface, lib_base, pointer)
+ super(interface)
+
+ @lib = lib_base
+ @ffi_interface = ffi_class.new(pointer)
+ end
+
+ # Gets the FFI struct class associated with the interface. This works
+ # by stripping the namespace off of the interface class and finding that
+ # same class within the `COM::FFI` namespace. For example:
+ # `VirtualBox::COM::Interface::Session` becomes `VirtualBox::COM::FFI::Session`
+ #
+ # @return [Class]
+ def ffi_class
+ # Take off the last part of the class, so `Foo::Bar::Baz` becomes
+ # just `Baz`
+ klass_name = interface.class.to_s.split("::").last
+
+ # Get the associated FFI class
+ COM::FFI.const_get(klass_name)
+ end
+
+ # Reads a property from the interface with the given name.
+ def read_property(name, opts)
+ call_vtbl_function("get_#{name}".to_sym, [[:out, opts[:value_type]]])
+ end
+
+ # Writes a property to the interface with the given name and value.
+ def write_property(name, value, opts)
+ call_vtbl_function("set_#{name}".to_sym, [opts[:value_type]], [value])
+ end
+
+ # Calls a function from the interface with the given name and args. This
+ # method is called from the {AbstractInterface}.
+ def call_function(name, args, opts)
+ spec = opts[:spec].dup
+ spec << [:out, opts[:value_type]] if !opts[:value_type].nil?
+
+ call_vtbl_function(name.to_sym, spec, args)
+ end
+
+ # Calls a function on the vtbl of the FFI struct. This function handles
+ # converting the spec to proper arguments and also handles reading out
+ # the arguments, dereferencing pointers, setting up objects, etc. so that
+ # the return value is filled with nicely formatted Ruby objects.
+ #
+ # If the vtbl function being called only has one out parameter, then the
+ # return value will be that single object. If it has multiple, then it will
+ # be an array of objects.
+ def call_vtbl_function(name, spec, args=[])
+ # Get the "formal argument" list. This is the list of arguments to send
+ # to the actual function based on the spec. This contains pointers, some
+ # arguments from `args`, etc.
+ formal_args = spec_to_args(spec, args)
+
+ # Call the function. Error checking TODO.
+ call_and_check(ffi_interface.vtbl[name], ffi_interface.vtbl_parent, *formal_args)
+
+ # Extract the values from the formal args array, again based on the
+ # spec (and the various :out parameters)
+ values_from_formal_args(spec, formal_args)
+ end
+
+ #############################################################
+ # Internal Methods, a.k.a. unless you're hacking on the code of this
+ # library, you should do well to leave these alone =]
+ #############################################################
+
+ # Checks the result of a method call for an error, and if an error
+ # occurs, then raises an exception.
+ def call_and_check(function, *args)
+ result = function.call(*args)
+
+ # Ignore NS_ERROR_NOT_IMPLEMENTED, since it seems to be raised for
+ # things which aren't really exceptional
+ if result != 2147500033 && (result & 0x8000_0000) != 0
+ # Failure, raise exception with details of the error
+ raise exception_map(result).new({
+ :function => function.to_s,
+ :result_code => result
+ })
+ end
+ end
+
+ # Maps a result code to an exception. If no mapping currently exists,
+ # then a regular {Exceptions::FFIException} is returned.
+ #
+ # @param [Fixnum] code Result code
+ # @return [Class]
+ def exception_map(code)
+ map = {
+ 0x80BB_0001 => Exceptions::ObjectNotFoundException,
+ 0x80BB_0002 => Exceptions::InvalidVMStateException,
+ 0x80BB_0003 => Exceptions::VMErrorException,
+ 0x80BB_0004 => Exceptions::FileErrorException,
+ 0x80BB_0005 => Exceptions::SubsystemException,
+ 0x80BB_0006 => Exceptions::PDMException,
+ 0x80BB_0007 => Exceptions::InvalidObjectStateException,
+ 0x80BB_0008 => Exceptions::HostErrorException,
+ 0x80BB_0009 => Exceptions::NotSupportedException,
+ 0x80BB_000A => Exceptions::XMLErrorException,
+ 0x80BB_000B => Exceptions::InvalidSessionStateException,
+ 0x80BB_000C => Exceptions::ObjectInUseException
+ }
+
+ map[code] || Exceptions::FFIException
+ end
+
+ # Converts a function spec to a proper argument list with the given
+ # arguments.
+ #
+ # @return [Array]
+ def spec_to_args(spec, args=[])
+ args = args.dup
+
+ spec = spec.collect do |item|
+ if item.is_a?(Array) && item[0] == :out
+ if item[1].is_a?(Array)
+ # For arrays we need two pointers: one for size, and one for the
+ # actual array
+ [pointer_for_type(T_UINT32), pointer_for_type(item[1][0])]
+ else
+ pointer_for_type(item[1])
+ end
+ elsif item == WSTRING
+ # We have to convert the arg to a unicode string
+ string_to_utf16(args.shift)
+ elsif item == T_BOOL
+ args.shift ? 1 : 0
+ elsif item.to_s[0,1] == item.to_s[0,1].upcase
+ # Try to get the class from the interfaces
+ interface = interface_klass(item.to_sym)
+
+ if interface.superclass == COM::AbstractInterface
+ # For interfaces, get the instance, then dig deep to get the pointer
+ # to the VtblParent, which is what the API expects
+ instance = args.shift
+
+ if !instance.nil?
+ instance.implementer.ffi_interface.vtbl_parent
+ else
+ # If the argument was nil, just pass a nil pointer as the argument
+ nil
+ end
+ elsif interface.superclass == COM::AbstractEnum
+ # For enums, we need the value of the enum
+ interface.index(args.shift.to_sym)
+ end
+ else
+ # Simply replace spec item with next item in args
+ # list
+ args.shift
+ end
+ end.flatten
+ end
+
+ # Takes a spec and a formal parameter list and returns the output from
+ # a function, properly dereferencing any output pointers.
+ #
+ # @param [Array] specs The parameter spec for the function
+ # @param [Array] formal The formal parameter list
+ def values_from_formal_args(specs, formal)
+ return_values = []
+ i = 0
+ specs.each do |spec|
+ # Output parameters are all we care about
+ if spec.is_a?(Array) && spec[0] == :out
+ if spec[1].is_a?(Array)
+ # We are dealing with formal[i] and formal[i+1] here, where
+ # the first has the size and the second has the contents
+ return_values << dereference_pointer_array(formal[i+1], spec[1][0], dereference_pointer(formal[i], T_UINT32))
+
+ # Increment once more to skip the size param
+ i += 1
+ else
+ return_values << dereference_pointer(formal[i], spec[1])
+ end
+ end
+
+ i += 1
+ end
+
+ if return_values.empty?
+ nil
+ elsif return_values.length == 1
+ return_values.first
+ else
+ return_values
+ end
+ end
+
+ # Dereferences a pointer with a given type into a proper Ruby object.
+ # If the type is a standard primitive of Ruby-FFI, it simply calls the
+ # proper `get_*` method on the pointer. Otherwise, it calls a
+ # `read_*` on the Util class.
+ #
+ # @param [FFI::MemoryPointer] pointer
+ # @param [Symbol] type The type of the pointer
+ # @return [Object] The value of the dereferenced pointer
+ def dereference_pointer(pointer, type)
+ c_type, inferred_type = infer_type(type)
+
+ if pointer.respond_to?("get_#{inferred_type}".to_sym)
+ # This handles reading the typical times such as :uint, :int, etc.
+ result = pointer.send("get_#{inferred_type}".to_sym, 0)
+ result = !(result == 0) if type == T_BOOL
+ result
+ else
+ send("read_#{inferred_type}".to_sym, pointer, type)
+ end
+ end
+
+ # Dereferences an array out of a pointer into an array of proper Ruby
+ # objects.
+ #
+ # @param [FFI::MemoryPointer] pointer
+ # @param [Symbol] type The type of the pointer
+ # @param [Fixnum] length The length of the array
+ # @return [Array<Object>]
+ def dereference_pointer_array(pointer, type, length)
+ # If there are no items in the pointer, just return an empty array
+ return [] if length == 0
+
+ c_type, inferred_type = infer_type(type)
+
+ array_pointer = pointer.get_pointer(0)
+ if array_pointer.respond_to?("get_array_of_#{inferred_type}".to_sym)
+ # This handles reading the typical times such as :uint, :int, etc.
+ array_pointer.send("get_array_of_#{inferred_type}".to_sym, 0, length)
+ else
+ send("read_array_of_#{inferred_type}".to_sym, array_pointer, type, length)
+ end
+ end
+
+ # Converts a symbol type into a MemoryPointer and yield a block
+ # with the pointer, the C type, and the FFI type
+ def pointer_for_type(type)
+ c_type, type = infer_type(type)
+
+ # Create the pointer, yield, returning the result of the block
+ # if a block is given, or otherwise just returning the pointer
+ # and inferred type
+ pointer = ::FFI::MemoryPointer.new(c_type)
+ if block_given?
+ yield pointer, type
+ else
+ pointer
+ end
+ end
+
+ # Converts a ruby string to a UTF16 string
+ #
+ # @param [String] Ruby String object
+ # @return [::FFI::Pointer]
+ def string_to_utf16(string)
+ return nil if string.nil?
+
+ ptr = pointer_for_type(:pointer)
+ lib.xpcom[:pfnUtf8ToUtf16].call(string, ptr)
+ ptr.read_pointer()
+ end
+
+ # Converts a UTF16 string to UTF8
+ def utf16_to_string(pointer)
+ result_pointer = pointer_for_type(:pointer)
+ lib.xpcom[:pfnUtf16ToUtf8].call(pointer, result_pointer)
+ lib.xpcom[:pfnUtf16Free].call(pointer)
+ result_pointer.read_pointer().read_string().to_s
+ end
+
+ # Reads a unicode string value from a pointer to that value.
+ #
+ # @return [String]
+ def read_unicode_string(ptr, original_type=nil)
+ address = ptr.get_pointer(0)
+ return "" if address.null?
+ utf16_to_string(address)
+ end
+
+ # Reads an interface from the pointer
+ #
+ # @return [::FFI::Struct]
+ def read_interface(ptr, original_type)
+ ptr = ptr.get_pointer(0)
+ return nil if ptr.null?
+
+ klass = interface_klass(original_type)
+ klass.new(self.class, lib, ptr)
+ end
+
+ # Reads an enum
+ #
+ # @return [Symbol]
+ def read_enum(ptr, original_type)
+ klass = interface_klass(original_type)
+ klass[ptr.get_uint(0)]
+ end
+
+ # Reads an array of enums
+ #
+ # @return [Array<Symbol>]
+ def read_array_of_enum(ptr, type, length)
+ klass = interface_klass(type)
+ ptr.get_array_of_uint(0, length).collect do |value|
+ klass[value]
+ end
+ end
+
+ # Reads an array of structs from a pointer
+ #
+ # @return [Array<::FFI::Struct>]
+ def read_array_of_interface(ptr, type, length)
+ klass = interface_klass(type)
+ ptr.get_array_of_pointer(0, length).collect do |single_pointer|
+ klass.new(self.class, lib, single_pointer)
+ end
+ end
+
+ # Reads an array of strings from a pointer
+ #
+ # @return [Array<String>]
+ def read_array_of_unicode_string(ptr, type, length)
+ ptr.get_array_of_pointer(0, length).collect do |single_pointer|
+ if single_pointer.null?
+ nil
+ else
+ utf16_to_string(single_pointer)
+ end
+ end
+ end
+ end
+ end
+ end
+end
View
165 lib/virtualbox/com/implementer/mscom.rb
@@ -0,0 +1,165 @@
+module VirtualBox
+ module COM
+ module Implementer
+ class MSCOM < Base
+ attr_reader :lib
+ attr_reader :object
+
+ # Initializes the MSCOM implementer.
+ #
+ # @param [AbstractInterface] inteface
+ # @param [FFI::Pointer] pointer
+ def initialize(interface, lib_base, object)
+ super(interface)
+
+ @lib = lib_base
+ @object = object
+ end
+
+ # Reads a property from the interface with the given name.
+ def read_property(name, opts)
+ # First get the basic value from the COM object
+ value = @object[COM::FFI::Util.camelize(name.to_s)]
+
+ # Then depending on the value type, we either return as-is or
+ # must wrap it up in another interface class
+ returnable_value(value, opts[:value_type])
+ end
+
+ # Writes a property from the interface with the given name and
+ # value.
+ def write_property(name, value, opts)
+ # Set the property with a prepared value
+ @object[COM::FFI::Util.camelize(name.to_s)] = spec_to_args([opts[:value_type]], [value]).first
+ end
+
+ # Calls a function from the interface with the given name
+ def call_function(name, args, opts)
+ # Convert args to proper values to send and send em!
+ args = spec_to_args(opts[:spec], args)
+ value = @object.send(COM::FFI::Util.camelize(name.to_s), *args)
+
+ # TODO: Multiple return values
+ returnable_value(value, opts[:value_type])
+ end
+
+ #############################################################
+ # Internal Methods, a.k.a. unless you're hacking on the code of this
+ # library, you should do well to leave these alone =]
+ #############################################################
+
+ # Takes a function spec and an argument list. This handles properly converting
+ # enums to ints and {AbstractInterface}s to proper MSCOM interfaces.
+ def spec_to_args(spec, args)
+ args = args.dup
+
+ # First remove all :out parameters from the spec, since those are of no
+ # concern for MSCOM at this point
+ spec = spec.collect do |item|
+ if item.is_a?(Array) && item[0] == :out
+ nil
+ else
+ item
+ end
+ end.compact
+
+ spec = spec.collect do |item|
+ if item == T_BOOL
+ args.shift ? 1 : 0
+ elsif item.to_s[0,1] == item.to_s[0,1].upcase
+ # Try to get the class from the interfaces
+ interface = interface_klass(item.to_sym)
+
+ if interface.superclass == COM::AbstractInterface
+ # For interfaces, get the instance, then dig deep to get the pointer
+ # to the VtblParent, which is what the API expects
+ instance = args.shift
+
+ if !instance.nil?
+ # Get the actual MSCOM object, rather than the AbstractInterface
+ instance.implementer.object
+ else
+ # If the argument was nil, just pass a nil pointer as the argument
+ nil
+ end
+ elsif interface.superclass == COM::AbstractEnum
+ # For enums, we need the value of the enum
+ interface.index(args.shift.to_sym)
+ end
+ else
+ # Simply replace spec item with next item in args
+ # list
+ args.shift
+ end
+ end
+ end
+
+ # Takes a value (returned from a WIN32OLE object) and a type and converts
+ # to a proper ruby return value type.
+ def returnable_value(value, type)
+ # Types which are void or nil just return
+ return nil if type.nil? || type == :void
+
+ klass = type.is_a?(Array) ? type.first : type
+ ignore, inferred_type = infer_type(klass)
+
+ array_of = type.is_a?(Array) ? "array_of_" : ""
+ send("read_#{array_of}#{inferred_type}", value, type)
+ end
+
+ def read_unicode_string(value, type)
+ # Return as-is
+ value
+ end
+
+ def read_char(value, type)
+ # Convert to a boolean
+ !(value == "0")
+ end
+
+ def read_ushort(value, type)
+ value.to_i
+ end
+
+ def read_uint(value, type)
+ value.to_i
+ end
+
+ def read_ulong(value, type)
+ value.to_i
+ end
+
+ def read_int(value, type)
+ value.to_i
+ end
+
+ def read_long(value, type)
+ value.to_i
+ end
+
+ def read_enum(value, type)