Accessing C structures in Ruby


#1

(I’ve already looked at Swig, btw. I’d like to do this one by hand.)

I have the following:

typedef struct {
double x;
double y;
double z;
} VelocityRecord;

typedef struct {
double x;
double y;
double z;
} PositionRecord;

typedef struct {
int id;
int type;
PositionRecord position;
VelocityRecord velocity;
} Player;

Player Players[10];

The number of Players is fixed ahead of time. I want to be able to
access that data from Ruby. Ideally, something like:

module Simulation
attr_accessor :players # Other stuff will be here
class Player
class VelocityRecord
attr_accessor :x, :y :z
end
class PositionRecord
attr_accessor :x, :y, :z
end
end
end


#2

On 12/12/05, Joe Van D. removed_email_address@domain.invalid wrote:

typedef struct {
} Player;
class VelocityRecord
attr_accessor :x, :y :z
end
class PositionRecord
attr_accessor :x, :y, :z
end
end
end

Hm, on second thought, it’s probably a lot easier to do:

class Player
attr_reader :x_position, :y_position, :z_position, :x_velocity,
:y_velocity, :z_velocity
end

So, maybe something like this?

void Init_Simulation()
{
rb_define_module(mSimulation, “Simulation”);
rb_define_class_under(cPlayer, “Player”);

rb_define_method(cPlayer, “x_position”, get_x_position, 0);
rb_define_method(cPlayer, “y_position”, get_y_position, 0);
rb_define_method(cPlayer, “z_position”, get_z_position, 0);
rb_define_method(cPlayer, “x_velocity”, get_x_velocity, 0);
rb_define_method(cPlayer, “y_velocity”, get_y_velocity, 0);
rb_define_method(cPlayer, “z_velocity”, get_z_velocity, 0);
}


#3

Joe Van D. wrote:

(I’ve already looked at Swig, btw. I’d like to do this one by hand.)

I have the following:

typedef struct {
double x;
double y;
double z;
} VelocityRecord;

typedef struct {
double x;
double y;
double z;
} PositionRecord;

typedef struct {
int id;
int type;
PositionRecord position;
VelocityRecord velocity;
} Player;

Player Players[10];

The number of Players is fixed ahead of time. I want to be able to
access that data from Ruby. Ideally, something like:

module Simulation
attr_accessor :players # Other stuff will be here
class Player
class VelocityRecord
attr_accessor :x, :y :z
end
class PositionRecord
attr_accessor :x, :y, :z
end
end
end

My typical recommendation is to just wrap a pointer to
the struct and pass that around. Then, for each method
just extract the pointer, access the correct field and
generate a VALUE out of that (inside the function that
corresponds to the method).

E


#4

On 12/12/05, Eero S. removed_email_address@domain.invalid wrote:

VelocityRecord velocity;
class Player
the struct and pass that around. Then, for each method
just extract the pointer, access the correct field and
generate a VALUE out of that (inside the function that
corresponds to the method).

Can you share more details? Is that essentially what I’ve done in my
later posts on this?


#5

On 12/12/05, Joe Van D. removed_email_address@domain.invalid wrote:

VelocityRecord velocity;
class Player

rb_define_class_under(cPlayer, “Player”);

rb_define_method(cPlayer, “x_position”, get_x_position, 0);
rb_define_method(cPlayer, “y_position”, get_y_position, 0);
rb_define_method(cPlayer, “z_position”, get_z_position, 0);
rb_define_method(cPlayer, “x_velocity”, get_x_velocity, 0);
rb_define_method(cPlayer, “y_velocity”, get_y_velocity, 0);
rb_define_method(cPlayer, “z_velocity”, get_z_velocity, 0);
}

So, I have a bunch of functions that look like this now:

VALUE get_player_x_pos(VALUE self)
{
Player* p;
Data_Get_Struct(self, Player, p);
rb_float_new(p->position.x);
}

That the way to go?


#6

On 12/12/05, Joe Van D. removed_email_address@domain.invalid wrote:

} VelocityRecord;
PositionRecord position;
attr_accessor :players # Other stuff will be here
My typical recommendation is to just wrap a pointer to
the struct and pass that around. Then, for each method
just extract the pointer, access the correct field and
generate a VALUE out of that (inside the function that
corresponds to the method).

Can you share more details? Is that essentially what I’ve done in my
later posts on this?

Here’s what I have so far. Seems to work well:

#include “ruby.h”
#include “simulation.h”

VALUE get_frame_count(VALUE self) { return INT2NUM(HIFEN2_Count); }
VALUE get_mission_time(VALUE self) { return INT2NUM(HIFEN2_Mission); }
VALUE get_max_players(VALUE self) { return INT2NUM(MAX_PLAYERS); }

VALUE player_new(VALUE self, VALUE rb_i)
{
int i = NUM2INT(rb_i);
return Data_Wrap_Struct(self, NULL, NULL, &Players[i]);
}

Player* get_player(VALUE self)
{
Player *p;
Data_Get_Struct(self, Player, p);
return p;
}

VALUE get_player_id(VALUE self) { return INT2NUM(get_player(self)->id);
}

VALUE get_player_x_pos(VALUE self) { return
rb_float_new(get_player(self)->position.x); }
VALUE get_player_y_pos(VALUE self) { return
rb_float_new(get_player(self)->position.y); }
VALUE get_player_z_pos(VALUE self) { return
rb_float_new(get_player(self)->position.z); }

VALUE get_player_x_vel(VALUE self) { return
rb_float_new(get_player(self)->velocity.x); }
VALUE get_player_y_vel(VALUE self) { return
rb_float_new(get_player(self)->velocity.y); }
VALUE get_player_z_vel(VALUE self) { return
rb_float_new(get_player(self)->velocity.z); }

VALUE cSimulation;
VALUE cPlayer;

void Init_Sim()
{
cSimulation = rb_define_class(“Simulation”, rb_cObject);
rb_define_method(cSimulation, “frame_count”, get_frame_count, 0);
rb_define_method(cSimulation, “mission_time”, get_mission_time, 0);
rb_define_method(cSimulation, “max_players”, get_max_players, 0);

cPlayer = rb_define_class(“Player”, rb_cObject);
rb_define_singleton_method(cPlayer, “new”, player_new, 1);
rb_define_method(cPlayer, “player_id”, get_player_id, 0);

rb_define_method(cPlayer, “x_position”, get_player_x_pos, 0);
rb_define_method(cPlayer, “y_position”, get_player_y_pos, 0);
rb_define_method(cPlayer, “z_position”, get_player_z_pos, 0);

rb_define_method(cPlayer, “x_velocity”, get_player_x_vel, 0);
rb_define_method(cPlayer, “y_velocity”, get_player_y_vel, 0);
rb_define_method(cPlayer, “z_velocity”, get_player_z_vel, 0);
}


#7

On 2005.12.13 14:14, Joe Van D. removed_email_address@domain.invalid wrote:

double z;
int type;
module Simulation

return Data_Wrap_Struct(self, NULL, NULL, &Players[i]);

rb_float_new(get_player(self)->velocity.y); }
rb_define_method(cSimulation, “mission_time”, get_mission_time, 0);
rb_define_method(cPlayer, “x_velocity”, get_player_x_vel, 0);
rb_define_method(cPlayer, “y_velocity”, get_player_y_vel, 0);
rb_define_method(cPlayer, “z_velocity”, get_player_z_vel, 0);
}

Yep, that looks fine to me. There are certainly other ways but aside
from some repetitiveness, this is easy and versatile (particularly if
you need to pass data to C libraries as well). If you have lots and
lots of functions like that, you could even do with a little #define.

E


#8

Eero S. removed_email_address@domain.invalid wrote:

Yep, that looks fine to me. There are certainly other ways but aside
from some repetitiveness, this is easy and versatile (particularly if
you need to pass data to C libraries as well). If you have lots and
lots of functions like that, you could even do with a little #define.

http://redshift.sourceforge.net/cgen/ might be helpful (not used it
myself, but this seems like a prime use case)

martin


#9

Martin DeMello wrote:

martin

I would have mentioned it but the OP wanted to do things the hard way :slight_smile:

Cgen takes care of accessors (including type checking/conversion,
marshalling, mark/free when accessors are typed to refer to objects),
and also manages inheritance of structure members in parallel with the
ruby inheritance hierarchy. It’s got a nice wrapper around
rb_parse_args. It’s also good if you want to dynamically generate the
extension in response to loaded code, user input, etc. I’ve been using
it for over 4 years now. The docs need work, though…

An example (simplified from
http://redshift.sourceforge.net/cgen/examples/complex.rb.html):

require ‘cgen/cshadow’

class MyComplex < Numeric
include CShadow
shadow_attr_accessor :re => “double re”, :im => “double im”
end

MyComplex.commit

z = MyComplex.new
z.re = 5
z.im = 1.3
p z

END

Output:

#<MyComplex:0x2b04630 im=1.3, re=5.0>

The full example also shows how to add methods written in C (but
embedded in your ruby source) to do abs() and in-place multiplication.

Running this example generates a directory with the following contents,
which is used to build the extension (all of this happens during the
commit call):

C:\ruby\prj\cgen\examples>ls -l MyComplex
total 220
-rw-rw-rw- 1 vjoel 0 45 2005-12-13 09:42 extconf.rb
-rw-rw-rw- 1 vjoel 0 150 2005-12-13 09:42 make.log
-rw-rw-rw- 1 vjoel 0 3619 2005-12-13 09:42 Makefile
-rw-rw-rw- 1 vjoel 0 25 2005-12-13 09:39 MyComplex-i386-mswin32.def
-rwxrwxrwx 1 vjoel 0 3589 2005-12-13 09:39 MyComplex.c
-rw-rw-rw- 1 vjoel 0 589 2005-12-13 09:40 MyComplex.exp
-rw-rw-rw- 1 vjoel 0 762 2005-12-13 09:39 MyComplex.h
-rw-rw-rw- 1 vjoel 0 1976 2005-12-13 09:40 MyComplex.lib
-rw-rw-rw- 1 vjoel 0 17259 2005-12-13 09:39 MyComplex.obj
-rw-rw-rw- 1 vjoel 0 91136 2005-12-13 09:40 MyComplex.pdb
-rw-rw-rw- 1 vjoel 0 20546 2005-12-13 09:40 MyComplex.so
-rw-rw-rw- 1 vjoel 0 53248 2005-12-13 09:39 vc60.pdb

Cgen is careful not to repeat all this work the second time you run the
same program–it writes files only if they have changed.

And just to give an idea of what functions it handles:

C:\ruby\prj\cgen\examples\MyComplex>grep “^\w” MyComplex.c
static void mark_MyComplex_Shadow(MyComplex_Shadow *shadow);
static void free_MyComplex_Shadow(MyComplex_Shadow *shadow);
VALUE module_MyComplex;
void Init_MyComplex(void)
static void mark_MyComplex_Shadow(MyComplex_Shadow *shadow)
static void free_MyComplex_Shadow(MyComplex_Shadow *shadow)
VALUE __dump__data_module_MyComplex_method(VALUE self)
VALUE __load__data_module_MyComplex_method(VALUE self, VALUE from_array)
VALUE im_module_MyComplex_method(VALUE self)
VALUE im_equals_module_MyComplex_method(int argc, VALUE *argv, VALUE
self)
VALUE re_module_MyComplex_method(VALUE self)
VALUE re_equals_module_MyComplex_method(int argc, VALUE *argv, VALUE
self)
VALUE new_module_MyComplex_singleton_method(int argc, VALUE *argv, VALUE
self)
VALUE alloc_func_module_MyComplex(VALUE klass)

Here’s an example (sample.rb) showing the argument parsing wrapper:

lib2.define_c_singleton_method(Point, :test).instance_eval {
c_array_args {
required :arg0, :arg1
optional :arg2, :arg3, :arg4
typecheck :arg2 => Numeric, :arg3 => Numeric
default :arg3 => “INT2NUM(7)”,
:arg4 => “INT2NUM(NUM2INT(arg2) + NUM2INT(arg3))”
rest :rest
block :block
}
body %{
rb_funcall(block, #{declare_symbol :call}, 6,
arg0, arg1, arg2, arg3, arg4, rest);
}
}