4

I am trying to define the Tinn C lib in Ruby using fiddle, but it is giving me an error with the struct

Tinn.h

typedef struct
{
    // All the weights.
    float* w;
    // Hidden to output layer weights.
    float* x;
    // Biases.
    float* b;
    // Hidden layer.
    float* h;
    // Output layer.
    float* o;
    // Number of biases - always two - Tinn only supports a single hidden layer.
    int nb;
    // Number of weights.
    int nw;
    // Number of inputs.
    int nips;
    // Number of hidden neurons.
    int nhid;
    // Number of outputs.
    int nops;
}
Tinn;

float* xtpredict(Tinn, const float* in);

float xttrain(Tinn, const float* in, const float* tg, float rate);

Tinn xtbuild(int nips, int nhid, int nops);

Ruby fiddle

module Tinn
    extend Fiddle::Importer
    dlload './tinn.so'

    Tinn = struct [
        # All the weights.
        'float* w',
        # Hidden to output layer weights.
        'float* x',
        # Biases.
        'float* b',
        # Hidden layer.
        'float* h',
        # Output layer.
        'float* o',
        # Number of biases - always two - Tinn only supports a single hidden layer.
        'int nb',
        # Number of weights.
        'int nw',
        # Number of inputs.
        'int nips',
        # Number of hidden neurons.
        'int nhid',
        # Number of outputs.
        'int nops'
    ]

    extern 'float* xtpredict(Tinn, const float* in)'

    extern 'float xttrain(Tinn, const float* in, const float* tg, float rate)'

    extern 'Tinn xtbuild(int nips, int nhid, int nops)'
end

I am gettting an error as such

/home/arjun/.rbenv/versions/2.3.3/lib/ruby/2.3.0/fiddle/cparser.rb:177:in `parse_ctype': unknown type: Tinn (Fiddle::DLError)
    from /home/arjun/.rbenv/versions/2.3.3/lib/ruby/2.3.0/fiddle/cparser.rb:90:in `block in parse_signature'
    from /home/arjun/.rbenv/versions/2.3.3/lib/ruby/2.3.0/fiddle/cparser.rb:90:in `collect'
    from /home/arjun/.rbenv/versions/2.3.3/lib/ruby/2.3.0/fiddle/cparser.rb:90:in `parse_signature'
    from /home/arjun/.rbenv/versions/2.3.3/lib/ruby/2.3.0/fiddle/import.rb:163:in `extern'
    from rb_tinn.rb:31:in `<module:Tinn>'
    from rb_tinn.rb:4:in `<main>'

Line 31 points to the first function where we pass struct as an argument float* xtpredict(Tinn, const float* in)

I have already defined Tinn as a struct, but still it is giving the error.

arjun
  • 1,594
  • 16
  • 33
  • 1
    Fiddle is not able to interface with functions that pass structs. Only struct pointers. See here: https://stackoverflow.com/a/31135028/823617 – Casper Jun 20 '18 at 04:04
  • So I need to wrap it into a function? Separately, what about FFI, I did try that as well but I started getting segmentation faults. – arjun Jun 20 '18 at 09:52
  • I would wrap it into a C-function that accepts a struct pointer. Then interface to that one with Fiddle. FFI is very hard to wrap your head around. It's probably possible to do it with FFI, but it's not very well documented so it might be hard to make it work properly. – Casper Jun 20 '18 at 14:14

1 Answers1

3

Structs with Fiddle in Ruby are slightly misleading as being a direct analog of its C counterpart. Many of the features that make structs easy and user-friendly are lacking within its Ruby representation.

The main principle to keep in mind is that a Fiddle::CStruct is really nothing more than glorified Fiddle::Pointer, and is in fact a subclass of it. The main limitations are that you can only declare primitive types within it. Unlike in C languages where if you define a struct, you can then use it as a type within another struct, a return value, etc.

This can still be accomplished in Ruby, though the documentation is light on the issue. As I stated previously, bearing in mind that a Ruby struct derives from Fiddle::Pointer, you simply use void* in your signature declaration (or Fiddle::TYPE_VOIDP depending on whether or not you are yusing the CParser).

In your above example, once you have declared your struct, you would then define your method signature like this:

float* xtpredict(void* tinn, const float* in)

Replace your struct types with void* instead of the name of the type, and you can then pass the struct directly to it and get the desired behavior you would expect.

The same way goes for getting a struct returned from a method. Once again use the void* in the signature, which Ruby will then return a Fiddle::Pointer object to. This pointer contains the address to where the struct is in memory. Since structs take an address in their initialize method, use the returned pointer to initialize your struct, and it will be created at that location in memory.

ptr = getMyStruct
MyStruct.new(ptr.to_i)

Conversely, if memory serves me correct, I believe you can also use it like this:

ptr = getMyStruct
MyStruct.new(ptr.ptr)
ForeverZer0
  • 2,379
  • 1
  • 24
  • 32
  • That works! I have to still define a separate function to accommodate `xtbuild` which returns a struct. – arjun Jun 24 '18 at 08:10
  • It is the same concept, define the return as `void*`, and initialize your struct with that address. It will return `Fiddle::Pointer`, which you use as the pointer to your struct. It is not as intuitive as using a C language, but it does work out okay once you get used to the differences. You basically need to reduce every "type" to a primitive or a pointer to a primitive. A "struct" in Ruby is just a fancy pointer, that offers some additional functionality for named values, takes care of the offsets for you, etc. – ForeverZer0 Jun 24 '18 at 18:05
  • I expanded upon my original answer to clarify some things. – ForeverZer0 Jun 25 '18 at 02:06
  • I think I got an idea how to progress now. I had already made the changes for `xtpredict` as soon as you posted, I was having doubts on `xtbuild`, the last function. Let me check. Appreciate your help, though. – arjun Jun 25 '18 at 11:17
  • This is completely WRONG. See [this answer](https://stackoverflow.com/a/31135028/1186624). They are not using the ```FFI_TYPE_STRUCT``` at all in the [conversion.c](https://github.com/ruby/ruby/blob/v3_2_2/ext/fiddle/conversions.c), which is required as can be seen at [this](https://github.com/libffi/libffi/blob/v3.4.4/doc/libffi.texi#L410). – relent95 May 27 '23 at 04:59
  • @relent95 Nothing in the linked answer contradicts this one, it even confirms that it must be passed as reference. This correctly answers how to accomplish it from a Ruby script, not how a direct port of the native `libffi` ought to have done it. I agree it would have been simpler if the functionality was built-in to Ruby's Fiddle library. – ForeverZer0 May 28 '23 at 18:20
  • No, the OP asked how to pass a structure as an argument and return a structure(call by value), not how to pass a pointer to a structure and return a pointer to a structure. So, the correct answer is "It's impossible with Fiddle currently" in short. It can be solved by other methods, such as implementing a C extension or using [Ruby FFI](https://github.com/ffi/ffi/wiki/Structs). – relent95 May 29 '23 at 01:29
  • @relent95 I don't understand what the misunderstanding is, it is explicitly stated in the above answer that Fiddle structs must be handled as pointers, and then offers a solution on how to solve the problem, which is the the actual question. The fact that OP replied that it solved their problem is testament to this. – ForeverZer0 May 30 '23 at 08:53
  • The OP asked how to call [Tinn API](https://github.com/glouw/tinn/blob/master/Tinn.h#L32) with Fiddle. Your answer implies the OP should modify Tinn API and therefore its implementation. Do you really think it's a correct answer? – relent95 May 30 '23 at 12:23
  • @relent95 The OP asked how to call Tinn API with Fiddle. I answered how to call Tinn API with Fiddle (which OP found helpful). This exchange feels circular and argumentative at this point. – ForeverZer0 May 31 '23 at 17:41
  • I am doubtful whether you understand the mechanism of passing a structure by value. Check this test module - [test_fiddle_struct.rb](https://gist.github.com/relent95/ad9542d8d01cf0745489a817eec81721). – relent95 Jun 01 '23 at 02:22
  • I perfectly understand the mechanism. The linked example is nonsense, as it does not even implement what the answer suggests to accomplish it, and is just a repeat of OP's problem before the solution was provided. Try it again as the answer instructs, and see how it works. Fiddle::Pointer has a "size" property for a reason. – ForeverZer0 Jun 10 '23 at 22:42