module CShadow

CShadow is a way of creating objects which transparently mix C data with Ruby data.

The CShadow module is a mix-in which adds a C struct to objects which derive from the class which includes it. The data members of the C struct, called shadow attributes, may vary in subclasses of this base class. Shadow attrs can be added to any class inheriting from the base class, and they will propagate along the ordinary ruby inheritance hierarchy. (For now, shadow attributes cannot be defined in modules.)

The CShadow module uses CGenerator's structure templates to handle the code generation. CGenerator is also useful for inline C definitons of methods that operate on shadow attributes.

CShadow cooperates with the CShadow::Attribute subclasses defined in attribute.rb to handle the mark and free functions, type checking, and so on. New attribute types can be added easily.

Usage

class MyClass
  include CShadow

Include CShadow in the base class(es) that need to have shadow attributes. The base class is assigned a Library (see cgen.rb), which can be accessed using the base class's shadow_library method. Each subclass of the base class will be associated with a struct defined in this library's .h files.

As usual, the initialize method of the class will be called when the object is created, and the arguments are whatever was passed to new, including the block, if any. You can write your own initialize method to assign initial values to the shadow attrs. (Typically, shadow attrs are initialized by default to nil or 0. See attribute.rb.)

The file name of the library is the same as the name of the class which includes CShadow, by default, and the library directory is generated in the current dir when commit is called. To change the name, or to use a different library:

shadow_library <Library, Class, or String>

The argument can be another library (instance of CGenerator::Library), a class which includes CShadow, in which case the library of the class is used, or a string, in which case a new library is created. Using this feature, several base classes (and their descendants) can be kept in the same library. It is not possible to split a tree (a base class which includes CShadow and its descendants) into more than one library.

Shadow classes that are placed in the same library can still be put in separate source files, using shadow_library_file:

shadow_library_file <CFile or String>

This setting is inherited (and can be overridden) by subclasses of the current class. If a class calls both shadow_library and shadow_library_file then they must be called in that order. Note that anything defined before the shadow_library_file statement will not be placed in the specifed file.

The include and source file for the current class can be accessed with shadow_library_include_file and shadow_library_source_file. This is not required for normal use.

Struct members

All shadow structs have a self member of type VALUE which points to the Ruby object.

The subclass struct inherits members of the base class struct.

There are two types of shadow attributes:

  1. C data: :d => "double d", :x => "char *foo"
  2. Ruby value: :str => String, :obj => Object

Accessors

CShadow#shadow_attr :var => decl, ...

Adds the specified declarations to the shadow struct of the current class without defining any accessors. The data can be accessed only in C code.

CShadow#shadow_attr_reader :var => decl, ...
CShadow#shadow_attr_writer :var => decl, ...
CShadow#shadow_attr_accessor :var => decl, ...

Like CShadow#shadow_attr, but adds a reader and/or writer named var or var=.

Each :var => decl pair generates one C struct member (:x => "int x,y" will generate an exception).

The same symbol :var cannot be used in both a shadow_attr_* definition and an ordinary attr_* definition. (You can always get around this by manually defining accessor methods.)

Type checking and conversion

In case (1), assignments to int and double struct members are done with NUM2INT and NUM2DBL, which convert between numeric types, but raise exceptions for unconvertible types. The char * case uses rb_str2cstr, which behaves analogously.

In case (2) the assigned value must always be a descendant of the specified type, except that nil is always accepted. The purpose of specifying a class (should also allow type predicate!) is to allow C code to assume that certain struct members are present in the shadow struct, and so it can be safely casted to the "ancestor" struct.

Adding methods

CGenerator provides a general interface to the Ruby-C api. However, for simplicity, CShadow provides two methods for defining methods and class methods

CShadow#define_method name, &block
CShadow#define_class_method name, &block

The block is evaluated in a context that allows commands for listing arguments, delcarations, C body code, etc. See cgenerator.rb for details. See examples in examples/matrix.rb and examples/complex.rb.

In the case of define_method, a pointer to the object's shadow struct is available in the C variable shadow.

Introspection

Each class which includes the CShadow module has the following methods to iterate over its shadow attributes.

sub_class#each_shadow_attr(&bl)

Note that the shadow attributes dynamically include inherited ones. (Dynamically in the sense that subsequent changes to superclasses are automatically reflected.)

sub_class#shadow_attrs

Returns a proxy Enumerable object referring to the same attributes as #each_shadow_attr. For example:

sub_class.shadow_attrs.collect { |attr| attr.var }

returns an array of variable names for all attributes of the class.

Memory management

Each type of attribute is responsible for managing any required marking of referenced Ruby objects or freeing of allocated bocks of memory. See attribute.rb for details.

C attribute plug-ins

CShadow's shadow_attr methods check for one (no more, no less) matching attribute class. The details of the matching depend on the attribute class. See attribute.rb for details. Additional attribute classes can easily be added simply by subclassing CShadow::Attribute.

Namespace usage

prefix every attribute, method, constant name with "cshadow_"?

Common problems and their solutions

Do you get a NameError because accessor methods are not defined? Make sure you commit your class.

You assign to an attribute but it doesn't change? Ruby assumes that, in an assignment like "x=1", the left hand side is a local variable. For all writer methods, whether Ruby or C attributes, you need to do "self.x=1".

Notes

Limitations:

To do: