Skip to main content

Application integration: Use Informix 4GL with WebSphere MQ

Ready-to-use C functions

Claus Samuelsen (csa@dk.ibm.com), Senior IT Specialist, IBM
Claus Samuelsen works on IBM Software Group's Software Innovation Team.

Summary:  Extend your IBM® Informix® 4GL applications to seamlessly communicate with the IBM WebSphere® MQ messaging system.

Date:  06 Jan 2005
Level:  Intermediate
Activity:  445 views

Introduction

Communication between different applications is becoming increasingly important. Whether you are implementing a full-blown Service Oriented Architechture or just need to transfer data between two systems, IBM WebSphere MQ is a solid foundation for data exchange. While IBM Informix 4GL has a database-centric, fourth-generation programming language, and has all the functionallity needed to work with the database and build business logic, it lacks the abillity to do external tasks. Fortunately, with Informix 4GL's capabillity of incorporating C-functions, extending 4GL to send and receive messages using WebSphere MQ is easy. This article gives a step-by-step description of how to do that. Full functional source code is included to help you.


WebSphere Message Queue Interface (MQI)

The WebSphere MQ Message Queue Interface (MQI) is a rich programming interface that gives the programmer access to all the facilities of the WebSphere MQ messaging platform along with detailed control over the messages and the way they behave. MQI supports several programming languages, but when extending Informix 4GL, you must use the C language.

MQI consists of CALLS through which applications can access the MQ manager, and STRUCTURES that applications use for passing messages to and from the queue manager. The most basic CALLS are:

  • MQCONN and MQDISC for connecting and disconnecting to a MQ Manager
  • MQOPEN and MQCLOSE for opening and closing a queue
  • MQPUT and MQGET for writing and reading messages

MQI contains also the following CALLS, but these are not used in this article:

  • MQINQ to retrieve attributes for a queue
  • MQSET to set or change a queue's attributes
  • MQBEGIN, MQCMT, and MQBACK to manage transactional handling through the MQ manager

Programming the C functions

All C functions in the following examples that are to be called within the 4GL code are named "fgl_mq...", for example fgl_mqconnect and fgl_mqclose. Communicating with the MQ server takes the following five steps, in this order:

  1. Connect to the MQ manager
  2. Open a queue
  3. Write or read messages on the queue
  4. Close the queue
  5. Disconnect from the MQ manager

Status and error handling

When designing the fgl_mq functions, the way to handle MQ status and error is important. The MQ Interface uses a completion code and a reason code. Since many of the functions must return both a value and code, you must make special considerations. It is possible to return two or more values, but then the functions will be restricted to be executed by the CALL statement only.

	CALL fgl_mqconnect() RETURNING mq_status, connect_handle
       

In order to get the most flexible coding and, for example, be able to use the functions in the LET statement or in expressions, the fgl_mq functions must only return one value. Therefore, a special status function, fgl_mqstatus, is implemented. The fgl_mqstatus function should be called immediately after any of the other fgl_mq functions. Thus the typical 4GL code will look like this:

	LET connect_handle = fgl_mqconnect()
	IF fgl_mqstatus() <> 0 THEN
	  error handling code
	END IF
       

All MQI CALLS return a completion code in the form of a 32-bit integer. The fgl_mq functions use a static long variable to contain the most recent completion code. Whenever a fgl_mq function runs error free, a status with value 0 is returned. The same static variable is also set to error (!=0) when a fgl_mq function is called with inappropriate arguments. When an error is detected, a reason code is set and a short error message is set, too. The functions fgl_mqreason and fgl_mqerrmsg are used to retrieve the reason code and the short error text.


Listing 1. The fgl_mqstatus function
	static long mqStatus;

	int fgl_mqstatus(int nargs)    /* number of parameters (ignored) */
	{
	  ibm_lib4gl_returnInt(mqStatus);
	  return(1);                   /* number of values pushed */
	}
       

The function fgl_mqstatus is used to get the return status code of any of the MQ interface functions. It should be called immediately after any fgl_mq service function. If the value returned by fgl_mqstatus is 0, then the MQ call was OK. If the value is not zero, then an error occurred. Two positive values can be returned, which are not errors, but warnings. If the value 1 (one) is returned, a warning is raised. This can happen if the message is truncated. When the status code 100 is returned, it means that no messages were found. Whenever a failure or warning occurs, the function fgl_mqreason should be called in order to get a reason for the error or warning. The function fgl_mqerrmsg can be used to get a textual description of the error or warning. To use this, the SupportPac MA0K must be installed.


Listing 2. The fgl_mqreason and fgl_mqerrmsg functions
	int fgl_mqstatus(int nargs)
	{
	  ibm_lib4gl_returnInt4(mqStatus);
	  return(1);
	}
	
	int fgl_mqreason(int nargs)
	{
	  ibm_lib4gl_returnInt4(mqReason);
	  return(1);
	}
	
	int fgl_mqerrmsg(int nargs)
	{
	  if (nargs == 1)
	    ibm_lib4gl_popInt4(&mqReason);
	  if (mqReason > 0)
	    MQReasonCodeMsg(mqReason, mqErrmsg);
	  ibm_lib4gl_returnString(mqErrmsg);
	  return(1);
	}
     

All reason codes set by the MQI CALLS are positive numbers. If the reason code is negative, the reason code has been set by the fgl_mq function itself, for example if memory allocation failed.

Connecting to the MQ Manager

Any 4GL application must make a successful connection to a queue manager before it can make any other MQI calls. When the application successfully makes the connection, the queue manager returns a connection handle. This is an identifier that the application must specify each time it issues an MQI call. An application can connect to only one queue manager at a time, so only one connection handle is valid (for that particular application) at a time. When the application has connected to a queue manager, all the MQI calls it issues are processed by that queue manager until it disconnects from that queue manager.

The connection handle is a 32-bit integer. In C, the connection handle is implemented as a long and the 4GL variable to contain the handle must be defined as INTEGER.


Listing 3. The fgl_mqconnect function
	int fgl_mqconnect(int nargs)
	{
	  char     QMName[MQ_Q_MGR_NAME_LENGTH];    /* queue manager name     */
	  MQHCONN  Hcon;                            /* connection handle      */
	
	  if (nargs > 0)
	    ibm_lib4gl_popString(QMName,MQ_Q_MGR_NAME_LENGTH);
	  else
	    QMName[0] = '\0';             /* use default queue manager      */
	
	  MQCONN(QMName,                  /* queue manager                  */
	         &Hcon,                   /* connection handle              */
	         &mqStatus,               /* completion code                */
	         &mqReason);              /* reason code                    */
	
	  if ((mqStatus != MQCC_OK) || (mqReason != MQRC_NONE))
	  {
	    ibm_lib4gl_returnInt4(0);
	    return(1);
	  }
	  ibm_lib4gl_returnInt4(Hcon);
	  return(1);
	}
    

The fgl_mqconnect takes one argument, the name of the MQ Manager. If no argument is provided, a connection to a default MQ Manager is made. The variables mqStatus and mqReason are static variables (see the section on error handling, above).


Example 2. Calling the fgl_mqconnect function
      
	DEFINE connect_handle INT
	
	CALL fgl_mqconnect('queueManager') RETURNING connect_handle
	IF fgl_mqstatus() <> 0 THEN
	  error handling
	END IF
    

Opening the queue

When you open a queue, you must tell the MQM how the queue is to be used - for browsing, for reading, or for writing. A queue may also be opened for both reading and writing. The mode the queue is opened is determined by parameters given to the open statement. For ease, two special open functions are provided: fgl_mqopen4read and fgl_open4write. The standard open function fgl_mqopen can be used as well. As there is no default queue, a queue name must always be provided when opening a queue.

The queue manager returns an object handle (queue handle) if the open request is successful. The 4GL application must specify this handle, together with the connection handle, whenever it issues a put or a get call. The object handle ensures that the request is carried out on the correct queue.

The object handle is a 32-bit integer. In C, the object handle is implemented as a long and the 4GL variable to contain the handle must be defined as INTEGER.


Listing 4. The fgl_mqopen4write function
	int fgl_mqopen4write(int nargs)
	{
	  MQHCONN  Hcon;                            /* connection handle      */
	  MQOD     queue = {MQOD_DEFAULT};          /* Object Descriptor      */
	  MQHOBJ   Hqueue;                          /* object handle          */
	  MQLONG   O_options;                       /* MQOPEN options         */
	
	
	  if (nargs < 2)
	  {
	    sprintf(mqErrmsg, "MQ-ERR: MQOPEN missing connection handle or queue_name");
	    mqStatus = -1;
	    mqReason = -1;
	    ibm_lib4gl_returnInt4(0);
	    return(1);
	  }
	  else
	  {
	    ibm_lib4gl_popString(queue.ObjectName, MQ_Q_NAME_LENGTH);
	    ibm_lib4gl_popInt4(&Hcon);
	  }	
	  O_options = MQOO_OUTPUT            /* open queue for output     */
	            | MQOO_FAIL_IF_QUIESCING /* but not if MQM stopping   */
	            ;                        /* = 0x2010 = 8208 decimal   */
	
	  MQOPEN(Hcon,                      /* connection handle            */
	         &queue,                    /* object descriptor for queue  */
	         O_options,                 /* open options                 */
	         &Hqueue,                   /* object handle                */
	         &mqStatus,                 /* MQOPEN completion code       */
	         &mqReason);                  /* reason code                  */
	
	  /* report reason, if any; stop if failed      */
	
	  if ((mqStatus != MQCC_OK) || (mqReason != MQRC_NONE))
	  {
	    ibm_lib4gl_returnInt4(0);
	    return(1);
	  }
	  ibm_lib4gl_returnInt4(Hqueue);
	  return(1);
	}
    


Example 3. Calling the fgl_mqopen function
	DEFINE connect_handle INT
	DEFINE queue_handle   INT
	
	LET connect_handle = fgl_mqconnect('queueManager')
	IF fgl_mqstatus() <> 0 THEN
	  error handling
	END IF

	LET queue_handle = fgl_mqopen4write(connect_handle)
	IF fgl_mqstatus() <> 0 THEN
	  error handling
	END IF
    

Writing to the queue

After opening the queue for write, the 4GL application can put a message on the queue. To do this, it uses another MQI call on which you have to specify a number of parameters and data structures. These define all the information about the message you are putting, including the message type, its destination, which options are set, and so on. The message data (that is, the application-specific contents of the message your application is sending) is defined in a buffer, which you specify in the MQI call. When the queue manager processes the call, it adds a message descriptor, which contains information that is needed to ensure the message can be delivered properly. The message descriptor is in a format defined by MQSeries; the message data is defined by your application (this is what you put into the message data buffer in your application code).


Listing 5. The fgl_mqwrite function
	int fgl_mqwrite(int nargs)
	{
	  MQHCONN  Hcon;                            /* connection handle             */
	  MQHOBJ   Hqueue;                          /* object handle                 */
	  char     *buffer;                         /* message buffer                */
	  MQLONG   messlen;                         /* message length                */
	  MQMD     md = {MQMD_DEFAULT};             /* Message Descriptor            */
	  MQPMO    pmo = {MQPMO_DEFAULT};           /* put message options           */
	
	  if (nargs != 4)
	  {
	    sprintf(mqErrmsg, "MQ-ERR: MQWRITE missing connection handle, queue_handle, message or msg_size");
	    mqStatus = -1;
	    mqReason = -1;
	    return(0);
	  }
	  else
	  {
	    ibm_lib4gl_popInt4(&messlen);
	    if (messlen > 0)
	    {
	      messlen++;
	      buffer = (char *)malloc(messlen);
	      if (buffer == NULL)
	      {
	        sprintf(mqErrmsg, "MQ-ERR: MQWRITE could not allocate %d bytes memory\n", messlen);
	        mqStatus = -1;
	        mqReason = -2;
	        return(0);
	      }
	    }
	    ibm_lib4gl_popString(buffer,messlen);
	    ibm_lib4gl_popInt4(&Hqueue);
	    ibm_lib4gl_popInt4(&Hcon);
	
	    memcpy(md.Format,           /* character string format            */
	         MQFMT_STRING, (size_t)MQ_FORMAT_LENGTH);
	
	    memcpy(md.MsgId,           /* reset MsgId to get a new one    */
	              MQMI_NONE, sizeof(md.MsgId) );
	
	    memcpy(md.CorrelId,        /* reset CorrelId to get a new one */
	              MQCI_NONE, sizeof(md.CorrelId) );
	
	    MQPUT(Hcon,                /* connection handle               */
	          Hqueue,              /* object handle                   */
	          &md,                 /* message descriptor              */
	          &pmo,                /* default options (datagram)      */
	          messlen,             /* message length incl. null term. */
	          buffer,              /* message buffer                  */
	          &mqStatus,           /* completion code                 */
       	   &mqReason);            /* reason code                     */
	
	    free(buffer);
	    return(0);
	  }
	}
    


Example 4. Calling the fgl_mqwrite function
	DEFINE connect_handle INT
	DEFINE queue_handle   INT
        DEFINE msg	      CHAR(80)
	
	# Snipped code for connect and open queue - see example 3

	DECLARE cust_curs FOR select fname || ' ' || lname from customer
        FOREACH cust_curs INTO msg
	  CALL fgl_mqwrite(connect_handle, queue_handle, msg, LENGTH(msg))
	  IF fgl_mqstatus() <> 0 THEN
	    error handling
	  END IF
        END FOREACH
    

Closing the queue

Queues are closed automatically when a program disconnects from the queue manager. However, it is good programming practice to close objects that you have opened.


Listing 6. The fgl_mqclose code
	int fgl_mqclose(int nargs)
	{
	  MQHCONN  Hcon;                            /* connection handle      */
	  MQHOBJ   Hqueue;                          /* object handle          */
	  MQLONG   C_options;                       /* MQCLOSE options        */
	
	
	  if (nargs != 2)
	  {
	    sprintf(mqErrmsg, "MQ-ERR: MQCLOSE missing connection handle or queue_name");
	    mqStatus = -1;
	    mqReason = -1;
	    return(0);
	  }
	  else
	  {
	    ibm_lib4gl_popInt4(&Hqueue);
	    ibm_lib4gl_popInt4(&Hcon);
	  }
	
	  C_options = MQCO_NONE;                    /* no close options        */
	
	  MQCLOSE(Hcon,                             /* connection handle       */
	          &Hqueue,                          /* object handle           */
	          C_options,
	          &mqStatus,                        /* completion code         */
	          &mqReason);                       /* reason code             */
	
	  ibm_lib4gl_returnInt4(mqStatus);
	  return(1);
	}
    

Disconnecting from the MQ Manager

The final step is to remove the connection between the 4GL application and the MQ Manager.


fgl_mqdisconnect code
	int fgl_mqdisconnect(int nargs)
	{
	  MQHCONN  Hcon;                            /* connection handle    */
	
	  if (nargs == 1)
	    ibm_lib4gl_popInt4(&Hcon);
	  else
	  {
	    sprintf(mqErrmsg, "MQ-ERR: MQDISCONNECT missing connection handle");
	    mqStatus = -1;
	    mqReason = -1;
	    return(0);
	  }
	  MQDISC(&Hcon,
	         &mqStatus,
	         &mqReason);
	  ibm_lib4gl_returnInt4(mqStatus);
	  return(1);
	}
    


Building a customized runner

When you compile the 4GL modules with r4gl (or fglpc), the resulting program is executed by a runner, or execution module. The standard runner, fglgo, supports only the built-in functions that are standard with 4GL. However, if your program calls a C function, that function must be linked into the runner.

To build a new runner, you must first edit the fgiusr.c file. The fgiusr.c file contains a list of the C functions to be included in the runner and a table describing the mapping between the 4GL call and the C functions and the number of arguments that each function takes. If the number of arguments is variable, the maximum number of arguments is given as a negative number.


Listing. The fgiusr.c code
	#include "fgicfunc.h"

	int fgl_mqstatus();
	int fgl_mqreason();
	int fgl_mqerrmsg();
	int fgl_mqconnect();
	int fgl_mqdisconnect();
	int fgl_mqopen();
	int fgl_mqopen4write();
	int fgl_mqopen4read();
	int fgl_mqclose();
	int fgl_mqwrite();
	int fgl_mqread();
	int fgl_mqrecieve();

	int fgl_mqwriteonce();

	cfunc_t usrcfuncs[] = 
	{
		{"fgl_mqstatus",     fgl_mqstatus, 0},
		{"fgl_mqreason",     fgl_mqreason, 0},
		{"fgl_mqerrmsg",     fgl_mqerrmsg, -1},
		{"fgl_mqconnect",    fgl_mqconnect, -1},
		{"fgl_mqdisconnect", fgl_mqdisconnect, 1},
		{"fgl_mqopen",       fgl_mqopen, 3},
		{"fgl_mqopen4write", fgl_mqopen4write, 2},
		{"fgl_mqopen4read",  fgl_mqopen4read, 2},
		{"fgl_mqclose",      fgl_mqclose, 2},
		{"fgl_mqwrite",      fgl_mqwrite, 4},
		{"fgl_mqread",       fgl_mqread, -4},
		{"fgl_mqrecieve",    fgl_mqrecieve, 3},
		{"fgl_mqwriteonce",  fgl_mqwriteonce, -3},
	    	{0, 0, 0}
	};
    

To compile the fgl_mq functions and build a new runner, issue the following command. Remember to change paths.

      $ cfglgo -I/usr/mqm/inc fgiusr.c mqfunc.c /usr/mqm/lib/libmqm.a -o fglmq
    

The new runner is called fglmq.



Download

DescriptionNameSizeDownload method
All the fgl_mq functions4glmqi.tar.gz5 KB FTP | HTTP

Information about download methods


Resources

About the author

Claus Samuelsen works on IBM Software Group's Software Innovation Team.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Information Management, WebSphere, Architecture
ArticleID=33091
ArticleTitle=Application integration: Use Informix 4GL with WebSphere MQ
publish-date=01062005
author1-email=csa@dk.ibm.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Special offers