[PREVIOUS] [NEXT] [UP]

Importing library objects

The most convenient way to extend Neo/NST with new objects is via the prog_unit. It can import C-functions and C++ classes from shared libraries, if a small amount of wrapper code is added to the library (or a separate file). Recent changes are indicated in color. For detailed information how to wrap existing C/C++ classes directly as new NST units, see the manual The tools Mku+ and Mku++. In the following, xxxx will generally stand for the name of a function or class, and nnnn will stand for the base name (without path) of a shared library. We will assume for the shared library always the suffix ".so", but this should just be taken as our shorthand for the suffix that is required in the actual case.

NOTE: the examples in this chapter require the compilation of shared libraries. If you don't own the tutorial directory, copy the files Makefile and demolib[1-5abc].[cC] into a suitable directory. Edit variable DEST_DIR in the Makefile to specify the directory where the created shared libraries shall become placed and include that directory in your NST_LD_LIBRARY_PATH (or LD_LIBRARY_PATH, if the former is not defined) so that Neo can find the libraries.

How to implement a new prog_unit function

The following example shows how to implement a prog_unit function float scalar_prod(float *u, float *v) that takes two float array arguments uand v (requiring them to be of the same dimension) and returns their scalar product. To add this function to the set of available prog_unit functions requires two steps:

1. implement the function in a shared library nnnn.soand add some simple wrapper code to inform the prog_unit about the function call interface (see below).
2. put the line #import "nnnn" into the header portion of any prog_unit program that wishes to use the function. Here, nnnn must be the basename (i.e. without suffix) of the shared library of step 1.

The implementation of step 1 consists of two parts. The first part is the line

   char *FUNCTIONS_nnnn[] = { "float scalar_prod(float*,float*)", NULL };

It tells the prog_unit that the library contains a function namedscalar_prod with two float* arguments and a float return value (the name suffix _nnnn is optional but recommended; its presence allows the library also to be statically linked with the executable). The second part is the implementation of the function itself. The prototype must be of the form

   float scalar_prod(int *piDim, void **ppvArg) { ... }

The first array piDim will pass the dimensions of all arguments (a value piDim[i]=0 indicates the the i-th argument is a scalar). The second array ppvArg will pass for each argument the address of its first element (scalars are treated like 1-dimensional arrays and can be recognized from the zero dimension passed in piDim), Note that an array parameter can be NULL. This will be passed as the address value NULL in ppvArg[], together with piDim[]=0 for the corresponding argument. In the present case, there are two float* arguments, so that the full implementation of scalar_prod in nnnn.c might look like

   float scalar_prod(int *piDim, void **ppvArg) {
      float *pfArg0 = (float*) ppvArg[0]; /* adr of 1st arg */
      float *pfArg1 = (float*) ppvArg[1]; /* adr of 2nd arg */
      float fReturn = 0;
      if (piDim[0] == piDim[1]) {
         int i; /* compute scalar product of two vectors: */
         for (i=0; i<change(*piArg0); i++) fReturn += (*pfArg0++)*(*pfArg1++);
      } /* else error message ... */
      return fReturn;
   }

The file nnnn.c = demolib1.c contains the full example. The command

   gcc -shared -o demolib1.so demolib1.c

will compile it into a shared library. Instead of the above, you can use the accompanying Makefile and just say make demolib1.so. Example#1 illustrates its use from a prog_unit.

How to implement a prog_unit function library

In the same manner one can implement several functions in a shared library. Each function prototype must then be declared in the FUNCTIONS_nnnn line. If a prototype has no parameter list, it is considered as the declaration of a constant. A single identifier ending with a colon ":" defines an additional name prefix for the implementation (this allows to define prog_unit function names that would otherwise give rise to name conflicts with existing functions). Here is an example:

    char *FUNCTIONS_nnnn[] = {
      /* define a prefix: */
      "__:",
      /* two exported constants: */
      "float MyPI",
      "int My4711",
      /* three exported functions: */
      "void  foo(int, float*)",
      "float bar(void)",
      "int   bcopy(char*, byte*)",
      NULL
   };

char   *NAMESPACE_nnnn = "mylib";

   float __MyPI = 3.14; /* implements float const MyPI */
   int __My4711 = 4711; /* implements int const My4711 */

   void  __foo(int *piDim, void **ppvArg)  { ... }
   float __bar(int *piDim, void **ppvArg)  { ... }
   int   __bcopy(int *piDim, void **ppvArg){ ... }

(a full implementation with some further functions explained below is in file demolib2.c). Note also that the (int*,void**) call interface is even used when a function is declared as parameterless (e.g., "void myfun(void)" in the FUNCTIONS[] line); in this case, piDim and ppvArg are passed as two NULL pointers that are not used. Exceptions are some functions with predefined, special identifiers (such as _init  or special class methods explained below). Their interface may deviate from the standard (int*,void**) form and must be inferred from their documentation.
The definition NAMESPACE_nnnn = "mylib"  defines an optional prefix for the prog_unit level (to be distinguished from the implementation level) and allows the exported functions and symbols also to be referenced by a "qualified name", e.g.
.mylib.foo()mylib.MyPi  etc. This is useful in the case of name conflicts when importing from several libraries.

How to define default values for function arguments

Like in C++, the trailing arguments of a function can be set to default values. In #import libraries this is restricted to the scalar data types, i.e., float and int arguments. Example:

   char *FUNCTIONS_nnnn[] = {  ...
         void foo(char*,float=3.14,int=4711);
          ...
    }

This makes foo callable with a trailing number of the default arguments omitted, e.g., foo("hello"), foo("hello",2.5) and foo("hello",2.5,753) are all legal function calls.

How to resize function arguments to pass back data

Often a function wishes to return data in a suitably sized memory block, but the size is not yet known when the function is called, so that no suitably sized argument can be passed. For this case, the #import library interface provides a mechanism for the called function to dynamically resize the memory (referenced by a passed function argument) with a call to the function

   int nst_redimension(void **ppAdr, int iNewDim)

Here, *ppAdr must be the address of the to-be-resized memory block (and ppAdr the adress where the address is stored, i.e,
to resize the i-th argument parameter, one might call nst_redimension(ppvArg+i,iNewDim)) and iNewDim the new number of elements (not bytes!).

The call will return 1, if the redimension operation on the argument was permitted, and 0 otherwise. In the former case, the old address *ppAdr will have been replaced by (the possibly changed) new address after the resize  operation. Using this function call will keep the prog_unit informed that after return the argument will have the new dimension iNewDim.

A redimension operation is not automatically allowed for all arguments. Nst_redimension() will return 1 (and thus have carried out the resizing) only on those arguments that were in the function call preceded by the special token  "(&)", which permits the resize(a "reference cast"). An additional effect is that the presence of a "(&)" in front of a function argument will restrict that argument to be a pointer variable (when the pointer variable is specified with an offset, e.g., (p+5), then the redimension operation will ensure that the "tail portion" (i.e, the part behind p+5) will have the specified iNewDim elements, with a corresponding larger dimension for p itself). Therefore, an implementation that invokes nst_redimension() should provide proper action for both outcomes, since some calls may use the reference cast and others may not, e.g, if they wish to pass an array or a string constant (which are not pointer variables and, therefore, cannot be preceded by the reference cast). Note also that inside a function defined in the prog_unit source code, all function parameters passed by address are considered as fixed arrays of the passed dimension and, therefore, will not be accepted after a (&) in an enclosed #import function call:

       void function foo(float *p) {  myfunction((&) p); } // not accepted : p is fixed array!

An example is implemented in demolib7.c

How to return values in scalar parameters

The (&) token can also be used to allow a function to pass back a value via a scalar parameter: if

     void foo(int *piDim,  float **ppfArg) {
            ...
            ppfArg[1][0] = 47.11;   // write something into scalar argument #1
            ...
      }

is the implementation of an #import library function foo(float x, float y, float z),  the writing of a value into the argument parameter y (argument #1) will not have any effect for a "normal" function call. However, the prefix "(&)" before any scalar argument will make any assignment of a new value, as in the above example, visible for the caller:

       y = 3.14;
       foo(x, y, z);      // y still 3.14
       foo(x,(&) y, z);   // now y=47.11

This allows to implement functions such that they optionally (i.e., when the caller uses the "(&)") can pass back result  values through their scalar argument parameters. NOTE: for implementation reasons, the use of the "(&)" before a scalar function parameter is restricted in two ways:  (i), the parameter must not be an array element (such as a[4] etc.) and (ii), the parameter must not be the first occurrence (and thus its implicit definition) of the variable: the variable must already have been defined before!
 

How to define overloaded functions

By using different prefixes, one can provide different implementations for functions that have the same name, but differ in their parameter list ("overloading"). Example:

   char *FUNCTIONS_nnnn[] = {
      "_1_:", "fun(int)",
      "_2_:", "fun(float)",
      "_3_:", "fun(int,float)",
      NULL
   };

   float _1_fun(int *piDim, void **ppvArg) { ... }
   float _2_fun(int *piDim, void **ppvArg) { ... }
   float _3_fun(int *piDim, void **ppvArg) { ... }

(if no return type is specified for a function in the FUNCTIONS_nnnn  line, the default is float). The prog_unit parser will ensure that each function is called precisely with those arguments that are specified in its parameter list in the FUNCTIONS line, i.e. _1_fun will get passed a single int, _2_fun a single float etc. File demolib2.c contains also the above example, and example#2 is a prog_unit that issues some test calls of these and some other functions.

Note: do not define overloaded functions with the same name, but different return types! Currently, this is not checked, but it may cause problems in certain cases.

The choice of the correct function is made according to an analogous set of rules as familiar from C++. One of these rules is that differing return types cannot be used for distinguishing between different prototypes.

How to define variadic functions

There is some support also for defining variadic functions. Here are two examples:

   char *FUNCTIONS_nnnn[] = {
      "variadic1(int, float*, ...)",  /* comma separated ... */
      "variadic2(int, float* ...)",   /* no comma before ... */
      "variadic3(int, int ...)",      /* variadic integer parameters */
   NULL };

Here, a comma-separated ellipsis without any preceding type specifier (as in variadic1) specifies any number (including zero) of trailing read-only float or float* arguments (note that the prog_unit allows to supply also a char* or byte* parameter for a float* argument; in this case, there will occur an automatic conversion to a float*, i.e., the called routine need not care and can rely on getting the data as a float* array in any case). In contrast, a non-comma-separated ellipsis (as in variadic2) specifies any number (including zero) of trailing readable or writeable array parameters (i.e., now scalars are excluded!) of the type to which the ellipsis were appended (here float*).
Finally, the "int ..." specifies a variable (including 0) number of trailing int parameters (currently, for scalar data types this is only provided for integers).

Variadic functions require a prototype ident(int,int*,void**) with an additional argument parameter that specifies the actual number of arguments in the call, e.g.:

   float variadic1(int iNumArgs, int *piDim, void **ppvArg) { ... }

How to define functions with function arguments

There is some  limited support to define functions with arguments that are functions themselves. Restrictions are that (i) a function may have maximally 10 such arguments, and currently each function argument is restricted to have itself arguments that can only be chosen from the types float, int, float*, int*, char*, byte*. Furthermore, the function must return a value of type float or void (i.e., currently no  int valued functions can be passed). The declaration follows the usual C-conventions, e.g.

   char *FUNCTIONS_nnnn[] = {  ...
         "void foo(int, (*)(float, char*), (*)(void))",
   ... }

defines foo() as an #import function with an int argument and two further function arguments. The argument parameters of the first function are float and char*, the second function is parameterless. The implementation for foo() takes the form

    void foo(int *piDim, void **ppvArg) {
        int *piArg0 = (int*) ppvArg[0];
        float (*pFunctionArg1)(double,...) = ((float(*)(double,...)) ppvArg[1];
        float (*pFunctionArg2)(void) = ((float (*)(void)) ppvArg[2];
        ...
        pFunctionArg1(2.45, "hello world!");   // using function arg 1
        pFunctionArg2();    // using function arg2
        ...
    }

i.e., just picks up from ppvArg[] the corresponding function pointers and uses them (the gray parts cast to the correct pointer type and are not really needed).

Important note: the passed function pointers must be declared as variadic function prototypes that use double arguments in all positions were a float parameter was declared!

When foo is used at the prog_unit level, the supplied function parameters may be either #import library functions (including class member functions), or functions define in the source code of the prog_unit, as well as most of the fixed builtin functions (such as sin, cos, ...
but not, e.g., exec_opnds() [but you can pass such functions enclosed in a wrapper routine]). However, NST transports for all array function parameters also their dimension. Therefore, when a function parameter has arrays (other than char*,
which are in this context expected only to be filled with null-terminated strings whose length+1 is then taken as the dimension) among its arguments, e.g.,
        ....
    "foo2(int, (*)(float,byte*), (*)(float*)",
    ...

the implementation will get passed pointers to "augmented" function prototypes that have for each array argument an additional int argument (in front of the array argument) that expects the array dimension when the function is called. E.g., the foo2 implementation would look like

    void foo2(int *piDim, void **ppvArg) {
        int *piArg0 = (int*) ppvArg[0];
        float (*pFunctionArg1)(double,...) = ((float(*)(double,...)) ppvArg[1];
        float (*pFunctionArg2)(int,...) = ((float (*)(int,...)) ppvArg[2];
        ...
        pFunctionArg1(2.45, 10, pcArrayOfDim10);   // using function arg 1
        pFunctionArg2(5,pfArrayOfDim5);  // using function arg2
        pFunctionArg2(0,NULL);    // using function arg2 another time
        ...
    }

Note that these additional dimension parameters do not appear in the implementation of the passed functions themselves (e.g., in their source text in the prog unit); they only appear when a function (such as foo2) references them via its (foo2's) parameter list. Note also that float* arrays are not transformed into double* and that byte* gets mapped to char* and differs from char* only in that it makes no assumptions about null-terminatedness and thus needs the extra int parameter to request a dimension value.

Passing overloaded functions.  When in a call, e.g. of foo(),one or more supplied function parameters are overloaded functions that can come in different signatures, the compiler needs a specification which signature shall be chosen from the alternatives. The specification takes the form of a type list appended to each overloaded function parameter that is supplied, e.g., a call of foo() would then have to look like

                       foo(42, fun1(int,char*), fun2(void));

How to import C++ classes for the prog_unit

In an analogous manner it is possible to define class objects for the prog_unit. The element functions and variables of each class are declared in a char*[] array similar to the *FUNCTIONS[] declaration above. The array names (one for each defined class) are then collected in a further char **CLASSES_nnnn[] array. For example, to define a library with a single class foo:

   char *foo_c[] = {
      "L_:",      // prefix against name collision
      "foo(int)", // class constructor
      "~foo()",   // class destructor (mapped to "FREE_foo()")
      "float fX", // a float variable
      "int iX",   // a int variable
      "readonly int iY", // as iX, but forbids write access
      "float* pfFloat", // a float array
      "void change(int)", // an exported method
   NULL};

   char **CLASSES_nnnn[] = {foo_c,NULL};

After an #import "nnnn" directive in a prog_unit, instances of the class can then be declared with

   class foo a(5), b(10);

and used in statements such as

   a.iX = 15; b.fX = a.fX + a.iY + 1;
   for (i=0; i <dimof(a.pfFloat); i++) a.pfFloat[i] = i;
   b.change(3);
   a.iY = 3;  // SYNTAX ERROR, since iY was def'd as 'readonly'

Scalar element variables may be defined with the optional type qualifier readonly (e.g., iY in the foo example); this then forbids to make direct changes at the level of the prog_unit source code. However, such variables can still be changed through class methods. This is useful, e.g., when the allowed changes to one or several class parameters must obey additional constraints which can then be enforced by the class methods, but might be violated by direct changes.

A library nnnn may contain the definitions of classes (CLASSES_nnnn[] array) and the definitions of functions (FUNCTIONS_nnnn[] array) simultaneously.

Class arrays. Of any imported class, one can define resizeable arrays:

     class foo b[10];   // array of 10 unistantiated elements: different from b(10)!
     ...
     for (i=0; i<10; i++) b[i] = foo(5);
     b[4].pfFloat[2] = 4711;
     b = resize(2*dimof(b));   // now 20 elements, the last 10 uninstantiated
      ...

Autoloading of classes: Although the CLASSES_nnnn[] array can specify the definitions of several classes within a single library nnnn, it is recommended to always define only a single class per library and to choose for the library base name the same name as for the class (e.g., "foo" here; the name of the class as used by the prog unit is determined by its constructor function, cf. below). This will then allow the prog_unit to automatically load the required library when a class declaration is encountered without a previous, explicit #import statement (but note that currently only the #import statement allows to select a particular directory to pick the library from; the automatical loading of classes simply uses the first matching library that it can find in $NST_LD_LIBRARY_PATH).

How to implement class member functions

The first function (here: foo(int)) in the class declaration array (here: *foo_c[]) is taken as the class constructor and will define the call name of the class (the class declaration array name (here: foo_c) need not be identical and plays no role for the usage of the class). Note also that the destructor must always be declared as "~xxxx()" (not "~xxxx(void)" or anything else). If no destructor was specified, the function "free()" will be used instead. The implementation of the class member functions follows an analogous scheme as for a function library, only that each function (except for the constructor, cf. below) now is passed a handle for "its" class instance as an additional last argument. The class instance itself must be created by the constructor function call, which must provide the handle as its return value. Usually, the implementation will be based on an existing C++ class (but it can also be based on plain C code) with similar or identical methods and variables, so that each implementation function is essentially a wrapper for the corresponding C++ class function call that wraps each function into the uniform (int *piDim, void **ppvArgs)-call interface. For example (note that we have chosen a prefix "L_" against name collisions):,

   // the constructor:
   class foo *L_foo(int *piDim, void **ppvArg) {
      int *piArg0 = (int*) (ppvArg[0]);
      // create C++ class instance & return handle:
      return new foo(*piArg0);
   }

   // the destructor:
   void L_FREE_foo(class foo *pData) {
      if (pData) delete pData; // must permit NULL argument!
   }

   // the class method change(int):
   void L_change(int *piDim, void **ppvArg, class foo *pData) {
      int *piArg0 = (int*) ppvArg[0];
      // call corresponding member function of instance pData:
      pData->change(*piArg0);
   }

A full implementation can be found in file demolib3.C. Note that the destructor is expected to always be implemented under the name FREE_xxxx (even though declared as "~xxxx()") plus any additionally specified prefix. Note also that here and everywhere else the name of the C++ class need not coincide with the class name chosen for the prog_unit import; however, to avoid too many general identifiers, we will stick with the same identifier for both. Note also that class member functions, including the
constructor, can be overloaded, as explained for FUNCTION's (an example is given below);

How to implement class member variables

Class member variables are restricted to the elementary types float, int, float*, int* and char*. Instead of float *foo one can also specify float foo[] (and for int*, char* likewise). The latter form foo[] is interpreted as additionally asserting that the array variable foo will be static (i.e. never be reallocated to a different address  or dimension) during the lifetime of the class instance (note however, this is neither checked nor automatically enforced: it is purely the communication of a committment by the implementor). This may make additional operations available for foo[] that would not be possible for *foo (e.g., making foo[] visible as a non-dynamic prog_unit output pin). Each class member variable requires the definition of a registration function with the name of the variable. Its return value must be the address of the variable. Additionally, if the variable is an array, the registration function must write into its first int* argument the dimension of the variable (for a scalar member variable, the value referenced by the first argument must be left unchanged, i.e., don't write e.g., a dimension value of 1 or 0). The last parameter is the class handle. In our example:

   float *L_fX(int *piDim, class foo *pData) { return &(pData->fX); }
   int   *L_iX(int *piDim, class foo *pData) { return &(pData->iX); }
   float *L_pfFloat(int *piDim, class foo *pData) {
      *piDim = pData->iFloatDim; return pData->pfFloat;
   }

Note that the actual allocation of array space is expected to take place in the constructor call and that the registration functions must rely on the C++ class handle to gain access to the dimensions of the array variables that are to be exported for the prog_unit (if the class is not designed to store such information, it may be necessary to work with a derived class that contains storage for the additional auxiliary information).

The registration function must be provided even if an element variable was defined as 'readonly'.

How to implement dynamic element variables

So far, the member variable pfFloat would appear from the prog unit as an array of fixed dimension, i.e., the prog_unit cannot change the size of the array. However, it would be possible for class method calls to resize the array, e.g., method change(int) could redimension the array to a new size given by its int argument. Whenever a class wishes to export to the prog_unit one or several array variables that can be redimensioned, the definition must declare an additional, parameterless update function "UPDATE_xxxx()" (this follows again a fixed naming convention, i.e., "UPDATE_foo()" here). The wrapper implementation must specify then a function pointer with a declaration

 void (*L_UPDATE_foo)(void) = NULL;

and any class method call that has redimensioned one of the exported array variables must issue a call (*L_UPDATE_foo)() before it returns (actually, this call must be made after both the redimensioning and the update of any dimension information accessed by the array registration functions (here: L_pfFloat()) has occurred; behind this is the following mechanism: when the prog_unit calls a member function of a class xxxx, it will set its UPDATE_xxxx function pointer to a suitable notifier function that re-registers all array variables). I.e., the implementation of our example class "foo" now becomes augmented as follows:

   char *foo_c[] = { ..., "UPDATE_foo()", ... };
   ...
   void (*L_UPDATE_foo)(void) = NULL;
   ...
   void L_change(int *piDim, void **ppvArg, class foo *pData) {
         int *piArg0 = (int*) ppvArg[0];
         // now assumed to change the dimension of pfFloat array:
         pData->change(*piArg0);
         (*L_UPDATE_foo)(); // the newly added update call
   }
   ...

Note that resizeable array variables should always be declared with the asterisk (e.g., float *foo), never with the brackets (e.g. float bar[]), since the system will take the [] as the assertion that  the variable is not affected by the UPDATE function (cf. the remark above).

How to allow resize operations from the prog unit level:  these require two additional things: (i) the corresponding array variable (say, myvect) must be prefixed with the (non-standard) type qualifier "dyn" [experimental: don't use yet!] and (ii) the implementation must provide a different registration function void **L_DYN_myvect(int **,class*) that is described more fully below:

        char *foo_c[] = { "L_:", ...  "dyn float *myvect", ... "UPDATE_foo()", ... }
        ...
        void **L_DYN_myvect(int **ppiDim,  class foo *pData) { .... }
 
 

How to make class instances persistent

Instances of a class xxxx can become included in the save/load mechanism of the prog_unit by specifying the two special member functions "SAVE_xxxx()" and "LOAD_xxxx()". They are expected to be implemented as

   char *LOAD_xxxx(FILE *fp, class xxxx *pHandle) { ... }
   char *SAVE_xxxx(FILE *fp, class xxxx *pHandle) { ... }

The return value of these functions should be NULL in case of proper operation, or a char* pointer to a statically stored error message otherwise. The expected semantics is that "LOAD_xxxx" will read and restore precisely what "SAVE_xxxx" has written about the class instance. It is allowed to have neither, one or both functions present. E.g., if there is only a SAVE_xxxx function implemented, all circuit files will contain saved information about the class instances which is not used during loads, but which may become useful later, when the missing LOAD_xxxx function is added to the implementation.

Note that data of an instance are only saved when the instances has been declared as static class.

If the class shall allow that static class variables can be uninstantiated (e.g., declarations such as static class xxxx p;), the class needs instead of the LOAD_xxxx function the implementation of a somewhat "stronger" RESTORE_xxxx function that can even create instances according to previously saved data in a file. To this end, its second parameter is not the pointer pHandle, but instead the address ppHandle of a pointer pHandle:

   char *RESTORE_xxxx(FILE *fp, class xxxx **ppHandle) { ... }

When *ppHandle!=NULL, The required behavior must be identical to what would be expected of SAVE_xxxx(fp,*ppHandle). Otherwise, when *ppHandle==NULL, the RESTORE_xxxx method must create an new instance according to the read data and replace the NULL value in *ppHandle by the address of the created instance.

How to make class instances or variables available at prog_unit connectors

Class instances can even be defined as input or output connectors with statements such as

INP class foo x(10); OUT class foo y(20);

With the above declaration, the class instances would appear at the Neo level as connectors of the user-defined type type "special" that can, e.g., be connected with wires, if they hold compatible instances of the same class (cf. below).
The user-defined "special" connectors are always opaque, i.e., a Neo wire can only transmit the entire connector contents and not any element variables selectively.

New: It is also possible, to make scalar or array members of a class selectively visible as output pins of corresponding
type. For instance, if
foo has element variables float vec[] and int num, these can be declared as

OUT float  y.vec[];     // yields array pin
OUT int y.num;          // yields scalar pin

Alternatively,  vec[] could also be made visible as a dynamic output pin:

OUT float *y.vec;      // yields dynarray pin

(if vec were declared as float *vec in the class interface, only this second possibility would exist).
Note that the mapping of members is restricted to OUTput pins. Also, mapping some element variables of a class instance may restrict some operations on it, such as, e.g., reassignments or re-instantiation (the latter only, if arrays are mapped to const dim pins).

To create instead non-opaque connectors in which the element variables appear as separate pins of a composite field you can use the declaration

INP class foo #x(10); OUT class foo #y(20);

The difference between opaque and non-opaque connectors is that the former transmit the entire state information of a class instance, while the latter transmit only the part that is captured in the element variables of the class that are visible at the prog unit source level, but with the benefit of making each element variable accessible for the Neo wiring level.

To permit the use of class instances as connectors requires the definition of a further special member function "COPY_xxxx()". It must be implemented as

   class xxxx *COPY_xxxx(class xxxx *pTo, class xxxx *pFrom) { ... }

and the required semantics is to return the handle of a copy of instance pFrom (or a make a default instance, if pFrom=NULL, which must be a permitted argument value). Moreover, when pTo != NULL, pTo will reference another class instance whose ownership is passed to the routine so that *pTo can be used to reduce any memory allocations/reallocations to make the copy (in one extreme case, it suffices to adjust some parameters of *pTo and return pTo as the result; in the other extreme, *pTo is of no use at all and must then be deleted [since ownership has been passed to the routine!] while an entirely newly built copy is returned). If a COPY_xxxx function has been defined, existing class instances can also be assigned to each other by statements such as

y = x; // y must already exist as class foo instance!

Example#3 illustrates the above with two prog_units each of which imports the class foo from example library demolib3.so. The left prog_unit instance has a class instance as its output connector. Each call assigns to the class member variables of the class instance new values, including a call of the member function change() to redimension the array variable pfFloat. The right prog_unit accesses this class instance over a wire and prints the newly set values.

Pin types for non-opaque connectors: in this case, all float*, int*, byte* and char* element variables are mapped to of the corresponding array type.  Alternatively, it is also possible to map each * element variable to a dynamic pin by providing adifferent registration function. If the name of the variable is  "varname" , the required alternative registration function must be of the following form:

          void **DYN_varname(int **ppiDim, class xxxx *pData)

Here, DYN_  is a fixed additional name prefix (any further, user-defined prefixes must be prepended to DYN_). The registration function itself is similar to the registration function that we know so far, but with one additional level of indirection: it must return the address where the address of the start of the array is stored, and *ppiDim must be set to the address of an int variable that holds the dimension of the dynamic array. This then allows the implementation to work with the element variable arrays in the usual way (i.e., ownership of the data is always with the implementation).

For variables that use a DYN_  registration function, any resizes can (in fact, should!) be made without a call to an UPDATE function, provided the new variable address and dimension are written into the locations whose address was registered with the DYN_varname function.  However, there may be situations when a resize would lead to undesireable effects (e.g., when the class instance is used as an INP variable). To safeguard agains such situation, the implementation should equip each class xxxx that uses DYN_  variables with an indicator variable int RESIZE_xxxx. When such a variable is defined, the prog unit will set it to 1 in all situations when a resize of any of its DYN_ variables is permitted, and to 0 otherwise.  The necessary definition looks as follows:

               char *xxxx_c[] =  {   //  xxxx denotes  the class name
                     "myprefix_:",
                      ...  the usual definitions ...
                      "myprefix_RESIZE_xxxx",    // the special RESIZE_xxxx  element variable
                       NULL
                };

                int myprefix_RESIZE_xxxx;    //  the implementation

Any resize operation to a DYN_ variable of class xxxx should then be bracketed in the form

                 if (myprefix_RESIZE_XXXX) { ... the resize op ...  } else { ... diagnostic message ... }

Note that the RESIZE_xxxx element variable is invisible at the prog_unit level and - unlike ordinary class variables - it needs no registration function.A class implementation for which this second registration function is missing for one or more of its array element variables can have its instances only declared in the opaque mode.

Example testdm.c illustrates this with a simple example. A more comprehensive example is the implementation of the dm library that implements the NST datamining connector as a prog unit class data type.

Compatibility control for class assignments: Finally, a (optional) further function "COMPATIBLE_xxxx()" can be specified to limit class assignments as the above to instances that are "sufficiently similar" to be semantically sensible. It must be implemented as

int COMPATIBLE_xxxx(class xxxx *pX, class xxxx *pY) { ... }

and should return 1 when x may be assigned to y (after which y is a copy of x), 0 otherwise. A corresponding restriction will be placed on the connectability of two opaque class connectors.

How to provide Neo popup-information for class connectors

By specifying a further (optional) function "DESCRIBE_xxxx()", which must be implemented as

   char *DESCRIBE_xxxx(class xxxx *pHandle) { ... }

one can define the contents of the popup-menue that appears when a class connector is selected at the Neo level in info-mode. The return value should be a pointer to a string (ownership is not passed to the caller) containing some useful description of the instance referenced by pHandle. In the absence of a DESCRIBE_xxxx definition, a default description is constructed.

How to define functions with class arguments or class return values

Besides the elementary data types int, float, int*, float*, char* and byte*, a  function may return class instances and may contain among its arguments also references to instances of classes. A class argument of class type classname is indicated by the pair class classname. For class member functions, the class name may be omitted when the  parent class of the member function is meant. The value NULL is not permitted for a class argument. Here is an example (more fully worked out in file demolib4.c; this also illustrates a C implementation instead of using C++) that involves three classes and also illustrates the use of overloading in this context:

   char *drawing_c[] = {
      "picture(void)",
      "_0_:", "void add(class)",
      "_1_:", "void add(int, class line)",
      "_2_:", "void add(class point ...)",
   NULL};

   char *line_c[]  = { ... };

   char *point_c[] = { ... };

   char **CLASSES[] = {drawing_c, line_c, point_c, NULL};

Here, "add(class)" (to be implemented under the name _0_add()) is interpreted as "add(class picture)", while the second function (to be implemented under the name _1_add()) expects an integer followed by a handle to a class line instance. The last function (to be implemented as _2_add()) is variadic and expects zero or more handles to class point instances. Example#4 is a prog_unit that imports the above classes from demolib4.so (make it by issueing make demolib4.so) and issues some calls that print messages about their call parameters (demolib4 provides no actual implementation of the suggested operations; each routine is a dummy that just prints information about its arguments).

Class arguments are passed by reference, i.e., the implementation function will get passed the address of the instance and the parser will have ensured that the address points to an object of the correct type expected at its argument position.

Note that it is legal to specify functions with arguments that are classes defined in other #import packages. However, in this case the class name must include the namespace prefix of the #import package from which the class is to be taken (the namespace prefix is separated with a single dot from the base name (e.g.,  class mylib.foo); if the package makes no explicitnamespace definition, the base name of its implementation file will be the default).

A class member function  can also pass as return value an instance of a different class. This requires a declaration of the form

         "class classtype my_class_valued_function(...)"

where classtype must be the qualified name of the return class type (if the return class type is the same class of which the function is a member, the classtype identifier may be omitted). Similar remarks apply to class valued plain (ie., non-member functions). The implementation of a class-valued function must return a pointer to a newly created object of the appropriate type and ownership of the object must be given to the caller.
 

How to define template classes

Template classes are classes that contain parametrized type specifiers. The following example shows how to implement a class foo that appears at the prog_unit level as a template class that has two adjustable type parameters:

      char *Foo_c[] = {
          "TEMPLATE<T2,T5>",  // defines two type parameters T2 and T5
          "foo(int n)",       // constructor
          "fun(int i, T2 x, T5 y)",
          "T5 fun2(z),
          NULL
      };

      ... implementation of member functions (cf. below)

Class foo offers two member functions that contain variable parameter types T1 and T2 in addition to the fixed parameter type int (for a worked example, see file texample.c and circuit texample.NST).

A prog_unit wishing to create a foo instance must specify for each type variable Ti the actual type that is desired for this variable. This is done in the familiar C++ syntax:

         class <hello,world> foo  f(5),g(3);  // choose T2=hello, T5=world for f and g
         class <world,hello>  foo  h(7);       // choose T2=world, T5=hello for h
         class world B(), I();  class hello F();   // example parameters
         // some example calls:
         f.fun(3, F, B);
         h.fun(4, I, F);

Note the f,g and h are from the same class foo, but instantiated to be used with different choices for the types T2 and T5.
Currently, allowable type parameters are restricted to names of #imported non-template classes and to structs defined at the prog unit level. An asterisk * in place of a type name may be used to indicate that the particular template type remains unspecified. This will restrict the callable methods of the instance to those that do not involve any of the unspecified parametrized types (unless the constructor function uses any parametrized type parameters among its arguments, it is allowed to specify all type parameters as * which is equivalent to omitting the <...> part altogether, or to omit a number of trailing type parameters).

IMPORTANT: The semantics of passing parametrized type variables (both via argument or as a return value) is always by reference. This differs from the semantics of returning an array or object instance, which is by copy.

At the implementation level, names of parametrized types are currently restricted to T0..T9 (i.e., currently there cannot be more than 10 type parameters in a class). At the C-level, arguments or return values that are a parametrized type are treated as void* pointers. Since the template class shall work for any combination of choices for its parametrized types (even for types not yet known at the time of the implementation), parametrized type variables must be considered as entirely opaque (never try to implement any specific operation on them). The main use of a template class, therefore, is to provide organized structured storage and access to parametrized type instances (e.g., in lists, trees or graphs). To this end, the following three operations on parametrized types are provided:

   FREE_T(void *x)       :  indicate that the caller has no claims to access object x anymore.
    y = BY_REF_COPY_T(y, x) :  replace an existing reference y to a parametrized type object  with the new reference x (all arguments and return values are of type void*). Also use, if initially y=NULL.
   void * COPY_T(void *x):  return ptr to a newly allocated copy of x.

Note that the implementation does not get passed pointers to the actual object instances, but instead to wrappers that contain additional information to allow FREE_T, BY_REF_COPY_T and COPY_T to work in a generic way, including management of ref counts (including references that may exist in the prog_unit code or other template instances) so that the persistence of all objects is ensured as long as there are still references around. To make this work, never make direct assignments, such as y = x, use y = BY_REF_COPY(y,x) instead! For examples, how to implement template classes, inspect demolib8.c (test circuit is ClassArg.NST); larger examples are in the source structures.c of the library structures.so which uses the flexibility of the template mechanism to provide a general List, Dictionary and Graph data type.

To make them more useful, template class functions may declare among their parameters functions that use the parametrized types also among their arguments. Example:
      ...
     "void foofunction(int i, float (*funarg)(T0,char*,T1), float y)",
     ...

This would define a member function whose middle parameter funarg is a function which expects as its first and last parameter the address of a type T0 and type T1 object, resp. (don't use the syntax T0* or T1* for this!). The main use of this is to permit the implementation of iterator functions that iterate a given function over a set of previously "incorporated" object(-references) of the parametrized type(s).

To return a parametrized type instance x (a void* pointer), simply end the method by return x;  (neither do a copy nor set piDim[-1]).

Examples:   demolib8.c and circuits TemplateDemo-1.HNST and TemplateDemo-2.HNST

Ensuring type compatibility:  for template classes, type compatibility checks (e.g., for connecting pins) must also include checking for matching type argument bindings. This cannot be done at the NST level alone, the implementation has to help a little:  1. provide a char *pcTypeInfo  variable in the object struct  (e.g. foo_t), 2. within the constructor function, initialize this variable with the line
                      pObj->pcTypeInfo = TEMPLATE_T();  // private ptr, don't free later!

(function TEMPLATE_T() is a new predefined system function).  3. Define a "minimal" COMPATIBLE function as follows:

          int COMPATIBLE_foo(foo_t *pObj1, foo_t *pObj2) {
             return pObj1->pcTypeInfo == pObj2->pcTypeInfo;
          }

and include it among the exported functions (an entry  "COMPATIBLE_foo()," in the method definition string list).
Steps 1-3 are only necessary for template classes. For an example and more information, inspect file demolib8.c.
The pcTypeInfo variable can also be used for a minimal implementatin of the DESCRIBE method:

          char *DESCRIBE_foo(foo_t *pObj) {  return pObj->pcTypeInfo; }
 

A final note: when implementing overloaded template classes, avoid overloading a class argument against a template type argument or vice versa:

        "TEMPLATE<T0>",
        ...
        "g1_:", "void bad(class ex.ample  x)",
        "g2_:", "void bad(T0 x)",   //  ambiguity, since T0 might become set to class type ex.ample!

since a template type may be filled with an object argument, this would cause an unresolvable ambiguity (there is currently no automatic diagnosis and warning of such situations!).
 

How to extend a class by new definitions: subclasses

An implementation can use an existing class (defined in the same or a different #import library) as a base class and define a new class (a derived class or subclass) by extending the existing class with new functions and/or variables. This is achieved by using the special keyword "EXTENDS" in the definition of the new class:

     char *derived_c[] = {
           "EXTENDS base FROM lib_of_base",
           "derived(...)",    /* constructor of derived class */
            ...  declarations of further new functions/variables here ...
           NULL
     }

This  defines a new class of name derived that extends the existing class base from #import library lib_of_base (specified without suffix). The "FROM ... " part is only needed, when the library name lib_of_base differs from the name base of the base class. The new class will appear at the prog_unit level as a class that has as its functions/variables the union of the functions/variables of the parent class(es) (in the example: class base) and the newly defined functions/variables (but the constructors of the parent(s) will no longer be accessible at the prog_unit level).

In the following, we explain the C-implementation. The data object of the derived class has the form (we assume that the base class uses the data type base_t for its objects):

     #include "base.h"

     typedef struct derived_ {
        base_t base;  /* instance of the base class */
        ... further elements of class derived here ...
     } derived_t;

I.e., its initial part contains a base instance. Therefore, the derived() constructor(s) first call(s) a base constructor to make a base instance and then resize(s) the base instance to the derived instance to accommodate the remaining data while keeping the base data intact:

     derived_t *derived(...arguments...) {
        base_t *pBase = base(...arguments...);    /* make base instance first */
        derived_t  *pDerived;
        pDerived = (derived_t*) realloc(pBase,sizeof(derived_t));
        ... initialization of additional data in pDerived ...
        return pDerived;
     }

Likewise, the derive destructor first handles destruction of any new data in pDerived and then passes pDerived to the parent class destructor for the remaining clean up (note that the destruction of the enclosing memory block is always left to the top-level parent in the class hierarchy)

      #include "base.h"    /* expect FREE_base to be declared */

     void FREE_derived(derived_t *pDerived) {
        ... free any dynamic elements among the new variables in pDerived ...
        FREE_base((base_t*) pDerived);   /* parent destructor does rest */
     }

If the derived class introduces no additional dynamic variables (i.e., all new elements in the derived_t struct use only memory within the struct) it need not define an own destructor. In this case, it will automaticall inherit the parent class destructor which then is sufficient to handle the situation.

All other functions/variables are defined in the same manner as before (each function will get passed a derived_t handle
as last argument, but can always downcast it to a base_t when access to the base variables is desired).

For function parameters (but not in assignments), a derived class can always represent an instance of its parent class (but not vice versa), e.g., when a function expects a base parameter in the present example, it will also accept a derived parameter. This will never cause a problem, since a derived instance has all base elements as a proper subset and in the same offset locations from its start address.

If the derived class defines a member function that is identical both in name and parameter list with a function in the base class, it will replace this function. If it is identical in name, but differs in the parameter list, it will overload the existing function.

A derived class may only inherit from a single base class (i.e., multiple inheritance is not supported). However, the parent class may itself be a derived class, i.e., one can extend a class in several "layers".

Regarding the LOAD/SAVE methods, the derived class'es LOAD/SAVEfunction needs only to care about its newly defined variables; for the variables of the parent class there will be an automatic invocation of the parent class functions (if the parent class has no LOAD/SAVE functions defined, then, of course, it might be useful to include load/save of parent class variables also in the derived class).

If the derived class resizes any dynamic variables, it must define an UPDATE function pointer, and invoke the UPDATE function after each resize. This will then take proper care of resizes both of new and/or old variables. If the new functions contain no resize operations, no UPDATE function pointer is necessary (any resizes within the base class will be properly handled within the base class implementation and need not be considered in the derived class implementation).

Regarding the COMPATIBLE, COPY and DESCRIBE functions, so far nothing is inherited, i.e., these functions must be entirely newly defined, if needed.

A fully worked out example#5 is contained in the files demolib5a.c, demolib5b.c and demolib5c.c. Demolib5b.c defines a class picture with functions to add points and to print a listing of the added points (no real graphics is involved). Demolib5c.c extends this class by functions to add lines and to translate all objects in a picture. It also replaces the old list function by a new version that can also print the added lines. Demolib5a.c implements the auxiliary classes point and line used by the picture class and its extension. The header files demolib5a.h, demolib5b.h and demolib5c.h define the necessary C-structs and also illustrate how to embed version check info for the prog_unit (explained in the *.c files).

Using a derived class required also to have all definitions for its base classes in memory. The prog unit will try to load them automatically (cf. Autoloading of classes) when a derived class is used; this, however, requires that all base class libraries must be in the dependency list of the library of the derived class. I.e., the derived class should be compiled with a suitable linker option, for gcc this is

    -Wl,rpath,BaseClassLib1,...,BaseClassLibK

where BaseClassLib1...BaseClassLibK is a comma-separated list (including suffix) of all required base class libraries.
Additionally, the BaseClassLibs (with their absolute paths!) must be specified among the objects that are linked into the library for the derived class.

Otherwise, the libraries for the required base classes must be explicitly imported with suitable #import statements.

Currently, a #verbose listing of a derived class may during the first instantiation list only the added elements. Subsequent instantiations will produce a full listing (this is an artefact of a postponed instantiation in the prog_unit that first tries to load all #import libraries before instantiation of derived classes is attempted).
 

How to make class instances respond to NST control calls

Normally, class instances are unaware of any NST control calls sent to their prog_unit.  However, by implementing a class method with the reserved name

   void CTRL_foo(int mode, class foo *pClassObj)  { ....  }

instances of a class foo can be made to respond to NST control calls. The string declaration of this method must be given in the form
    ...
    "CTRL_foo()",
    ... further methods ...

i.e., type and arguments must not be specified (but are implied), similarly as for the other reserved methods (such as LOAD, SAVE, etc.; also any prefix handling is the same). For calls such as INIT or RESET that also cause execution of prog unit code, the calls (one for each class instance) to CTRL_foo() are issued before prog unit source code processing starts. You should not make any assumptions about the order in which the calls to the class objects are issued.

About package initialization and clean-up

The special identifiers _init() and _fini() are usually reserved for global initialization when a shared library is loaded and for clean-up when it is closed. To allow a similar initialization/clean-up on a by-prog_unit basis (i.e., for each #import directive from a prog_unit instance separately), one can define in the FUNCTIONS[] array line the two void functions "_init()" and "_fini()" (obviously, you will also need to define a prefix string, e.g., "myprefix", to avoid a name clash of their implementation with the above special identifiers). They should be declared as

   FUNCTIONS_nnnn[] = { ...,  "_init(int)", "_fini(int)", ... }

and implemented as (note that you must not use the (int,void**) call interface here!)

   void myprefix_init(int iArg) { ... }
   void myprefix_fini(int iArg) { ... }

myprefix_init will be called whenever a prog_unit #imports the library, myprefix_fini will be called whenever the prog_unitinstance is deleted. To keep calls from different prog_unit instances apart, both functions get passed an integer argument iArg that is a unique identifier for the calling prog_unit (this is a new feature in version NST 7.1).

Backward compatibility remark: you also can use the old, parameterless function definitions

   FUNCTIONS_nnnn[] = { ..., "_init()", "_fini()", ... }

with the corresponding implementation

   void myprefix_init(void) { ... }
   void myprefix_fini(void) { ... }

In this case, the prog_unit identifier can be obtained by calling the predefined function int nst_get_prog_unit_id(void) inside both functions (or any other function that is exported for the prog_unit).

The predefined function nst_prog_loadlib(pcName) can be called to ensure that another import library of name pcName (must include suffix) is imported.

How to throw NST exceptions from within #imported objects

Any #imported function (currently the special functions LOAD_xxxx, SAVE_xxxx, RESTORE_xxxx, DESCRIBE_xxxx, COPY_xxxx and the destructor functions are excluded) or object can use the call

   prog_throw(char *pcErrorType, char*pcFmt, ... );

to "throw" a NST exception. This will cause an immediate backjump to the prog_unit invocation point of the function and from there a search for a suitable exception handler. The passed string pcErrorType is used to specify a particular exception type (NST exception handlers will inspect this string and decide on the basis of its contents whether to handle an exception or not). The recommended syntax for pcErrorType is

            pcErrorType = "ident1:ident2:...identk"

where each substring identi consists of alphanumeric characters (no white space) only. The remaining arguments pcFmt,... are processed like in a printf() call to assemble an error message that is propagated with the exception. In the prog unit itself, exception handlers can be implemented in the form of catch-blocks that decide about acceptance of an exception on the basis of a const string argument, e.g.

        //  after call point of #import function (possibly in more outer scope):
       catch ("badop:index") {
          ... handler code for all exceptions thrown with pcErrorType ="badop:index:*"
       }
       catch ("badres") {
          ... handler code for all exceptions thrown with pcErrorType = "badres:*"
       }

The advantage of this type of error handling is that the exception can progagate even more upwards into the NST execution chain where more global exception handlers can be positioned in the form of suitable NST units (e.g., other prog_units). If no handler is found, the exception will end with a printout of the supplied error text plus some additional information abouot the location where the error had occured. For more details, see chapter Runtime Error Handling  and the prog_unit manual page.

How to wrap an existing library as a prog_unit import library

For simplicity, the above assumed that all wrapper code can be directly added to the code of the to-be-used library. If the library is only available as a shared object, the wrapper code has to be placed into a separate library "nnnn.so" which, during initialization, should load the other library into its name space (if this is not already known to be done by other modules). To this end, nnnn.c should contain initialization and clean-up functions such as

   static char *pcLibraryPath = ....;
   static void *pLibraryHandle = NULL;

   void _init(void) {
      if (!pLibraryHandle) pLibraryHandle = dlopen(pcLibraryPath,RTLD_GLOBAL|RTLD_NOW);
      /* now functions in library pcLibraryPath are visible */
      ...
   }

   void _fini(void) {
      ...
      if (pLibraryHandle) dlclose(pLibraryHandle);
   }
   ...
   FUNCTIONS[]/CLASSES[] definitions
   & wrappers for library functions
   ...

Instead of the _init() and _fini() functions, also their NST counterparts defined in the FUNCTIONS[] array can be used.

Statically linked prog_unit import libraries

The FUNCTION_nnnn or CLASSES_nnnn definitions of a prog_unit #import library can also exist in the statically linked part of the executable (this possibility is the reason for the introduction of the _nnnn suffix to allow different library definitions to coexist in the same name space). However, any statically linked import library nnnn must be "registered" by a call

      nst_prog_def_builtin_lib("nnnn");

so that the prog_unit can know that #import "nnnn" should not trigger a search for an external shared library, but should use the library handle of the main program instead. Newly added statically linked import libraries should be prefereably placed in the file nst_prog_libs.c, with a corresponding nst_prog_def_builtin_lib(...) call inserted in the initialization function init_builtin_libraries() in nst_prog_aux.c.

If the builtin library has the name of one of the compiled folders nst/neo_nnnn.c of Neo, its definitions can also be placed in the nst_nnnn.c file. Neo then will automatically issue the necessary registration call, so nothing needs to be added to routine init_builtin_libraries() in this case (Neo automatically issues a registration call nst_prog_def_builtin_lib("nnnn") for each compiled folder "nnnn" during startup).

The purpose of builtin import libraries is to make some prog_unit functions relyably available, without needing to care about the presence of shared objects (this, e.g., simplifies the writing of network portable NST programs for the nstplugin).

Pitfalls

1. Functions with void argument lists: Even if an #import function is specified with a void argument list (e.g.,  "foo(void)"), its implementation will require a parameter list foo(int* piDim, void **ppvArg) (or foo(int *piDim, void **ppvArg, void *pClassHandle) in the case of a class element function), although the parameters remain unused in this case).
There may be no warning if this rule is violated, even the function call may seem to work nicely, until much later strange things can happen during processing of entirely different code portions!

2. Rule 1 does not apply to the special class member functions (such as FREE_nnnn, SAVE_nnnn, COPY_nnnn etc). These are always defined with an empty bracket, but  have non-void, prespecified parameter lists, as explained above.
Rule 1 also does not apply to the oldstyle definition "foo<0>"  (not explained in this tutorial, but in the prog_unit man page, now deprecated and not commented any further).

3. Data of a class instance are not saved, although a SAVE_nnnn method is defined: probably the keyword static is missing in front of the class declaration.

4. When the return types of the implementation of #import functions do not coincide with the specified return type in the FUNCTIONS[] or CLASSES[] array,  strange errors may result in apparently very unrelated places. For the same reason, it is important that function pointers passed via the ppvArg array are accessed with the correct function type.

Summary of restrictions

1. Argument types. All function argument types are restricted to one of the types float, float*, int, int*, char* (only readable), byte* (= for readable and writeable char*), double, double*, long, long*, short, short*, ushort, ushort*, byte and functions of types as described in Chapter How to define functions with function arguments.  Class member functions can additionally have the argument types class (implies reference to instance from parent class) and class classname.Here, long is synonymous for int;  since the types double and double* are not available among the prog unit data types, one may substitute float and float* parameters for such argument (this will cause a transparent conversion and the implementation actually receives the converted doubleand double* parameters). Similarly, for the types long, short, ushort and byte one may substitute int,  and for long*,short*,ushort* one may substitute int* (again with automatic conversion).
2. Variadic functions. Ellipses "..." may occur in the last argument position and may follow any of float*, int*, char*, byte*, class or class classname and denote (together with the preceding type specifier) zero or more parameters of that type. A comma-separated ellipsis as the last argument denotes zero or more arguments of type either float or float*.
3. Return values. Function return values are restricted to the types int, int*, float, float*, byte*, any class type, or void. If no declaration is given, float is the expected implementation default.
4. Constants and element variables. Constants may be declared as int or float and must be implemented as such. If no declaration is given, float is the expected implementation default. Element variables can additionally be of types float*, int*, char* or byte*. Such variables will appear for the importing prog_unit as arrays.An optional readonly type qualifier for float or int element variables allows to forbid the use of such variables as lvalues.
5. Class instances can only occur as static variables. They can neither appear as argument parameters nor as automatic local variables of functions defined in the prog unit source code.
6. Special functions.  The special member functions FREE_xxxx, LOAD_xxxx, Restore_xxxx, SAVE_xxxx, COPY_xxxx, DESCRIBE_xxxx, COMPATIBLE_xxxx, as well as the package initialization/clean-up functions  _init and _fini will remain invisible for the #importing prog_unit.
7. Class arguments. Only the first 26 class definitions in a library can be used as class argument parameters in class member functions of that library.

For backward compatibility, float*, byte* and char* (but not int*) variables are interchangeable as function arguments. This is very different from C and involves an invisible copy-conversion operation, when one in the pair is a float*. For new work, the same effect (without the copying overhead) can be achieved with overloading.

For additional information, see the prog_unit manual page. It also describes an (older) shorthand notation (deprecated!) for the function interface specifications, with some additional possibilities (deprecated!).
 

CORBA Interface

Import libraries allow a very simple implementation of CORBA interfaces. For further details, see the
nstcorba manual.


[PREVIOUS] [NEXT] [UP]