NAME

nst_remote_var -- create unit to import variables

PROTOTYPE

unitptr nst_remote_var( char *pcInputs, char *pcOutputs, char *pcMethods, char *pcOptions, unitptr uHost)

ARGUMENTS

char *pcInputs
a (comma or space separated) list of names of variables (or variable groups) to be made accessible as input pins (or pin groups) of the unit. A semicolon (instead of a comma or space) will end the current and start a new input field.
char *pcOutputs
dito for the output side
char *pcMethods
a comma separated list of method names. The first list element will become the exec function. To omit an exec function, supply a minus sign as the first name.
char *pcOptions
Expects name to identify variable set, optionally followed (no commas! Separator is space!) by a server name. An optional -v or %v enables verbose mode. If no server name is given, the unit will ??? program.
unitptr uHost
host unit

SYNOPSIS:

Make selected C or C++ variables in a different process accessible as input or output pins of a NST remote_var unit when the processs has a user-written, very simple lookup routine. The lookup routine is identified by a symbolic name and has to follow a simple interface convention for 'publishing' a set of variables in a type-safe way to NST. 'Publishing' is based on a simple interface declaration string, containing a human-readable C-style declaration of the to-be-exported variables (similar to a prog_unit loadlib-interface). A larger number of variables may be organized into several smaller subsets, each with their own lookup routine. The variable subsets may overlap, and the same application variable(s) may even be 'published' under different symbolic names by different lookup routines. Besides variables, an remote_var unit can by the same mechanism also import a parameterless function as its exec-method. Additional parameterless functions can be bound as pin-less method subunits, turning the remote_var unit into a class container. Finally, the option to use a library mutex facilitates working with multiple threads.

SERVER NAME AND COMMUNICATION PROTOCOL:

A server name a.b.c.d:portnr selects the TCP protocol with server address a.b.c.d and port number portnr. Alternatively, one may also use the form hostname:portnr. Any other server name name selects the UNIX protocol via a socket name (this restricts communication to within a file system). Remote server and local client must use the same representation for int and float values (however, this is checked and an error message issued when the check fails).

RETURN VALUE:

A pointer to the created unit or NULL in the case of an error.

INTERFACE OF CREATED UNIT:

For each variable that has been selected for input (output), a corresponding input (output) pin. Input (output) variables are selected by specifying their symbolic name as comma or semicolon-separated elements of argument string pcInput ( pcOutput). Commas separate variables as pins of the same field, semicolons partition the pin sequence into fields.

EXECUTION OF CREATED UNIT:

Write all input variables and fetch all output variables. If an exec method was specified for the unit, this method is executed after all input variables were and before any output variables are set. If the definitions specify a lock(),unlock() function pair (cf. below), the above is bracketed between a pair of lock(),unlock() calls.

THE LOOKUP ROUTINE:

To make a number of variables accessible for an remote_var unit, the exporting application or library has to export a specially written lookup routine. Writing such a routine is very simple and most easily explained along with an Example: assume you wish to export the following variables

   float a,b,*c; double u,v[3]; char ac[]="hello";

as a set named "mygroup". Then, the remote_var unit expects the lookup routine to be named VARIABLES_mygroup or VAR_mygroup (tried as a second option, provided only for shortsighted linkers) and to have parameters (int*,int) and a void* return value (in both cases, only the 'base name' mygroup should be specified in pcOptions). The call

   VARIABLES_mygroup(NULL,-1)

must return a pointer to a (static!) description string that informs the remote_var constructor routine about the available variables. It differs from the variable's C-style declaration mainly in that array dimensions specifiers and initializers have been removed, i.e, in our example it would read

   "float a,b,*c; double u,v[]; char ac[];"

Additionally, any call

   VARIABLES_mygroup(&dim,k)

must return the address and set dim to the dimension of the k-th variable that is mentioned in the description string (.dim needs only be set for non-scalar variables). Information in the passed value of dim: if the call is in the context of a read access, then dim<0. You can take a non-negative dim>=0 as a hint that the call is in the context of a write-access for dim elements. You may (but need not!) resize the selected variable accordingly. In either case, the return value must be the dimension of the variable at the time when the routine returns. Therefore, a suitable implementation might look as follows (assuming that DimOfC() is some available routine that tells the current allocation dimension of the dynamic variable *c): void *VARIABLES_mygroup(int *dim, int k) static char DefStr[]="float a,b,*c; double u,v[]; char ac[]";

   switch (k) {
      case -1: return DefStr;
      case 0: return &a;
      case 1: return &b;
      case 2: *dim=DimOfC(); return c;
      case 3: return &u;
      case 4: *dim=3; return v;
      case 5: *dim=sizeof(ac)/sizeof(char);
               return ac;
   }

The general pattern is:

- VARIABLES_xxxx(&dim,k) must return the address of the k-th variable in the group. If the variable is non-scalar, dim must be set to its dimension. - VARIABLES_xxxx(NULL,0) must return a string holding the definitions (arrays indicated with empty brackets) of the variables: this permits the calling remote_var unit to get for each variable a symbolic name, type info and the positional index k in the group (which is the position of the variable's definition in the string). Note that the latter operation is only done once when the remote_var unit is constructed. After parsing the string, it knows once and for all for each variable the associated serial index k for fast access to the address and dimension. The difference between definitions "c[]" and "*c" is that the former appears as a fixed dimension array pin, the latter as a dynamic array pin (only in that case, the variable may also become redimensioned on the C/C++ side). The definition string may also use the non-NST types double and short. For vector variables, they will be converted to float and int, resp. Scalar variables (including char and byte) will always appear as scalar float pins on the NST side, with byte interpreted as unsigned char.

ADDITIONAL OPTIONS FOR THE LOOKUP ROUTINE:

The definition string may contain before each type declarator the additional keywords const or readonly (equivalent to const) in order to forbid such variables to become selected for input pins, thereby protecting them against any change through an remote_var unit. The somewhat rigid counting scheme for the k-values can be made more flexible by inserting 'numbering anchors' of the form #n: (also just n: suffices), which force k-counting to continue with the value k=n for the next variable. Note that any renumbering needs corresponding changes for the case values in the lookup-routine (and, as a consequence, never affects the NST side). Using #n: to create generous numbering gaps facilitates the subsequent introduction of further definitions without changes to already existing case-values in the lookup routine. Note also that the names in the definition string need not coincide with the C-names: the actual C-variables for which the lookup routine returns their data can be chosen at will (but, of course, fatal errors will occur, if their type specifications and dimensions do not match with associated specification in the description string). To facilitate import of entire groups of variables by a single symbolic name, groups of variable definitions may be enclosed in curly brackes, with a group name prepended: DefStr="float a,b; foodouble u,v[]; char ac[];" This will permit to import the three variables u,v[],ac[] under the single name of foo. Note that introducing a group name does not change the serial position index for the other items. Additionally, the description string may contain newlines for print formatting (useful with the -v option of the remote_var unit that sends a copy of the description string to stderr) and comments. These are introduced with a '/' or '//', and are ended by the next single '/' that is not on both sides adjacent to an alphanumeric char, or the next newline. With some of these features, our example could also be written as

   void *VARIABLES_mygroup(int *dim, int k) {

   static char DefStr[]=
   "float A,B, /two scalars/ *C; /a pointer \n"
   "foo { #20: / start group + renumber \n"
   " double U,V[];// NST converts to float \n"
   " #10: const char AC[]; //some fixed text\n"
   "} // end group foo";

   "float A,B,*C; #20: double U,V[]; #10: char AC[]";

   if (!dim) return DefStr;
   else switch (k) {
      case 0: return &a;
      case 1: return &b;
      case 2: *dim=DimOfC(); return c;
      case 20: return &u;
      case 21: *dim=3; return v;
      case 10: *dim=1+strlen(ac); return ac;
   }
with now capital letter names for the same variables, and the possibility to import the sequence of the three variables u,v,ac[] by the single name foo. Note also that the initial call VARIABLES_mygroup(dim,-1) that retrieves the definition string may also be used to accomodate initializiation operations that may be needed, e.g., to initialize mutex attributes etc. If a group of variables of the same type is known to lie contiguous in memory, one may access them as a single vector, e.g.

   float a,b,c[10];

can be accessed from the NST unit as a single 12-dimensional vector named abcvec by specifying the definition string as

   DefStr[] = "#n: float abcvec[12]"

with a corresponding line

   case n: dim=12; return &a;

This is also more efficient (1 lookup routine call instead of 3). Char* variables with a constant initializer, such as

   char *pc="hello";

should always be specified as const or readonly to protect them against write access: the compiler may have put them as read-only strings into memory. In this case, an attempt by the NST unit to write into them will cause a segmentation fault.

PROVIDING ACCESS TO PARAMETERLESS FUNCTIONS:

The definition string may also contain declarations for parameterless functions, e.g.:

   DefStr="float a,b; hello(),bar(); int x; .."

Such function definitions are recognized by a pair of round brackets and the absence of a type specifier. Function definitions are included in the positional numbering of variables and the lookup routine must simply call the respective routine when it receives the associated k-index as its second argument. I.e.

   ...
   case k: return hello();
   ...

The pcMethods string of the remote_var unit may then specify a (comma-separated) list of such function names: the first element in the list will become the exec-method of the created unit (and will then be called after the input pin values were set and before the output pin values are set). Each further element will give rise to a pinless method subunit with the parameterless function as its exec method. (if the created unit itself shall remain without binding to a function, supply a minus sign for the name of the first method). In the library, the exported methods may use the exported variables as their context. Thus, a variable group with a set of parameterless methods can act much like a general class (of which only a single instance is possible, however). Note that a more powerful interface (at the expense of requiring a bit more programming effort) to export classes into NST is offered by the prog_unit loadlib interface). PLACING INITIALIZATION/DEINITIALIZATION CODE: A special status applies to parameterless functions of name _init() or _fini(): if present, _init() ( _fini()) will be called before (after) any of the exec unit operations (fetching input pin values, calling the exec method, setting output pin values) are carried out. This happens automatically on each exec_unit call of the remote_var unit. It may be useful, e.g., to bracket the variable access in the server in a pair of lock/unlock calls to a mutex in order to avoid concurrent access in the server. When a var_input unit is created, its first operation is to retrieve the definition string with a call

   VARIABLES_xxxx(&dim,-1);

This first call will pass in the dim parameter a unique positive integer that identifies the calling remote_var unit instance. Later, when the remote_var unit is removed, there will be a second call, but this time with dim set to the negative id value. This permits to invoke instance-specific (or general, by just looking at the sign of dim) initialization or de-initialization operations if such are needed.

SUMMARY OF VARIABLE CALLS:

For convenience, here is a short list of the different calls of the VARIABLES_xxxx routine that the implementation must be prepared of:
VARIABLES_xxxx(&dim,-1):
dim>0 : initialization call. Must return pointer to static copy of variable declaration string. Optionally: carry out initialization operations, may use value of dim as id of the client-side remote_var unit.
VARIABLES_xxxx(&dim,-1):
dim<0 : de-initialization call. Allows to carry out de-initialization operations in server. Value -dim will be the same id of the cliend-side remote_var unit as in the initialization call. Return NULL pointer.
VARIABLES_xxxx(&dim,k):
(k non-negative) : request of address of variable defined with serial position k in definition string. If dim>0: this is a read-access, expecting dim elements to read. If the actual number of available elements differs, set dim to the actual value. If dim<0: this is a write-access, expecting (after having received the address) to deposit -dim>0 elements in the variable. If the actual number of available locations is smaller, optionally re-allocate the variable before returning its (new!) address. In all these cases: replace dim by the actual (non-negative!) number of elements that are available for access, and return their starting address.
VARIABLES_xxxx(NULL,k):
(k non-negative) : request to call parameterless function defined at serial position k in the definition string. Return value unused.

PROVIDING INFORMATION ABOUT VARIABLE SETS:

A library package may implement a variable

   char VARINDEX_sharedlibname[] =
   "name1 name2 ... namek";

(or char VARINDEX[]="..." in main()) to provide lookup information about exported variable sets (or anything the package wishes to inform about): the string itself may be arbitrarily formatted, it will just be printed when a remote_var unit is created with -v or %V given as option alone, or in response to certain error situations.

SEE ALSO:

The (somewhat more complex) prog_unit loadlib interface permits to map entire classes, including class methods.

FILE

/amnt/loge/users/nistaff02/nistaff/rhaschke/nst7/man/../o.linux//../remotesrc/nst_remote.c