Creating Message Passing IPC Classes with Kylix

Rick RossPILLAR Technology Group, Inc.


Introduction

Key Concepts

Synchronization

Task

Processes

Threads

Message Passing

Message Passing IPCs available in Linux

Pipes

FIFOs (Named Pipes)

System V Message Queues

Creating a Pipe Class

Class Definition

Constructor

Destructor

Reading and Writing

Redirecting Standard Input

Redirecting Standard Output

Closing the Pipe

Creating a FIFO Class

Class Definition

Constructor

Destructor

Permissions

Reading and Writing

Closing the FIFO

Creating a Message Queue Class

Class Definition

Constructor

Permissions

Opening and Closing

Reading and Writing

Using the Classes

Pipe Example

FIFO Example

System V Message Queue Example

Additional Resources


Introduction

The purpose of this paper is to learn how to use Message Passing Interprocess Communication (IPC) classes with Kylix. In this paper, you will learn what message passing IPCs are available in Linux, what purpose each is best used for, and how to create reusable classes.

In order to avoid the phrase “multiple processes and/or threads”, I will simplify the phrase to “multiple threads”. Unless I specifically say that a certain topic applies only to multiple threads, you can assume that it also applies to multiple processes. I will point out differences whenever they are applicable.

Key Concepts

Listed below are several key concepts that need to be understood before diving into the topic at hand. Since these concepts are woven into this entire paper, it is essential that these concepts be clearly understood.

Synchronization

Synchronization allows multiple threads to coordinate the usage of shared resources. It prevents multiple threads from accessing the same resource at the same time. Linux provides the low-level APIs, and Kylix provides a method for creating easy to use classes. Once these classes have encapsulated the IPC functions, more time can be spent on coding the actual solution, rather than the underlying plumbing that is needed to make it all work.

Task

A task is a unit of work that is scheduled by the Linux scheduler. It includes both processes and threads.

Processes

Processes have existed from the beginning of the programming era. A process is an executable program that is currently loaded. It can either be running, stopped or blocked. Stopped processes are those are currently being debugged or have received a SIGSTOP, SIGSTP, SIGTTIN, or a SIGTTOU signal. Blocked processes are those that are waiting to be executed. Each process has it’s own instruction pointer, address space, memory space, and other various CPU registers.

The instruction pointer is used to keep track of what the current instruction the program is running. Address space (also known as the text segment) refers to the memory location of where the process’ program code is loaded. Memory space refers to the heap, where memory is dynamically allocated, the stack where local variables are located and the global process data.

Without using an IPC of some kind, a process cannot communicate with another process. The good news is a process cannot directly read or write another process’ memory. This is good news because a process cannot inadvertently corrupt another process. Imagine if a program could begin to write data in a valid working process. The results could be disastrous. At the same time, the bad news is a process cannot directly read or write another process’ memory. When processes need to communicate, they need to use an IPC like pipes or shared memory. Coordinating and using the chosen IPC mechanism requires diligent design and adds additional overhead.

New processes are created by using one of the exec APIs: execl, execlp, execle, execv, and execvp. The definition of these APIs can be found in Libc.pas.

Threads

A thread is path of execution in a process. In a normal process, there is only one path of execution – a single threaded program. Each thread has its own instruction pointer and various CPU registers. It shares the same memory space with the process that created the thread. All of the threads in the process share the same global memory, but each thread is allocated a chuck of memory for its stack space.

Threads are sometimes referred to as “Lightweight Processes”. In Linux, they are scheduled in the kernel. At the kernel level, threads are viewed as processes that share the same global memory. This makes it much easier for the kernel to schedule tasks. However, the kernel does not know that a process is a collection of threads.

Linux does not address multi-threaded applications in the same way as other environments. It does not recognize a particular application boundary for a collection of related threads; rather it treats threads and processes similarly. For most applications this myopia is fine. When dealing with multi-processor computers, however, the scheduling module that is currently implemented does not allow for applications to take advantage of the additional processors. The reason for this is that the kernel has no association between two tasks of the same process.

The system call that is usually used to create threads is the pthread_create function. Another API that can be used is the clone function, but it is more complex to use and is not as portable as the pthread_create function. In fact, pthread_create uses the kernel clone function to create a thread. Both functions are located in Libc.pas. Kylix has made it even easier to use by developing the abstract TThread class located in Classes.pas. In addition, applications that descend from the TThread class are cross-platform, as long as they do not use platform specific code.

In Linux, threads can be attached or detached. Use attached threads when they need to be controlled. Valid reasons for using attached threads are: waiting for the thread to finish, the need to get a return value from the thread or to cancel a thread. When a thread is independent, use detached threads. In the current implementation of TThread object, threads are created as attached and are detached, using the pthread_detach function, just before the thread is finished.

Message Passing

Message passing is sending information, or messages between multiple threads. In Linux, messages can be sent by using either Pipes, FIFOs (a UNIX term for named pipes) or System V Message Queues. Other versions of UNIX can also using POSIX Message Queues, however, they are not available at the time of this writing.

Message Passing IPCs available in Linux

Linux has three message passing IPCs available. They are Pipes, FIFOs (aka Named Pipes) and System V Message Queues.

Pipes

A pipe is a one way communication channel. For bi-directional communication, an additional pipe is needed. Since pipes are not named, they can only be used by processes that are related. They are frequently using in processes that create child processes. (A child process is one that is created from another process). Shell programs like bash or csh create a pipe when they are specified on the command line with the vertical bar symbol (|). The shell creates a pipe and forks a child process. In the parent process, it redirects the standard output to the write side of the pipe and in the child, it redirects standard input to the read side of the pipe.

Note: Two-way pipes do exist in other versions of UNIX like SVR4.

The following table lists the functions available for manipulating pipes:

Pipe functions

Description

pipe

Creates a one way pipe. Returns two file descriptors for each end of the pipe.

__read

Reads from a pipe or any file descriptor

__write

Writes to a pipe or any file descriptor.

__close

Closes a pipe or any file descriptor

dup2

Duplicates a file descriptor, closing the new file descriptor if necessary.

popen

Creates a pipe and starts another process that can read from or write to the pipe.

fgets (and more)

Reads from a pipe or any file stream.

fputs (and more)

Writes to a pipe or any file stream.

pclose

Closes a file stream and waits for the process spawned by popen to terminate.

 

FIFOs (Named Pipes)

FIFOs (First In, First Out) remove a major limitation of a pipe: it is identified by a name, allowing for unrelated proceses to communicate. The name given to the FIFO needs to be a valid Linux filename and also has file permissions associated with it. Like pipes, FIFOs are a one way communication device.

The following table lists the functions available for creating, opening, reading, writing and deleting FIFOs:

FIFO Functions

Description

mkfifo

Creates a FIFO

open

Opens a FIFO as well as normal files.

__read

Reads from a pipe or any file descriptor

__write

Writes to a pipe or any file descriptor.

__close

Closes a pipe or any file descriptor

unlink

Deletes a FIFO

 

System V Message Queues

A System V Message Queue is a one way communication device with the most flexibility. Unrelated processes can add and remove messages as long as they have the proper permissions and know the name of the message queue. Furthermore, messages can be prioritized, allowing for the removal of messages by priority, not a first in, first out basis. The messages that are sent and received are defined by the applications and not by the functions that manipulate them.

The following table lists the functions available for creating, accessing and deleting System V Message Queues:

System V Message Queue Functions

Description

ftok

Creates an IPC key from a filename and integer

msgget

Creates or opens a message queue

msgsnd

Puts a message on the queue

msgrcv

Removes a message from the queue

msgctl

Removes the message queue from the system and other message queue control operations.

 

Creating a Pipe Class

The basic operations that are needed for creating a Pipe class are: creating, reading, writing and closing. Two additional methods have been added: PipeWritesToStdOut redirects write side of the pipe to standard ouptut and PipeReadsFromStdIn redirects standard input to the read side of the pipe. In the example, the usage of these additional methods will be shown.

Note: All of the classes are descendants of a class called TIPCBase. This class contains debugging and helper methods for reporting errors.

Class Definition

TPipeBuffer = array of char;
TPipeSide   = (psRead, psWrite);

TPipe = class (TIPCBase)
private
  FHandles : array[0..1] of Integer;
public
  constructor Create;
  destructor  Destroy; override;
  function Read(var buffer : TPipeBuffer; bufSize : Integer) : boolean;
  function Write(var buffer : TPipeBuffer; bufSize : Integer) : boolean;
  function Close(what : TPipeSide) : boolean;
  function WriteToStdOut : boolean;
  function ReadFromStdIn : boolean;
end;

Constructor

In the constructor, the pipe is created, and the handles are saved in an array. The read side of the pipe is position zero and the write side is position one.

constructor TPipe.Create;
var
  pptr : PInteger;
  ret : integer;

begin
  inherited;

  pptr   := @FHandles;
  ret    := pipe(pptr);
  if ret <> LIBC_SUCCESS then
     ErrorMessage('pipe');
end;

 

Destructor

The destructor closes both handles of the pipe.

destructor TPipe.Destroy;
begin
  __close(FHandles[IDX_READ_PIPE]);
  __close(FHandles[IDX_WRITE_PIPE]);
  inherited;
end;

 

Reading and Writing

Reading and writing to the pipe are accomplished by using the Libc functions __read and __write. These functions retrieve and send information to and from the pipe. After writing the data, the method flushes the buffer so that the data is sent out over the pipe.

function TPipe.Read(var buffer : TPipeBuffer; 
                    bufSize : Integer) : boolean;
var
  ret : integer;

begin
  // IDX_READ_PIPE = 0
  ret := __read(FHandles[IDX_READ_PIPE],buffer[0],bufSize);
  Result := (ret = LIBC_SUCCESS);
end;

function TPipe.Write(var buffer : TPipeBuffer; 
                         bufSize : Integer) : boolean;
var
  ret : integer;

begin
  // IDX_WRITE_PIPE = 1
  ret := __write(FHandles[IDX_WRITE_PIPE],buffer[0],bufSize);
  // flush the write down the pipe..
  fsync(FHandles[IDX_WRITE_PIPE]);
  Result := (ret = LIBC_SUCCESS);
end;

 

Redirecting Standard Input

When developing command line tools, it is very common to use what is called a pipeline. A pipeline takes the output of one program and sends it to the input of another program, creating a “pipeline” between the two programs. ReadFromStdIn first closes standard input, then it makes standard input point to the read side of the pipe, thereby redirecting standard input to the pipe.

function TPipe.ReadFromStdIn : boolean;
var
  ret : integer;

begin
  // IDX_READ_PIPE = 0
  ret := dup2(FHandles[IDX_READ_PIPE], STDIN_FILENO);
  if (ret = LIBC_FAILURE) then
    ErrorMessage('ReadFromStdIn:dup2');

  Result := (ret = LIBC_SUCCESS);
end;

 

Redirecting Standard Output

Similarly, WriteToStdOut first closes standard output, then it makes a copy of the write side of the pipe point to standard output, thereby redirecting the output of the pipe to standard output.

function TPipe.WriteToStdOut : boolean;
var
  ret : integer;

begin
  // IDX_WRITE_PIPE = 1
  ret := dup2(FHandles[IDX_WRITE_PIPE], STDOUT_FILENO);
  if (ret = LIBC_FAILURE) then
    ErrorMessage('WriteToStdOut:dup2');

  Result := (ret = LIBC_SUCCESS);
end;

 

Closing the Pipe

When a specific side of the pipe needs to be closed, use the close method.

function TPipe.Close(what : TPipeSide) : boolean;
begin
  __close(FHandles[ord(what)]);
  Result := true;
end;

 

Creating a FIFO Class

Like a pipe, a FIFO has the same basic operations as the Pipe class, namely reading and writing.

 

Class Definition

TPipeSide   = (psRead, psWrite);

TNamedPipe = class(TIPCBase)
private
  FOpen     : boolean;
  FFileDesc : integer;
  FPipeName : string;
  FPipeSide : TPipeSide;

  function PipeSideToBits(pipeSide : TPipeSide) : integer;
public
  constructor Create(name : string; pipeSide : TPipeSide;
                     perm : TIPCFilePermissions);
  destructor Destroy; override;
  function Read(var buffer : TPipeBuffer; bufSize : Integer) : boolean;
  function Write(var buffer : TPipeBuffer; bufSize : Integer) : boolean;
  function Close : boolean;
end;

 

Constructor

The constructor creates the named pipe with the appropriate permissions, based on which side of the pipe that is opened. The read-side only needs read only permissions, while the write side needs write permissions. After creating the FIFO, it is opened. Any errors raise exceptions.

constructor TNamedPipe.Create(name : string;
                              pipeSide : TPipeSide;
                              perm : TIPCFilePermissions);
var
  ret : integer;

begin
  inherited Create;

  FOpen := false;

  // create the pipe
  ret := mkfifo(PChar(name), FilePermissionsToBits(perm));
  // It's okay if the pipe already exists..
  if (ret < 0) and (GetLastError <> EEXIST) then
     ErrorMessage('mkfifo');

  FPipeName := name;
  FPipeSide := pipeSide;

  // open the pipe
  FFileDesc := open(PChar(name), PipeSideToBits( FPipeSide ), 0);
  if FFileDesc <> LIBC_SUCCESS then
     ErrorMessage('NamedPipe.__open');

  FOpen := true;
end;

 

Destructor

In the destructor, the pipe is closed.

destructor TNamedPipe.Destroy;
begin
  Close;
  inherited;
end;

 

Permissions

PipeSideToBits is a private method used in the constructor. Its purpose is to map the side of the pipe with the proper permissions. Reading requires read only permissions, writing requrires write only permissions.

function TNamedPipe.PipeSideToBits(pipeSide : TPipeSide) : integer;
begin
  Result := 0;
  case pipeSide of
    psRead :  Result := O_RDONLY;
    psWrite : Result := O_WRONLY;
  else
    ErrorMessage('PipeSideToBits: unknown argument');
  end;
end;

 

Reading and Writing

Reading information from the pipe is accomplished by using the Read method while writing data to the pipe is accomplished by using the write method. Both methods take a buffer and the size of the buffer. They return a boolean to indicate success or failure.

function TNamedPipe.Read(var buffer : TPipeBuffer; 
                         bufSize : Integer) : boolean;
var
  ret : integer;

begin
  ret := __read(FFileDesc,buffer[0],bufSize);
  Result := (ret = LIBC_SUCCESS);
end;


function TNamedPipe.Write(var buffer : TPipeBuffer; 
                          bufSize : Integer) : boolean;
var
  ret : integer;

begin
  ret := __write(FFileDesc,buffer[0],bufSize);
  // flush the write down the pipe..
  fsync(FFileDesc);
  Result := (ret = LIBC_SUCCESS);
end;

 

Closing the FIFO

When finished using a FIFO, close it with the Close method. It closes the file descriptor then it attempts to delete the pipe name. It is important to note that the last call to the Close method will actually delete the pipe, since another process or thread still has it opened.

function TNamedPipe.Close : boolean;
begin
   if FOpen then
   begin
     __close(FFileDesc);
     FOpen := false;

     // we skip error checking because it will fail if another
     // process still has the file open. When the last process
     // ends, the pipe will be deleted.
     DeleteFile(FPipeName);
   end;
   Result := true;
end;

 

Creating a Message Queue Class

One of the advantages for using a System V Message Queue is that the messages are programmer defined. Whatever type of information that is needed to be queued can be packaged and be placed it. In this implementation of a System V Message Queue class, blocking can be specified for both reading and writing. This allows a process or thread to either wait for the read or write to finish or continue to do something else and try the read or write again at another time.

Note: A useful Linux command for dealing with System V Message Queues, System V Semaphores and shared memory is ipcs. This command provides IPC information that the system knows about. Another useful command is ipcrm which allows the deletion of the IPC resources listed above. Also, there is a kernel option to either enable and disable System V IPCs.

Class Definition

TMQExampleMsg is only an example of what a message might look like. The only requirement is that the first field must be an integer that indicates the message type. It must be greater than zero. Anything after the first field can be whatever is needed and is not limited to character data types.

TMQExampleMsg = packed record
  MessageType : integer;                // required, > 0
  MessageText : array[0..1] of char;    // whatever you require
end;

TMQFilePermission = (fpOwnerRead, fpOwnerWrite,
                     fpGroupRead, fpGroupWrite,
                     fpOtherRead, fpOtherWrite);

TMQFilePermissions = set of TMQFilePermission;

TSystemVMQ = class(TIPCBase)
private
  FMsgKey      : key_t;    // key needed for Message Queue
  FMsgID       : integer;  // refers to the queue created by msgget
  FOpened      : boolean;
  FPrivateKey  : boolean;
  FPathName    : string;
  FIPCKey      : integer;
  FPermissions : TMQFilePermissions;
  FOpenExist   : boolean;
  FBlockRead   : boolean;
  FBlockWrite  : boolean;

  procedure SetIPCKey(const value : integer);
  function  FilePermissionsToBits : integer;
protected
  procedure CheckProperties;

public
  constructor Create;
  destructor  Destroy; override;
  procedure   Open;
  function    Write(buffer : Pointer; MessageLength : integer) : boolean;
  function    Read(buffer : Pointer;  buflen : integer;
                   MessageType : integer; var NumRead : integer) : boolean;
  procedure   Close;

  property    PrivateKey   : boolean            read  FPrivateKey  
                                                write FPrivateKey;
  property    PathName     : string             read  FPathName    
                                                write FPathName;
  property    IPCKey       : integer            read  FIPCKey      
                                                write SetIPCKey;
  property    Permissions  : TMQFilePermissions read  FPermissions 
                                                write FPermissions;
  property    OpenExisting : boolean            read  FOpenExist   
                                                write FOpenExist;
  property    BlockOnRead  : boolean            read  FBlockRead   
                                                write FBlockRead;
  property    BlockOnWrite : boolean            read  FBlockWrite  
                                                write FBlockWrite;
end;

 

Constructor

The constructor initializes the properties to their default values. Notice that by default, blocking will occur on the reads and writes.

constructor TSystemVMQ.Create;
begin
  inherited;
  FPrivateKey  := false;
  FOpened      := false;
  FPathName    := '';
  FIPCKey      := 0;
  FPermissions := [];
  FOpenExist   := false;   // default it to create mq if it does not exist.
  FBlockRead   := true;    // default is to block
  FBlockWrite  := true;    // default is to block
  FMsgID       := 0;
end;

  

Permissions

FilePermissionsToBits maps the file permissions to the bit values needed for System V Message Queue permissions. Unfortunately, Linux does not define the XXX_MSG_R and XXX_MSG_W bits. They have been defined in SysVMsgQueueConstants unit.

function TSystemVMQ.FilePermissionsToBits : integer;
begin
   Result := 0;

   if fpOwnerRead  in FPermissions then
      Result := Result or USR_MSG_R;

   if fpOwnerWrite in FPermissions then
      Result := Result or USR_MSG_W;

   if fpGroupRead  in FPermissions then
      Result := Result or GRP_MSG_R;

   if fpGroupWrite in FPermissions then
      Result := Result or GRP_MSG_W;

   if fpOtherRead  in FPermissions then
      Result := Result or WRLD_MSG_R;

   if fpOtherWrite in FPermissions then
      Result := Result or WRLD_MSG_W;
end;

 

Opening and Closing

There are two ways of opening a Message Queue. The first way is to specify a filename and IPC key. Using the ftok function, Linux generates a key that is then used when communicating with the message queue. Another option is to use a private key, which is guaranteed to be unique. Typically, unrelated processes use a filename and IPC key where related processes or threads use a private key. When specifying a filename and IPC key, the filename must exist, so the Open method creates an empty file, if needed.

In the Close method, the message queue is closed and the file is deleted if is no longer in use.

procedure TSystemVMQ.Open;
var
  ret,oflag : integer;
  tmpFile : file;

begin
  ValidateProperties;

  if FPrivateKey then
    FMsgKey := IPC_PRIVATE
  else
  begin
    // make sure the file exists..
    if not FileExists(FPathName) then
    begin
      // create a file if we can..
      Assign(tmpFile,FPathName);
      Rewrite(tmpFile);
      CloseFile(tmpFile);
    end;
    FMsgKey := ftok(PChar(FPathName),FIPCKey);
    if FMsgKey = LIBC_FAILURE then
      ErrorMessage('Unable to ftok message queue');
  end;

  oflag := IPC_CREAT;
  // if we want to only open it, then mask in the IPC_EXCL bit(s)
  if FOpenExist then
     oflag := oflag or IPC_EXCL;

  // now get the permission bits and add them to oflag
  oflag := oflag or FilePermissionsToBits;

  // do the open
  ret := msgget(FMsgKey, oflag);
  if (ret = LIBC_FAILURE) then
    ErrorMessage('msgget failed');

  FOpened := true;
end;


procedure TSystemVMQ.Close;
var
  ret : integer;

begin
  if FOpened then
  begin
    ret := msgctl(FMsgID, IPC_RMID, nil);
    if ret = LIBC_FAILURE then
      ErrorMessage('Error closing msq queue (msgctl)');
    FOpened := false;
    // clean up the file if we can..
    DeleteFile(FPathName);
  end;
end;

 

Reading and Writing

Reading and writing to the message queue is more complex then pipes and FIFOs. Reading allows a message to be removed from the queue in order (first in, first out), or by priority. Specifying a message type of zero indicates that the first message (or the oldest) in queue be returned. A message type that is positive will return the first message that matches the specified type, and a negative message type indicates that the first message with the lowest type that is less than or equal to the absolute value of the specified, negative, message type. The NumRead parameter returns the number of bytes read excluding the initial field of message type.

To write a message that is placed on the queue, a buffer and the message length is passed to the Write method. MessageLength parameter is the size of the MESSAGE and not the entire record. Using the TMQExampleMsg for instance, would give us a message length of sizeof(TMQExampleMsg) – sizeof(integer).

function TSystemVMQ.Read(buffer : Pointer;
                         buflen : integer;
                         MessageType : integer;
                         var NumRead : integer) : boolean;
var
   ret,flag : integer;

begin
  if not FOpened then
    Result := false
  else
  begin
    flag := 0;
    if not FBlockRead then
      flag := IPC_NOWAIT;

    Result := true;
    ret := msgrcv(FMsgID, buffer, buflen, MessageType, flag);
    if ret = LIBC_FAILURE then
      Result := false;
  end;
end;


function TSystemVMQ.Write(buffer : Pointer;
                          MessageLength : integer) : boolean;
var
  ret,flag : integer;

begin
  if not FOpened then
    Result := false;
  else
  begin
    flag := 0;
    if not FBlockWrite then
      flag := IPC_NOWAIT;

    Result := true;
    ret := msgsnd(FMsgID, buffer, MessageLength, flag);
    if (ret = LIBC_FAILURE) then
      Result := false;
  end;
end;

 

Using the Classes

Pipe Example

The PipeTest program below demonstrates how to use a pipe to send and receive messages.

program PipeTest;

uses SysUtils,Pipes;

const PIPE_NAME = '/tmp/fifo.1';

var
  pipe : TPipe;
  tmpbuf : string;
  sendBuf : TPipeBuffer;
  recvBuf : TPipeBuffer;

begin
  pipe := TPipe.Create;

  tmpbuf := 'Hello Pipe world!';
  SetLength(sendBuf,length(tmpbuf)+1);
  StrPCopy(PChar(sendBuf),tmpbuf);

  // write something on one end and see 
  // if we get the same thing on the read side
  if not pipe.Write(sendBuf, length(sendBuf)) then
     writeln('Error writing buffer!');

  SetLength(recvBuf,length(tmpbuf)+1);
  
  // now read something on the other end
  if not pipe.Read(recvBuf, length(recvBuf)) then
     writeln('Error reading buffer!');

  writeln('Hey I got the following from the pipe ->',StrPas(PChar(recvBuf)),'<--');
  pipe.Free;
end.

FIFO Example

For the FIFO example, there is a server program and a client program. The server side creates a pipe and blocks while waiting for the client to send a message. In the client, the pipe is opened and the message is sent to the server. Then, the server retrieves the message and displays whatever the client sent to it.

// The Fifo Server program
program fifoserver;

uses SysUtils, Pipes;

const PIPE_NAME = '/tmp/fifotest';

var
  fifo    : TNamedPipe;
  recvBuf : TPipeBuffer;

begin
  writeln('Starting fifo server...');
  fifo := TNamedPipe.Create( PIPE_NAME, psRead, 
                            [fpOwnerRead, fpOwnerWrite,
                             fpGroupRead, fpGroupWrite]);

  SetLength(recvBuf,1024);   
  if not fifo.Read(recvBuf, length(recvBuf)) then
    writeln('Server: Error reading from fifo!')
  else
    writeln('Received ->',StrPas(PChar(recvBuf)),'<- from a client');

  fifo.Free;

  writeln('Server is done.');
end.

// The Fifo Client program
program fifoclient;

uses SysUtils, Pipes;

const PIPE_NAME = '/tmp/fifotest';

var
   fifo    : TNamedPipe;
   tmpstr  : string;
   sendBuf : TPipeBuffer;

begin
  writeln('Starting fifo Client..');
  fifo := TNamedPipe.Create( PIPE_NAME, psWrite, 
                             [fpOwnerRead, fpOwnerWrite,
                              fpGroupRead, fpGroupWrite]);

  tmpstr := 'Hello from a fifo client!';
  SetLength(sendBuf,length(tmpstr));   
  StrPCopy(PChar(sendBuf),tmpstr);

  if not fifo.Write(sendBuf, length(sendBuf)) then
    writeln('Client: error writing to fifo!')
  else
    writeln('Message successfully sent!');

  fifo.Free;

  writeln('Client is done.');
end.

System V Message Queue Example

To show how to use the System V Message Queue class, the examples below shows two programs. One adds a message to the queue and the other retrieves a message from the queue. In the addmessage program, it places the message on the queue and exits. The getmessage program, removes an item from the queue and displays the contents to the console window. Notice that neither program calls the Close message on the queue, so that the queue is not deleted from the system.

// Adds a message to the queue
program addmessage;

uses SysUtils, SysVMsgQueue;

const
  MQ_PATH_NAME = '/tmp/msg1';
  MAX_CHARS    = 80;

type
   TMyMsgQueueRecord = packed record
     MessageType : integer;
     MyInteger   : integer;
     MyFloat     : double;
     MyCharStr   : array[0..MAX_CHARS] of char; // notice that this is not a string!
   end;

var
   msg : TSystemVMQ;
   buf : TMyMsgQueueRecord;

begin
   writeln('Adding a message');
   msg              := TSystemVMQ.Create;

   writeln('Setting properties..');
   msg.Debug        := true;
   msg.PrivateKey   := false;
   msg.PathName     := MQ_PATH_NAME;
   msg.Permissions  := [fpOwnerRead, fpOwnerWrite, 
                        fpGroupRead, fpGroupWrite];
   msg.OpenExisting := false;  // create it if necessary
   msg.BlockOnWrite := true;
   writeln('Opening the msg queue..');
   msg.Open;

   // build the "message"
   buf.MessageType := 1;
   buf.MyInteger   := 7;
   buf.MyFloat     := 1734.58;
   StrPCopy(buf.MyCharStr,'This is my message to say' + 
                          ' hello from addmessage');

   writeln('Sending the message..');
   if not msg.Write(@buf,sizeof(TMyMsgQueueRecord)-sizeof(integer)) then
      writeln('error writing message!')
   else
      writeln('message sent!');

   msg.Free;
end.

// Retrieves a message from the Queue
program getmessage;

uses SysUtils, SysVMsgQueue;

const
  MQ_PATH_NAME = '/tmp/msg1';
  MAX_CHARS    = 80;

type
   TMyMsgQueueRecord = packed record
     MessageType : integer;
     MyInteger   : integer;
     MyFloat     : double;
     MyCharStr   : array[0..MAX_CHARS] of char;
   end;

var
   msg : TSystemVMQ;
   buf : TMyMsgQueueRecord;
   NumRead : integer;

begin
   writeln('Retrieving a message');
   msg              := TSystemVMQ.Create;

   writeln('Setting properties..');
   msg.Debug        := true;
   msg.PrivateKey   := false;
   msg.PathName     := MQ_PATH_NAME;
   msg.Permissions  := [fpOwnerRead, fpOwnerWrite, 
                        fpGroupRead, fpGroupWrite];
   msg.OpenExisting := false;  // create it if necessary
   msg.BlockOnRead  := true;
   writeln('Opening the msg queue..');
   msg.Open;

   // clear the message
   buf.MessageType := 0;
   buf.MyInteger   := 0;
   buf.MyFloat     := 0;
   StrPLCopy(buf.MyCharStr,'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',MAX_CHARS);

   writeln('receiving the message..');
   if not msg.Read(@buf,
                   sizeof(TMyMsgQueueRecord)-sizeof(integer),1,NumRead) then
      writeln('error reading message!')
   else
   begin
      writeln('message received! read ',NumRead,' messages');
      writeln('contents of message');
      writeln('MessageType = ',buf.MessageType,' MyInteger = ',
               buf.MyInteger,' MyFloat = ',buf.MyFloat,
               ' MyCharStr = ',buf.MyCharStr);
      writeln;
   end;

   msg.Free;
end.

Additional Resources

Kylix 2 Development by Eric Whipple and Rick Ross, published by Wordware, ISBN # 1556227744

Understanding POSIX Threads: Programming with POSIX Threads by David R. Butenhof, published by Addison-Wesley, ISBN # 0-201-63392-2.

Interprocess Communication: UNIX Network Programming Interprocess Communications Volume 2, Second Edition by W. Richard Stevens, published by Prentice Hall, ISBN 0-13-081081-9

LINUX Core Kernel Commentary by Scott Maxwell, published by Coriolis, ISBN 1-57610-469-9

Interesting notes on the Linux Kernel and Threads: http://boudicca.tux.org/hypermail/linux-kernel/1998/1998week50/0701.html

POSIX and Unix Threads:: http://www-users.cs.umn.edu/~seetala/ResourceCenter/systems-programming.html

POSIX Threads Explained: http://www-106.ibm.com/developerworks/library/posix1.html?dwzone=linux

Additional online resources can be found at http://rick-ross.com

Source Code

Paper Specific and Common

Leave a Reply