Working With Messages


This section discusses how to construct, access, and destroy a message. To learn about sending a message, see Chapter 2, Connections. The following example program constructs the NUMERIC_DATA message shown in Figure 1 (except for the sequence number property, which is set automatically when a message is sent through a connection). It also shows how to access the message’s values and how to destroy the message. The example code is discussed in detail in the next section.

The source code files for this example are located in these directories:

UNIX:
$RTHOME/examples/smrtsock/manual 
OpenVMS:
RTHOME:[EXAMPLES.SMRTSOCK.MANUAL] 
Windows:
%RTHOME%\examples\smrtsock\manual 

The online source files have additional #ifdefs to provide C++ support. These #ifdefs are not shown to simplify the example.

/* msg.c -- messages example */ 
#include <rtworks/ipc.h> 
 
/* =============================================================== */ 
/*..main -- main program */ 
int main() 
{ 
  T_IPC_MT mt; 
  T_IPC_MSG msg; 
  T_STR str_val; 
  T_REAL8 real8_val; 
 
  TutOut("Create the message.\n"); 
  mt = TipcMtLookupByNum(T_MT_NUMERIC_DATA); 
  if (mt == NULL) { 
    TutOut("Could not look up NUMERIC_DATA msg type: error 
<%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  msg = TipcMsgCreate(mt); 
  if (msg == NULL) { 
    TutOut("Could not create message: error <%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  TutOut("Set the message properties.\n"); 
  if (!TipcMsgSetSender(msg, "/_conan_5415")) { 
    TutOut("Could not set message sender: error <%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  if (!TipcMsgSetDest(msg, "/system/thermal")) { 
    TutOut("Could not set message dest: error <%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  if (!TipcMsgSetPriority(msg, 2)) { 
    TutOut("Could not set message priority: error <%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  if (!TipcMsgSetDeliveryMode(msg, T_IPC_DELIVERY_ALL)) { 
    TutOut("Could not set message delivery mode: error <%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  if (!TipcMsgSetDeliveryTimeout(msg, 20.0)) { 
    TutOut("Could not set message delivery timeout: error <%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  if (!TipcMsgSetLbMode(msg, T_IPC_LB_WEIGHTED)) { 
    TutOut("Could not set message load balancing mode: error 
<%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  if (!TipcMsgSetHeaderStrEncode(msg, TRUE)) { 
    TutOut("Could not set message header str encode: error 
<%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  if (!TipcMsgSetUserProp(msg, 42)) { 
    TutOut("Could not set message user-defined prop: error 
<%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
    TutOut("Append fields.\n"); 
  if (!TipcMsgAppendStr(msg, "voltage")) { 
    TutOut("Could not append first field: error <%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  if (!TipcMsgAppendReal8(msg, 33.4534)) { 
    TutOut("Could not append second field: error <%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  if (!TipcMsgAppendStr(msg, "switch_pos")) { 
    TutOut("Could not append third field: error <%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  if (!TipcMsgAppendReal8(msg, 0.0)) { 
    TutOut("Could not append fourth field: error <%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
    TutOut("Access fields.\n"); 
  if (!TipcMsgSetCurrent(msg, 0)) { 
    TutOut("Could not set current field: error <%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  if (!TipcMsgNextStr(msg, &str_val)) { 
    TutOut("Could not read first field: error <%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  if (!TipcMsgNextReal8(msg, &real8_val)) { 
    TutOut("Could not read second field: error <%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  TutOut("%s = %s\n", str_val, TutRealToStr(real8_val)); 
    if (!TipcMsgNextStr(msg, &str_val)) { 
    TutOut("Could not read third field: error <%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  if (!TipcMsgNextReal8(msg, &real8_val)) { 
    TutOut("Could not read fourth field: error <%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
  TutOut("%s = %s\n", str_val, TutRealToStr(real8_val)); 
    TutOut("Destroy the message.\n"); 
  if (!TipcMsgDestroy(msg)) { 
    TutOut("Could not destroy message: error <%s>.\n", 
           TutErrStrGet()); 
    TutExit(T_EXIT_FAILURE); 
  } 
    return T_EXIT_SUCCESS; /* all done */ 
 }  /* main */ 

Compiling, Linking, and Running

To compile, link, and run the example program, first you must either copy the program to your own directory or have write permission in these directories:

UNIX:
$RTHOME/examples/smrtsock/manual  
OpenVMS:
RTHOME:[EXAMPLES.SMRTSOCK.MANUAL] 
Windows:
%RTHOME%\examples\smrtsock\manual 

Use these commands to compile and link the program:

UNIX:
$ rtlink -o msg.x msg.c 
OpenVMS:
$ cc msg.c 
$ rtlink /exec=msg.exe msg.obj 
Windows:
$ nmake /f msgw32m.mak 

On UNIX the rtlink command by default uses the cc command to compile and link. To use a C++ compiler or a C compiler with a name other than cc, set the environment variable CC to the name of the compiler, and then rtlink uses this compiler. For example, this can be used to compile and link on UNIX with the GNU C++ compiler g++:

UNIX:
$ env CC=g++ rtlink -o msg.x msg.c 

Use these commands to run the program:

UNIX:
$ msg.x 
OpenVMS:
$ run msg.exe 
Windows:
$ msg.exe 

The output from the program is:

Create the message. 
Set the message properties. 
Append fields. 
Access fields. 
voltage = 33.4534 
switch_pos = 0 
Destroy the message. 

Include Files

Code written in C or C++ that uses the IPC Application Programming Interface (API) must include the header file <rtworks/ipc.h>. This file is located in these directories:

UNIX:
$RTHOME/include/$RTARCH/rtworks  
OpenVMS:
RTHOME:[INCLUDE.RTWORKS] 
Windows:
%RTHOME%\include\rtworks 

The SmartSockets IPC API includes all the functions used for interprocess communication.

Constructing a Message

These three steps are required when constructing a message:

  1. Create a message of a particular type.
  1. Set the header properties of the message.
  2. Append fields to the message data.

Step 1

Create a message

A message is created using the TipcMsgCreate function with the message type. This returns a message that is filled in later. For example:

mt = TipcMtLookupByNum(T_MT_NUMERIC_DATA); 
if (mt == NULL) { 
  TutOut("Could not look up NUMERIC_DATA msg type: error <%s>.\n", 
         TutErrStrGet()); 
  TutExit(T_EXIT_FAILURE); 
} 
msg = TipcMsgCreate(mt); 
if (msg == NULL) { 
  TutOut("Could not create message: error <%s>.\n", 
         TutErrStrGet()); 
  TutExit(T_EXIT_FAILURE); 
} 

Step 2

Set header properties of a message

The header (non-data) properties are set using the TipcMsgSetProperty function where Property is replaced by the property being set, such as sender or priority. For example:

if (!TipcMsgSetSender(msg, "/_conan_5415")) { 
  TutOut("Could not set message sender: error <%s>.\n", 
         TutErrStrGet()); 
  TutExit(T_EXIT_FAILURE); 
} 
if (!TipcMsgSetDest(msg, "/system/thermal")) { 
  TutOut("Could not set message dest: error <%s>.\n", 
         TutErrStrGet()); 
  TutExit(T_EXIT_FAILURE); 
} 
if (!TipcMsgSetPriority(msg, 2)) { 
  TutOut("Could not set message priority: error <%s>.\n", 
         TutErrStrGet()); 
  TutExit(T_EXIT_FAILURE); 
} 
if (!TipcMsgSetDeliveryMode(msg, T_IPC_DELIVERY_ALL)) { 
  TutOut("Could not set message delivery mode: error <%s>.\n", 
         TutErrStrGet()); 
  TutExit(T_EXIT_FAILURE); 
} 
if (!TipcMsgSetDeliveryTimeout(msg, 20.0)) { 
  TutOut("Could not set message delivery timeout: error <%s>.\n", 
         TutErrStrGet()); 
  TutExit(T_EXIT_FAILURE); 
} 
if (!TipcMsgSetLbMode(msg, T_IPC_LB_WEIGHTED)) { 
  TutOut("Could not set message load balancing mode: error 
<%s>.\n", 
         TutErrStrGet()); 
  TutExit(T_EXIT_FAILURE); 
} 
if (!TipcMsgSetHeaderStrEncode(msg, TRUE)) { 
  TutOut("Could not set message header str encode: error <%s>.\n", 
         TutErrStrGet()); 
  TutExit(T_EXIT_FAILURE); 
} 
if (!TipcMsgSetUserProp(msg, 42)) { 
  TutOut("Could not set message user-defined prop: error <%s>.\n", 
         TutErrStrGet()); 
  TutExit(T_EXIT_FAILURE); 
  } 

Step 3

Append fields to message data

Fields are appended to the message using one of a number of append functions. These begin with TipcMsgAppendType where Type is replaced by the field type, such as TipcMsgAppendStr. For example:

if (!TipcMsgAppendStr(msg, "voltage")) { 
  TutOut("Could not append first field: error <%s>.\n", 
         TutErrStrGet()); 
  TutExit(T_EXIT_FAILURE); 
} 
if (!TipcMsgAppendReal8(msg, 33.4534)) { 
  TutOut("Could not append second field: error <%s>.\n", 
         TutErrStrGet()); 
  TutExit(T_EXIT_FAILURE); 
} 
if (!TipcMsgAppendStr(msg, "switch_pos")) { 
  TutOut("Could not append third field: error <%s>.\n", 
         TutErrStrGet()); 
  TutExit(T_EXIT_FAILURE); 
} 
if (!TipcMsgAppendReal8(msg, 0.0)) { 
  TutOut("Could not append fourth field: error <%s>.\n", 
         TutErrStrGet()); 
  TutExit(T_EXIT_FAILURE); 
} 

Fields are always appended to the end of the message.

Using the TipcMsgWrite Convenience Function

The TipcMsgWrite convenience function handles a variable number of arguments and allows you to append one or more fields to a message. The function takes enumerated values that begin with T_IPC_FT_TYPE, where TYPE is replaced by a field type as shown in Table 1, such as T_IPC_FT_STR. A final parameter of NULL is used to terminate the variable number of arguments. Using this function, the above values could be added to the message.

For example:

if (!TipcMsgWrite(msg, 
                  T_IPC_FT_STR, "voltage", 
                  T_IPC_FT_REAL8, 33.4534, 
                  T_IPC_FT_STR, "switch_pos", 
                  T_IPC_FT_REAL8, 0.0, 
                  NULL)) { 
  TutOut("Could not append all fields: error <%s>.\n", 
         TutErrStrGet()); 
  TutExit(T_EXIT_FAILURE); 
}
C/C++ functions that use a variable number of arguments often require fewer lines of code to use, but there is no type checking done by the C/C++ compiler on the variable arguments.
 

As an added convenience, TipcMsgWrite also allows enumerated values that begin with T_IPC_PROP_NAME, where NAME is replaced by a message property name, such as T_IPC_PROP_DELIVERY_MODE. For example, the entire message shown above could have been constructed with one call to TipcMsgWrite:

if (!TipcMsgWrite(msg, 
                  T_IPC_PROP_TYPE, TipcMtLookup("numeric_data"), 
                  T_IPC_PROP_SENDER, "/_conan_5415", 
                  T_IPC_PROP_DEST, "/system/thermal", 
                  T_IPC_PROP_PRIORITY, 2, 
                  T_IPC_PROP_DELIVERY_MODE, T_IPC_DELIVERY_ALL, 
                  T_IPC_PROP_DELIVERY_TIMEOUT, 20.0, 
                  T_IPC_PROP_LB_MODE, T_IPC_LB_WEIGHTED, 
                  T_IPC_PROP_HEADER_STR_ENCODE, TRUE, 
                  T_IPC_PROP_USER_PROP, 42, 
                  T_IPC_FT_STR, "voltage", 
                  T_IPC_FT_REAL8, 33.4534, 
                  T_IPC_FT_STR, "switch_pos", 
                  T_IPC_FT_REAL8, 0.0, 
                  NULL)) { 
  TutOut("Could not construct message: error <%s>.\n" 
         TutErrStrGet()); 
  TutExit(T_EXIT_FAILURE); 
} 

Adding Fields by Name

As an alternative to appending fields to a message, you can also add fields to a message and give each field a name. This allows you to access the field using that name. To add fields by name, use the TipcMsgAddNamed* functions, such as TipcMsgAddNamedStr. A single message may contain both fields with names and fields without names. There is no conflict.

For example, the following lines of code operate on the same message. One appends an INT4 and the next adds a string by name, in this case, string one:

/* Add a non-named int4 field, and a named string field */ 
  TipcMsgAppendInt4(msg, 5); 
  TipcMsgAddNamedStr(msg, "string one", "hello"); 

A named field is like any other field in the message, except that it also has a name.

Accessing the Fields of a Message

There are two steps involved in accessing the fields of a message. First, the current field in the message must be set, and then it can be accessed.

Setting the Current Field

By default the fields of a message are accessed in the order they are appended. The first field accessed in the previous example is STR with the value of voltage, the second field accessed is REAL8 with a value of 33.4534, and so on.

The field being accessed is considered the current field. When a field has been accessed, the next field becomes the current field. The order in which the fields are accessed is changed using the TipcMsgSetCurrent function. For example:

if (!TipcMsgSetCurrent(msg, 2)) { 
  /* error */ 
}
The first field in a message is considered field 0.
 

With the current field set to two (2), the field accessed in the previous example would be str with a value of switch_pos, the next accessed value would be REAL8 with a value of zero (0.0). Because this is the last field in the message, unless the current field is reset, no other fields would be accessed. Changing the current field does not change the order of the fields or the number of fields in the message.

The number of fields in a message is found using the TipcMsgGetNumFields function. For example:

if (!TipcMsgGetNumFields(msg, &num_fields)) { 
  /* error */ 
} 

When beginning to access the fields of a message, the current field should always be set first so that the desired field is the current field. In the above example, the current field is set to the first field as follows:

if (!TipcMsgSetCurrent(msg, 0)) { 
  /* error */ 
} 

Accessing the Current Field

The fields of a message are accessed by making calls to the TipcMsgNextType function where Type is replaced by the field type, such as TipcMsgNextStr. To access the first two fields of the message in Figure 1, a call is made to TipcMsgNextStr and TipcMsgNextReal8. Something could be done with these values, such as printing them with TutOut, and then the next two fields could be accessed. For example:

if (!TipcMsgNextStr(msg, &str_val)) { 
  TutOut("Could not read first field: error <%s>.\n", 
         TutErrStrGet()); 
  TutExit(T_EXIT_FAILURE); 
} 
if (!TipcMsgNextReal8(msg, &real8_val)) { 
  TutOut("Could not read second field: error <%s>.\n" 
         TutErrStrGet()); 
  TutExit(T_EXIT_FAILURE); 
} 
TutOut("%s = %s\n", str_val, TutRealToStr(real8_val)); 
  if (!TipcMsgNextStr(msg, &str_val)) { 
  TutOut("Could not read third field: error <%s>.\n", 
         TutErrStrGet()); 
  TutExit(T_EXIT_FAILURE); 
} 
if (!TipcMsgNextReal8(msg, &real8_val)) { 
  TutOut("Could not read fourth field: error <%s>.\n", 
         TutErrStrGet()); 
  TutExit(T_EXIT_FAILURE); 
} 
TutOut("%s = %s\n", str_val, TutRealToStr(real8_val)); 

All pointer-sized field information, such as BINARY, STR, INT4_ARRAY, and MSG values, is accessed with pointers directly into the message data so that no potentially-large memory copies are needed. The data stored in these pointers should not be modified or deallocated (TipcMsgDestroy automatically deallocates the entire message data). All non-pointer field information, such as INT4 and the size of a REAL8_ARRAY, is copied into the appropriate parameter to TipcMsgNextType.

Using the TipcMsgRead Convenience Function

The TipcMsgRead convenience function handles a variable number of arguments and allows you to access one or more fields in a message (and also advance the current field). The function takes enumerated values that begin with T_IPC_FT_ TYPE, where TYPE is replaced by a field type as shown in Table 1, such as T_IPC_FT_STR. A final parameter of NULL is used to terminate the variable number of arguments. Using this function, the first two values could be accessed as follows:

if (!TipcMsgRead(msg, 
         T_IPC_FT_STR, &str_val, 
         T_IPC_FT_REAL8, &real8_val, 
         NULL)) { 
  TutOut("Could not read first two fields: error <%s>.\n", 
           TutErrStrGet()); 
  TutExit(T_EXIT_FAILURE); 
} 
TutOut("%s = %s\n", str_val, TutRealToStr(real8_val)); 

As an added convenience, TipcMsgRead also allows enumerated values that begin with T_IPC_PROP_NAME, where NAME is replaced by a message property name, such as T_IPC_PROP_DELIVERY_MODE.

Accessing Fields by Name

If a message has fields that were added by name, you can access those fields using that name. This is a different approach from the sequential access described in the previous sections. A field that has a name may be accessed either by that name or sequentially.

This example shows accessing fields from a message that contains both named and unnamed fields. It also shows how to get the name of the current field:

#include <rtworks/ipc.h> 
 
/*============================================================*/ 
/*..main -- named fields example */ 
void main(int argc, char **argv) 
{ 
  T_IPC_MSG msg; 
  T_STR str; 
  T_INT4 i; 
 
  /* Create the message */ 
  msg = TipcMsgCreate(TipcMtLookupByNum(T_MT_INFO)); 
 
  /* Add a non-named int4 field, and a named string field */ 
  TipcMsgAppendInt4(msg, 5); 
  TipcMsgAddNamedStr(msg, "string one", "hello"); 
 
  /* Now get the string field */ 
  TipcMsgGetNamedStr(msg, "string one", &str); 
  TutOut("named string field is %s\n", str); 
 
  /* Rewind the index back to the first field, and get the int4 field */ 
  TipcMsgSetCurrent(msg, 0); 
  TipcMsgNextInt4(msg, &i); 
  TutOut("first field is %d\n", i); 
/*  
   * Get the string field again.  Note that we don't have to use the 
   * name to get it, it's still just an indexed field, like any other. 
   */ 
  TipcMsgNextStr(msg, &str); 
  TutOut("second field is %s\n", str); 
/* 
   * Rewind the index pointer again, and we can "name" the int4 field. 
   */ 
  TipcMsgSetCurrent(msg, 0); 
  TipcMsgSetNameCurrent(msg, "int4 zero");  
/* 
   * We can also get the name of the current field. 
   */ 
  TipcMsgGetNameCurrent(msg, &str); 
  TutOut("name of first field is %s\n", str); 
} /* main */ 

Destroying a Message

When a process finishes with a message, the message can be destroyed to free up the memory it occupies. This is accomplished using the TipcMsgDestroy function. For example:

if (!TipcMsgDestroy(msg)) { 
  /* error */ 
} 

TipcMsgDestroy decrements the message reference count and destroys the message if the reference count is zero. TipcMsgDestroy can also call TipcMsgAck to acknowledge the message for GMD. For a detailed discussion of GMD, see Chapter 4, Guaranteed Message Delivery.

Sometimes it is difficult to know when TipcMsgDestroy should be used and when it shouldn’t. Many of the API functions discussed in Chapter 2, Connections, give access to messages at various points of operation, and TipcMsgDestroy must be used with care with these functions. The following simple model can be used to clarify message memory management: most functions do not change who owns the message and who is responsible for destroying it, but some functions give away destroy responsibility and a few functions take away destroy responsibility. In other words, some functions require the caller to call TipcMsgDestroy later, and some functions require the caller not to call TipcMsgDestroy.

These functions give destroy responsibility to the caller:

These functions take destroy responsibility from the caller:

Reusing a Message

If a process is finished with a message, but will be constructing other messages in the near future, the message can simply be reused. All of the non-data properties can be set to other values if necessary, and the function TipcMsgSetNumFields can be used to remove all existing fields from the messages. For example:

if (!TipcMsgSetNumFields(msg, 0)) { 
  /* error */ 
} 

TIBCO SmartSockets™ User’s Guide
Software Release 6.8, July 2006
Copyright © TIBCO Software Inc. All rights reserved
www.tibco.com