A Photon application can't always work in isolation — sometimes it needs to communicate with other processes.
This chapter describes:
The QNX Neutrino operating system supports various methods of interprocess communication (IPC), including:
These methods can be used in a Photon application, as long as you're careful. However, it's best to use Photon connections:
On the other hand, here's why raw Neutrino messages and/or pulses might sometimes be better:
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 MsgReceive() to obtain events from Photon. This means your application has to be careful if it needs to call MsgReceive(), 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.
The process of establishing a connection uses an object called a connector. The connector is a name that the server creates and owns, and the client attaches its connection to. The connector is used only for establishing a connection.
The connector has a numeric ID and may also have a name associated with it. Both the name and the ID are unique in their Photon session. Here are a few examples of how the name can be used:
You can define unique names for your connectors by following these naming conventions:
Here's how you typically use connections:
The client can make repeated attempts (within a specified time limit or until the server terminates) to find the server by calling PtConnectionWaitForName().
You can pass user data with the connection. The server calls PtConnectionServerSetUserData() to specify the data that the client can retrieve by calling PtConnectionClientGetUserData(). Similarly, the client calls PtConnectionClientSetUserData() to specify the data that the server can retrieve by calling PtConnectionServerGetUserData().
You can set up functions to handler errors; the server does this by calling PtConnectionServerSetError(), and the client by calling PtConnectionClientSetError().
The server can also use events to communicate with the client:
It's possible for a process to create a connection to itself. The behavior of such a connection differs a little bit from a normal connection:
If the connection is local, PtConnectionSend(), PtConnectionSendmx(), and PtConnectionNotify() invoke the handler directly, and both the calling code and the handler must take into account any side effect of that.
The simplest way around this is to avoid sending notifications from within a message handler — instead, the notification can be placed in the reply.
This application uses a connector to determine if there's already another instance of the application running. The program takes two command-line options:
Here's the code:
/* Standard headers */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> /* Toolkit headers */ #include <Ph.h> #include <Pt.h> #include <Ap.h> /* Local headers */ #include "abimport.h" #include "proto.h" enum MyMsgType { MY_MSGTYPE_EXIT, MY_MSGTYPE_OPEN_DOC, MY_MSGTYPE_TOFRONT }; enum MyReplyType { MY_REPTYPE_SUCCESS, MY_REPTYPE_BADMSG }; struct MyMsg { char docname[ PATH_MAX ]; }; struct MyReply { enum MyReplyType status; }; /* Handle a message from a client: */ static PtConnectionMsgFunc_t msghandler; static void const *msghandler( PtConnectionServer_t *connection, void *data, unsigned long type, void const *msgptr, unsigned msglen, unsigned *reply_len ) { struct MyMsg const *msg = (struct MyMsg const*) msgptr; static struct MyReply reply; reply.status = MY_REPTYPE_SUCCESS; switch ( type ) { case MY_MSGTYPE_EXIT : PtConnectionReply( connection, sizeof(reply), &reply ); PtExit( EXIT_SUCCESS ); break; case MY_MSGTYPE_OPEN_DOC : reply.status = OpenNewDocument( msg->docname ); break; case MY_MSGTYPE_TOFRONT : break; default : reply.status = MY_REPTYPE_BADMSG; } PtWindowToFront( ABW_base ); *reply_len = sizeof(reply); return &reply; } /* Set up a new connection: */ static PtConnectorCallbackFunc_t connector_callback; static void connector_callback( PtConnector_t *connector, PtConnectionServer_t *connection, void *data ) { static const PtConnectionMsgHandler_t handlers = { 0, msghandler }; if ( PtConnectionAddMsgHandlers( connection, &handlers, 1 ) != 0 ) { fputs( "Unable to set up connection handler\n", stderr ); PtConnectionServerDestroy( connection ); } } /* Application Options string */ const char ApOptions[] = AB_OPTIONS "ef:"; /* Add your options in the "" */ /* Application initialization function */ int init( int argc, char *argv[] ) { struct MyMsg msg; int opt; long msgtype = MY_MSGTYPE_TOFRONT; const char *document = NULL; static const char name[] = "me@myself.com/ConnectionExample"; while ( ( opt = getopt( argc, argv, ApOptions ) ) != -1 ) switch ( opt ) { case '?' : PtExit( EXIT_FAILURE ); case 'e' : msgtype = MY_MSGTYPE_EXIT; break; case 'f' : document = optarg; } if ( document ) if ( msgtype == MY_MSGTYPE_EXIT ) { fputs( "You can't specify both the -e and -f options\n", stderr ); PtExit( EXIT_FAILURE ); } else { msgtype = MY_MSGTYPE_OPEN_DOC; strncpy( msg.docname, document, sizeof(msg.docname)-1 ); } while ( PtConnectorCreate( name, connector_callback, 0 ) == NULL ) { /* If this failed, another instance of the app must be already running */ PtConnectionClient_t *clnt; if ( ( clnt = PtConnectionFindName( name, 0, 0 ) ) != 0 ) { struct MyReply reply; int result = PtConnectionSend( clnt, msgtype, &msg, &reply, sizeof(msg), sizeof(reply) ); PtConnectionClientDestroy( clnt ); if ( result == 0 ) PtExit( reply.status ); } } /* Since PtConnectorCreate() has succeeded, we're the only instance of the app running */ if ( msgtype == MY_MSGTYPE_EXIT ) { fputs( "Can't tell it to exit; it's not running\n", stderr ); PtExit( EXIT_FAILURE ); } if ( document ) OpenNewDocument( document ); return Pt_CONTINUE; }
A Photon application can use MsgSend() to pass messages to another process, but the other process needs to MsgReply() promptly, as Photon events aren't processed while the application is blocked. (Promptness isn't an issue if your application has multiple threads that process events, and you call PtLeave() before MsgSend(), and PtEnter() after. For a discussion of writing applications that use multiple threads, see the Parallel Operations chapter.)
As an example, here's a callback that extracts a string from a text widget, sends it to another process, and displays the reply in the same text widget:
/* Callback that sends a message to another process */ /* Standard headers */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/neutrino.h> /* Needed for MsgSend() */ /* Toolkit headers */ #include <Ph.h> #include <Pt.h> #include <Ap.h> /* Local headers */ #include "globals.h" #include "abimport.h" #include "proto.h" extern int coid; int send_msg_to_b( PtWidget_t *widget, ApInfo_t *apinfo, PtCallbackInfo_t *cbinfo ) { char *a_message; /* eliminate 'unreferenced' warnings */ widget = widget, apinfo = apinfo, cbinfo = cbinfo; /* Get the string from the text widget. */ PtGetResource (ABW_msg_text, Pt_ARG_TEXT_STRING, 0, 0); /* Send the string to another process. */ a_message = (char *)args[0].value; if ( MsgSend (coid, a_message, msg_size, rcv_msg, msg_size) == -1) { perror ("Send to B failed"); PtExit (-1); } /* Remember the UI is "hung" until the other process replies! */ /* Display the reply in the same text widget. */ PtSetResource (ABW_msg_text, Pt_ARG_TEXT_STRING, rcv_msg, 0); return( Pt_CONTINUE ); }
For more information on messages, see the QNX Neutrino System Architecture guide.
To handle non-Photon events in a Photon application, you need to register an input handling procedure (or input handler), otherwise the events are lost. This is because the unconditional MsgReceive() performed by the Photon event handling loop ignores non-Photon events.
You can create your own channel and call MsgReceive() on it, but 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. 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. When a message is received from that process, the widget library starts calling the handlers in the list, starting with the last one registered, and continuing up the list in reverse order until a handler recognizes the message (that is, it doesn't return Pt_CONTINUE). See the description of Pt_CONTINUE below.
You can register a nonspecific input handler by specifying a value of zero for the pid. This handler is called when the application receives:
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:
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, int rcvid, void *msg, size_t msglen );
The arguments are:
You can also declare the input handler to be of type PtInputCallbackProcF_t to take advantage of the prototype checking done by the compiler.
If your input handler changes the display, it should call PtFlush() to make sure the display is updated. |
An input handler must return one of the following:
If possible, you should use a Photon connection instead of name_attach() to establish a connection with another process. However, you can't use a Photon connection in these cases:
PtAppAddInput() and name_attach() both try to create a channel with _NTO_CHF_COID_DISCONNECT and _NTO_CHF_DISCONNECT set (see the QNX Neutrino Library Reference). If your application calls both functions, you need to let Photon use the same channel as name_attach(), by calling PhChannelAttach() first, like this:
PhChannelAttach( chid, -1, NULL );
before calling name_attach() or PtAppAddInput().
If you want to create a separate channel for Photon, it doesn't matter whether you create it and give it to PhChannelAttach() before or after calling name_attach(). But keep in mind that since certain mechanisms in Photon library expect the Photon channel to have the two DISCONNECT flags, they might not work properly if it doesn't. One such mechanism is the detection of broken connections (see PtConnectionClientSetError() and PtConnectionServerSetError()) and anything that relies on it.
To remove an input handler:
Or:
As described above, arguments to your input function include:
This buffer might not be large enough to hold the entire message. One way of handling this is to have the first few 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:
Or:
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.
PtResizeEventMsg() won't reduce the message buffer beyond a certain minimum size. This is so that the widget library will continue to function. |
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, int rcvid, 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 (MsgRead(rcvid, &header, msg_offset, 0) == -1) { status = errno; MsgError( rcvid, status); return Pt_HALT; /* 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 || MsgRead( rcvid, log_msg, log_msglen, 0) == -1) { status = errno; MsgError( rcvid, status); return Pt_HALT; /* bail out */ } log = log_msg; } add_msg(text, log); status = 0; MspReply( rcvid, 0, 0, 0); } return Pt_HALT; }
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 MsgRead() 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_HALT. This instructs the Photon widget library not to remove the input handler.
In addition to synchronous message-passing, Photon supports pulses. 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).
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 PtPulseArm(). 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 by calling MsgDeliverEvent() function.
Under the QNX Neutrino OS version 6, PtPulseMsg_t is a sigevent
structure.
The bits in msg.sigev_value.sival_int that correspond to
_NOTIFY_COND_MASK are clear, but can be set by the application
that sends the pulse.
For more information, see
ionotify()
in the QNX Neutrino Library Reference.
PtPulseArm() (described in the Photon Library Reference) simply takes a sigevent structure. PtPulseArmFd() and PtPulseArmPid() are for compatibility with earlier version of the QNX Neutrino OS and the Photon microGUI. |
Let's look at the code you'll need to write to support pulses in a:
It's the recipient of a Photon pulse that has to do the most preparation. It has to:
The sections below discuss each step, followed by an example.
Before exiting, the recipient process should tell the delivering process to stop sending pulses. |
To create a Photon pulse, call PtAppCreatePulse():
pid_t PtAppCreatePulse( PtAppContext_t app, int priority );
The arguments are:
PtAppCreatePulse() returns a pulse process ID, which is negative but never -1. This is the receiver's end of the pulse.
Arming the pulse fills in the sigevent structure, which can be used for most of the QNX Neutrino calls that take this type of argument.
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. |
To arm a pulse, call PtPulseArm(). The prototype is:
int PtPulseArm( PtAppContext_t app, pid_t pulse, struct sigevent *msg );
The arguments are:
This function returns a pointer to a pulse message ID, which you'll need later.
The method you use to send the pulse message depends on the process that will deliver the pulse. For example,
ionotify (fd, _NOTIFY_ACTION_POLLARM, _NOTIFY_COND_INPUT, &pulsemsg);
/* Create your own message format: */ msg.pulsemsg = pulsemsg; MsgSendv (connection_id, &msg, msg_parts, &rmsg, rmsg_parts);
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: it matches the pulse ID on the bits defined by _NOTIFY_DATA_MASK (see ionotify() in the QNX Neutrino Library Reference), but the other bits are taken from the Neutrino pulse that was received.
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 PtAppContext_t structure that defines the application context (typically NULL) and the pulse ID returned by PtAppCreatePulse().
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 PtAppContext_t structure that defines the application context (typically NULL) and the pulse ID returned by PtAppCreatePulse().
Here's an application that receives Photon pulses. It opens a message queue (/dev/mqueue/testqueue by default), sets up a pulse, and uses mq_notify() to give itself a pulse when there's something to read from the message queue:
/* Standard headers */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <mqueue.h> #include <fcntl.h> #include <errno.h> #include <sys/stat.h> /* Toolkit headers */ #include <Ph.h> #include <Pt.h> #include <Ap.h> /* Local headers */ #include "abimport.h" #include "proto.h" mqd_t mqd = -1; struct sigevent sigev; static void readqueue( void ) { static unsigned counter; unsigned mprio; ssize_t msize; char mbuf[ 4096 ]; while ( ( msize = mq_receive( mqd, mbuf, sizeof(mbuf), &mprio ) ) >= 0 ) { char hbuf[ 40 ]; PtTextModifyText( ABW_mtext, 0, 0, -1, hbuf, sprintf( hbuf, "Msg #%u (prio %d):\n", ++counter, mprio ) ); PtTextModifyText( ABW_mtext, 0, 0, -1, mbuf, msize ); } if ( errno != EAGAIN ) perror( "mq_receive" ); } static int input_fun( void *data, int rcvid, void *message, size_t mbsize ) { if ( mq_notify( mqd, &sigev ) == -1 ) perror( "mq_notify" ); readqueue(); return Pt_HALT; } pid_t pulse; /* Application Options string */ const char ApOptions[] = AB_OPTIONS ""; /* Add your options in the "" */ int init( int argc, char *argv[] ) { if ( ( pulse = PtAppCreatePulse( NULL, -1 ) ) == 0 || PtAppAddInput( NULL, pulse, input_fun, NULL ) == NULL ) { fputs( "Initialization failed\n", stderr ); exit( EXIT_FAILURE ); } PtPulseArm( NULL, pulse, &sigev ); /* eliminate 'unreferenced' warnings */ argc = argc, argv = argv; return( Pt_CONTINUE ); } int open_queue( PtWidget_t *link_instance, ApInfo_t *apinfo, PtCallbackInfo_t *cbinfo ) { const char *name; PtArg_t arg; if ( mqd >= 0 ) mq_close( mqd ); PtSetArg( &arg, Pt_ARG_TEXT_STRING, &name, 0 ); PtGetResources( ABW_qname, 1, &arg ); if ( ( mqd = mq_open( name, O_RDONLY | O_CREAT | O_NONBLOCK, S_IRUSR | S_IWUSR, NULL ) ) < 0 ) perror( name ); else if ( mq_notify( mqd, &sigev ) == -1 ) { perror( "mq_notify" ); mq_close( mqd ); mqd = -1; } else readqueue(); /* eliminate 'unreferenced' warnings */ link_instance = link_instance, apinfo = apinfo; cbinfo = cbinfo; return( Pt_CONTINUE ); }
A Photon application that's going to deliver a pulse must:
Save the rcvid from the message that contains the pulse message — you'll need it to deliver the pulse.
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
AND
By handling signals in this way, you're not getting strict realtime performance, since your signal-processing function isn't called right away. |
To add a signal-processing function, use the PtAppAddSignalProc() function. You typically call this in
Or
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:
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:
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).
To remove a signal-processing function:
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.
If your fd handler changes the display, it should call PtFlush() to make sure the display is updated. |