Class: VirtualBox::COM::Implementer::FFI

Inherits:
Base show all
Defined in:
lib/virtualbox/com/implementer/ffi.rb

Instance Attribute Summary collapse

Attributes inherited from AbstractImplementer

#interface, #lib

Instance Method Summary collapse

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_interfaceObject (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_classClass

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