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
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.
-
#ffi_interface ⇒ Object
Instantiates the FFI class with the given pointer and caches the result for later.
-
#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.
-
#valid? ⇒ Boolean
This checks if the interface pointer was valid.
-
#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.
11 12 13 14 15 16 17 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 11 def initialize(interface, lib_base, pointer) super(interface, lib_base) # Store the pointer which is used later to instantiate the actual # structure. We don't instantiate here to save some CPU cycles. @pointer = pointer 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.
96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 96 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.
57 58 59 60 61 62 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 57 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.
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 72 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} #{spec.inspect} #{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.
274 275 276 277 278 279 280 281 282 283 284 285 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 274 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.
294 295 296 297 298 299 300 301 302 303 304 305 306 307 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 294 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.
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 115 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, 0x8007_0057 => Exceptions::InvalidArgException } map[code] || Exceptions::COMException 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`
36 37 38 39 40 41 42 43 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 36 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::Util.versioned_interface(klass_name) end |
#ffi_interface ⇒ Object
Instantiates the FFI class with the given pointer and caches the result for later.
26 27 28 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 26 def ffi_interface @ffi_interface ||= ffi_class.new(@pointer) 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
311 312 313 314 315 316 317 318 319 320 321 322 323 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 311 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
379 380 381 382 383 384 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 379 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
389 390 391 392 393 394 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 389 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
399 400 401 402 403 404 405 406 407 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 399 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
371 372 373 374 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 371 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
360 361 362 363 364 365 366 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 360 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.
46 47 48 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 46 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.
351 352 353 354 355 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 351 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
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 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 148 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 # Build a pointer to an array of values result = ::FFI::MemoryPointer.new(c_type, data.length) adder = result.method("put_#{c_type}") data.each_with_index do |single, index| value = [] single_type_to_arg([single], item[0], value) adder.call(index, value.first) end results << result 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.
139 140 141 142 143 144 145 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 139 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
329 330 331 332 333 334 335 336 337 338 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 329 def string_to_utf16(string) return nil if string.nil? # Note that the string below must be duplicated as of FFI 1.0.8 # since this modifies the string in-place and causes a memory leak # otherwise. ptr = pointer_for_type(:pointer) lib.xpcom[:pfnUtf8ToUtf16].call(string.dup, ptr) ptr.read_pointer() end |
#utf16_to_string(pointer) ⇒ Object
Converts a UTF16 string to UTF8
341 342 343 344 345 346 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 341 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 |
#valid? ⇒ Boolean
This checks if the interface pointer was valid.
20 21 22 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 20 def valid? !ffi_interface.nil? 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.
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 228 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)) # Skip 2: size param + pointer i += 2 else return_values << dereference_pointer(formal[i], spec[1]) # Skip 1: Pointer i += 1 end elsif spec.is_a?(Array) && spec.length == 1 # This is an array argument, meaning it takes two arguments: # one for size and one is a pointer to the array items. Therefore, # skip two items. i += 2 else i += 1 end 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.
51 52 53 |
# File 'lib/virtualbox/com/implementer/ffi.rb', line 51 def write_property(name, value, opts) call_vtbl_function("set_#{name}".to_sym, [opts[:value_type]], [value]) end |