Skip to content
Jimmy H edited this page Apr 23, 2023 · 24 revisions

Traditionally with pointers you’ll have a method, like attach_function :GetForegroundWindow, [ ], :pointer What this returns to you is a pointer that, probably, some other library is managing. You don’t need to release this pointer or anything.

To create your own pointer, do something like ptr = MemoryPointer.new(4) 4 bytes worth of memory It will be freed when the pointer object is GC’ed or you call ptr.free.

ptr.address is the address it points at. To copy some other pointer, it’s another_ptr = ptr

A pointer is merely a Fixnum that holds a native memory address. Think of “Pointer” as “fixnum with methods to read/write the native memory at the address”.

Handing off memory to external libraries

Some situations will require allocating native memory and handing off that buffer to an external library. The external library then handles the lifecycle of that buffer including eventually freeing it.

Wrap libc and use its malloc() and free() functions to allocate and free native memory.

module LibC
  extend FFI::Library
  ffi_lib FFI::Library::LIBC
  
  # memory allocators
  attach_function :malloc, [:size_t], :pointer
  attach_function :calloc, [:size_t], :pointer
  attach_function :valloc, [:size_t], :pointer
  attach_function :realloc, [:pointer, :size_t], :pointer
  attach_function :free, [:pointer], :void
  
  # memory movers
  attach_function :memcpy, [:pointer, :pointer, :size_t], :pointer
  attach_function :bcopy, [:pointer, :pointer, :size_t], :void
  
end # module LibC

In the Ruby code, calls to these functions will return instances of FFI::Pointer. Use the methods defined on FFI::Pointer to move data from Ruby memory to native memory.

foo = "a Ruby string"
bar = 3.14159
baz = [1, 2, 3, 4, 5]

buffer1 = LibC.malloc foo.size
buffer1.write_string foo

buffer2 = LibC.malloc bar.size
buffer2.write_float bar

# all of the array elements need to be the same type
# meaning you can't mix ints, floats, strings, etc.
buffer3 = LibC.malloc(baz.first.size * baz.size)
buffer3.write_array_of_int baz

MemoryPointer

The FFI::MemoryPointer class allocates native memory with automatic garbage collection as a sweetener. When a MemoryPointer goes out of scope, the memory is freed up as part of the garbage collection process.

The MemoryPointer constructor takes 3 arguments: size, count and clear. The size argument is a symbol type that determines the number of bytes to allocate. The size argument can also be any object that responds to #size in which case it will allocate that specific number of bytes. The count argument is a multiplier for size; it will allocate size * count bytes of memory. Lastly, the clear argument tells the memory allocator to zero/initialize the memory when true, skip initialization when false.

The block form of MemoryPointer is also useful for automatically freeing the pointer when finished, as follows:

baz = [ 5, 2, 88, 92, 32, 1291 ]
FFI::MemoryPointer.new(:int, baz.size) do |p|
  p.write_array_of_int(baz)
  C.DoSomethingWithArrayOfInt(p)
end

String pointer

a pointer into a Ruby string:

FFI::MemoryPointer.from_string('some string')

Fresh Memory

Some C libraries will return you a pointer to “fresh” memory. That means you’re responsible for freeing that memory after you’re finished with it. Such a library (“foo”) might have the following interface:

// Encodes data of length data_size. The caller is responsible for destroying
// the return value using foo_free() when finished with it.
char * foo_encode (const byte *data, size_t data_size);

// Frees a pointer returned by foo_encode().
void foo_free (char * data);

So foo_encode() takes a pointer to some data, and a length of that data. It’ll do s

A very basic way to release the memory after being done with it this is:

module Foo
  # Module with native functions of libfoo.
  module FFI
    extend FFI::Library
    ffi_lib 'libfoo'
    attach_function :foo_encode, [:pointer, :size_t], :pointer # returns fresh memory
    attach_function :foo_free, [ :pointer ], :void             # frees memory
  end
end

# ...

begin
  p_input = FFI::MemoryPointer.from_string("input")
  p_out = Foo::FFI.foo_encode(p_input, p_input.size)

  # with p_out, we can:
  #   * get a Ruby string from it, when needed:
  #       puts p_out.read_string
  #   * pass it to another C function
  #   * use it to free the memory

ensure # make sure this block is called, no matter what

  # free it, if p_out still holds a pointer
  Foo::FFI.free(p_out) if p_out 
end

Pretty straight-forward, but not very clean. We’re explicitly freeing the memory here, and we had to put it in an ensure block to be sure it’ll be freed. But it’s a bit cumbersome to do it this way. In Ruby, we’re not used to worry about these things, because there’s the Garbage Collector.

What if we want to keep the pointer around for longer, maybe to pass it to another C function later (outside of this block)? We want to be sure that the memory eventually gets freed, to avoid a memory leak. This should happen when our reference to it (like p_out) goes out of scope, because that’s the point where we won’t have access to that memory anymore.

To do this, we can wrap our pointer in an instance of FFI::AutoPointer along with our custom release strategy as a Method object:

module Foo
  # Module with native functions of libfoo.
  module FFI
    extend FFI::Library
    ffi_lib 'libfoo'
    attach_function :foo_encode, [:pointer, :size_t], :pointer # returns fresh memory
    attach_function :foo_free, [ :pointer ], :void             # frees memory
  end
end

# ...

begin
  p_input = FFI::MemoryPointer.from_string("input")

  # wrap result in a Foo::FFI::AutoPointer
  p_out = FFI::AutoPointer.new(Foo::FFI.foo_encode(p_input, p_input.size),
            Foo::FFI.method(:foo_free))

  # ...
end

# p_input is out of scope here, so it gets GC'd soon ...

As you can see, we’re not explicitly freeing the pointer here. There’s no need to manually free it here. It’ll be freed as soon as p_input goes out of scope and is GC’d.

As an alternative, we could have inherited from FFI::AutoPointer and specified our custom release strategy in its release class method:

module Foo
  module FFI
    # Used to automatically free memory using the foo_free() function.
    class AutoPointer < ::FFI::AutoPointer
      # This method will be called by FFI::AutoPointer::DefaultReleaser.
      def self.release(ptr)
        FFI.foo_free(ptr)
      end
    end
  end
end

# ...
p_out = Foo::FFI::AutoPointer.new(p_out)

Whatever suits your needs. But providing a Method object is the preferred way.

Fresh Strings

If you want to access the memory as a String (every time), you can use :strptr as a convenience:

module Foo
  # Module with native functions of libfoo.
  module FFI
    extend FFI::Library
    ffi_lib 'libfoo'
    attach_function :foo_encode, [:pointer, :size_t], :strptr # return String AND Pointer
    attach_function :foo_free, [ :pointer ], :void
  end
end

# ...

p_input = FFI::MemoryPointer.from_string("input")

# call foo_encode() and get a String and a Pointer back
string_out, p_out = Foo::FFI.foo_encode(p_input, p_input.size)

# wrap pointer in a Foo::FFI::AutoPointer
p_out = FFI::AutoPointer.new(p_out, Foo::FFI.method(:foo_free))

puts "encoded string: #{string_out}"

# ...

This way, you don’t have to call #read_string before having access to the Ruby string.

Beware that the returned String might have the wrong encoding set. Use String#force_encoding. See Binary Data as well.

Passing by reference

Since C can only return 1 value, most C APIs take pointers to output locations which they will write to.
For an example, let’s call the C-function:

void set_value(int *x) {
  *x = (*x + 1) % 31337;
}

Since this function treats int *x as equivalent to int x[1], this situation can be handled by defining a 1-element array (as in the last section) or, alternately by defining a 1-element structure,

class IntPtr < FFI::Struct
    layout  :value, :int
end
module ValueSetter
    extend FFI::Library
    ffi_lib '/usr/local/lib/set_value.so'

    attach_function :set_value, [IntPtr], :void
end

xptr = IntPtr.new
xptr[:value] = 42
ValueSetter::set_value(xptr)
puts xptr[:value]
Clone this wiki locally