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 conversion, serialization, 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 CShadow::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.

Behavior at commit time can be controlled by scheduling before_commit and after_commit procs. These procs are called immediately before and after the actual commit, which allows, for example, removing instances that would otherwise cause an exception, or creating the instance of a singleton class. The before_commit and after_commit class methods of CShadow delegate to the shadow_library's methods. See the cgen documentation for details.

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

In addition to shadow attributes, you can declare other struct members using

shadow_struct.declare ...

as in cgen.

Accessors

CShadow.shadow_attr [options,] :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.)

The available options are:

:nonpersistent
Do not serialize this attribute. When loading, it is initialized using the attribute's init code (which, typically, sets it to zero or nil).
:reader, :writer
Creates a reader or writer method, just like shadow_attr_reader and shadow_attr_writer. (This is mostly for internal use.)

Type checking and conversion

In case (1) (C data attribute), 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) (Ruby value attribute) 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 defines two methods in the client class for defining methods and class methods

CShadow.define_c_method name, subclass = Method, &block
CShadow.define_c_class_method name, subclass = SingletonMethod, &block

The block is evaluated in a context that allows commands for listing arguments, declarations, C body code, etc. See cgenerator.rb for details. See examples in matrix.rb and complex.rb. The subclass argument is optional and allows the template to belong to a subclass of the function template it would normally belong to.

In the case of define_c_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.

CShadow.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.) The order is from root to leaf of the inheritance chain, and within each class in order of definition. (TEST THIS)

CShadow.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.

CShadow#each_attr_value(&bl)

Iterate over each shadow attr and instance var of self, yielding the attr name and value in this instance to the block. Differs in three ways from the above introspection methods: it is an instance method of shadow objects, it iterates over both shadow attrs and instance vars, and it yields both the name and the value. (In the case of instance vars, the name does _not_ include the "@".)

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_"?

Using CShadow classes with YAML

As of v0.11, CShadow classes can serialize via YAML. Both shadow attributes and ordinary ruby instance variables are serialized. No additional attribute code is needed, because of the generic load/dump mechanism used in cshadow (attributes are pushed into or shifted out of an array, which is passed to or from the serialization protocol, either Marshal or YAML). The ordering of shadow attributes is preserved when dumping to a YAML string.

The only user requirement is that, before attempting to load shadow class instances from YAML string, the shadow class types must be registered with YAML. This is simple:

CShadow.allow_yaml

See examples/yaml.rb

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: