This is one of a series of short blog posts on z/OS topics. This post is about using Dynamic Link Library. It follows on from One minute MVS: Binder and loader which explains some of the concepts used.
Self contained load modules are good but…
An application may have a main program and use external functions bound to the main program. This is fine for the simple case. In more complex environments where a program loads external modules this may be less effective.
If MOD1 and MOD2 are modules and they both have common subroutines MYSUBS, then when MOD1 and MOD2 are loaded, they each have a copy of MYSUBS. This can lead to inefficient use of storage (and not as fast as one copy which every thing uses).
You may not be responsible for MYSUBS, you just use the code. If they are bound to your load module, then in order to use a later version you will have to rebind your load modules. This is not very usable.
Stubs are better
There are several techniques to get round these problems.
In your program you can use a stub module. This sits between your application and the code which does all of the work.
- For example for many of the z/OS provided facilities, the stub modules chain through z/OS control blocks to find the address of the routine to use.
- You can have the subroutine code in a self-contained load module. Your stub can load the module then branch to it. This way you can use the latest available version of code. For example MQCONN loads a module, other MQ verbs use the module, MQDISC releases the load module.
If the stub loads a module, then other threads using the stub just increment the in-use count and do not have to reload the module (from disk). This means you do not have multiple copies of the load module in memory.
The Dynamic Link Library support does this load of the module for you, is it a build option rather than change your code.
Creating a DLL Object
When you compile the C source you need to have options DLL and EXPORTALL. (The documentation says it is better to use #PRAGMA EXPORT(name) for each entry, rather than use the EXPORTALL option – but this means making a small change to your program).
When you bind the module you need parameter DYNAM=DLL, and specify //BIND.SYSDEFSD DD… pointing to a member, for example COLIN.SIDE(CERT)
The member will have information (known as a side deck) with information about the functions your program has exposed. For example
IMPORT DATA,'CERT','ascii_tab' IMPORT DATA,'CERT','other_tab' IMPORT CODE,'CERT','isbase64' IMPORT CODE,'CERT','printHex' IMPORT CODE,'CERT','UnBase64' f
- IMPORT …. ‘CERT’ says these are for load module CERT
- IMPORT DATA – these labels are data constants
- IMPORT CODE – these are functions within CERT
If this was a 64 bit program, it would have IMPORT CODE64, and IMPORT DATA64.
If you compile in Unix Services you get a side-deck file like /u/tmp/console/cert.x containing
IMPORT DATA64,'cert.so','ascii_tab' IMPORT CODE64,'cert.so','isbase64' ...
and the load module /u/tmp/console/cert.so.
The .x file is only used by the binder. The .so file is loaded and used by programs.
When your program tries to use one of these functions, for example isbase64, module CERT(cert.so) is loaded and used. This means you need the library containing this module to be available to the job.
Binding with the DLL
Instead of binding your program with the CERT object. You bind it with the side deck and use the equivalent to INCLUDE COLIN.SIDE(CERT).
The binder associates the external reference isbase64 with the code in load module CERT.
Conceptually the isbase64 reference, points to some stub code which does
- Load module CERT
- Find entry point isbase64 within this module
- Replace the address of the isbase64 external reference, so it now points to the real isbase64 entry point in module CERT.
- Branch to the real isbase64 code.
The next time isbase64 is used – it goes directly to the isbase64 function in the CERT module.
You can use DLLs in Unix, or normal address spaces.
For a program running in Unix services you can use the C run time functions