Storing binary data in instances

The most common way of storing data in class instances is by defining member variables or constants that refer to the data. When dealing with existing C frameworks, however, it is often desirable to store C level data structures or pointers inside Alore objects. This section gives an overview of including arbitrary C level binary data within Alore objects.

See also: You can also use functions described in section Memory management for the same purpose.

Using per instance binary data

These macros can be used to allocate fixed size data blocks in each instance of the class. These blocks can be used to store any C level data such as pointers, structs or floating point numbers. Binary data is always initialized to zero early during object construction, even before create or #i is called. See section Accessing binary data below for information on accessing and modifying the binary data.

A_BINARY_DATA(size)
Include a data block of the given size, in bytes, in each instance of the class.
A_BINARY_DATA_P(size, intPtr)
Include a data block of the given size, in bytes, in each instance of the class. Store the offset of the data block in *intPtr (intPtr should have type int *). This offset can be used to access the data.

Using external data

A_EXTERNAL_DATA()
If this definition is present in a class definition, you can use the function ASetExternalDataSize to set the amount of external data not visible to the garbage collector that is attached to an object of the class. If this definition is included, you must also include the #f method in the class. A_EXTERNAL_DATA() can only be used in classes that do not inherit another class (other than std::Object). This macro also creates a single member slot in the class.

Defining initialization and finalization methods

The following special method names can be used for controlling the life cycle of objects:

#i

The method "#i", if defined, will be called during object construction, before calling create, at least when both of these conditions are satisfied:

This method should be used to initialize any binary data attached to the object. Note that both the #i method and the original create method can be called during object construction: the create method defined in the subclass may call the original create method using the super keyword.

#f

The method "#f", if defined, is called just before an object is freed by the garbage collector. This method, also called the finalizer, is special in several ways, including these:

Note also that when #f is called, the object might already be partially freed, and the heap might be in an inconsistent state. This means that:

In practice, the only thing that should be done in a #f method is to free any external resources (e.g. operating system resources, memory allocated using malloc) so that they will not be leaked when the object is freed. The method should check if any data has been left uninitialized, since it is possible that #f is called before the object has been fully constructed.

As a side effect of defining #f, the member slot 0 is reserved by the implementation for internal use. See Accessing member variable slots directly for additional information.

Note: You can only define #f in a class if the class inherits from std::Object or if one of the superclasses also defines #f.

Accessing binary data

The following API functions can be used to access binary data in instances. They are described in the API function reference, linked below.

All of these functions require the offset of the data to be accessed. This first A_BINARY_DATA definition in a class gets the offset 0, and additional A_BINARY_DATA definitions get larger offsets. Binary data defined in subclasses always gets larger offsets that data defined in superclasses.

For example, consider class X that inherits from Y. Y defines a single binary data block of 8 bytes and X defines two blocks sized 4 and 16 bytes. The following table lists the offsets and sizes of these blocks in objects of class X:

Offset Size
0 8
8 (0 + 8) 4
12 (0 + 8 + 4) 16

Alignment of binary data

Binary data at offset 0 is aligned to a 32-bit boundary (if using a 32-bit AValue type) or to a 64-bit boundary (if using a 64-bit AValue type). If there is more than a single A_BINARY_DATA declaration in in a class hierarchy, the offset of a binary data declaration other than the first declaration (which always has offset 0) is simply the sum of the data sizes in previous declarations. Alignment is thus enforced only for the first binary data declaration in a class hierarchy. Example of unaligned binary data:

A_MODULE(...)
    A_CLASS("Example")
        A_BINARY_DATA(1)    /* Offset 0 (32/64-bit aligned) */
        A_BINARY_DATA(2)    /* Offset 1 */
        A_BINARY_DATA(8)    /* Offset 3 */
        ...
    A_END_CLASS()
A_END_MODULE()

Since some architectures do not allow accessing unaligned data items directly, you either have to enforce the alignment manually by rounding the sizes of data blocks up, for example, or by copying the data to/from another properly aligned location using ADataPtr and memcpy.

You can use the macro A_VALUE_BITS to query the current size of the AValue type. Its value is currently either 32 or 64.

Example

This example defines a simple class that is a wrapper for data allocated using malloc. Very little error checking is performed to highlight the key issues. A more robust implementation would at least check that method parameters are in a valid range to avoid memory corruption.

#include <alore/alore.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

static AValue Create(AThread *t, AValue *frame)
{
    int size;
    char *ptr;

    size = AGetInt(t, frame[1]);
    ptr = malloc(size);
    if (ptr == NULL)
        return ARaiseMemoryError(t);
    memset(ptr, 0, size);
    ASetData_M(frame[0], 0, char *, ptr);
    ASetExternalDataSize(t, frame[0], size);
    return frame[0];
}

static AValue Finalize(AThread *t, AValue *frame)
{
    char *ptr = AGetData_M(frame[0], 0, char *);
    free(ptr); /* Note that ptr may be 0. */
    return ANil;
}

static AValue Set(AThread *t, AValue *frame)
{
    int index = AGetInt(t, frame[1]);
    int value = AGetInt(t, frame[2]);
    char *ptr = AGetData_M(frame[0], 0, char *);
    ptr[index] = value;
    return ANil;
}

static AValue Get(AThread *t, AValue *frame)
{
    int index = AGetInt(t, frame[1]);
    char *ptr = AGetData_M(frame[0], 0, char *);
    return AMakeInt(t, ptr[index]);
}

A_MODULE(malloc, "malloc")
    A_CLASS("Malloc")
        A_EXTERNAL_DATA()
        A_BINARY_DATA(sizeof(char *))
        A_METHOD("create", 1, 0, Create)
        A_METHOD("#f", 0, 0, Finalize)
        A_METHOD("set", 2, 0, Set)
        A_METHOD("get", 1, 0, Get)
    A_END_CLASS()
A_END_MODULE()