Passing generic C or C++ data to ECLiPSe

## 3.4 Passing generic C or C++ data to ECLiPSe

- Wrapping and unwrapping external data in an ECLiPSe term
- The method table

It is possible to include any C or C++ data in an ECLiPSe term. To do this it is wrapped into a handle to tell ECLiPSe that this is external data. You also have to supply a method table, which is a set of functions that are called when ECLiPSe wants to make common operations that it assumes can be done on any data (eg. comparing, printing).

### 3.4.1 Wrapping and unwrapping external data in an ECLiPSe term

To create an ECLiPSe wrapper for a C/C++ object, the function `handle()` is used. It takes a pointer to any C or C++ data, and a pointer to a suitable method table (`t_ext_type` structure) and creates an ECLiPSe handle term which refers to them. Method tables for the common case of arrays of char, long or double are predefined. For example a handle for a double array is made like this

        double my_array[5] = {1.1, 2.2, 3.3, 4.4, 5.5};
        EC_word w = handle(&ec_xt_double_arr, my_array);

where `ec_xt_double_arr` is a predefined method table for arrays of doubles. To convert back from an ECLiPSe term `is_handle()` is used. The method table passed in indicates the sort of data you expect to get. If the ECLiPSe handle contains the wrong sort, the function returns TYPE_ERROR:

        if ((EC_succeed == w.is_handle(&ec_xt_double_arr, &obj))
            obj->my_method();
        else
            cerr << "unexpected type\n";

### 3.4.2 The method table

Apart from the predefined method tables ec_xt_double_arr, ec_xt_long_arr and ec_xt_char_arr, new ones can easily be defined. The address of the table is used as a type identifier, so when you get an external object back from ECLiPSe you can check its type to determine the kinds of operations you can do on it. You can choose not to implement one or more of these functions, by leaving a null pointer (`(void*)0`) in its field.

        typedef void *t_ext_ptr;

        typedef struct {
            void       (*free)       (t_ext_ptr obj);
            t_ext_ptr  (*copy)       (t_ext_ptr obj);
            void       (*mark_dids)  (t_ext_ptr obj);
            int        (*string_size)(t_ext_ptr obj, int quoted);
            int        (*to_string)  (t_ext_ptr obj, char *buf, int quoted);
            int        (*equal)      (t_ext_ptr obj1, t_ext_ptr obj2);
            t_ext_ptr  (*remote_copy)(t_ext_ptr obj);
            EC_word    (*get)        (t_ext_ptr obj, int idx);
            int        (*set)        (t_ext_ptr obj, int idx, EC_word data);
        } t_ext_type;

void free(t_ext_ptr obj)
  This is called by ECLiPSe if it loses a reference to the external data. This could happen if the ECLiPSe execution were to fail to a point before the external object was created, or if a permanent copy was explicitly removed with built-ins like setval/2, erase/2 or bag_dissolve/2. Note that an invocation of this function only means that one reference has been deleted (while the copy function indicates that a reference is added).
t_ext_ptr copy(t_ext_ptr obj)
  This is called by ECLiPSe when it wants to make a copy of an object. This happens when calling ECLiPSe built-ins like setval/2 or recordz/2 which make permanent copies of data. The return value is the copy. If no copy-method is specified, these operations will not be possible with terms that contain an object of this type. A possible implementation is to return a pointer to the original and e.g. increment a reference counter (and decrement the counter in the free-method correspondingly).
void mark_dids( t_ext_ptr obj)
  This is called during dictionary garbage collection. If an external object contains references to the dictionary (`dident`) then it needs to mark these as referenced.
int string_size(t_ext_ptr obj, int quoted)
int to_string(t_ext_ptr obj,char *buf, int quoted)
  When ECLiPSe wants to print an external object it calls `string_size()` to get an estimate of how large the string would be that represents it. This is used by ECLiPSe to allocate a buffer. The string representation must be guaranteed to fit in the buffer.
  Finally the `to_string()` function is called. This should write the string representation of the object into the buffer, and return the actual size of that string.
int equal(t_ext_ptr obj1, t_ext_ptr obj2)
  This is called when two external objects are unified or compared. Prolog views the external object as a ground, atomic element. It should return non-zero if the objects are considered equal.
t_ext_ptr remote_copy(t_ext_ptr obj)
  This is called by parallel ECLiPSe when it needs to make a copy of an object in another worker. If the object is in shared memory, this method can be the same as the copy method.
EC_Word get(t_ext_ptr obj, int idx)
  Returns the value of a field of the C++ object. This methods gets invoked when the ECLiPSe predicate xget/3 is called. The mapping of index values to fields is defined by the get/set-method pair.
int set(t_ext_ptr obj, int idx, EC_word data)
  Set the value of a field of the C++ object. This methods gets invoked when the ECLiPSe predicate xset/3 is called. The mapping of index values to fields is defined by the get/set-method pair.

Example of the simplest possible user-defined method table:

        t_ext_type my_type = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};
        my_struct data_in;
        ...
        // creating a handle for data_in
        EC_word w = handle(&my_type, &data_in);
        ...
        // checking a handle and extracting the data pointer
        my_struct *data_out;
        if ((EC_succeed == w.is_handle(&my_type, &data_out))
            data_out->my_method();
        else
            cerr << "unexpected type\n";