扩展 IBM® Informix® 4GL 应用程序,以便与 IBM WebSphere® MQ 消息传递系统无缝地通信。 简介 不同应用程序之间的通信正变得越来越重要。无论您是要实现完善的面向服务的架构(Service Oriented Architechture),还是要在两个系统之间传输数据,IBM WebSphere MQ 都可以作为数据交换的坚实基础。虽然 IBM Informix 4GL 拥有以数据库为中心(database-centric)的第四代编程语言,并且具备使用数据库和构建业务逻辑所需的所有功能,但是它不能处理外部任务。幸运的是,Informix 4GL 有结合 C-函数的能力,因而扩展 4GL 并使其通过 WebSphere MQ 发送和接收消息就变得比较容易。本文将逐步描述如何做到这一点。文中还包含完整有效的源代码,以帮助您学习。
WebSphere Message Queue Interface (MQI) WebSphere MQ Message Queue Interface (MQI) 是一种功能丰富的编程接口。程序员可以通过它访问 WebSphere MQ 消息传递平台的所有功能,同时可以对消息和消息的行为方式进行详细的控制。MQI 支持多种编程语言,但在扩展 Informix 4GL 的时候,必须使用 C 语言。
MQI 由一些 CALL 和 STRUCTURE 组成,应用程序通过这些 CALL 访问 MQ 管理器,而使用 STRUCTURE 来与队列管理器来回传递消息。最基本的 CALL 有:
MQCONN 和 MQDISC,用于建立和断开与 MQ Manager 的连接。 MQOPEN 和 MQCLOSE,用于打开和关闭队列。 MQPUT 和 MQGET,用于编写和读取消息。 MQI 还包含下面这些 CALL,但它们在本文中没有用到:
MQINQ,用于获取队列的属性。 MQSET,用于设置或更改队列的属性。 MQBEGIN、MQCMT 和 MQBACK,用于通过 MQ 管理器管理事务处理。 编写 C 函数 在后面的例子中,所有在 4GL 代码中调用的 C 函数都被命名为 "fgl_mq...",例如 fgl_mqconnect 和 fgl_mqclose。与 MQ 服务器进行通信要采取以下 5 个步骤,按顺序,这 5 个步骤分别是:
连接到 MQ 管理器。 打开一个队列。 在队列上编写或读取消息。 关闭队列。 断开与 MQ 管理器的连接。 状态和错误处理 当设计 fgl_mq 函数时,处理 MQ 状态和错误的方式很重要。MQ Interface 使用了一个完成代码(completion code)和一个原因代码(reason code)。由于很多函数必须同时返回一个值和一个代码,因此这里必须进行特殊的考虑。返回两个或更多的值也是可以的,但这样的话就必须限制函数只能通过 CALL 语句来执行。
CALL fgl_mqconnect() RETURNING mq_status, connect_handle
例如,为了得到编码的最大灵活性,同时又能够在 LET 语句或表达式中使用函数,那么 fgl_mq 函数必须只返回一个值。因此,这里要实现一个特殊的状态函数,即 fgl_mqstatus。fgl_mqstatus 函数应该可以在任何其他 fgl_mq 函数之后立即调用。所以通常的 4GL 代码看上去将如下所示:
LET connect_handle = fgl_mqconnect() IF fgl_mqstatus() <> 0 THEN error handling code END IF
所有的 MQI CALL 返回一个 32 位整数格式的完成码。fgl_mq 函数使用一个静态 long 变量来保存最近的完成码。如果 fgl_mq 函数顺利运行,就会返回一个值为 0 的状态。如果用不适合的参数调用 fgl_mq 函数,则这个静态变量还会被设置为错误(!=0)。当检测到一个错误时,就会设置一个原因代码,同时还会设置一个简短的错误消息。函数 fgl_mqreason 和 fgl_mqerrmsg 用于获取原因代码和简短的错误文本。
清单 1. fgl_mqstatus 函数
static long mqStatus;
int fgl_mqstatus(int nargs) /* number of parameters (ignored) */ { ibm_lib4gl_returnInt(mqStatus); return(1); /* number of values pushed */ }
函数 fgl_mqstatus 用于获取任何 MQ 接口函数的返回状态码。应该在任何 fgl_mq 服务函数之后立即调用这个函数。如果 fgl_mqstatus 返回的值为 0,那么 MQ 调用就成功了。如果这个值不是 0,则有错误发生。该函数调用还可以返回两种正值,它们不是指错误,而是指警告。如果返回的值是 1,则表示警告。在消息被截断时就会出现这种情况。当返回状态码 100 时,则意味着没有发现消息。每当出现错误或警告,都应该调用 fgl_mqreason,以便获得错误或警告的原因。函数 fgl_mqerrmsg 可用于获得错误或警告的文本描述。为了使用这个函数,必须安装 SupportPac MA0K。
清单 2. fgl_mqreason 和 fgl_mqerrmsg 函数
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); }
由 MQI CALL 设置的所有原因代码都是整数。如果原因代码为负,则该原因代码已经被 fgl_mq 函数本身进行了设置,例如在内存分配失败时就会出现这种情况。
连接到 MQ Manager 任何 4GL 应用程序在进行其他 MQI 调用之前,都必须首先建立到某个队列管理器的连接。当应用程序成功地建立连接时,队列管理器返回一个连接句柄。这是一个标识符,应用程序每次发出 MQI 调用时,都必须指定该句柄。一个应用程序每次只能连接到一个队列管理器,所以(对于特定的应用程序)每次只有一个连接句柄是合法的。当应用程序连接到某一队列管理器时,队列管理器将处理应用程序发出的所有 MQI 调用,直到应用程序断开与队列管理器的连接。
连接句柄是一个 32 位的整数。在 C 中,连接句柄由一个 long 型变量实现,而用于包含句柄的 4GL 变量则必须定义为 INTEGER。
清单 3. fgl_mqconnect 函数
int fgl_mqconnect(int nargs) { char QMName[MQ_Q_MGR_NAME_LENGTH]; /* 队列管理器 name */ MQHCONN Hcon; /* connection handle */ if (nargs > 0) ibm_lib4gl_popString(QMName,MQ_Q_MGR_NAME_LENGTH); else QMName[0] = '\0'; /* use default 队列管理器 */ MQCONN(QMName, /* 队列管理器 */ &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); }
fgl_mqconnect 带一个参数,即 MQ Manager 的名称。如果没有提供参数,则会建立到默认 MQ Manager 的连接。变量 mqStatus 和 mqReason 是静态变量(请参阅前面关于错误处理的小节)。
例 2. 调用 fgl_mqconnect 函数
DEFINE connect_handle INT CALL fgl_mqconnect('queueManager') RETURNING connect_handle IF fgl_mqstatus() <> 0 THEN error handling END IF
打开队列 当打开一个队列时,必须告诉 MQM 队列的使用方式 —— 浏览、读或写。还可以同时以读和写的方式打开一个队列。打开队列的模式由提供给打开语句的参数决定。为了方便起见,这里提供了两个打开函数:fgl_mqopen4read 和 fgl_open4write。还可以使用标准打开函数 fgl_mqopen。由于没有默认队列,因此在打开一个队列时必须提供队列名称。
如果打开请求成功,队列管理器将返回一个对象句柄(队列句柄)。4GL 应用程序在发出 put 或 get 调用时,必须指定这个句柄,一起指定的还有连接句柄。该对象句柄确保请求能够在适当的队列上得到处理。
对象句柄是 32 位的整数。在 C 中,对象句柄以 long 型变量实现,而用于包含该句柄的 4GL 变量则必须定义为 INTEGER。
清单 4. fgl_mqopen4write 函数
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); }
例 3. 调用 fgl_mqopen 函数
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
写队列 以写的方式打开队列之后,4GL 应用程序可以将消息放在该队列上。为此,它使用另一个 MQI 调用,调用时必须指定一些参数和数据结构。这些参数和数据结构定义了与要放入队列的消息有关的所有信息,这些信息包括消息类型、目的地、设置的选项,等等。消息数据(即应用程序所发送消息中特定于应用程序的内容)是在一个缓冲区中定义的,而这个缓冲区则是在调用 MQI 时指定的。当队列管理器处理该调用时,它添加了一个消息描述符(message descriptor),其中包含确保能够正确传递消息所需的信息。消息描述符的格式是由 MQSeries 定义的,消息数据(也就是在应用程序代码中放入到消息数据缓冲区的内容)则由应用程序定义。
清单 5. fgl_mqwrite 函数
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); } }
例 4. 调用 fgl_mqwrite 函数
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
关闭队列 当程序断开与队列管理器的连接时,队列将自动关闭。但是,关闭已打开的对象是一种良好的编程习惯。
清单 6. fgl_mqclose 代码
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); }
断开与 MQ Manager 的连接 最后一步是删除 4GL 应用程序与 MQ Manager 之间的连接。
fgl_mqdisconnect 代码
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); }
构建定制的 runner 当使用 r4gl(或 fglpc)编译 4GL 模块时,所得到的程序由一个 runner(即执行模块)执行。标准的 runner 是 fglgo,它只支持符合 4GL 标准的内建函数。但是,如果程序调用一个 C 函数,那么必须将该函数链接到 runner 中。
为构建一个新的 runner,首先必须编辑 fgiusr.c 文件。fgiusr.c 文件包括 runner 中包含的 C 函数的列表,还包括一个表,这个表描述了 4GL 调用与 C 函数之间的映射,另外,这个文件还包含每个函数所带参数的数量。如果参数的数量是可变的,则参数的最大数量是以负数的形式给出的。
清单 . fgiusr.c 代码
#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} };
发出下列命令来编译 fgl_mq 函数并构建一个新的 runner。要记住更改路径。
$ cfglgo -I/usr/mqm/inc fgiusr.c mqfunc.c /usr/mqm/lib/libmqm.a -o fglmq
这个新的 runner 名为 fglmq。
All the fgl_mq functions http://www-900.ibm.com/developerWorks/cn/dmdd/library/techarticles/dm-0501samuelsen/4glmqi.tar.gz
| | |