tags:

views:

474

answers:

2

I'm having some beginner problems setting an FFI struct in Ruby. What I want to do is pass a pointer to a C string by setting a string property in an FFI::Struct object:

class SpSessionConfig < FFI::Struct
  layout :api_version,          :int,
           :cache_location,       :string,
           :settings_location,    :string,
           :application_key,      :pointer,
           :application_key_size, :int,
           :user_agent,           :string,
           :sp_session_callbacks, :pointer,
           :user_data,            :pointer 
  end
end


sessionConf = SpotifyLibrary::SpSessionConfig.new() 
puts sessionConf # => '#<SpotifyLibrary::SpSessionConfig:0x9acc00c>'

sessionConf[:api_version] = 1
puts "Api Version: #{sessionConf[:api_version]}"

myTempDir = "tmp"
sessionConf[:cache_location] = myTempDir # !Error!

But when I run the code I get this error:

jukebox.rb:44:in `[]=': Cannot set :string fields (ArgumentError)
from jukebox.rb:44:in `<main>'

So I don't really know where to go from here.

Also, if you know of any good documtation or tutorials on this subject please leave a response! So far I have found the wiki documentation on Project Kenai very useful but the more the merrier!

Thanks!

I have tried to declare the string data members as [:char, 5] but that gives another error:

jukebox.rb:44:in `put': put not supported for FFI::StructLayoutBuilder::ArrayField_Signed8_3 (ArgumentError)
    from jukebox.rb:44:in `[]='
    from jukebox.rb:44:in `<main>

There is a good suggestion to try out the memory pointer type and I will try that after work today.

+1  A: 

FFI automatically rejects setting strings. Try changing it from :string to :char_array, as mentioned on this page:

:char_array - used ONLY in a struct layout where struct has a C-style string (char []) as a member

If that doesn't work, you're going to have to use a :pointer and convert it back into a string. It's not well documented, but MemoryPointer has a bunch of available functions, such as write_string, that should help.

Pesto
Thanks for the information, it really set me on the right direction. I have tried the :char array and it doesnt seem to work in the struct declaration, please see edited question.
mikelong
I think that your suggestion to use a memory pointer is right on the money, I'll give it a try today after work and update here. Thanks for your help.
mikelong
A: 

So thanks to the answer from Pesto (accepted) I have found a solution. write_string returns early if there is a zero byte in the buffer (follows c-string semantics). Here is the code for anyone who might stumble onto this problem in the future.

# Open my application key file and store it in a byte array
appkeyfile = File.read("spotify_appkey.key")

# get the number of bytes in the key
bytecount = appkeyfile.unpack("C*").size

# create a pointer to memory and write the file to it
appkeypointer = FFI::MemoryPointer.new(:char, bytecount)
appkeypointer.put_bytes(0, appkeyfile, 0, bytecount)
mikelong