Class: VirtualBox::COM::Implementer::FFI
- Inherits:
-
Base
- Object
- AbstractImplementer
- Base
- VirtualBox::COM::Implementer::FFI
- Defined in:
- lib/virtualbox/com/implementer/ffi.rb
Instance Attribute Summary collapse
-
#ffi_interface ⇒ Object
readonly
Returns the value of attribute ffi_interface.
Attributes inherited from AbstractImplementer
Instance Method Summary collapse
-
#call_and_check(function, *args) ⇒ Object
Checks the result of a method call for an error, and if an error occurs, then raises an exception.
-
#call_function(name, args, opts) ⇒ Object
Calls a function from the interface with the given name and args.
-
#call_vtbl_function(name, spec, args = []) ⇒ Object
Calls a function on the vtbl of the FFI struct.
-
#dereference_pointer(pointer, type) ⇒ Object
Dereferences a pointer with a given type into a proper Ruby object.
-
#dereference_pointer_array(pointer, type, length) ⇒ Array<Object>
Dereferences an array out of a pointer into an array of proper Ruby objects.
-
#exception_map(code) ⇒ Class
Maps a result code to an exception.
-
#ffi_class ⇒ Class
Gets the FFI struct class associated with the interface.
-
#initialize(interface, lib_base, pointer) ⇒ FFI
constructor
Initializes the FFI implementer which takes an AbstractInterface instant and FFI pointer and initializes everything required to communicate with that interface via FFI.
-
#pointer_for_type(type) ⇒ Object
Converts a symbol type into a MemoryPointer and yield a block with the pointer, the C type, and the FFI type.
-
#read_array_of_enum(ptr, type, length) ⇒ Array<Symbol>
Reads an array of enums.
-
#read_array_of_interface(ptr, type, length) ⇒ Array<::FFI::Struct>
Reads an array of structs from a pointer.
-
#read_array_of_unicode_string(ptr, type, length) ⇒ Array<String>
Reads an array of strings from a pointer.
-
#read_enum(ptr, original_type) ⇒ Symbol
Reads an enum.
-
#read_interface(ptr, original_type) ⇒ ::FFI::Struct
Reads an interface from the pointer.
-
#read_property(name, opts) ⇒ Object
Reads a property from the interface with the given name.
-
#read_unicode_string(ptr, original_type = nil) ⇒ String
Reads a unicode string value from a pointer to that value.
-
#single_type_to_arg(args, item, results) ⇒ Object
Converts a single type and args list to the proper formal args list.
-
#spec_to_args(spec, args = []) ⇒ Array
Converts a function spec to a proper argument list with the given arguments.
-
#string_to_utf16(string) ⇒ ::FFI::Pointer
Converts a ruby string to a UTF16 string.
-
#utf16_to_string(pointer) ⇒ Object
Converts a UTF16 string to UTF8.
-
#values_from_formal_args(specs, formal) ⇒ Object
Takes a spec and a formal parameter list and returns the output from a function, properly dereferencing any output pointers.
-
#write_property(name, value, opts) ⇒ Object
Writes a property to the interface with the given name and value.
Methods inherited from Base
#infer_type, #interface_klass, #ruby_version
Methods included from Logger
included, #logger, #logger_output=
Constructor Details
#initialize(interface, lib_base, pointer) ⇒ FFI
Initializes the FFI implementer which takes an AbstractInterface instant and FFI pointer and initializes everything required to communicate with that interface via FFI.
13 14 15 16 17 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 13 def initialize(interface, lib_base, pointer) super(interface, lib_base) @ffi_interface = ffi_class.new(pointer) end |
Instance Attribute Details
#ffi_interface ⇒ Object (readonly)
Returns the value of attribute ffi_interface.
5 6 7 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 5 def ffi_interface @ffi_interface end |
Instance Method Details
#call_and_check(function, *args) ⇒ Object
Checks the result of a method call for an error, and if an error occurs, then raises an exception.
85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 85 def call_and_check(function, *args) result = ffi_interface.vtbl[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 |
#call_function(name, args, opts) ⇒ Object
Calls a function from the interface with the given name and args. This method is called from the AbstractInterface.
46 47 48 49 50 51 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 46 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 |
#call_vtbl_function(name, spec, args = []) ⇒ Object
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.
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 61 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. logger.debug("FFI call: #{name} #{args.inspect} #{formal_args.inspect}") call_and_check(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) result = values_from_formal_args(spec, formal_args) logger.debug(" = #{result.inspect}") result end |
#dereference_pointer(pointer, type) ⇒ Object
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.
247 248 249 250 251 252 253 254 255 256 257 258 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 247 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 |
#dereference_pointer_array(pointer, type, length) ⇒ Array<Object>
Dereferences an array out of a pointer into an array of proper Ruby objects.
267 268 269 270 271 272 273 274 275 276 277 278 279 280 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 267 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 |
#exception_map(code) ⇒ Class
Maps a result code to an exception. If no mapping currently exists, then a regular Exceptions::FFIException is returned.
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 104 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 |
#ffi_class ⇒ Class
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`
25 26 27 28 29 30 31 32 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 25 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(::VirtualBox::COM::Util.version_const).const_get(klass_name) end |
#pointer_for_type(type) ⇒ Object
Converts a symbol type into a MemoryPointer and yield a block with the pointer, the C type, and the FFI type
284 285 286 287 288 289 290 291 292 293 294 295 296 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 284 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 |
#read_array_of_enum(ptr, type, length) ⇒ Array<Symbol>
Reads an array of enums
349 350 351 352 353 354 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 349 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 |
#read_array_of_interface(ptr, type, length) ⇒ Array<::FFI::Struct>
Reads an array of structs from a pointer
359 360 361 362 363 364 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 359 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 |
#read_array_of_unicode_string(ptr, type, length) ⇒ Array<String>
Reads an array of strings from a pointer
369 370 371 372 373 374 375 376 377 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 369 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 |
#read_enum(ptr, original_type) ⇒ Symbol
Reads an enum
341 342 343 344 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 341 def read_enum(ptr, original_type) klass = interface_klass(original_type) klass[ptr.get_uint(0)] end |
#read_interface(ptr, original_type) ⇒ ::FFI::Struct
Reads an interface from the pointer
330 331 332 333 334 335 336 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 330 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 |
#read_property(name, opts) ⇒ Object
Reads a property from the interface with the given name.
35 36 37 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 35 def read_property(name, opts) call_vtbl_function("get_#{name}".to_sym, [[:out, opts[:value_type]]]) end |
#read_unicode_string(ptr, original_type = nil) ⇒ String
Reads a unicode string value from a pointer to that value.
321 322 323 324 325 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 321 def read_unicode_string(ptr, original_type=nil) address = ptr.get_pointer(0) return "" if address.null? utf16_to_string(address) end |
#single_type_to_arg(args, item, results) ⇒ Object
Converts a single type and args list to the proper formal args list
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 136 def single_type_to_arg(args, item, results) 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 results << pointer_for_type(T_UINT32) results << pointer_for_type(item[1][0]) else results << pointer_for_type(item[1]) end elsif item.is_a?(Array) && item.length == 1 # Array argument data = args.shift # First add the length of the array results << data.length # Create the array c_type, type = infer_type(item.first) # If its a regular type (int, bool, etc.) then just make it an # array of that if type != :interface results << data.inject([]) do |converted_data, single| single_type_to_arg([single], item[0], converted_data) end else # Then convert the rest into a raw MemoryPointer array = ::FFI::MemoryPointer.new(:pointer, data.length) data.each_with_index do |datum, i| converted = [] single_type_to_arg([datum], item.first, converted) array[i].put_pointer(0, converted.first) end results << array end elsif item == WSTRING # We have to convert the arg to a unicode string results << string_to_utf16(args.shift) elsif item == T_BOOL results << (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 results << 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 results << interface.index(args.shift.to_sym) end else # Simply replace spec item with next item in args # list results << args.shift end end |
#spec_to_args(spec, args = []) ⇒ Array
Converts a function spec to a proper argument list with the given arguments.
127 128 129 130 131 132 133 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 127 def spec_to_args(spec, args=[]) args = args.dup results = spec.inject([]) do |results, item| single_type_to_arg(args, item, results) end end |
#string_to_utf16(string) ⇒ ::FFI::Pointer
Converts a ruby string to a UTF16 string
302 303 304 305 306 307 308 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 302 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 |
#utf16_to_string(pointer) ⇒ Object
Converts a UTF16 string to UTF8
311 312 313 314 315 316 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 311 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 |
#values_from_formal_args(specs, formal) ⇒ Object
Takes a spec and a formal parameter list and returns the output from a function, properly dereferencing any output pointers.
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 209 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 |
#write_property(name, value, opts) ⇒ Object
Writes a property to the interface with the given name and value.
40 41 42 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 40 def write_property(name, value, opts) call_vtbl_function("set_#{name}".to_sym, [opts[:value_type]], [value]) end |