TINE offers a wide range of system recognized data types as shown in the list below.
In addition to the fundemental (primitive) data types (e.g. short, float, double, etc.) there are a number of compound format types. For instance, the fundemental types can be paired with a long integer given rise to data formats such as FLTINT (a float value plus a long integer value). Such data types are important when transfering archived data for example, where the returned data consists of a value plus a UTC timestamp. There are also a set of fix-length string format types, such as NAME32, which deliver (an array of) ascii character strings of the given length. There are also a range of data triplets and quadruplets (such as INTFLTINT or NAME16FI or DADDRESS) which are useful in certain circumstances. The most complex format type offered systematically is DUSTRING, offering a quadruplet of data types plus an 80-character string.
In addition to the data type offered, TINE allows user-defined data structures (format CF_STRUCT).
The issue here is the recognition of data sets by client and server over the network. If the client asks for N of something, both sides must know how big (how many bytes) that something is. Furthermore both sides must know how to make sure all the bytes end up in the right order for the platform in question. This is guaranteed to work if you stick to data of a standard format or one of the control system types. If you want to define your own data structure type and use it, things are a bit more complicated but can also work just fine if you pay attention on both the client and server ends.
The first step on each end is to register the structure. You do this at initialization time by declaring a structure tag name and filling in the sizes and locations of the individual elements. Then when a client sends or asks for a structure and it sends the structure tag along with the request, the server knows all about the structure and how to handle it. As an example, we’ll look at another snippet from the sineqm.c routine.
Using tagged structures also has the side benefit of allowing queries from the client-side to determine the structure of the structure. Essentially a client can discover all it needs to know as to how to handle the structure. (see AcquireAndRegisterStruct()).
typedef struct { float a[3]; long b[2]; short c[1]; short reserved; char d[32]; } Test1Struct; #define Test1StructSize ((sizeof(float)*3) +\ (sizeof(long)*2) +\ (sizeof(short)*1) +\ (sizeof(short)*1) +\ 32) /* maximum structure array length you're willing to manage: */ #define MAX_TEST1 10 #define quit(i) { printf("Register struct: out of memory\n"); exit(i); } void registerStructs(void) { /* this must follow the order of the structure explicitly! */ if (AddFieldToStruct("TEST1",OFFSETIN(Test1Struct,a),3,CF_FLOAT,"a")) quit(1); if (AddFieldToStruct("TEST1",OFFSETIN(Test1Struct,b),2,CF_LONG,"b")) quit(1); if (AddFieldToStruct("TEST1",OFFSETIN(Test1Struct,c),1,CF_SHORT,"c")) quit(1); if (AddFieldToStruct("TEST1",OFFSETIN(Test1Struct,reserved),1,CF_SHORT,"reserved")) quit(1); if (AddFieldToStruct("TEST1",OFFSETIN(Test1Struct,d),32,CF_TEXT,"d")) quit(1); /* terminate the structure definition like this! */ if (SealTaggedStruct("TEST1",sizeof(Test1Struct),MAX_TEST1)) quit(1); }
The registerStructs() function above is to be called in an initialization routine on both the client and server end. The client can then simply pass a pointer to (an array of ) the structure in question. The server will then see (an array of ) the structure in question.
The server equipment function might do something like this:
int registerProperties(void) { DTYPE dout; // ... memset(&dout,0,sizeof(DTYPE)); dout.dFormat = CF_STRUCT; dout.dArrayLength = 10; strncpy(dout.dTag,"TEST1",TAG_NAME_SIZE); RegisterPropertyInformation("MYEQM","structTest",&dout,NULL,CA_READ,AT_CHANNEL,0,"structure test",PRP_STRUCTTEST,NULL); // ... } int eqm(char *devName,char * devProperty,DTYPE *dout,DTYPE *din, short access) { int devnr,prpid; // ... devnr = GetDeviceNumberEx(EQPMODNAME,devName,devProperty); if (devnr < 0) return illegal_equipment_number; prpid = GetPropertyId(EQPMODNAME,devProperty); switch (prpid) { //... case PRP_STRUCTTEST: if (dout->dFormat == CF_STRUCT) { /* we'll insist that the caller tag his structure request: */ if (!strncmp(dout->dTag,"TEST1",TAG_NAME_SIZE)) { if (access & CA_WRITE) { if (strncmp(din->dTag,dout->dTag,TAG_NAME_SIZE)) return illegal_read_write; memcpy(&MyTest,din->data.vptr,sizeof(Test1Struct)); } printf("structure TEST1 requested (%d elements)\n",dout->dArrayLength); for (i=0; i<dout->dArrayLength; i++) { ((Test1Struct *)(dout->data.vptr))[i].a[0] = MyTest.a[0] + cnt + i; ((Test1Struct *)(dout->data.vptr))[i].a[1] = MyTest.a[1] + cnt + i; ((Test1Struct *)(dout->data.vptr))[i].a[2] = MyTest.a[2] + cnt + i; ((Test1Struct *)(dout->data.vptr))[i].b[0] = MyTest.b[0] + cnt + i; ((Test1Struct *)(dout->data.vptr))[i].b[1] = MyTest.b[1] + cnt + i; ((Test1Struct *)(dout->data.vptr))[i].c[0] = MyTest.c[0] + cnt + i; strcpy(((Test1Struct *)(dout->data.vptr))[i].d,MyTest.d); } } else { printf("Untagged structure received !!\n"); return illegal_format; } return 0; // etc. ...
A client application wishing to receive this structure might do something like:
... Test1Struct st[10]; DTYPE dout; int i, k, cc; registerStructs(); dout.dFormat = CF_STRUCT; dout.dArrayLength = 10; dout.data.vptr = st; strcpy(dout.dTag,"TEST1"); cc = ExecLinkEx("/TEST/TestServer/device0","StructTest",&dout,NULL,CA_READ,200); if (cc != 0) { printf("Link error : %s\n",erlst[cc]); return; } ...
Java examples can be found here. Visual Basic examples can be found here. C# examples can be found here. MatLab examples can be found here.
Tagged structures can be composed of any other TINE data type, and in particular other (registered) tagged structures. In java, the registration API is essentially independent of data type as the 'addField()' method is overloaded to take any type including other TTaggedStructures. In C, one also uses the 'AddFieldToStruct()' routine to register all of the individual fields, regardless of data type, but here one has to pay attention when registering fields which are also structures. In this case, the data type CF_STRUCT only signals that the field is a structure but do does specify 'which' structure. The structure tag is in this case passed along with the field, using the special syntax '<structure tag>field name'.
As an example, consider the following structure, composed of two other structures:
/* The code section below illustrates how one registers a structure to be exported. */ typedef struct { int a; float b; char t[16]; } StHdr; typedef struct { int c; float d; FLTINT e; } StBod; typedef struct { StHdr hdr; StBod body[4]; } StCmp; typedef struct { float amplitude; float frequency; float noise; float phase; int numberCalls; char description[64]; } SineInfo; #define quit(i) { printf("Register struct: out of memory\n"); exit(i); } void registerStructs(void) { static int done = 0; if (done) return; done = TRUE; /* this must follow the order of the structure explicitly! */ if (AddFieldToStruct("SineInfo",OFFSETIN(SineInfo,amplitude),1,CF_FLOAT,"amplitude")) quit(1); if (AddFieldToStruct("SineInfo",OFFSETIN(SineInfo,frequency),1,CF_FLOAT,"frequency")) quit(1); if (AddFieldToStruct("SineInfo",OFFSETIN(SineInfo,noise),1,CF_FLOAT,"noise")) quit(1); if (AddFieldToStruct("SineInfo",OFFSETIN(SineInfo,phase),1,CF_FLOAT,"phase")) quit(1); if (AddFieldToStruct("SineInfo",OFFSETIN(SineInfo,numberCalls),1,CF_LONG,"numberCalls")) quit(1); if (AddFieldToStruct("SineInfo",OFFSETIN(SineInfo,description),64,CF_TEXT,"description")) quit(1); /* terminate the structure definition like this! */ if (SealTaggedStruct("SineInfo",sizeof(SineInfo),NUM_DEVICES)) quit(1); /* below a status header struct */ if (AddFieldToStruct("StHdr",OFFSETIN(StHdr,a),1,CF_INT32,"a")) quit(1); if (AddFieldToStruct("StHdr",OFFSETIN(StHdr,b),1,CF_FLOAT,"b")) quit(1); if (AddFieldToStruct("StHdr",OFFSETIN(StHdr,t),16,CF_TEXT,"t")) quit(1); if (SealTaggedStruct("StHdr",sizeof(StHdr),NUM_DEVICES)) quit(1); /* below a status body struct */ if (AddFieldToStruct("StBod",OFFSETIN(StBod,c),1,CF_INT32,"c")) quit(1); if (AddFieldToStruct("StBod",OFFSETIN(StBod,d),1,CF_FLOAT,"d")) quit(1); if (AddFieldToStruct("StBod",OFFSETIN(StBod,e),1,CF_FLTINT,"e")) quit(1); if (SealTaggedStruct("StBod",sizeof(StBod),NUM_DEVICES)) quit(1); /* below a struct composed of the above header a 4 X the above body : */ if (AddFieldToStruct("StCmp",OFFSETIN(StCmp,hdr),1,CF_STRUCT,"<StHdr>hdr")) quit(1); if (AddFieldToStruct("StCmp",OFFSETIN(StCmp,body),4,CF_STRUCT,"<StBod>body")) quit(1); if (SealTaggedStruct("StCmp",sizeof(StCmp),NUM_DEVICES)) quit(1); } SineInfo sineInfoTable[NUM_DEVICES]; void init(void) { DTYPE dout; int i; /* register the structure ... */ registerStructs(); /* register the properties which use the structures ...*/ dout.dFormat = CF_STRUCT; dout.dArrayLength = NUM_DEVICES; strcpy(dout.dTag,"SineInfo"); RegisterPropertyInformation(SINEQM_TAG,"SineInfo",&dout,NULL,CA_READ,AT_SCALAR,0,"Sine Curve Information",PRP_SINEINFO,NULL); dout.dFormat = CF_STRUCT; dout.dArrayLength = NUM_DEVICES; strcpy(dout.dTag,"StCmp"); RegisterPropertyInformation(SINEQM_TAG,"Status",&dout,NULL,CA_READ,AT_SCALAR,0,"Status Information",PRP_STATUSINFO,NULL); // etc. ... }
Tagged structures can also be composed of so-called variable length format types, such as CF_STRING (an free, mutable string) or CF_AIMAGE (an adjustable image type) or CF_ASPECTRUM (an adjustable spectrum type). For instance consider the following structure registration:
/* The code section below illustrates how one registers a structure to be exported. */ typedef struct { float amplitude; float frequency; float noise; float phase; char *strfields[4]; DIMAGE imgfield; } FUNKYSTRUCT; #define quit(i) { printf("Register struct: out of memory\n"); exit(i); } void registerStructs(void) { static int done = 0; if (done) return; done = TRUE; /* this must follow the order of the structure explicitly! */ if (addFieldToStruct("Funky",OFFSETIN(FUNKYSTRUCT,amplitude),1,CF_FLOAT,"amplitude")) quit(1); if (addFieldToStruct("Funky",OFFSETIN(FUNKYSTRUCT,frequency),1,CF_FLOAT,"frequency")) quit(1); if (addFieldToStruct("Funky",OFFSETIN(FUNKYSTRUCT,noise),1,CF_FLOAT,"noise")) quit(1); if (addFieldToStruct("Funky",OFFSETIN(FUNKYSTRUCT,phase),1,CF_FLOAT,"phase")) quit(1); if (addFieldToStruct("Funky",OFFSETIN(FUNKYSTRUCT,strfields),4,CF_STRING,"strings")) quit(1); if (addFieldToStruct("Funky",OFFSETIN(FUNKYSTRUCT,imgfield),1,CF_AIMAGE,"image")) quit(1); if (sealTaggedStruct("Funky",sizeof(FUNKYSTRUCT),10)) quit(1); } SineInfo sineInfoTable[NUM_DEVICES]; void init(void) { DTYPE dout; int i; /* register the structure ... */ registerStructs(); /* register the properties which use the structures ...*/ dout.dFormat = CF_STRUCT; dout.dArrayLength = NUM_DEVICES; strcpy(dout.dTag,"Funky"); RegisterPropertyInformation(SINEQM_TAG,"FunkyInfo",&dout,NULL,CA_READ,AT_SCALAR,0,"Sine Curve and other Information",PRP_FUNKYINFO,NULL); dout.dFormat = CF_STRUCT; dout.dArrayLength = NUM_DEVICES; strcpy(dout.dTag,"StCmp"); RegisterPropertyInformation(SINEQM_TAG,"Status",&dout,NULL,CA_READ,AT_SCALAR,0,"Status Information",PRP_STATUSINFO,NULL); // etc. ... }
Here, there the structure as defined in C code contains pointers. The string field is an array of free strings (array of pointers) and the image field contains a pointer to the image frame. Using this structure to send items implies the (temporary) assigning of the pointer references and using this structure to receive items implies that the references assigned following the call completion or callback are 'volatile' and the associated data should be copied into a 'safe' location prior to the next call or callback.
A server processing such a structure might do something similar to the following:
int registerProperties(void) { DTYPE dout; // ... memset(&dout,0,sizeof(DTYPE)); dout.dFormat = CF_STRUCT; dout.dArrayLength = 10; strncpy(dout.dTag,"TEST1",TAG_NAME_SIZE); RegisterPropertyInformation("MYEQM","structTest",&dout,NULL,CA_READ,AT_CHANNEL,0,"structure test",PRP_FUNKYINFO,NULL); dout.dFormat = CF_STRUCT; dout.dArrayLength = NUM_DEVICES; strcpy(dout.dTag,"Funky"); RegisterPropertyInformation(EQM_TAG,"FunkyInfo",&dout,NULL,CA_READ,AT_SCALAR,0,"Sine Curve and other Information",PRP_FUNKYINFO,NULL); // ... } #define NUM_VALUES 100000 BYTE frame[NUM_VALUES]; int gImageFrameSize = NUM_VALUES; DIMAGE gImage; void pushImageBytes(DIMAGE *img) { static int imgSeed = 1; int i; img->sourceHeader.baseTag = 55; img->sourceHeader.cameraPortId = 66; img->sourceHeader.totalLength = 188 + NUM_VALUES; strcpy(img->sourceHeader.cameraPortName,"MY Port"); img->frameHeader.sourceWidth = 100; img->frameHeader.sourceHeight = 200; img->frameBufferSize = NUM_VALUES; for (i=0; i<NUM_VALUES; i++) frame[i] = imgSeed+i; img->frameBuffer = frame; imgSeed = (imgSeed + 1) % 155; } int eqm(char *devName,char * devProperty,DTYPE *dout,DTYPE *din, short access) { int devnr,prpid; FUNKYSTRUCT *fs; DSPECTRUM *sp; DIMAGE *img; // ... devnr = GetDeviceNumberEx(EQPMODNAME,devName,devProperty); if (devnr < 0) return illegal_equipment_number; prpid = GetPropertyId(EQPMODNAME,devProperty); switch (prpid) { //... case PRP_FUNKYINFO: if (din->dArrayLength) { if (din->dFormat != CF_STRUCT) return illegal_format; if (strncmp(din->dTag,"Funky",TAG_NAME_SIZE)) return invalid_structure_tag; if (din->dArrayLength != 1) return dimension_error; fs = (FUNKYSTRUCT *)(din->data.vptr); printf("amplitude: %g\n",fs->amplitude); printf("frequency: %g\n",fs->frequency); printf("phase: %g\n",fs->phase); printf("noise: %g\n",fs->noise); printf("%s\n",fs->strfields[0]); printf("%s\n",fs->strfields[1]); printf("%s\n",fs->strfields[2]); printf("%s\n",fs->strfields[3]); memcpy(&gImage,fs->imgfield,IMAGE_HDR_SIZE); if (fs->imgfield.frameBufferSize < NUM_VALUES) { memcpy(frame,fs->imgfield.frameBuffer,fs->imgfield.frameBufferSize); gImageFrameSize = fs->imgfield.frameBufferSize; gImage.frameBuffer = frame; } } if (dout->dArrayLength) { if (dout->dFormat != CF_STRUCT) return illegal_format; if (strncmp(dout->dTag,"Funky",TAG_NAME_SIZE)) return invalid_structure_tag; if (dout->dArrayLength > NUM_DEVICES) dout->dArrayLength = NUM_DEVICES; fs = (FUNKYSTRUCT *)(dout->data.vptr); for (i=0; i<(int)dout->dArrayLength; i++) { memset(&fs[i],0,sizeof(FUNKYSTRUCT)); fs[i].amplitude = sineInfoTable[(i+devnr)%NUM_DEVICES].amplitude; fs[i].frequency = sineInfoTable[(i+devnr)%NUM_DEVICES].frequency; fs[i].phase = sineInfoTable[(i+devnr)%NUM_DEVICES].phase; fs[i].noise = sineInfoTable[(i+devnr)%NUM_DEVICES].noise; fs[i].strfields[0] = sineInfoTable[(i+devnr)%NUM_DEVICES].description; fs[i].strfields[1] = "and another string"; fs[i].strfields[2] = "and yet another"; fs[i].strfields[3] = "just a filler"; pushImageBytes(&fs[i].imgfield); } } return 0; // etc. ...
A client wishing to receive such a structure might do something like:
BYTE frame[10][100000]; ... FUNKYSTRUCT st[10]; DTYPE dout; int i, k, cc; registerStructs(); dout.dFormat = CF_STRUCT; dout.dArrayLength = 5; dout.data.vptr = st; strcpy(dout.dTag,"Funky"); cc = ExecLinkEx(testTarget,"FunkyInfo",&dout,NULL,CA_READ,200); if (cc != 0) { printf("Link error : %s\n",erlst[cc]); return; } for (i=0; i<dout.dArrayLength; i++) { printf("[%d] ampl %g freg %g noise %g phase %g\n",i,st[i].amplitude,st[i].frequency,st[i].noise,st[i].phase); for (k=0; k<4; k++) { printf("[%d,%d] %s\n",i,k,st[i].strfields[k]); } printf("cam port id %d\n",st[i].imgfield.sourceHeader.cameraPortId); if (st[i].imgfield.frameBufferSize < 100000) { memcpy(frame[i],st[i].imgfield.frameBuffer[0],st[i].imgfield.frameBufferSize); // .... other checking etc. } } ...
Some special purpose data types are also available for use. These include the variable length formats CF_SPECTRUM which passes a USTRING header plus a variable length of float values and CF_IMAGE which passes a DIMAGE header plus a variable length video frame (byte array). A pre-packaged data type DSPECTRUM offers a float array of 4096 bytes, otherwise the user is free to make use of the format and map to a USTRING + float array of any dimension of his choosing. Using the DIMAGE data type requires passing the location of the buffer to hold the video frame which must contain enough room to buffer the maximum sized video frame.
In addition, one can specifically make use of the adjustable variants of SPECTRUM or IMAGE for use in arrays. These correspond to the format type specifications CF_ASPECTRUM and CF_AIMAGE, and map to the data type definitions ASPETRUM and DIMAGE. Note that both CA_ASPECTRUM and CF_AIMAGE take the number of array elements as the 'dArrayLength' field of the DTYPE object (C-Lib) or TDataType object (jave, .NET).
int getspectarray(void) { DTYPE dout; ASPECTRUM asp[10]; int i, cc; dout.dFormat = CF_ASPECTRUM; dout.dArrayLength = 10; dout.data.vptr = asp; dout.dTag[0] = 0; cc = ExecLinkEx("/TEST/SineServer/SineGen0","SineSpectrum",&dout,NULL,CA_READ|CA_MUTABLE,1000); printf("call returned (%d values):\n",dout.dArrayLength); if (cc == 0) { for (i=0; i<dout.dArrayLength; i++) { printf("%s (%g): %d values\n",asp[i].comment,asp[i].s_inc,asp[i].spectBufferSize); displaySineSpectrum(asp[i].spectBufferSize,asp[i].spectBuffer); } } // ... }
Bit fields make use of the format types CF_BITFIELD8, CF_BITFIELD16, and CF_BITFIELD32. Bit fields registered similar to structures, with the exception that the object length is known a priori. under most circumstance the users does not make use of the data type DBITFIELD but uses either the registration routines OpenBitField() and AddFieldToBitField() or the access routines GetFieldFromBitfield() and GetBitfieldAsString().
The data type CF_HISTORY is a special format used for archive data retrieval and is designed to be able to provide a data timestamp and other data stamps and to carry any other format type. This is used exclusively in archive calls.
The data type CF_STRING can be used to transport non-fixed length string arrays. Fixed length string arrays (e.g. arrays of NAME16, NAME32, NAME64, or USTRING entities) are often the most efficient way of passing arrays of string data, as they can be easily traversed, individual elements can be easily located, and there is no sudden memory reallocation when the carried string lengths change. However, the ability to transfer and to think in terms of an array of strings of any length is freqently desireable (no matter how inefficent it might turn out to be). Keep in mind that a string itself is fundementally an array of characters terminated by the character '0'. So a string array becomes an array of pointers. The TINE format CF_STRING will manage all of the necessary string (re-)allocation for the caller. An example of server code written in C making use of CF_STRING is given below. We see that even in C this is a straightforward matter, even when forced to deal with pointers and the like.
char *testStrings[] = { "string number 1","string number 2","string number 3","string number 4", "really and truly, fantastically, incredibly, unimaginatively, you-wouldn't-believe-it's-possible, long string 5", "really and truly, fantastically, incredibly, unimaginatively, you-wouldn't-believe-it's-possible, long string 6", "really and truly, fantastically, incredibly, unimaginatively, you-wouldn't-believe-it's-possible, long string 7", "really and truly, fantastically, incredibly, unimaginatively, you-wouldn't-believe-it's-possible, long string 8", "str 9", "str 10" }; int numStrings = sizeof(testStrings)/sizeof(testStrings[0]); int eqm(char *devName,char * devProperty,DTYPE *dout,DTYPE *din, short access) { int devnr,prpid; // ... devnr = GetDeviceNumberEx(EQPMODNAME,devName,devProperty); if (devnr < 0) return illegal_equipment_number; prpid = GetPropertyId(EQPMODNAME,devProperty); switch (prpid) { //... case PRP_STRINGARRAY: if (dout->dArrayLength > 0) { if (dout->dFormat != CF_STRING) return illegal_format; for (i=0; i<dout->dArrayLength && i<numStrings; i++) { dout->data.strptr[i] = testStrings[i]; } } return 0; // ... } return illegal_property; }
An example of client code receiving a string array using CF_STRING is given below.
// ... DTYPE dout; char *strings[10]; int cc, i; dout.dFormat = CF_STRING; dout.dArrayLength = 10; dout.data.strptr = &strings; dout.dTag[0] = 0; cc = ExecLinkEx("/TEST/WinSineServer/SineGen0","StringArray",&dout,NULL,CA_READ,1000); printf("call returned: %d\n",cc); if (cc == 0) { for (i=0; i<10; i++) printf("%s\n",strings[i]); }
CF_STRING may also be used within tagged structures.
List of format type macro definitions:
1.5.8