APIs Sometimes Fail (But Programmers Don’t Have To)
October 3, 2007 Ted Holt
My ambition is to eventually be as smart as my teenagers. Some days I think I’ll make it. After all, my old man became downright brilliant once I left home and had to start paying my own bills. On other days, like days when I try to write computer programs, I have the gravest of doubts. One of those days occurred recently, when I spent half a day trying to find a bug in my code. It turned out that a call to the Send Program Message API, QMHSNDPM, was failing, and I wasn’t bothering to check whether the call succeeded or not. My friend Cletus the Codeslinger reveled in ragging me about it. “What moron told you you were fit to be a programmer?” he sneered. “If you’d read my assertions article, that wouldn’t have happened.” Of course, I did read the article–I’m the editor. But it’s foolish to argue with someone when he’s right. Most System i APIs, including QMHSNDPM, use a data structure to report errors. (Exceptions are the ILE CEE APIs, which use feedback codes and conditions, and the Unix-type, and National Language Data Conversion, which use the errno variable.) The data structure has two formats–ERRC0100 and ERRC0200. You can use either one. Or you can tell the API to send an escape message, rather than load a data structure. Let’s look at an RPG prototype for the API that got me in trouble. D SendPgmMsg pr extpgm('QMHSNDPM') D MsgID 7 const D MsgFile 20 const D MsgDta 80 const D MsgDtaLen 10i 0 const D MsgType 10 const D MsgQ 10 const D MsgQNbr 10i 0 const D MsgKey 4 D Error ???? The ninth parameter, which I have defined with question marks for now, tells QMHSNDPM how the programmer wants the API to report errors. The first four bytes of the error parameter contain a binary number, which is the key to the whole thing. If that number is zero, the API is to report errors by sending diagnostic and escape messages. If that number is positive, the API is to report errors through a data structure formatted according to the ERRC0100 format. If that number is negative one, the API is to report errors through the data structure according to the ERRC0200 format. If you want to receive diagnostic and escape messages, you don’t even need a data structure. A scalar value will do. Notice the last line of the prototype, where the error parameter is defined as a four-byte integer. D SendPgmMsg pr extpgm('QMHSNDPM') D MsgID 7 const D MsgFile 20 const D MsgDta 80 const D MsgDtaLen 10i 0 const D MsgType 10 const D MsgQ 10 const D MsgQNbr 10i 0 const D MsgKey 4 D Error 10i 0 const Here’s a call to the API that illustrates this option. /free monitor; SendPgmMsg ('CPF9898': 'QCPFMSG QSYS': MsgDta: %len(MsgDta): '*INFO': '*PGMBDY': 1: MsgKey: *zero); on-error; // it bombed; do something endmon; If you prefer to use the data structure, that’s fine. I have found ERRC0100 to be sufficient. You can pass it as many bytes as you wish. I recommend a minimum of 16 bytes, but I have come to prefer 272, as this length allows for 256 bytes of message data. Here’s the revised prototype and the data structure for the error feedback. D SendPgmMsg pr extpgm('QMHSNDPM') D MsgID 7 const D MsgFile 20 const D MsgDta 80 const D MsgDtaLen 10i 0 const D MsgType 10 const D MsgQ 10 const D MsgQNbr 10i 0 const D MsgKey 4 D Error like(ErrorDS) D ErrorDS ds 272 qualified D BytesProv 10i 0 inz(%size(ErrorDS)) D BytesAvail 10i 0 D ExceptionID 7 D 1 D ExceptionDta 256 Here’s the call. SendPgmMsg ('CPF9898': 'QCPFMSG QSYS': MsgDta: %len(MsgDta): '*INFO': '*PGMBDY': 1: MsgKey: ErrorDS); if ErrorDS.BytesAvail > *zero; // it bombed; do something endif; If the call succeeds, you will find a zero in the bytes-available subfield. If the call fails, you will receive a positive value in the bytes-available subfield, a seven-byte message ID in ErrorDS.ExceptionID, and the message data for the error in ErrorDS.ExceptionDta. Do not test the call by checking those last two subfields for non-blanks. After a successful call, they may continue to contain data from an earlier, failed call. You must use ERRC0200 if you want convertible character support. I’ve never used ERRC0200, so I threw together an example. You may come up with something better. Here’s the prototype again, with the revised error data structure and a separate scalar variable for exception data. D SendPgmMsg pr extpgm('QMHSNDPM') D MsgID 7 const D MsgFile 20 const D MsgDta 80 const D MsgDtaLen 10i 0 const D MsgType 10 const D MsgQ 10 const D MsgQNbr 10i 0 const D MsgKey 4 D Error like(ErrorDS) D ErrorDS ds 1024 qualified D Key 10i 0 inz(-1) D BytesProv 10i 0 inz(%size(ErrorDS)) D BytesAvail 10i 0 D ExceptionID 7 D 1 D CCSID 10i 0 D ExcDtaOffset 10i 0 D ExcDtaLength 10i 0 D ExceptionDta s 256 The stand-alone exception data variable is necessary because the location of exception data is not fixed within the data structure. You have to look at the offset and length subfields in order to find the exception data. The following code loads the exception data if the call fails. clear ExceptionDta; SendPgmMsg ('CPF9898': 'QCPFMSG QSYS': MsgDta: %len(MsgDta): '*INFO': '*PGMBDY': 1: MsgKey: ErrorDS); if ErrorDS.BytesAvail > *zero; // it bombed // retrieve the exception data if ErrorDS.ExcDtaLength <= %size(ExceptionDta); ExceptionDta = %subst(ErrorDS: ErrorDS.ExcDtaOffset + 1: ErrorDS.ExcDtaLength); else; ExceptionDta = %subst(ErrorDS: ErrorDS.ExcDtaOffset + 1: %size(ExceptionDta)); // do whatever else endif; endif; I learned a lesson from my experience: Checking for API execution errors is easy. The hard part might be remembering to do so.
|