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

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

Instance Attribute Summary

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.

Parameters:

  • inteface (VirtualBox::COM::AbstractInteface)
  • pointer (FFI::Pointer)


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.

Parameters:

  • pointer (FFI::MemoryPointer)
  • type (Symbol)

    The type of the pointer

Returns:

  • (Object)

    The value of the dereferenced pointer



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.

Parameters:

  • pointer (FFI::MemoryPointer)
  • type (Symbol)

    The type of the pointer

  • length (Fixnum)

    The length of the array

Returns:

  • (Array<Object>)


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.

Parameters:

  • code (Fixnum)

    Result code

Returns:

  • (Class)


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_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`

Returns:

  • (Class)


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_interfaceObject

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

Returns:

  • (Array<Symbol>)


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

Returns:

  • (Array<::FFI::Struct>)


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

Returns:

  • (Array<String>)


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

Returns:

  • (Symbol)


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

Returns:

  • (::FFI::Struct)


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.

Returns:

  • (String)


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.

Returns:

  • (Array)


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

Parameters:

  • Ruby (String)

    String object

Returns:

  • (::FFI::Pointer)


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.

Returns:

  • (Boolean)


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.

Parameters:

  • specs (Array)

    The parameter spec for the function

  • formal (Array)

    The formal parameter list



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