Guru: Binding Directory Entries
June 5, 2023 Bob Cozzi
I assume you’ve heard about *BNDDIR (Binding Directory) objects introduced circa 1994 with OS/400 V3R2. The infamous QC2LE binding directory is used by a huge number of RPG applications to access C runtime and unblocked MI functions such as system cvthc, cpybytes, and matmatr. You have probably seen RPG IV source code with the BNDDIR(‘QC2LE’) keyword on the header specification.
I was one of the first developers outside of IBM to use Binding Directories for my own code. When I go back and look at my own RPG IV code created prior to mid-2007, well over 90 percent of it has BNDDIR(‘QC2LE’) in the header specification. Today, a number of RPG IV developers assume BNDDIR(‘QC2LE’) is one of those “just gotta include it” controls like the OPTIONS(*NODEBUGIO) keyword. But that hasn’t been the case since at least V6R1.
Binding Directories
Binding Directory objects (*BNDDIR) are a simple list of *MODULE and *SRVPGM object names. Binding Directory Entries are the *MODULE and *SRVPGM object names stored in the Bind Directory object.
That list is used when you compile your own programs. The compiler (technically the Binder or CRTPGM step) uses binding directories to resolve the procedures you call in your own code. That is, it is a directory of objects that contain each of the procedures you’re calling; that is procedures not defined locally within your RPG IV program itself. For example, if you have the following in your RPG IV program:
ctl-opt BNDDIR('QC2LE'); system('ovrprtf QPRINT OUTQ(COZZI)');
The binder needs to locate the system procedure so it can resolve this reference. It uses the binding directory QC2LE that you provided, to create a list of modules and service programs known as Binding Directory Entries. It then checks the export list of each entry for the procedure named system (case sensitive). If it finds it, everything is wonderful, if not, you get an unresolved external reference error message.
The QC2LE binding directory contains over two dozen entries that are searched for various C and MI runtime procedures. Each entry can export thousands of procedures or variable names.
QC2LE Is No Longer Needed
After a few years of using BNDDIR(‘QC2LE’), I was speaking with IBM Rochester legend Bruce Vining on a different topic and mentioned the pervasive QC2LE use, suggesting it should be integrated into the compiler so it wouldn’t be required in every program. Turns out Bruce was on the same page. He was working on the regular APIs binding directory (QUSAPIBD) at the time and wanted to do just that; consolidate QC2LE with QUSAPIBD. Most operating system APIs were already exported via that QUSAPIDB binding directory. Only C and MI interfaces required QC2LE. The QUSAPIDB binding directory is always associated with the binder (CRTPGM and CRTSRVPGM) so it was never explicitly specified. In fact, no one outside of IBM needed to be aware of QUSAPIBD at all.
To combine QC2LE with QUSAPIBD, we needed to extract what was in QC2LE and add those entries to QUSAPIBD. The problem was that RTVBNDSRC materialized the exported procedures and variables, but nothing other than WRKBNDDIRE and DSPBNDDIR showed you the contents of a Binding Directory. During the early days of ILE (which was named “NPM” before it was announced) I had written a program to read *BNDDIRE objects and dump out the entries. The *BNDDIR objects are a simple space object with fixed length entries. Therefore, it was relatively easy to do a little reverse engineering/trial and error to get it working.
Later, Bruce used a similar technique to enhance the QUSAPIBD binding directory. This meant that things like QlgConvertCase, cvthc, and system would no longer require BNDDIR(‘QC2LE’) on the top of your RPG IV source code. So, beginning with IBM i5/OS V6R1, the need to include BNDDIR(‘QC2LE’) vanished. Today, if you see it in your code, you can feel confident to safely remove it.
As the years went by, IBM enhanced DSPBNDDIR with OUTFILE support. You can now direct the entries of a Binding Directory to a database file with a simple CL command:
DSPBNDDIR QSYS/QC2LE OUTPUT(*OUTFILE) OUTFILE(QGPL/PICKLES)
After running the command, you have a list of the QC2LE entries in a database file named PICKLES. If you also send the QUSAPIBD entries to an outfile, you’ll notice that all the entries from QC2LE are replicated there. Therefore, you no longer need to reference QC2LE in your code.
Binding Source
How do you know what is exported from the various Binding Directory entries? What does a service program such as QC2SYS actually export? To find out, you need to use the WRKSRVPGM command and drill down to the Export list. Or you can use the RTVBNDSRC (Retrieve Binder Source) CL command and generate the Binder Source for the QC2SYS service program. For example:
RTVBNDSRC SRVPGM(QC2SYS) SRCFILE(QGPL/QSRVSRC)
The generated Binder Source is stored in QGPL/QSRVSRC(QC2SYS) and looks like this:
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE(X'000000000000000000009485A3A2A8A2') /***********************************************************/ /* *SRVPGM QC2SYS QSYS 05/17/23 09:06:15 */ /***********************************************************/ EXPORT SYMBOL("system") EXPORT SYMBOL("_C_NEU_system") ENDPGMEXP
You can see the infamous system C runtime function is exposed in two variations: the original named ‘system,’ and a CCSID neutral version named ‘_C_NEU_system’.
Binding Directories Versus Binder Source
A long-standing point of confusion has been the terms “Binder Source” and “Binding Directory.” One is source code, the other is an object type of *BNDDIR.
- A Binding Directory (*BNDDIR) object contains a list of objects that can be imported or bound to your programs.
- Binder Source is a source file member that contains EXPORT statements to identify the procedures in a *SRVPGM that may be imported by other programs.
Binder Source
When you create a *SRVPGM you specify binder source to control what is exported from that service program. The binder source parameters of CRTSRVPGM are:
- EXPORT – Controls whether Binder Source controls exports or *ALL items are exported.
- SRCFILE – The source file that contains the binder source member (when EXPORT(*SRCFILE) is specified.
- SRCMBR – The binder source file member (when EXPORT(*SRCFILE) is specified.
Binder Source is not required. Instead, you can specify EXPORT(*ALL) and force the binder to export all external procedures and variables. Exported procedure names can be quite lengthy. For example, our SQL iQuery ships with several *SRVPGM objects. Below is a segment of the binder source for one of the service programs. This excerpt contains C++ class objects that are used to when output to CSV, XLS, FTP, JSON, HTML, etc. is requested. You can see that the long export names are continued onto subsequent lines.
EXPORT SYMBOL("__ct__6CChartFv") EXPORT SYMBOL("__ct__7CcsvObjFv") EXPORT SYMBOL("__ct__7CxlsObjFP6CLIobj") EXPORT SYMBOL("__ct__7CSysFtpFv") EXPORT SYMBOL("__ct__8CcsvPropFv") EXPORT SYMBOL("__ct__8CxlsObj") EXPORT SYMBOL("__ct__8CHtmlObjFv") EXPORT SYMBOL("__ct__8CJSONObjFv") EXPORT SYMBOL("__ct__8CSylkObjFv") EXPORT SYMBOL("__ct__9CHtmlPropFv") EXPORT SYMBOL("__dftct__Q2_3std12basic_stringXTcTQ2_3std11char_traitsXTc_TQ2 + _3std9allocatorXTc__Fv") EXPORT SYMBOL("__dftdt__Q2_3std13basic_filebufXTcTQ2_3std11char_traitsXTc__F + v")
Binder Source may also be used to export variables. The only variable that gets used with any kind of regularity is _EXCP_MSGID, which is exported from the QC2UTIL1 service program (also in the QC2LE binding directory).
EXPORT SYMBOL("_C_proc_list") EXPORT SYMBOL("_C_space_list") EXPORT SYMBOL("_C_streams") EXPORT SYMBOL("_C_stream_exit") EXPORT SYMBOL("errno") EXPORT SYMBOL("_EXCP_MSGID") EXPORT SYMBOL("_C_UTIL1_dont_use1") EXPORT SYMBOL("_C_ag_type") EXPORT SYMBOL("_C_stdin_buffer") EXPORT SYMBOL("_C_act_mark") EXPORT SYMBOL("_C_removemsg")
Tip: Compile your *MODULE and/or *SRVPGM and then use RTVBNDSRC to generate the binder language statements source code for the exported items. Then go into that source and remove and reorder the list of exported items as desired. This is typically how most developers handle binder source and exports.
Binding Directory Entries SQL Function
While Binder Source is, well, source, Binding Directories are actual IBM i objects. Internally *BNDDIR objects are a user space-like object, as I previously mentioned that hold a list of binding directory entries in fixed-length positions that repeats for each entry.
At some point, System Value QSECURITY(40) shut down MI and C access to a lot of fun things on the system. One of them was direct access to *BNDDIR objects. With no API to read *BNDDIR entries, we are now limited to the following two CL commands:
- WRKBNDDIRE – Work with the entries of a Binding Directory
- DSPBNDDIR – Display the entries of a Binding Directory
The DSPBNDDIR supports options OUTPUT(* | *PRINT | *OUTFILE). The OUTFILE support can be used directly in RPG, or we can create an SQL Function to handle everything for us.
Calling an API today is “so 1999.” Using CL OUTFILE support to access system interfaces just feels like using Indicators in RPG IV. You know you can, it has been around forever, but it makes you think you’re not as smart as you think you are.
Last year I created an SQL User-defined Table Function (UDTF) named BNDDIR_ENTRIES that returns the list of entries for a Binding Directory. Today, if I’m in IBM ACS RUNSQL Scripts or using RUNiQRY on the CL command line, I use this table function to quickly view the entries in any Binding Directory.
This SQL function accepts the name of the binding directory and generates a list of entry names along with all other available Entry information, including the date it was added to the Binding Directory. Here’s a look at the legacy QC2LE binding directory and returned by my BNDDIR_ENTRIES SQL Function:
The BNDDIR_ENTRIES SQL Function returns the binding directory entries, along with the object type, activation setting, and date it was added to the binding directory. It uses the DSPBNDDIR OUTPUT(*OUTFILE) support to get the data and return it as an SQL resultSet. Normally I would use an API to get my SQL Function results internally, but with no such API and my access to the *BNDDIR object blocked . . . this is the way.
The code is available over on my GitHub page: https://github.com/bobcozzi/BNDDIR_ENTRIES
Just copy/paste the code into IBM ACS Runsql Scripts, or create a source file member named BNDDIRE and “compile” it with the RUNSQLSTM CL command.
This version of the function works on IBM i V7R2 and later. Recently IBM added an SQL VIEW that contains all the binding directories on the system. I am not a fan of that approach. It tends not to perform well, and hand returns everything in the world and relies on you to filter it out with a WHERE clause. Why not just tell a function what you want, and get back what you want? That’s what I prefer. But if you’re on V7R4 or later, you can also check out the BINDING_DIRECTORY_INFO VIEW in QSYS2. Note that the documentation for this IBM-provided VIEW indicates that the date/timestamp being returned is the “timestamp when the object was created.” This is not correct. The timestamp is the timestamp of when the entry was added to the Binding Directory.
Bob Cozzi is an IBM i contractor and consultant as well as the author of The Modern RPG Language, developer of SQL iQuery and SQL Tools, and a speaker on SQL and RPG IV topics you can actua
This is really cool. You can learn something every day.
Thank you for this knowledge.
Thanks!! Then, after kowing all the entries from a BD, how can I find the source used to create them? It is included somewhere?
Sorry, didn’t see this until just now. The RTVBNDSRC CL command is how you pull in the Binder Source. If that’s the source you’re looking for.
Sadly, Bruce has passed away. I had the pleasure of working with him at the Villages.