[Previous] [Contents] [Index] [Next]

Interprocess Communication and Lengthy Operations

This chapter discusses:

The QNX and Neutrino operating systems support various methods of interprocess communication (IPC), including:

These methods can be used in a Photon application, as long as you're careful. The Photon main event-handling loop that your application calls is responsible for handling Photon events so that widgets update themselves and your callback functions are called.

This simple event-driven model of programming used with the Photon widget library presents some challenges for the application developer because the event-handling loop performs an unconditional Receive() to obtain events from Photon. This means your application has to be careful if it needs to call Receive(), or Photon events might go astray and the user interface might not be updated.

If you need to:

you'll need a way to hook your application code into the event-handling loop. Similarly, you may want to be able to add time-outs to your application and associate callback functions with them.

If one of your callback functions takes a long time to execute, events won't be dispatched during that interval and the application will appear to be "hung" as far as the user is concerned. You should develop a strategy for handling lengthy operations within your application.

Sending QNX messages

A Photon application can use Send() to pass messages to another process, but the other process needs to Reply() promptly, as Photon events aren't processed while the application is blocked.

As an example, here's a callback that extracts a string from a text widget, sends it to another process with ID proc_b, and displays the reply in the same text widget:

/* Callback that sends a message to another process      */
/*                            AppBuilder Photon Code Lib */
/*                                         Version 1.13A */

/* Standard headers */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/kernel.h> // Needed for Send()

/* Toolkit headers */
#include <Ph.h>
#include <Pt.h>
#include <Ap.h>

/* Local headers */
#include "globals.h"
#include "abimport.h"
#include "proto.h"


int
send_msg_to_b( PtWidget_t *widget, 
               ApInfo_t *apinfo, 
               PtCallbackInfo_t *cbinfo )

{

  PtArg_t args[1];
  char *a_message;

  /* eliminate 'unreferenced' warnings */
  widget = widget, apinfo = apinfo, cbinfo = cbinfo;

  // Get the string from the text widget.

  PtSetArg (&args[0], Pt_ARG_TEXT_STRING, 0, 0);
  PtGetResources (ABW_msg_text, 1, &args);

  // Send the string to another process.

  a_message = (char *)args[0].value;
  if ( Send (proc_b, a_message, rcv_msg, 
             msg_size, msg_size) == -1)
  {
    perror ("Send to B failed");
    exit (-1);
  }

  // Remember the UI is "hung" until the other
  // process replies!

  // Display the reply in the same text widget.

  PtSetArg (&args[0], Pt_ARG_TEXT_STRING, rcv_msg, 0);
  PtSetResources (ABW_msg_text, 1, &args);

  return( Pt_CONTINUE );

}

The application's initialization function determines proc_b by calling qnx_name_locate() as described in the Watcom C Library Reference. For more information on messages, see the QNX System Architecture guide.

Receiving QNX messages

To obtain events from Photon, the widget library performs an unconditional Receive(), placing the received message in the event buffer for the application context. If the message isn't a Photon event, it will be discarded unless you register an input handling procedure (or input handler) in your application.


Note: Don't call Receive() with a process ID of 0 anywhere else in your application. If you do, Photon events won't be processed properly and the user interface won't be updated.

You can call Receive() for a specific function, but this isn't a good idea. Remember that your application and its interface will be blocked until that process sends a message. It's better to use an input handler as described in this section.


An input handler is responsible for handling messages received by the application from a particular process ID (PID). When you register the handler with the widget library, you identify the PID the input handler is associated with.

You can define more than one input handler in your application for a PID, but only the last one registered is called by the widget library when a message is received from that process.

You can register a nonspecific input handler by specifying a value of zero for the PID. This handler is called when the application receives any non-Photon messages that don't have an input handler specifically associated with the sender's PID.

Adding an input handler

To register an input handler, call PtAppAddInput() when you initialize the application. The syntax is given below; for more information, see the Photon Library Reference.

PtInputId_t *PtAppAddInput( 
                 PtAppContext_t app_context,
                 pid_t pid,
                 PtInputCallbackProc_t input_func,
                 void *data );

The arguments are:

app_context
The address of the application context, a structure that manages all the data associated with this application. For Photon 1.1x, this must be specified as NULL, so that the default context is used.
pid
The ID of the process whose messages this handler is to deal with, or 0 if the handler is for messages from all processes.
input_func
Your input handler.
data
Extra data to be passed to the input handler.

PtAppAddInput() returns a pointer to an input-handler ID, which you'll need if you want to remove the input handler later.

The prototype for an input handler is as follows:

int input_proc( void *data,
                pid_t rcvid,
                void *msg,
                size_t msglen );

The arguments are:

data
A pointer to any extra data you want to pass to the input handler.
rcvid
The ID of the process that sent the message:
msg
A pointer to the message sent.
msglen
The size of the message buffer.

You can also declare the input handler to be of type PtInputCallbackProcF_t to take advantage of the prototype checking done by the compiler.


Note: If your input handler changes the display, it should call PtFlush() to make sure the display is updated.

Removing an input handler

To remove an input handler:

Message buffer size

As described above, arguments to your input function include:

msg
A pointer to an event buffer that was used to receive the message.
msglen
The size of the buffer.

This buffer might not be large enough to hold the entire message. One way of handling this is to have the first two bytes of the message indicate the message type and from that determine how big the message should be. Once you know the message size, you can:

Alternatively, you can set the event buffer to be the size of the largest message your application will receive (if known). This can be done by calling PtResizeEventMsg(). You'd typically call this before you expect to receive any messages.


Note: PtResizeEventMsg() won't reduce the message buffer beyond a certain minimum size. This is so that the widget library will continue to function.

Example - logging error messages

The following code fragment shows how a nonspecific input handler may be used to respond to error-logging messages from any process. When one of these messages is received, the application displays the message's contents in a multiline text widget. (This example assumes log_message is declared elsewhere.)

int input_proc(void *client_data, pid_t pid, void *msg,
               size_t msglen)
{
   struct log_message *log = (struct log_message *)msg;

   /* Only process log messages */
   if (log->type == LOG_MSG)
   {
      PtWidget_t *text = (PtWidget_t *)client_data;
      struct log_message header;
      int msg_offset = offsetof(struct log_message, msg);
      int log_msglen;
      int status;

      /* See if our entire header is in the buffer --
         it should be */
      if (msglen < msg_offset)
      {
         /* Read in the whole header */
         if (Readmsg(pid, 0, &header, msg_offset)  == -1)
         {
            status = errno;
            Reply(pid, &status, sizeof(status));
            return 0;    /* bail out */
         }
         log = &header;
      }

      log_msglen = msg_offset+log->msg_len;

      /* See if the whole message is in the buffer */

      if (msglen < log_msglen)
      {
         struct log_message *log_msg = 
            (struct log_message *)alloca(log_msglen);

         /* Read the remainder of the message into
            space on the stack */

         if (log_msg == NULL || Readmsg(pid, 0, log_msg,
             log_msglen) == -1)
         {
            status = errno;
            Reply(pid, &status, sizeof(status));
            return 0;    /* bail out */
         }
         log = log_msg;
      }

      add_msg(text, log);
      status = 0;
      Reply(pid, &status, sizeof(status));
   }

   return Pt_CONTINUE;
}

main(int argc, char *argv[])
{
   union {
      struct log_message msg;
      char   msgbuf[1<<10];
   } startup_msg;
   PtWidget_t    *window, *text;
   PhDim_t       dim;
   PtArg_t       arg[2];
   int           nargs;
   int           name;
   PtInputId_t   *id;

   if ((name = qnx_name_attach(0, "logger")) == -1)
      exit(EXIT_FAILURE);

   nargs = 0;
   PtSetArg(&arg[nargs], Pt_ARG_WINDOW_TITLE, "Error Log",
            0); nargs++;
   if ((window = PtAppInit(NULL, &argc, argv, nargs,
                           arg)) == NULL)
   {
      qnx_name_detach(0, name);
      exit(EXIT_FAILURE);
   }
   dim.w = 320;
   dim.h = 240;
   nargs = 0;
   PtSetArg(&arg[nargs], Pt_ARG_DIM, &dim, 0); nargs++;
   text = PtCreateWidget(PtMultiText, window, nargs, arg);

   strcpy(startup_msg.msg.msg, "Started error log");
   startup_msg.msg.msg_len = strlen(startup_msg.msg.msg);
   startup_msg.msg.msg_severity = MSG_INFO;
   add_msg(text, &startup_msg.msg);

   /* Set an input proc to receive from anybody */
   id = PtAppAddInput(NULL, 0, input_proc, text);

   PtRealizeWidget(window);

   PtMainLoop();
}

This application registers the input_proc() function as an input handler for handling non-Photon messages from any other process.

The input_proc() function first checks the message type of the incoming message. If the input handler isn't responsible for this type of message, it returns immediately. This is important because any other nonspecific input handlers that were registered will be called as well, and only one of them should respond to a given message.

If the type of message received is a log message, the function makes sure that Photon has read the entire message into the Photon event buffer. This can be determined by looking at the message length provided as the msglen to the input handler. If part of the message isn't in the event buffer, a message buffer is allocated and Readmsg() is called to get the whole message. The input_proc() function then calls add_msg() to add the message to the text widget and replies to the message.

When input_proc() is complete, it returns the value Pt_CONTINUE. This instructs the Photon widget library not to remove the input handler.

Example - replying to sin ver commands

Here's an input handler that replies to messages sent by the sin ver command:

#include <errno.h>
#include <sys/kernel.h>
#include <sys/psinfo.h>
#include <sys/sys_msg.h>

typedef struct {
    struct _sysmsg_hdr      hdr;
} message_t;

typedef struct {
    struct _sysmsg_hdr_reply        hdr;
    struct _sysmsg_version_reply    version;
} reply_t;

/* For a PhAB app do the following two lines from the application
 * initialization function.
 * For a non-PhAB app do the following two lines from main() before
 * calling PtMainLoop().
 */

/* Register as a process that has versions to report; sin ver will
 * then send us version messages.
 */

qnx_pflags(_PPF_SERVER, _PPF_SERVER, NULL, NULL);

/* Tell the main loop to call input_function() when a non-photon
 * message arrives.
 */
PtAppAddInput(NULL, 0, input_function, NULL);


/*
 * And now to handle the version request message from sin ver ...
 */

int
input_function(void *data, pid_t rcvid, void *msg, size_t size)
{
    message_t *vmsg = (message_t *) msg;
    reply_t reply;

    if (vmsg->hdr.type == _SYSMSG &&
        vmsg->hdr.subtype == _SYSMSG_SUBTYPE_VERSION) {
        reply.hdr.status = EOK;
        strcpy(reply.version.name, "My App");
        strcpy(reply.version.date, __DATE__);
        reply.version.version = 123; /* = 1.23 (i.e. version times 100) */
        reply.version.letter = 'B'; /* will be 1.23B */
        reply.version.more = 0; /* no more versions to report */
        Reply(rcvid, &reply, sizeof(reply));
    } else {
        /* don't recognize this message */
    }

    return(Pt_CONTINUE);
}

Photon pulses

In addition to synchronous message-passing, Photon supports an asynchronous method of communication called a pulse. A process that wants to notify another process but doesn't want to wait for a reply can use a Photon pulse. For example, a server can use a pulse to contact a client process in a situation where sending a message would leave both SEND-blocked (and hence deadlocked).

The implementation of Photon pulses depends on the platform your application uses - they were made to be portable.


QNX 4: Under QNX 4, a Photon pulse uses a proxy.
Neutrino: Under Neutrino, a Photon pulse uses Neutrino pulses.

In a future version of Photon for Neutrino, the functions may generate either a Neutrino pulse or a realtime signal, depending on whether your application is using a channel when you call PtPulseArmPid(). Signals are cheaper-they don't require a channel-but they don't obey the priorities of pulses.

If you want to make sure that it will be a pulse, call PtChannelCreate() at the beginning of your application.


A Photon pulse is identified by a negative PID that can be used as the pid argument to PtAppAddInput(). This PID is local to your application. If you want another process to send pulses to you, you must "arm" the pulse using PtPulseArmPid() or PtPulseArmFd(). This creates a PtPulseMsg_t object that can be sent to the other process in a message. The other process will then be able to send pulses using the PtPulseDeliver() function.


QNX 4: In QNX 4, the PtPulseMsg_t object is a pid_t containing the PID of the (virtual) proxy.
Neutrino: In Neutrino, PtPulseMsg_t is a sigevent structure.

In Neutrino, the bits in msg.sigev_value.sival_int that correspond to _NOTIFY_COND_MASK are clear, but can be set by the application. For more information, see ionotify() in the Neutrino Library Reference.


Let's look at the code you'll need to write to support pulses in a:

Photon application that receives a pulse

It's the recipient of a Photon pulse that has to do the most preparation. It has to:

  1. Create the pulse.
  2. Arm the pulse.
  3. Send the pulse message to the process that will deliver it.
  4. Register an input handler for the pulse message.
  5. Deliver the pulse to itself, if necessary.
  6. Disarm the pulse.
  7. Destroy the pulse when it's no longer needed.

The sections below discuss each step, followed by an example.


Note: Before exiting, the recipient process should tell the delivering process to stop sending pulses.

Creating a pulse

To create a Photon pulse, call PtAppCreatePulse():

pid_t PtAppCreatePulse( PtAppContext_t app, 
                        int priority );

The arguments are:

app
The address of the application context, a structure that manages all the data associated with this application. For Photon 1.1x, this must be specified as NULL, so that the default context is used.
priority
The priority of the pulse. If this is -1, the priority of the calling program is used.

PtAppCreatePulse() returns a pulse process ID, which is negative but never -1. This is the receiver's end of the pulse.

Arming a pulse

It might seem odd to arm the pulse and then send the pulse message to the process that's going to deliver it back to the application, but arming it sets up a virtual proxy under QNX 4 if the other process is on another node.


Note: There's nothing wrong with having more than one process deliver the same pulse, although the recipient won't be able to tell which process sent it.

The method of arming a pulse depends on how you're going to send the pulse message to the process that will be delivering it:

The prototype for PtPulseArmFd() is:

PtPulseMsgId_t *PtPulseArmFd( PtAppContext_t app, 
                              pid_t pulse, 
                              int fd, 
                              PtPulseMsg_t *msg );

The arguments are:

app
A pointer to the current application context (NULL in Photon 1.1x).
pulse
The pulse created by PtAppCreatePulse().
fd
The file descriptor associated with the QNX I/O manager or Neutrino resource manager.
msg
A pointer to a pulse message that this function creates. This is the deliverer's end of the pulse, and we'll need to send it to that process, as described below.

This function returns a pointer to a pulse message ID, which you'll need later to disarm the pulse.

The prototype for PtPulseArmPid() is:

PtPulseMsgId_t *PtPulseArmPid( PtAppContext_t app, 
                               pid_t pulse, 
                               pid_t pid, 
                               PtPulseMsg_t *msg);

The parameters are similar to those for PtPulseArmFd(), except that instead of the fd, you'll pass:

Sending the pulse message to the deliverer

The method you use to send the pulse message depends on the process that will deliver the pulse:

Registering an input handler

Registering an input handler for the pulse is similar to registering one for a message; see "Adding an input handler" earlier in this chapter. Pass the pulse ID returned by PtAppCreatePulse() as the pid parameter to PtAppAddInput().

The rcvid argument for the input handler won't necessarily have the same value as the pulse ID:


QNX 4: In QNX 4, it's a proxy ID.
Neutrino: In Neutrino, it matches the pulse ID on the bits defined by _NOTIFY_DATA_MASK (see ionotify() in the Neutrino Library Reference), but the other bits will be taken from the Neutrino pulse or signal that was received.

Delivering a pulse to yourself

If the application needs to send a pulse to itself, it can call PtAppPulseTrigger():

int PtAppPulseTrigger( PtAppContext_t app, 
                       pid_t pulse );

The parameters for this function are the application context (NULL in Photon 1.1x), and the pulse ID returned by PtAppCreatePulse().

Disarming a pulse

When the pulse is no longer to be delivered by a process currently doing so, you should call PtPulseDisarm() to clean up any resources (such as a virtual proxy) allocated for the pulse:

void PtPulseDisarm( PtPulseMsgId_t *mid );

The parameter to this function is the pulse message ID returned by PtPulseArmFd() or PtPulseArmPid().

The pulse can be armed for delivery from another process, if necessary.

Destroying a pulse

When your application no longer needs the pulse, it can be destroyed by calling PtAppDeletePulse():

int PtAppDeletePulse( PtAppContext_t app,
                      pid_t pulse_pid );

The parameters are the application context (NULL in Photon 1.1x), and the pulse ID returned by PtAppCreatePulse().

Example

This is a goofy little application that receives Photon pulses; every time it gets a pulse, it simply toggles the fill color of the base window. First of all, here's the application's header file. It defines the global variables and the structure for the messages sent from this application to the pulse deliverer:

/* Header "globals.h" for pulse_recipient Application */

// Message types:

#define PULSE_MSG     0
#define STOP_PULSING  1

typedef struct my_msg_struct {
  int msg_type;
  PtPulseMsg_t id;
} MyMsg;

extern pid_t pulse_deliverer, pulse;
extern PtPulseMsgId_t *pulse_msg_id;

Here's the input handler for the pulse, and the application's initialization function:

/* Pulse recipient                                       */
/*                            AppBuilder Photon Code Lib */
/*                                         Version 1.13A */

/* Standard headers */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/name.h>
#include <sys/kernel.h>

/* Toolkit headers */
#include <Ph.h>
#include <Pt.h>
#include <Ap.h>

/* Local headers */
#include "globals.h"
#include "abimport.h"
#include "proto.h"

/* Application Options string */
const char ApOptions[] =
    AB_OPTIONS ""; /* Add your options in the "" */

pid_t pulse_deliverer, pulse;
PtInputId_t *input_ID;
PtPulseMsgId_t *pulse_msg_id;
PtPulseMsg_t pulse_msg;
int pulse_on = 0;

PtInputCallbackProcF_t pulse_input_proc;

int pulse_input_proc (void *client_data, pid_t pid, 
                void *msg, size_t msglen)
{
  PtArg_t args[1];

  // This pulse handler simply toggles the fill color
  // for the base window.

  if (pulse_on) {
    pulse_on = 0;
    PtSetArg (&args[0], Pt_ARG_FILL_COLOR, Pg_GREY, 0);
  } else {
    pulse_on = 1;
    PtSetArg (&args[0], Pt_ARG_FILL_COLOR, Pg_RED, 0);
  }
  PtSetResources (ABW_base, 1, args);

  return (Pt_CONTINUE);
}

int
init_app( int argc, char *argv[] )

{

  MyMsg smsg;

  /* eliminate 'unreferenced' warnings */
  argc = argc, argv = argv;

  pulse_deliverer = qnx_name_locate ( 0, "PulseDeliverer", 
                                      0, NULL);
  if (pulse_deliverer == -1)
  {
    perror ("Couldn't find PulseDeliverer");
    exit (EXIT_FAILURE);
  }

  // Set up the pulse and arm it.

  pulse = PtAppCreatePulse (NULL, -1);
  pulse_msg_id = PtPulseArmPid ( NULL, pulse, 
                                 pulse_deliverer, 
                                 &pulse_msg );

  // Send the pulse message to the process that
  // will deliver it to us.

  smsg.msg_type = PULSE_MSG;
  smsg.id = pulse_msg;
  Send (pulse_deliverer, &smsg, NULL, 
        sizeof (smsg), 0);

  // Register the input handler for the pulse.

  input_ID = PtAppAddInput ( NULL, pulse, pulse_input_proc, 
                             NULL);
  return( Pt_CONTINUE );
}

Here's the callback that exits the application:

/* Callback that exits the pulse recipient               */
/*                            AppBuilder Photon Code Lib */
/*                                         Version 1.13A */

/* Standard headers */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include <sys/kernel.h>  // We need this for Send()

/* Toolkit headers */
#include <Ph.h>
#include <Pt.h>
#include <Ap.h>

/* Local headers */
#include "globals.h"
#include "abimport.h"
#include "proto.h"

int
quit_callback( PtWidget_t *widget, ApInfo_t *apinfo, 
               PtCallbackInfo_t *cbinfo )

{
   MyMsg smsg;

   /* eliminate 'unreferenced' warnings */
   widget = widget, apinfo = apinfo, cbinfo = cbinfo;

   // Tell the pulse deliverer to stop.

   smsg.msg_type = STOP_PULSING;
   smsg.id = 0;

   Send (pulse_deliverer, &smsg, NULL,
         sizeof (smsg), 0);

   // Disarm and destroy the pulse.

   PtPulseDisarm (pulse_msg_id);
   PtAppDeletePulse (NULL, pulse);

   exit (EXIT_SUCCESS);
   return( Pt_CONTINUE );
}

Photon application that delivers a pulse

A Photon application that's going to deliver a pulse must:

There's an example after the descriptions of these steps.

Registering an input handler

The input handler for messages from the pulse recipient is created as described in "Receiving QNX messages" earlier in this chapter. It will need to handle messages that:

Save the rcvid from the message that contains the pulse message - you'll need it to deliver the pulse.

Delivering the pulse

A Photon application delivers a pulse by calling PtPulseDeliver():

int PtPulseDeliver( pid_t rcvid, 
                    PtPulseMsg_t const *pulse );

The arguments are:

rcvid
The ID of the process that's to receive the pulse.
pulse
A pointer to the pulse message created when the recipient of the pulse called PtPulseArmFd() or PtPulseArmPid().

Example

This application is the Photon counterpart to the example given earlier for the pulse recipient. It has a PtButton widget with a callback that delivers a pulse. This button is disabled when the recipient tells this application to stop delivering pulses.

The header file, globals.h defines the same message structure, MyMsg and message types. Here's the input handler for messages sent by the pulse recipient, and the deliverer's initialization function:

/* Pulse deliverer                                       */
/*                            AppBuilder Photon Code Lib */
/*                                         Version 1.13A */

/* Standard headers */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/kernel.h>  // We need this for Send()
#include <sys/name.h>

/* Toolkit headers */
#include <Ph.h>
#include <Pt.h>
#include <Ap.h>

/* Local headers */
#include "globals.h"
#include "abimport.h"
#include "proto.h"

/* Application Options string */
const char ApOptions[] =
    AB_OPTIONS ""; /* Add your options in the "" */

pid_t pulse_recipient;
PtPulseMsg_t pulse_msg;
PtInputId_t *input_id;

PtInputCallbackProcF_t input_func;

int input_func ( void *data, pid_t pid,
                 void *msg, size_t msglen )
{
  MyMsg received_msg;
  PtArg_t args[1];

  // Reply promptly! Remember the other process is
  // SEND-blocked, so its UI is "hung", until you do.

  if ( Reply (pid, NULL, 0) == -1)
  {
    perror ("Reply failed");
    exit (-1);
  }

  received_msg = *(MyMsg *)msg;

  switch (received_msg.msg_type) {
    case PULSE_MSG: 

           // Save the pulse message and the pid of the 
           // process we're going to deliver the pulse to.

           pulse_msg = received_msg.id;
           pulse_recipient = pid;
           break;

    case STOP_PULSING: 

           // Disable the button used to send pulses.

           PtSetArg (&args[0], Pt_ARG_FLAGS, 
                     Pt_GHOST | !Pt_SELECTABLE,
                     Pt_GHOST | Pt_SELECTABLE);
           PtSetResources (ABW_pulse_button, 1, &args);
           break;
  }

  return (Pt_CONTINUE);
}

int
init_app( int argc, char *argv[] )

{

   /* eliminate 'unreferenced' warnings */
   argc = argc, argv = argv;

   if ( qnx_name_attach ( 0, "PulseDeliverer" ) == -1)
   {
     perror ("Couldn't register myself as PulseDeliverer");
     exit (EXIT_FAILURE);
   }

  // Register an input handler for messages sent to us

  input_id = PtAppAddInput ( NULL, 0, input_func, NULL);

  return( Pt_CONTINUE );
}

Finally, here's the callback that delivers the pulse:

int
send_pulse( PtWidget_t *widget, ApInfo_t *apinfo, 
            PtCallbackInfo_t *cbinfo )

{

   /* eliminate 'unreferenced' warnings */
   widget = widget, apinfo = apinfo, cbinfo = cbinfo;

   PtPulseDeliver (pulse_recipient, &pulse_msg);

   return( Pt_CONTINUE );
}

Note: When you deliver a pulse by calling PtPulseDeliver(), it doesn't matter if the application is to run on QNX 4 or Neutrino - the code's the same.

Non-Photon application that delivers a pulse

A non-Photon application that delivers a pulse is similar to a Photon one, except that the non-Photon application needs to include <photon/PtPulseDeliver.h>.

This header file includes a macro version of PtPulseDeliver() that invokes the appropriate QNX or Neutrino functions. The application doesn't need to know which OS it's running on, nor does it need to be linked with the Photon libraries.

Processing signals

If your application needs to process signals, you'll need to set up a signal handler. The problem is that you can't call Photon functions from a signal handler because the widget library isn't signal-safe or reentrant.

To get around this problem, the Photon library includes a signal handler. You register a signal-processing function, and Photon calls it after


Caution: By handling signals in this way, you're not getting strict realtime performance, since your signal-processing function isn't called right away.

Adding a signal-processing function

To add a signal-processing function, use the PtAppAddSignalProc() function. You typically call this in

You'll need to include <signal.h>.

The syntax for PtAppAddSignalProc() is as follows:

int PtAppAddSignalProc( PtAppContext_t app, 
                        sigset_t const *set,
                        PtSignalProc_t func, 
                        void *data);

The arguments are as follows:

app
The address of the application context, a structure that manages all the data associated with this application. For Photon 1.1x, this must be specified as NULL, so that the default context is used.
set
A pointer to the set of signals that should cause the signal-processing function to be called. Use the sigemptyset() and sigaddset() functions to build this set. See the Watcom C Library Reference for more information.
func
The signal-processing function.
data
Any data to be passed to the function.

PtAppAddSignalProc() returns 0 on success, or -1 if an error occurs.

Your signal-processing function has the following prototype:

int signalProcFunctions (int signum
                         void *data);

The arguments are:

signum
The number of the signal to be processed.
data
The data parameter specified in the call to PtAppAddSignalProc().

If you want the signal handler to remain installed, return Pt_CONTINUE. To remove it for the current signal, return Pt_END (if the function was registered for other signals, it's still called if they're raised).

Removing a signal-processing function

To remove a signal-processing function:


Caution: There's another function, PtAppRemoveSignalProc() that removes all the signal-processing functions for a given set of signals, but we don't recommend using it because it will remove handlers used internally by the libraries.

Other I/O mechanisms

If your application needs to perform I/O such as reading from or writing to a pipe, you should add an fd handler. An fd handler is a function that's called by the main event loop when a given file descriptor (fd) is ready for input or output:

These functions are described in the Photon Library Reference.


Note: If your fd handler changes the display, it should call PtFlush() to make sure the display is updated.

Lengthy operations

When you have to perform an operation that will take a long time to execute, it's not a good idea to implement the operation as a simple callback. During the time the callback is executing, the widgets in your application won't repair damage and they won't respond to user inputs at all. You should develop a strategy for handling lengthy operations within your application that involves returning from your callback as quickly as possible.

Returning from your callback allows the widgets to continue to update themselves visually. It also gives some visual feedback if the user attempts to do anything. If you don't want the user to be able to perform any UI operations during this time, you should deactivate the menu and command buttons. You can do this by setting the Pt_BLOCKED flag in the application window widget's Pt_ARG_FLAGS resource.

You might consider one of several different mechanisms for dealing with lengthy operations.

If the operation can be easily broken down into small chunks, you may wish to have a function that keeps track of the current state and executes one small chunk of the operation at a time. You can then set up a timer widget and attach it to a callback that invokes the function whenever the timer goes off. Or you may call the function from within a work procedure. These methods are especially effective for iterative operations where the function may be executed once per iteration.

Operations that aren't easily decomposed are harder to deal with. In difficult cases (e.g. an operation that may take up to several minutes or more to complete), you can use PtBkgdHandlerProcess() within a loop, or spawn another process in the callback and have the other process return its results to the application by sending it messages. In this case, it's very important to be able to monitor the operation's progress and give the user visual feedback.


Note: It's safe to call PtBkgdHandlerProcess() in callbacks, work procedures, and input procedures, but not in a widget's Draw method or a PtRaw widget's drawing function.

The following functions process Photon events:

Another difficult situation to deal with is when you wish to have your program prompt the user for information before continuing. This is usually handled by popping up a dialog for the user to enter the required information. If you don't want the user to be able to select any other operations until the information has been provided, you should create the dialog as a modal dialog. A modal dialog doesn't allow user input to go to any of the other widgets in the application. To use a modal dialog to prompt for information, you have to provide your own event-handling loop within the callback function.

Work procedures

A work procedure is run whenever there are no messages for your application to respond to. In every iteration of the Photon event-handling loop, this procedure is called if no messages have arrived (rather than block on a Receive() waiting for more messages). This procedure will be run very frequently, so it should be as short as possible.


Note: If your work procedure changes the display, it should call PtFlush() to make sure the display is updated.

A work procedure is a callback function that takes a single void * parameter, client_data. This client_data is data that you associate with the work procedure when you register it with the widget library. You should create a data structure for the work procedure that contains all its state information and provide this as the client_data.

You register, or add, a work procedure using the PtAppAddWorkProc():

PtWorkProcId_t *PtAppAddWorkProc( 
                   PtAppContext_t app_context,
                   PtWorkProc_t work_func,
                   void *data );

The parameters are:

PtAppAddWorkProc() returns a pointer to a PtWorkProcId_t structure that identifies the work procedure.

To remove a a work procedure when it's no longer needed, call PtAppRemoveWorkProc():

void PtAppRemoveWorkProc( 
                PtAppContext_t app_context,
                PtWorkProcId_t *WorkProc_id );

passing it the same application context and the pointer returned by PtAppAddWorkProc().

A practical example of the use of work procedures would be too long to cover here, so we've provided a simple iterative example. The work procedure counts to a large number, updating a label to reflect its progress on a periodic basis.

#include <Pt.h>

typedef struct workDialog {
   PtWidget_t *widget;
   PtWidget_t *label;
   PtWidget_t *ok_button;
} WorkDialog_t;

typedef struct countdownClosure {
   WorkDialog_t *dialog;
   int value;
   int maxvalue;
   int done;
   PtWorkProcId_t *work_id;
} CountdownClosure_t;

WorkDialog_t *create_working_dialog(PtWidget_t *parent)
{
   PhDim_t      dim;
   PtArg_t      arg[3];
   int          nargs;
   PtWidget_t   *window, *group;
   WorkDialog_t *dialog = 
      (WorkDialog_t *)malloc(sizeof(WorkDialog_t));

   if (dialog)
   {
      nargs = 0;
      PtSetArg(&arg[nargs], Pt_ARG_WIN_PARENT, parent, 0);
               nargs++;
      PtSetParentWidget(NULL);
      dialog->widget = window = 
         PtCreateWidget(PtWindow, parent, nargs, arg);

      nargs = 0;
      PtSetArg(&arg[nargs], Pt_ARG_GROUP_ORIENTATION,
               Pt_GROUP_VERTICAL, 0); nargs++;
      PtSetArg(&arg[nargs], Pt_ARG_GROUP_VERT_ALIGN,
               Pt_GROUP_VERT_CENTER, 0); nargs++;
      group = PtCreateWidget(PtGroup, window, nargs, arg);

      nargs = 0;
      dim.w = 200;
      dim.h = 100;
      PtSetArg(&arg[nargs], Pt_ARG_DIM, &dim, 0); nargs++;
      PtSetArg(&arg[nargs], Pt_ARG_TEXT_STRING,
               "Counter:        ", 0); nargs++;
      dialog->label = PtCreateWidget(PtLabel, group,
                                        nargs, arg);

      PtCreateWidget(PtSeparator, group, 0, NULL);

      nargs = 0;
      PtSetArg(&arg[nargs], Pt_ARG_TEXT_STRING, "Stop", 0);
               nargs++;
      dialog->ok_button = PtCreateWidget(PtButton, group,
                                            1, arg);
   }
   return dialog;
}

int done(PtWidget_t *w, void *client, 
          PtCallbackInfo_t *call)
{
   CountdownClosure_t *closure = 
      (CountdownClosure_t *)client;

   call = call;

   if (!closure->done) {
      PtAppRemoveWorkProc(NULL, closure->work_id);
   }
   PtDestroyWidget(closure->dialog->widget);
   free(closure->dialog);
   free(closure);
   return (Pt_CONTINUE);
}

int
count_cb(void *data)
{
   CountdownClosure_t *closure = 
      (CountdownClosure_t *)data;
   char    buf[64];
   PtArg_t arg[1];
   int     finished = 0;

   if ( closure->value++ == 0 || closure->value % 
        1000 == 0 )
   {
      sprintf(buf, "Counter: %d", closure->value);
      PtSetArg(&arg[0], Pt_ARG_TEXT_STRING, buf, 0);
      PtSetResources(closure->dialog->label, 1, arg);
   }

   if ( closure->value == closure->maxvalue )
   {
      closure->done = finished = 1;
      PtSetArg(&arg[0], Pt_ARG_TEXT_STRING, "Done", 0);
      PtSetResources(closure->dialog->ok_button, 1,
                     arg);
   }

   return finished ? Pt_END : Pt_CONTINUE;
}

int push_button_cb(PtWidget_t *w, void *client,
                   PtCallbackInfo_t *call)
{
   PtWidget_t   *parent = (PtWidget_t *)client;
   WorkDialog_t *dialog;

   w = w; call = call;

   dialog = create_working_dialog(parent);

   if (dialog)
   {
      CountdownClosure_t *closure = 
         (CountdownClosure_t *)
          malloc(sizeof(CountdownClosure_t));

      if (closure)
      {
         PtWorkProcId_t *id;
         PtArg_t    arg[3];

         closure->dialog = dialog;
         closure->value = 0;
         closure->maxvalue = 200000;
         closure->done = 0;
         closure->work_id = id =
            PtAppAddWorkProc(NULL, count_cb, closure);

         PtAddCallback(dialog->ok_button, Pt_CB_ACTIVATE,
                       done, closure);
         PtRealizeWidget(dialog->widget);
      }
   }
   return (Pt_CONTINUE);
}

int main(int argc, char *argv[])
{
   PhDim_t      dim;
   PtArg_t      arg[4];
   PtWidget_t   *window;
   PtCallback_t callbacks[] = {{push_button_cb, NULL}};

   dim.w = 200;
   dim.h = 100;
   PtSetArg(&arg[0], Pt_ARG_DIM, &dim, 0);
   if ((window = PtAppInit(NULL, &argc, argv, 1, arg))
       == NULL) exit(1);

   callbacks[0].data = window;
   PtSetArg(&arg[0], Pt_ARG_TEXT_STRING, "Count Down...", 0);
   PtSetArg(&arg[1], Pt_ARG_TEXT_FONT, "helv14B", 0);
   PtSetArg(&arg[2], Pt_CB_ACTIVATE, callbacks,
            sizeof(callbacks)/sizeof(PtCallback_t));
   PtCreateWidget(PtButton, window, 3, arg);

   PtRealizeWidget(window);

   PtMainLoop();
   return (EXIT_SUCCESS);
}

When the pushbutton is pressed, the callback attached to it creates a working dialog and adds a work procedure, passing a closure containing all the information needed to perform the countdown and to clean up when it's done.

The closure contains a pointer to the dialog, the current counter, and the value to count to. When the value is reached, the work procedure changes the label on the dialog's button and attaches a callback that will tear down the entire dialog when the button is pressed. Upon such completion, the work procedure returns Pt_END in order to get removed.

The done() function is called if the user stops the work procedure, or if it has completed. This function destroys the dialog associated with the work procedure and removes the work procedure if it was stopped by the user (i.e. it did not run to completion).

If you run this example, you may discover one of the other features of work procedures - they preempt one another. When you add a new work procedure, it preempts all others. The new work procedure will be the only one run until it has completed or is removed. After that, the work procedure that was previously running resumes. This is illustrated in the above example if the user presses the Count Down... button before a countdown is finished. A new countdown dialog is created, and that countdown runs to the exclusion of the first until it's done.

The granularity for this preemption is at the function call level. When the callback function for a work procedure returns, that work procedure may be preempted by another work procedure.

Timers

If you wish to schedule your own operations at particular time intervals, or if you just want to implement a time-out or trigger an event at a particular time, you may want to have a timer-based callback function. The easiest way to provide this is to make use of the PtTimer widget. See the Widget Reference for more information.


Note: When you create a PtTimer widget in PhAB, it appears as a black box. The box doesn't appear when you run the application; it's just a placeholder.

PtTimer is easy to use, but doesn't give accurate timer events. In particular, it doesn't guarantee a constant repeat rate; since the repetition is handled by rearming the timer for each event, any delays in handling the events accumulate. Kernel timers guarantee an accurate repeat rate even if your application can't keep up with them.


Modal dialogs

To create a modal dialog, you have to create a new PtWindow widget, normally as a child of the main application window.

To activate the modal dialog, you have to realize the dialog widget and block all the other window widgets in the application. To block a widget, set the Pt_BLOCKED flag in the widget's Pt_ARG_FLAGS resource. This affects the widget and all its descendants that aren't windows.

After the modal dialog has been activated, you process Photon events by repeatedly calling PtProcessEvent() until a termination condition is met.

When the operation associated with the modal dialog is completed or aborted, you have to dismiss the dialog. To do so:

  1. Unblock any window widgets that you blocked when you created the dialog.
  2. Destroy or unrealize the dialog itself.

To unblock a widget, clear its Pt_BLOCKED flag.

We can easily change our previous example of work procedures so that its progress dialog behaves as a modal dialog. A flag is added to the callback closure to indicate that the work has been completed or aborted. The done() callback is altered to set this flag rather than free the closure:

int done(PtWidget_t *w, void *client, 
          PtCallbackInfo_t *call)
{
   CountdownClosure_t *closure = 
      (CountdownClosure_t *)client;

   call = call;

   if (!closure->done) {
      PtAppRemoveWorkProc(NULL, closure->work_id);
   }
   PtDestroyWidget(closure->dialog->widget);
   free(closure->dialog);

   // New: Mark the dialog as destroyed instead of
   // destroying the closure structure. This signals the
   // end of the PtProcessEvent() loop.
   closure->destroyed = 1;
   return (Pt_CONTINUE);
}

Here's a little routine that we'll use to block or unblock the windows in the application:

void BlockWindows( int block, PtWidget_t *leave_alone )
{
   PtWidget_t *widget;
   PtArg_t     args;

   // This function blocks all top-level widgets that 
   // are windows, except for the given widget.
   PtSetArg( &args, Pt_ARG_FLAGS, block ? Pt_BLOCKED : 0,
             Pt_BLOCKED );
   widget = NULL;
   while( NULL != (widget = PtNextTopLevelWidget( widget ))) 
   {
       if ((widget != leave_alone) &&
           (PtWidgetIsClass( widget, PtWindow )))
           PtSetResources( widget, 1, &args );
   }
}

PtNextTopLevelWidget() is a function that gets a pointer to the next top-level widget. For more information, see the Photon Library Reference.

All that remains at this point is to change the push_button_cb() callback function so that it finds which widgets to block and blocks them before realizing the progress dialog.

After the progress dialog is realized, it loops until the termination flag in the closure has been set, processing Photon events on each iteration of the loop. Once the work has terminated, the closure is then freed and all the blocked widgets are unblocked.

Here's the new version of the push_button_cb() callback function:

int push_button_cb(PtWidget_t *w, void *client,
                   PtCallbackInfo_t *call)
{
   PtWidget_t   *parent = (PtWidget_t *)client;
   WorkDialog_t *dialog;

    // New: The count for PtModalStart() / PtModalEnd()
   int           count;

   w = w; call = call;

   dialog = create_working_dialog(parent);

   if (dialog)
    {
      CountdownClosure_t *closure = 
         (CountdownClosure_t *)
          malloc(sizeof(CountdownClosure_t));

      if (closure)
        {
         PtWorkProcId_t *id;
         PtArg_t    arg[3];

         closure->dialog = dialog;
         closure->value = 0;
         closure->maxvalue = 200000;
         closure->done = 0;

         // New: Initialize the destroyed flag
         closure->destroyed = 0;
         closure->work_id = id =
            PtAppAddWorkProc(NULL, count_cb, closure);

         PtAddCallback(dialog->ok_button, Pt_CB_ACTIVATE,
                       done, closure);
         PtRealizeWidget(dialog->widget);

         // New: Block all the windows except the dialog,
         // process events until the dialog is closed,
         // and then unblock all the windows.

         BlockWindows (1, dialog->widget);

         count = PtModalStart();
         while( !closure->destroyed) {
           PtProcessEvent();
         }
         PtModalEnd( count );
         free (closure);

         BlockWindows (0, dialog->widget);

        }
    }
   return (Pt_CONTINUE);
}

Here's the new version of the whole program:

#include <Pt.h>

typedef struct workDialog {
   PtWidget_t *widget;
   PtWidget_t *label;
   PtWidget_t *ok_button;
} WorkDialog_t;

typedef struct countdownClosure {
   WorkDialog_t *dialog;
   int value;
   int maxvalue;
   int done;
   // New: a flag that indicates when the dialog
   // is destroyed
   int destroyed;
   PtWorkProcId_t *work_id;
} CountdownClosure_t;

void BlockWindows( int block, PtWidget_t *leave_alone )
{
   PtWidget_t *widget;
   PtArg_t     args;

   // This function blocks all top-level widgets that 
   // are windows, except for the given widget.
   PtSetArg( &args, Pt_ARG_FLAGS, block ? Pt_BLOCKED : 0,
             Pt_BLOCKED );
   widget = NULL;
   while( NULL != (widget = PtNextTopLevelWidget( widget ))) 
   {
       if ((widget != leave_alone) &&
           (PtWidgetIsClass( widget, PtWindow )))
           PtSetResources( widget, 1, &args );
   }
}

WorkDialog_t *create_working_dialog(PtWidget_t *parent)
{
   PhDim_t      dim;
   PtArg_t      arg[3];
   int          nargs;
   PtWidget_t   *window, *group;
   WorkDialog_t *dialog = 
      (WorkDialog_t *)malloc(sizeof(WorkDialog_t));

   if (dialog)
    {
      nargs = 0;
      PtSetArg(&arg[nargs], Pt_ARG_WIN_PARENT, parent, 0);
               nargs++;
      PtSetParentWidget(NULL);
      dialog->widget = window = 
         PtCreateWidget(PtWindow, parent, nargs, arg);

      nargs = 0;
      PtSetArg(&arg[nargs], Pt_ARG_GROUP_ORIENTATION,
               Pt_GROUP_VERTICAL, 0); nargs++;
      PtSetArg(&arg[nargs], Pt_ARG_GROUP_VERT_ALIGN,
               Pt_GROUP_VERT_CENTER, 0); nargs++;
      group = PtCreateWidget(PtGroup, window, nargs, arg);

      nargs = 0;
      dim.w = 200;
      dim.h = 100;
      PtSetArg(&arg[nargs], Pt_ARG_DIM, &dim, 0); nargs++;
      PtSetArg(&arg[nargs], Pt_ARG_TEXT_STRING,
               "Counter:        ", 0); nargs++;
      dialog->label = PtCreateWidget(PtLabel, group,
                                        nargs, arg);

      PtCreateWidget(PtSeparator, group, 0, NULL);

      nargs = 0;
      PtSetArg(&arg[nargs], Pt_ARG_TEXT_STRING, "Stop", 0);
               nargs++;
      dialog->ok_button = PtCreateWidget(PtButton, group,
                                            1, arg);
    }
   return dialog;
}

int done(PtWidget_t *w, void *client, 
          PtCallbackInfo_t *call)
{
   CountdownClosure_t *closure = 
      (CountdownClosure_t *)client;

   call = call;

   if (!closure->done) {
      PtAppRemoveWorkProc(NULL, closure->work_id);
   }
   PtDestroyWidget(closure->dialog->widget);
   free(closure->dialog);

   // New: Mark the dialog as destroyed instead of
   // destroying the closure structure. This signals the
   // end of the PtProcessEvent() loop.
   closure->destroyed = 1;
   return (Pt_CONTINUE);
}

int
count_cb(void *data)
{
   CountdownClosure_t *closure = 
      (CountdownClosure_t *)data;
   char    buf[64];
   PtArg_t arg[1];
   int     finished = 0;

   if ( closure->value++ == 0 || closure->value % 
        1000 == 0 )
    {
      sprintf(buf, "Counter: %d", closure->value);
      PtSetArg(&arg[0], Pt_ARG_TEXT_STRING, buf, 0);
      PtSetResources(closure->dialog->label, 1, arg);
    }

   if ( closure->value == closure->maxvalue )
    {
      closure->done = finished = 1;
      PtSetArg(&arg[0], Pt_ARG_TEXT_STRING, "Done", 0);
      PtSetResources(closure->dialog->ok_button, 1,
                     arg);
    }

    return finished ? Pt_END : Pt_CONTINUE;
}

int push_button_cb(PtWidget_t *w, void *client,
                   PtCallbackInfo_t *call)
{
   PtWidget_t   *parent = (PtWidget_t *)client;
   WorkDialog_t *dialog;

    // New: The count for PtModalStart() / PtModalEnd()
   int           count;

   w = w; call = call;

   dialog = create_working_dialog(parent);

   if (dialog)
    {
      CountdownClosure_t *closure = 
         (CountdownClosure_t *)
          malloc(sizeof(CountdownClosure_t));

      if (closure)
        {
         PtWorkProcId_t *id;
         PtArg_t    arg[3];

         closure->dialog = dialog;
         closure->value = 0;
         closure->maxvalue = 200000;
         closure->done = 0;

         // New: Initialize the destroyed flag
         closure->destroyed = 0;
         closure->work_id = id =
            PtAppAddWorkProc(NULL, count_cb, closure);

         PtAddCallback(dialog->ok_button, Pt_CB_ACTIVATE,
                       done, closure);
         PtRealizeWidget(dialog->widget);

         // New: Block all the windows except the dialog,
         // process events until the dialog is closed,
         // and then unblock all the windows.

         BlockWindows (1, dialog->widget);

         count = PtModalStart();
         while( !closure->destroyed) {
           PtProcessEvent();
         }
         PtModalEnd( count );
         free (closure);

         BlockWindows (0, dialog->widget);

        }
    }
    return (Pt_CONTINUE);
}

int main(int argc, char *argv[])
{
   PhDim_t      dim;
   PtArg_t      arg[4];
   PtWidget_t   *window;
   PtCallback_t callbacks[] = {{push_button_cb, NULL
   }
   };

   dim.w = 200;
   dim.h = 100;
   PtSetArg(&arg[0], Pt_ARG_DIM, &dim, 0);
   if ((window = PtAppInit(NULL, &argc, argv, 1, arg))
       == NULL) exit(1);

   callbacks[0].data = window;
   PtSetArg(&arg[0], Pt_ARG_TEXT_STRING, "Count Down...", 0);
   PtSetArg(&arg[1], Pt_ARG_TEXT_FONT, "helv14B", 0);
   PtSetArg(&arg[2], Pt_CB_ACTIVATE, callbacks,
            sizeof(callbacks)/sizeof(PtCallback_t));
   PtCreateWidget(PtButton, window, 3, arg);

   PtRealizeWidget(window);

   PtMainLoop();
   return (EXIT_SUCCESS);
}

[Previous] [Contents] [Index] [Next]