Now on to the
really fun stuff. We've got the vendor's
library that controls the audio CD jukebox units, and we're ready to
wire it into Ruby. The vendor's header file looks like this:
typedef struct _cdjb {
int statusf;
int request;
void *data;
char pending;
int unit_id;
void *stats;
} CDJukebox;
// Allocate a new CDPlayer structure and bring it online
CDJukebox *CDPlayerNew(int unit_id);
// Deallocate when done (and take offline)
void CDPlayerDispose(CDJukebox *rec);
// Seek to a disc, track and notify progress
void CDPlayerSeek(CDJukebox *rec,
int disc,
int track,
void (*done)(CDJukebox *rec, int percent));
// ... others...
// Report a statistic
double CDPlayerAvgSeekTime(CDJukebox *rec);
|
This vendor has its act together; while the vendor might not admit it, the
code is written with an object-oriented flavor. We don't know what
all those fields mean within the
CDJukeBox
structure, but that's
okay---we can treat it as an opaque pile of bits. The vendor's code
knows what to do with it, we just have to carry it around.
Anytime you have a C-only structure that you would like to handle as a
Ruby object, you should wrap it in a special, internal Ruby class
called
DATA
(type
T_DATA
).
There are two macros to do this wrapping, and one to retrieve your
structure back out again.
C Datatype Wrapping |
VALUE�
|
Data_Wrap_Struct(VALUE class, void (*mark)(),
void (*free)(), void *ptr")
|
� |
Wraps the given C datatype ptr, registers the
two garbage collection routines (see below), and returns
a VALUE pointer to a genuine Ruby object. The C type of the
resulting object is T_DATA and its Ruby class is class.
|
VALUE�
|
Data_Make_Struct(VALUE class, c-type,
void (*mark)(), void (*free)(), c-type *")
|
� |
Allocates a structure of the indicated
type first, then proceeds as Data_Wrap_Struct . c-type
is the name of the C datatype that you're wrapping, not a
variable of that type.
|
�
|
Data_Get_Struct(VALUE obj,c-type,c-type *")
|
� |
Returns the original pointer. This macro
is a type-safe wrapper around the macro
DATA_PTR(obj) , which evaluates the pointer.
|
The object created by
Data_Wrap_Struct
is a normal Ruby object,
except that it has an additional C datatype that can't be accessed
from Ruby. As you can see in Figure 17.1 on page 177, this C
datatype is separate from any instance variables that the object
contains.
But since it's a separate thing, how do you get rid of it when the
garbage collector claims this object? What if you have to release
some resource (close some file, clean up some lock or IPC mechanism,
and so on)?
In order to participate in Ruby's mark-and-sweep garbage collection process,
you need to define a
routine to free your structure, and possibly a routine to mark any
references from your structure to other structures. Both routines take a
void
pointer, a reference to your structure.
The
mark routine will be called by the garbage collector
during its ``mark'' phase. If your structure references other Ruby
objects, then your mark function needs to identify these objects using
rb_gc_mark(value)
. If the structure doesn't reference
other Ruby objects, you can simply pass
0
as a function pointer.
When the object needs to be disposed of, the garbage collector will
call the
free routine to free it. If you have allocated any
memory yourself (for instance, by using
Data_Make_Struct
),
you'll need to pass a free function---even if it's just the standard C
library's
free
routine. For complex structures that you have
allocated, your free function may need to traverse the structure to
free all the allocated memory.
First a simple example, without any special handling. Given the
structure definition
typedef struct mp3info {
char *title;
char *artist;
int genre;
} MP3Info;
|
we can create a structure, populate it, and wrap it as an
object.
[We cheat a bit in this example. Our MP3Info
structure has a couple of char
pointers in it. In our code we
initialize them from two static strings. This means that we don't
have to free these strings when the MP3Info
structure is freed.
If we'd allocated these strings dynamically, we'd have to write a
free method to dispose of them.]
MP3Info *p;
VALUE info;
p = ALLOC(MP3Info);
p->artist = "Maynard Ferguson";
p->title = "Chameleon";
...
info = Data_Wrap_Struct(cTest, 0, free, p);
|
info
is a
VALUE
type, a genuine Ruby object of class
Test
(represented in C by the built-in type
T_DATA
). You
can push it onto an array, hold a reference to it in an object, and so
on. At some later point in the code, we may want to access this
structure again, given the
VALUE
:
VALUE doit(VALUE info) {
MP3Info *p;
Data_Get_Struct(info, MP3Info, p);
...
p->artist -> "Maynard Ferguson"
p->title -> "Chameleon"
...
}
|
In order to follow convention, however, you may need a few more
things: support for an
initialize
method, and
a ``C-constructor.'' If you
were writing Ruby source, you'd allocate and initialize an object by
calling
new
. In C extensions, the corresponding call is
Data_Make_Struct
. However, although this allocates memory for
the object, it does
not automatically call an
initialize
method; you need to do that yourself:
info = Data_Make_Struct(cTest, MP3Info, 0, free, one);
rb_obj_call_init(info, argc, argv);
|
This has the benefit of allowing subclasses in Ruby to override or
augment the basic
initialize
in your class. Within
initialize
, it is allowable (but not necessarily advisable) to
alter the existing data pointer, which may be accessed directly with
DATA_PTR(obj)
.
And finally, you may want to define a ``C-constructor''---that
is, a globally available C function that will
create the object in one convenient call. You can use this function
within your own code or allow other extension libraries to use it.
All of the built-in classes support this idea with functions such as
rb_str_new
,
rb_ary_new
, and so on. We can make our own:
VALUE mp3_info_new() {
VALUE info;
MP3Info *one;
info = Data_Make_Struct(cTest, MP3Info, 0, free, one);
...
rb_obj_call_init(info, 0, 0);
return info;
}
|