Update: There is a far easier way to do this. See here.
I have always had a standard that whatever output I produce from the IBM i I always put the program's name on it. This is very useful when a user brings me a screen print or report I can quickly go to the program they have a question about. Many years ago I found that some of my colleagues would copy my display and printer file code without bothering to change the name of the program "hard coded" on the file. To overcome this I code the program name as a field in the file, and I always retrieve the name of the program from within the program itself. In RPG the program name is a subfield in the Program Data Structure so it can be easily retrieved, for an example see the post Externally described Data Structures.
Unfortunately there is no equivalent of the Program Data Structure in CL. The program name can be retrieved in a two steps by sending and receiving a program message. There are two versions of my example depending on what I want to retrieve:
Before I go into the example programs I want to describe the part of these example programs that does the retrieving:
06 SNDPGMMSG MSG(' ') TOPGMQ(*SAME) MSGTYPE(*INFO) + KEYVAR(&KEYVAR) 07 RCVMSG PGMQ(*SAME) MSGTYPE(*INFO) RMV(*YES) + SENDER(&SENDER) SENDERFMT(*LONG) |
The first step is to send a message to the current call stack's message queue, using the Send Program Message Command, SNDPGMMSG. The message, MSG parameter, can be anything, I have chosen for it to be blank. I am sending it to the current program message queue, TOPGMQ(*SAME), as an informational message, MSGTYPE(*INFO). I have found that I have to use the KEYVAR parameter, I just use a blank variable.
Having sent a message I need to receive it, when I do I get the program and module name that the message was sent from. Since my program sent the message this will give me the current program's name. I use the Receive Message command, RCVMSG, to get the message and its information. The program queue parameter needs to be for the current program message queue, PGMQ(*SAME), and the message type needs to be the same as the one in the SNDPGMMSG, MSGTYPE(*INFO). I want to remove the message from the message queue once it is retrieved, this is done using RMV(*YES)
The next two parameters of the RCVMSG are where the program's, module's, and procedure's name can be found. The SENDER parameter contains various bits of information about the sender of the message. As this message was sent by the same program it contains information about this program. This information comes in two layouts, depending on what value is in sender format parameter, SENDERFMT. If the short sender format, SENDERFMT(*SHORT), I can retrieve just the program's name. If the long sender format, SENDERFMT(*LONG), I can retrieve the program's, module's, and the procedure's names. Where the program's name is in the SENDER data differs between the two formats.
Sender format | Information | Length | Starting position |
Short | Program's name | 10 | 27 |
Long | Program's name | 10 | 42 |
Module's name | 10 | 54 | |
Procedure's name1 | 256 | 64 |
1 According to the documentation this could contain a list of the nested procedure names'. No matter what I did in testing (RPG procedure calling a CL procedure, one CL procedure calling another) I could only get the procedure name that the SNDPGMMSG was in.
How to get the program's, module's and procedure's name out of &SENDER variable? I could use three Change Variable commands, CHGVAR, each with a substring. But in my opinion that is lines of code and work I do not need. I would use Defined Variables instead, which acts like defining RPG data structure subfields. If you unfamiliar with them you should read Data Structures in CL.
03 DCL VAR(&SENDER) TYPE(*CHAR) LEN(720) 04 DCL VAR(&PROGRAM) TYPE(*CHAR) STG(*DEFINED) + LEN(10) DEFVAR(&SENDER 42) 05 DCL VAR(&MODULE) TYPE(*CHAR) STG(*DEFINED) + LEN(10) DEFVAR(&SENDER 54) 06 DCL VAR(&PROCEDURE) TYPE(*CHAR) STG(*DEFINED) + LEN(256) DEFVAR(&SENDER 64) |
In the example above the variables &PROGRAM, &MOPDULE, and &PROCEDURE are "subfields" of SENDER. For example: &PROGRAM is ten long starting at position 42 of &SENDER.
And now on to the example programs…
Retrieve only the program's name
Most of the time I just want to display the program's name with a suffix, for example CLPGM-1. For that I can use the short format of the Sender data, which happens to be the command's default value for this parameter.
01 PGM 02 DCL VAR(&KEYVAR) TYPE(*CHAR) LEN(4) 03 DCL VAR(&SENDER) TYPE(*CHAR) LEN(80) 04 DCL VAR(&PROGRAM) TYPE(*CHAR) STG(*DEFINED) + LEN(10) DEFVAR(&SENDER 27) 05 SNDPGMMSG MSG(' ') TOPGMQ(*SAME) MSGTYPE(*INFO) + KEYVAR(&KEYVAR) 06 RCVMSG PGMQ(*SAME) MSGTYPE(*INFO) RMV(*YES) + SENDER(&SENDER) 07 CHGVAR VAR(&PROGRAM) VALUE(&PROGRAM |< '-1') 08 ENDPGM |
Line 2: This is the blank parameter I am going to use in the SNDPGMMSG's KEYVAR parameter.
Line 4: In the short format of the Sender's data the program's name is ten long and starts in the 27th position.
Line 5: This is the Send Program Message command, as I have described it above.
Line 6: I only want the short format of the Sender's data. As this is the command default it does not have to be given. The short format is 80 characters, see the definition of the &SENDER variable on line 2.
Line 7: As the variable &PROGRAM is a Defined variable, "subfield", of &SENDER I can use it immediately. Here I am applying the suffix -1 to the name of the program, so the variable will contain the value CLPGM-1.
Retrieve the program's, module's, and procedure's names
To be able to get the module's and procedure's name I must use the long format of the Sender's data.
01 PGM 02 DCL VAR(&KEYVAR) TYPE(*CHAR) LEN(4) 03 DCL VAR(&SENDER) TYPE(*CHAR) LEN(720) 04 DCL VAR(&PGMMODPRC) TYPE(*CHAR) LEN(32) 05 DCL VAR(&PROGRAM) TYPE(*CHAR) STG(*DEFINED) + LEN(10) DEFVAR(&SENDER 42) 06 DCL VAR(&MODULE) TYPE(*CHAR) STG(*DEFINED) + LEN(10) DEFVAR(&SENDER 54) 07 DCL VAR(&PROCEDURE) TYPE(*CHAR) STG(*DEFINED) + LEN(256) DEFVAR(&SENDER 64) 08 SNDPGMMSG MSG(' ') TOPGMQ(*SAME) MSGTYPE(*INFO) + KEYVAR(&KEYVAR) 09 RCVMSG PGMQ(*SAME) MSGTYPE(*INFO) RMV(*YES) + SENDER(&SENDER) SENDERFMT(*LONG) 10 CHGVAR VAR(&PGMMODPRC) VALUE(&PROGRAM |< '/' + || &MODULE |< '/' || &PROCEDURE) 11 ENDPGM |
Line 2: As I am using the long format of the Sender parameter it has to be defined as 720 character.
Lines 5 - 7: I now have three "subfields" for the program's, module's, and procedure's names. Notice that the program's name starts in a different position, 42, than it did when I used the short format.
Line 9: I need to set the SENDERFMT to *LONG.
Line 10: I can concatenate the three "subfields" together into a new variable to display all three separated by a slash character. In my tests this would display 'TESTRPG/PROCCL/PROCCL'.
Personally I only use the program's name, so my programs contain the code shown in the first example.
You can learn more about this from the IBM website:
This article was written for IBM i 7.2, and should work for earlier releases too.
Update
If you just want the program and library name you will find it easier to use the method describe in this post: Easiest way to retrieve the program's name in a CL program.
Great article, as usual. The SNDPGMMSG/RCVMSG combination could also be coded as:
ReplyDeleteSNDPGMMSG MSG(' ') TOPGMQ(*SAME) KEYVAR(&MSGKEY)
RCVMSG MSGKEY(&MSGKEY) SENDER(&SNDINFO) SENDERFMT(*LONG)
The KEYVAR parameter of the SNDPGMMSG command is actually a return variable which is populated with a unique message identifier after the command is run, which can then be used in the MSGKEY parameter of the RCVMSG command to get the message just sent. Also, the RMV parameter of RCVMSG defaults to *YES, so I didn't include it.
I'm able to retrieve program name even without using KEYVAR.
ReplyDeleteSNDPGMMSG MSG(' ') TOPGMQ(*SAME)
RCVMSG MSGTYPE(*INFO) SENDER(&SENDER)
Will this cause a problem later?
dunno why this is not implemented in the RTVJOBA already:
ReplyDeleteRTVJOBA PGM(&PGM) &MODULE(&MOD) &PROC(&PROC)
probably and most likely too easy for IBM
It would be really nice if they added those parms to the RTVJOBA command.
DeleteExcellent. Thanks. Is there any way to retrieve the library name of the program as well as its name?
ReplyDeleteClick on the link in the Update section above. It will take you to a post where getting the program and library name is much easier.
Delete