Wrapping C data structures
When developing a ruby extension in C, it may be necessary to save an allocated C structure inside a Ruby object. For instance, in the ruby-libvirt bindings, a virConnectPtr (which points to a libvirt connection object) is saved inside of Libvirt::Connect ruby object, and that pointer is fetched from the object any time an instance method is called. Note that the pointer to the C structure is stored inside the Ruby object in a way that the ruby code can't get to; only C extensions will have access to this pointer. There are only 3 APIs that are used to manipulate these pointers:- Data_Wrap_Struct(VALUE klass, void (*mark)(), void (*free)(), void *ptr) - Wrap the C data structure in ptr into a class of type klass. The free argument is a function pointer to a function that will be called when the object is being garbage collected. If the C structure references other ruby objects, then the mark function pointer must also be provided and must properly mark the other objects with rb_gc_mark(). This function returns a VALUE which is an object of type klass.
- Data_Make_Struct(VALUE klass, c-type, void (*mark)(), void (*free)(), c-type *ptr) - Similar to Data_Wrap_Struct(), but first allocates and then wraps the C structure in an object. The klass, mark, free, and ptr arguments have the same meaning as Data_Wrap_Struct(). The c-type argument is the actual name of the type that needs to be allocated (sizeof(type) will be used to allocate).
- Data_Get_Struct(VALUE obj, c-type, c-type *ptr) - Get the C data structure of c-type out of the object obj, and put the result in ptr. Note that this pointer assignment works because this is a macro.
An example will demonstrate the use of these functions:
1) static VALUE m_example;
2) static VALUE c_conn;
3)
4) struct mystruct {
5) int a;
6) int b;
7) };
8)
9) static void mystruct_free(void *s)
10) {
11) xfree(s);
12) }
13)
14) static VALUE example_open(VALUE m)
15) {
16) struct mystruct *conn;
17) conn = ALLOC(struct mystruct);
18) conn->a = 25;
19) conn->b = 99;
20) return Data_Wrap_Struct(c_conn, NULL, mystruct_free, conn);
21) }
22)
23) static VALUE conn_get_a(VALUE c)
24) {
25) struct mystruct *conn;
26) Data_Get_Struct(c, struct mystruct, conn);
27) return INT2NUM(conn->a);
28) }
29)
30) void Init_example(void)
31) {
32) m_example = rb_define_module("Example");
33) rb_define_module_function(m_example, "open", example_open, 0);
34) c_conn = rb_define_class_under(m_example, "Conn", rb_cObject);
35) rb_define_method(c_conn, "get_a", conn_get_a, 0);
36) }
On lines 32 and 33, we define the Example module and give it a module function called "open". Lines 34 and 35 define a Conn class under the Example module, and gives the Conn class a "get_a" method. Lines 14 through 21 are where we implement the Example::open function. There, we allocate memory for our C structure, then use Data_Wrap_Struct() to wrap that C structure in a ruby object of type Example::Conn. Note that we also pass mystruct_free() as the free callback; when the object gets reaped by the garbage collector, this function on lines 9 through 12 will be called to free up any memory. Now when the user calls "get_a" on the Example::Conn ruby object, the function on lines 23 through 27 will be called. There we use Data_Get_Struct() to fetch the structure back out of the object, and then return a ruby number for the integer stored inside. Update: added links to all of the previous articles. Update Jan 27, 2014: Updated the example to fix the use of ALLOC(). Thanks to Thomas Thomassen in the comments.
Would it be possible to post links to the entire series of articles on this topic? Look forward to reading them. -CS
ReplyDeleteGood point. I'll add links from this post back to the original 12 posts.
ReplyDeleteALLOC(sizeof(struct mystruct)) isn't correct. It should be the type only. The macro itself uses sizeof: #define ALLOC(type) ((type*)xmalloc(sizeof(type)))
ReplyDeleteYep, good call. I've updated the post now. Thanks!
DeleteI'll bet you would get a lot more comments if some of the most critical aspects of your code did not run off my screen based on Firefox Mozilla.
ReplyDeletehi, thank you for sharing this blog
ReplyDeletepython full stack online training in hyderabad
nice blog keep posting
ReplyDeletedigital marketing course in kphb