ruby / fiddle

A libffi wrapper for Ruby.
BSD 2-Clause "Simplified" License
163 stars 36 forks source link

How to pass struct as argument of function #114

Open nerzh opened 2 years ago

nerzh commented 2 years ago

Hi, sorry for my english. I will show the problem with examples:

header.h

typedef struct {
    const char* content;
    uint32_t len;
} tc_string_data_t;

typedef struct tc_string_handle_t tc_string_handle_t;

tc_string_handle_t* tc_create_context(tc_string_data_t config);

tc_string_data_t tc_read_string(const tc_string_handle_t* handle);

void tc_destroy_string(const tc_string_handle_t* handle);

How its work in ruby with gem ffi

require "json"
require "ffi"

module Binding
    class TcStringDataT < FFI::Struct
      layout :content, :pointer,
        :len, :uint32
    end
end

module Binding
    extend FFI::Library
    ffi_lib "/path_to_lib"

    def self.setup_bindings
      attach_function :tc_create_context, [TcStringDataT.by_value], :pointer

      attach_function :tc_read_string, [:pointer], TcStringDataT.by_value

      attach_function :tc_destroy_string, [:pointer], :void
    end
end

config_json = {network: {endpoints: ["http://some_url"]}}.to_json

# make struct
data_struct = Binding::TcStringDataT.new
data_struct[:content] = FFI::MemoryPointer.from_string(config_json)
data_struct[:len] = config_json.bytesize

# create context
string_ref = Binding.tc_create_context(data_struct)

# read context response
result_data_struct = Binding.tc_read_string(string_ref)
result_string = result_data_struct[:content].read_string(result_data_struct[:len])

# destroy ref
Binding.tc_destroy_string(string_ref)

ffi gem works, but sometimes i get segmentation fault :(

So I decided to try to do it with fiddle !

But how ? How to do it with fiddle ? I climbed the entire Internet. I haven't found a single example of how to pass a struct to a function. There are only very few examples of how to pass ordinary primitives and not a single example of how to pass a structure. I have tried many options and still have not been able to achieve a result. Please help me !!! Write an example of calling these functions and passing arguments to them. Really looking forward to the answer.

Here is what I do with fidl:

require 'fiddle'
require 'fiddle/import'
require 'json'

module TestFiddle
  extend Fiddle::Importer
  extend Fiddle::CParser
  dlload('/lib_client.dylib')

  TcStringDataT = struct [
    'const char* content',
    'uint len'
  ]

  # tc_string_handle_t* tc_create_context(tc_string_data_t config);
  # extern 'TcStringDataT* tc_create_context(TcStringDataT config)' ## unknown type TcStringDataT
  extern 'TcStringDataT* tc_create_context(TcStringDataT* config)' ## OK

  # tc_string_data_t tc_read_string(const tc_string_handle_t* handle);
  # extern 'TcStringDataT tc_read_string(const TcStringDataT* handle)' ## unknown type TcStringDataT
  extern 'TcStringDataT* tc_read_string(const TcStringDataT* handle)' ## OK

  # void tc_destroy_string(const tc_string_handle_t* handle)
  # extern 'void tc_destroy_string(const TcStringDataT handle)' ## unknown type TcStringDataT
  extern 'void tc_destroy_string(const TcStringDataT* handle)' ## OK
end

config_json = {network: {endpoints: ["http://some_url"]}}.to_json

struct = TestFiddle::TcStringDataT.malloc
struct.content = Fiddle::Pointer.to_ptr(config_json)
struct.len = config_json.bytesize

# How to pass this struct ???
ctx_ptr = TestFiddle.tc_create_context(struct)  ## ERROR ERROR ERROR, many many zeros and ones

string_ptr = TestFiddle.tc_read_string(ctx_ptr)
struct = Test::TcStringDataT.new(string_ptr)
p struct.content.to_s

please help me with this πŸ™

nerzh commented 2 years ago

Hi, maybe it is impossible 🫣 ? Then please tell me about it, then I will not try to make the code work πŸ•°

kou commented 2 years ago

It's impossible for now...

nerzh commented 2 years ago

It's impossible for now...

oh, that's sad to hear πŸ˜” Thanks for your answer 🀝

Aesthetikx commented 11 months ago

I also ran into this, trying to wrap a library that has a function that requires a struct passed by value. I could get around it with some type erasure, if only it was shorter -- a void* is 8 bytes and the struct is 16. Is this something that is not supported by libffi, or just not by fiddle?

kou commented 11 months ago

It's just not implemented yet. We can do this by using FFI_TYPE_STRUCT.

gr33n7007h commented 3 weeks ago

It's just not implemented yet. We can do this by using FFI_TYPE_STRUCT.

Be awesome if this could be implemented in a future version, for real ;)