![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Shared Libraries and the Application Program Interface (API) A common organization for large software systems is a collection of main programs, each of which references a common set of implementation units. Apex provides a facility for packaging these implementation units into a collection of "library views", which, taken together, form a "library." The object code for the units of a library can be bound up into a single shared object that is dynamically loaded by a main program that references it. Several concurrently running main programs can share the same copy of the shared library. Using shared libraries reduces the disk space and memory requirements of a set of main programs by keeping only one copy of the common library units. However, main programs can also be statically linked against library views, in which case the code for the referenced common units is included as part of each referencing main program.
A software vendor who wishes to distribute a library without distributing the source code for the library can do so by binding the library views into an API (Application Program Interface) view. An API view contains only the source code for those units specified by the author as part of the published interface for the library. An API view also contains the compilation artifacts necessary to compile and link programs against the published specification, but the complete source code cannot be derived from these artifacts.
The following topics are covered in this chapter:
- Building a Simple API Step-by-Step
- Library Views vs Non-Library Views
- Root View Switches
- API Builder
Building a Simple API Step-by-StepTo build a simple API, follow these steps. This API could, for example, be used to hide classified source code.
- 1 . Create a subsystem for the API. In our example, this subsystem will be called hidden.ss
- 2 . Add a unique subsystem name switch to hidden.ss, for example, SUBSYSTEM_NAME: hidden.sciance.gdls.com. To do this, select hidden.ss from the directory viewer and do a Control > Show > Switches. Click on the Edit box and add the switch in alphabetical order.
Note that, unlike views, subsystems have only a few switches, so the list of switches will be relatively short.
- 3 . Create two views, one for the API source code and one for the API itself. For our example, these will be named api_source.wrk and api_rel_1.wrk.
- 4 . In the api_source.wrk view, copy or create the package containing the source code that your wish to hide. For simplicity, this package should not have any dependencies.
- 5 . Add the line, "pragma Api (hidden) as the last line in the package specification (after the `end').
Note that any other package specifications that are part of this API will need the same pragma.
- 6 . Code the API source view (api_source.wrk).
- 7 . Use the Apex shell (Tools > Shell) to build the API.
The command is build_api. It requires two parameters: the API destination view and the API source view. In our example, the command line is:
% build_api /usr1/hidden.ss/api_rel_1.wrk /usr1/hidden.ss/api_source.wrkYou can now import api_rel_1 and reference the hidden source code.
Library Views vs Non-Library ViewsThe difference between library views and non-library views is relevant only when linking main programs. If a main program depends on a unit in a library, it depends on the library itself. Consequently, every unit in the library must be compiled so that the code objects can be built. If any of these units fail to compile, the main program will not link, even if the erroneous units are not in the closure of the main program.
BUILD_POLICY switch
A view is declared to be a library view by setting the BUILD_POLICY switch. This switch, when it is used in Ada views, can have the following values.
- OBJECT_FILES
This is the default value for the switch. It indicates that view is not a library view, unless it is imported by a library view with the appropriate switch set (see LAYER_LIBRARY and CLOSURE_LIBRARY).
- LIBRARY
The simplest form of library, which consists of only this view, plus any views that mutually import it.
- LAYER_LIBRARY
The library consists of this view and all the views that are directly imported by it, but are not members of other libraries.
- CLOSURE_LIBRARY
The library consists of this view and all the views that are directly or indirectly imported by it, but are not members of other libraries.
- LIBRARY_MEMBER
This value indicates the view is a library view, but not a root view. The compiler will issue an error message if the rules for member inclusion do not imply that this view is a member of some library.
- API
An API is a special form of library view that contains compiler artifacts, but only limited source files. Only the units that are part of the published specification have source files in the view. A user should never set a the BUILD_POLICY switch to this value; it should only be set to this value by the API Builder (See API Builder).
A fundamental rule is that a given view may be a member of at most one library. The compiler will use the switch settings and imports to partition views into libraries if it can be done unambiguously. However, it is possible to set up switches and imports so that there is no unambiguous partition.
Consider the following example.
Figure 1 API Library Example - 1
![]()
In this case, A and B form one library, and C and D form another. Even though A imports both C and D, it does not claim either as member views. When collecting imports to include as member views, the compiler never includes any views whose BUILD_POLICY is LIBRARY, LAYER_LIBRARY, CLOSURE_LIBRARY, API, or (recursively) any members of those views.
But now consider a different example:
Figure 2 API Library Example - 2
![]()
Here there is no clear indication of whether C should be a member of library A or B. Since it cannot be a member of both, the compiler will issue an error message and fail.
Root View SwitchesAll of the object code for a library is bound up into a UNIX shared library and UNIX archive, or both. This behavior is controlled by the following switches, which should be set in the root view of a library:
- CREATE_ARCHIVED_LIBRARY
If this boolean switch is set to TRUE, then a UNIX archive (.a file) is created. The default value of this switch is TRUE.
- CREATE_SHARED_LIBRARY
If this boolean switch is set to TRUE, then a UNIX shared library (.so or .sl file) is created. If a library has more than one view, this switch must be set in all the member views. On some architectures, this is a forcing switch; changing its value causes all the units in the view to be recoded. The default value of this switch is FALSE.
- LIBRARY_NAME
This switch is meaningful only in the root view of a library in which the CREATE_ARCHIVED_LIBRARY switch is TRUE. It specifies the name of the archive file. The default value is libname.a, where name is the simple name of the subsystem.
- SHARED_LIBRARY_NAME
This switch is meaningful only in the root view of a library in which the CREATE_SHARED_LIBRARY switch is TRUE. It specifies the name of the shared library file. The default value is libname.so, where name is the simple name of the subsystem.
Archive and shared library files must have a name of the form libNNNN.XX, where NNNN may be any name, but .XX is .a for archives, and either .so or .sl (depending on the operating system) for shared libraries. If the value specified for a library name switch does not conform to these rules, the name will be adjusted by prepending lib if necessary, and/or adding a suffix or replacing the one given.
The names chosen for archive files don't matter very much, since internally, Apex uses full pathnames for them. However, when a program is linked against a shared library object, the name of that object is embedded in the executable for the main program. The main program will not execute unless the shared library objects it was linked with (or copies of those objects) can be located. UNIX uses the following algorithm for locating the shared library objects. First, it checks for the full pathname the shared library had at link time. If no such object is found, it examines the environment variable LD_LIBRARY_PATH, which should be a list of directories separated by colons. It examines each directory on that list in order, searching for a shared library object with the appropriate simple name. If no such library can be found, the program will not successfully execute. Hence, if a main program depends upon shared libraries, and is to be executed in an environment other than the one in which it was linked, the programmer must ensure that when the executable for main program is copied, any shared libraries that it depends upon are also copied into one of the directories named by LD_LIBRARY_PATH. Moreover, the names of the shared libraries must be chosen so that the UNIX search-list algorithm locates the correct libraries.
When linking a main program against library views that contain both an archive and a shared library, the compiler chooses which to use based on the following switches.
ADA_LINK_MODE switch
The ADA_LINK_MODE switch may appear in the switch file for a view containing the main program, or it may be specified on the compiler command line. If the switch appears both places, the value on the command line overrides the value in the switch file. The switch may have the following values:
- STATIC
Archives are used for linking all library views. It is an error if a library view in the closure of the main program does not have an archive.
- DYNAMIC
Shared libraries are used for linking all library views. It is an error if a library view in the closure of the main program does not have a shared library.
- DYNAMIC_OR_STATIC
For each library view in the closure, a shared library is used if one is present. Otherwise the archive is used.
- DEFAULT
For each library in the closure, the root view's LINK_CONTRIBUTION_DEFAULT_MODE switch, which can be set to STATIC, DYNAMIC or DYNAMIC_OR_STATIC, is queried, and the decision is made as if the ADA_LINK_MODE switch had that value.
The default value of ADA_LINK_MODE is DEFAULT. The default value of LINK_CONTRIBUTION_DEFAULT_MODE is DYNAMIC_OR_STATIC.
When a main program is linked using library views, the compiler detects whether any units have been added, deleted, or recompiled since the last time the libraries were used, and rebuilds any code objects that are obsolete. In any given view, the compiler will only rebuild the code object selected by the ADA_LINK_MODE switch. When a code object is rebuilt, it is always rebuilt from the object files. Incremental updates of archives is not attempted, and incremental updates of shared libraries is impossible.
The compiler requires that library views must import only library views. If a library view contains a shared library, it is a good idea (though not required) that all the views it imports also contain shared libraries.
When the link contribution of a library view is static, only the units that are needed to resolve external references are included in the program being linked. However, when the link contribution is dynamic, all the units in the library will dynamically loaded when the program is run. If the shared library imports a library with a static contribution, units in the archive may be linked into the main program to satisfy unresolved references in the shared library, even if those units can never be reached when the main program is executed. And these extra units may, in turn, pull in more extra units.
However, the inclusion of these extra units does not affect the execution of the program; they are neither executed nor elaborated. The inclusion of extra units can be avoided by arranging that if a library has a dynamic link contribution, then all the libraries it imports also have a dynamic contribution.
If a unit in the closure of a main program contains:
pragma Link_With ("options");
when the main program is linked, "options" is added to the linker command line. The option string is scanned into tokens separated by spaces. If a token begins with the characters '+', '-', or '/' the token is added to the linker command line verbatim. If the token begins with anything else, it is presumed to name a file using a relative name. The full pathname of the directory containing the unit with the pragma is prepended to the filename before adding it to the linker command line.
When a program is linked with non-library views, only the Link_With pragmas in units in the closure of the main program are processed. However, when a main program depends on a library view (shared or archive), every pragma Link_With in the view is processed, since the linker options and extra files may be necessary to satisfy unresolved references in shared libraries.
API BuilderThe API Builder allows a vendor to package and distribute a set of views as a library that clients can link into their programs without distributing the source code for the library. The acronym API stands for Application Program Interface.
The following steps are used to create an API.
First, determine which views will make up the source code to the API. Then determine which units in those views will be part of the published specification for the API. For each of these units, insert "pragma Api (name)" at the very end of the unit. The name must be an Ada identifier. The same name should be used on every unit in the API, although the name is not actually used by the current version of the API Builder. The name is present for compatibility with previous (and possibly future) versions of the API Builder.
The units that make up the specification of the API must be a closed set. That is, if a unit has a pragma Api, then every unit it withs must also have a pragma Api. If a child unit or subunit has a pragma Api, there must be a pragma Api on the parent unit. And if a body unit has a pragma Api, the corresponding spec unit must have a pragma Api. There are only a few situations in which it makes sense to put a pragma Api on a body unit, but it is legal to do so.
This closure restriction sounds reasonable in principle, but it often forces the API author to make some unpleasant choices. The problem is that if a unit U is withed by a package specification with a pragma Api, even if U is only referenced within the private part of the package, U must have a pragma Api and will be part of the published specification. Since this rule applies to U as well, the author can be forced to expose far more of the implementation than expected.
Possible solutions to this problem are:
- 1 . Complete private types T in API specs with declarations like:
type t_target; type t is access t_target;
- 2 . Use export sets to indicate which units the clients of the API are expected to reference. Although architectural control will prevent the client from referencing the implementation units, those names will be used up and unavailable to the client. For that reason, names like "types" and "utilities" are poor choices for those units.
- 3 . Write "sanitized" type completions in the private part of API specs, and use unchecked_conversion in the implementation to convert between the sanitized types and the actual types used by the implementation.
If the API is to be licensed, then each pragma Api should be followed by a pragma License. The format of this pragma is:
pragma License ("feature" [, version]);
Where feature is a string literal, and version is a floating point literal. If version is omitted, it defaults to 1.000. A License pragma has no effect in a source view, but if an API view with a pragma License participates in a compilation, the compiler passes the string "feature:version" to the FLEXlm license manager. If the manager refuses to grant a license, the compilation will be terminated. The next step is to locate all the views that will be part of the API, plus all views imported, directly or indirectly by those views. For each of those views, the switch file in the containing subsystem (not the view!) must have the SUBSYSTEM_NAME switch set.
SUBSYSTEM_NAME switch
The SUBSYSTEM_NAME switch is string-valued switch that defaults to the null string. The intent is that every subsystem in the known universe have a unique value for the SUBSYSTEM_NAME switch. By convention, the subsystem switch has a value such as "asis.rational.com". The value of the switch should end in an Internet domain name. The portion of the name that precedes the domain name should be managed by a scheme prescribed by the named organization to guarantee uniqueness.
Of course, the compiler has no way enforcing this convention. The real requirement is that all the subsystems that are in the closure of a given compilation must have unique SUBSYSTEM_NAMEs. This rule extends to any views that have been packaged as APIs. The Internet domain-name convention assures that APIs produced by different vendors will not contain conflicting SUBSYSTEM_NAMEs. All Rational-supplied APIs use SUBSYSTEM_NAMEs that end in ".rational.com".
The Apex Ada compiler uses a name-mangling scheme to construct the external names of object. The mangled name depends, in part, on the value of the SUBSYSTEM_NAME switch. Consequently, library unit names in the unpublished portion of an API can be chosen without concern for duplicating names in client programs. Even if the simple library names are the same, the SUBSYSTEM_NAMEs (obtained from the switch file, or supplied by the compiler if none is present) will be different, and hence, the mangled names will be different. However, the SUBSYSTEM_NAME does not participate in the C++ name-mangling scheme, and C names are not mangled at all. Therefore, names in C code that will go into an API must be chosen to minimize the possibility of conflicting with names in client code. Note that the mangling schemes do preclude naming conflicts between C names and Ada names.
After these steps have been completed, the API Builder is run.
build_api [options
]api_view source_views
...
The api_view is the where the API is built. Any contents of that view are destroyed and replaced with the API specification. As a safety measure, the API Builder requires that api_view must be an already empty view or a view whose BUILD_POLICY switch has the value API.
source_views is the collection of Ada and C views that will make up the API. source_views may include one or more configuration files (with the suffix .cfg), in which case the views named by those files will be included.
The import closure of the source views must not contain two different views of the same subsystem. If there are any mutually importing views among the source views, all the mutually importing views of that set must be included. The source views must all have the same compiler key. None of them can be RCI views or API views. They all must be the same language dialect, Ada 83 or Ada 95.
The API builder first examines all the units in the source views, and attempts to compile any that are not compiled. Units in the closure of these views are compiled if necessary. If any of these compilations fail, the API builder does not update the API view.
Next, the API builder copies any unit in the source view containing pragma Api into the API view. The API builder does not create any subdirectories; units get copied directly into the API view no matter where they appeared in the directory hierarchy in the source view. The API builder also copies sufficient compiler artifacts so that clients can compile against the API just as if they had imported the source views, but only referenced the units containing pragma Api.
- Options
The object files in the source views are copied into a UNIX archive, and a shared_library if the -shared_library option is specified. If a name is given after the option, that name is used for the shared library. The actual library name is derived from <name> using the same conventions used by the CREATE_SHARED_LIBRARY switch. By default, the name used is libname.so, where <name> is the simple subsystem name of the API view.
If -shared_library is specified, the CREATE_SHARED_LIBRARY switch must be present in all the source views.
The API builder will not attempt to compile any C views among the source view unless this switch is set.
This switch causes the API builder to ignore all the pragma APIs in the source view, and produce an API with an empty specification. See API Views for an explanation of why anyone would ever want to do this.
This option suppresses the copying of the diana trees from the source views. For large APIs the diana trees can be quite large, so that this option can save disk space. However, this option can only be used to construct an API view if the view, and any API views that import it, contain in the published spec no inlined routines or replicated generics whose bodies are not published. The ability to use the -lightweight option is one reason for putting pragma API on a body. This restriction is not checked by the API builder, so caveat emptor.
The view constructed by the API builder is considered by the compiler to be a read-only view. Do not edit, add, or delete Ada units from a view that has been built by the API builder. The compiler cannot be coaxed into performing any compilation in an API view. Any Ada units that are copied into the API view after the builder has run will be treated as ordinary text files by the compiler.
Because compilation cannot take place in API views, care should be taken not to delete any of the compilation artifacts, since they can only be recreated by rerunning the API builder. The Apex clean command will not modify an API view. If users have their own shell scripts for deleting compilation artifacts, they should be carefully written to avoid damaging API views.
When the API Builder is run, it records any export sets defined in the source views and uses them to enforce architectural control in the clients of the API. Any exports sets defined for the API view itself are ignored by the compiler. That is, if a unit U withs a unit V in an API view, the legality of that with determined using the exports sets defined in the source view from which V was copied when the API builder was run. If an API is created that uses export sets, it is a good idea to define a set of export sets in the API view that is the union of the exports sets in the source views. Although these sets will be ignored by the compiler, having them will minimize confusion among the users of the API.
The final steps in creating an API are to make any additions or adjustments necessary to the switch file produced by the API builder (see API Builder), copy in any documentation or readme files, and then copy the API to the place it will be referenced by the clients. A collection of related APIs will need specific import relations set up (see section yyy). After the APIs are installed for client use, and the imports are properly set up, they should be frozen. Because Apex requires that frozen views must import only other frozen views, the freezing of API views must wait until those views are copied to their final home.
If you take the set of source views used to construct an API, take their import closure, and then subtract the original source views, what remains is the import closure of the API. This set will never be empty; at minimum it will include a view of Rational's LRM subsystem, which must be imported by every view. When an API is installed in a client area, it must import a view of every subsystem in the API's import closure. The views imported by the API need not be identical to the views imported by the source views, but they must be compatible. The compiler performs a compatibility check whenever compiling units that reference API views.
Warning: The Apex clean command contains a special check that prevents it from touching an API. However, some developers may have private clean scripts that simply delete material from .Rational/Compilation directories without going through Apex. These scripts should not be used! If the contents of .Rational/Compilation are deleted in an API, it cannot be created by compilation, because not all the source code is present.
API Views
An API view is a library view, and follows all the rules and restrictions of library views. In particular, API views may only import other library views. Usually API views will import other API views, though one could deliver an API view along with the source code for a view that must be imported by the API. However, in that case, the imported view must be a library view. The compiler will check that the imported view is compatible with the API view.
Another library rule that applies to API views is that a given view may be a member of, at most, one library. The API author who is only producing a single API view need not worry about this restriction. However, creating multiple APIs that have source code in common requires a little more care. Suppose there are three views with the following import structure (see Figure 3) and that one wants to create APIs for A and B. It is certainly possible to use the API Builder to bind A and C into API_A, and B and C into API_B. Each of these APIs would work fine in isolation. However, if a client tried to write a program that used both API_A and API_B, the compiler would reject the program. The problem is that both API_A and API_B have a copy of C hidden inside them. Including both APIs gets two views (or two copies of the same view) of the same subsystem into a compilation, which is illegal.
Figure 3 API Views
![]()
The only way to ensure that the clients do not get into this kind of trouble is to follow the following rule: Never include a view of the same subsystem in more than one API. In this case, you could deliver a single API constructed from A, B, and C. Its published spec would contain the union of units with pragma API on them from A and B. (In this example, there are no such units in C).
The second choice is to create and deliver three APIs for A, B, and C, and arrange that the API views in the client area have the same import structure as these source views.
This option is a little confusing; let us explore it further. One might expect that it is necessary to go through the view C, find the units referenced by A and B and put pragma Api on them. This task is not necessary, assuming no published units from A or B them reference units from C. Simply run the API builder three times on A, B, and C, producing API_A, API_B, and API_C. Since C contains no units with pragma API, the spec of API_C will be empty. But that is not a problem, since the specs of API_A and API_B contain no references to units in C. In the client area, any view that imports API_A, API_B, or both, will get API_C in its import closure.
This scheme only works if the source view for A and B import the source view for C. There may be situations where this approach is impractical. Perhaps A, B, and C are large views (or collections of views) that are implemented by three different programming teams. It may be organizationally expedient to publish an internal specification for C, and have the group working on C to create APIs and deliver them for use by the groups developing A and B. However, when API_C is delivered to the clients of API_A and API_B, it can be built with the -specless option so that the internal specification is not exposed.
API Views with Dependencies on Apex Supplied Subsystems
The APIs have imports, the Imports/*Description.cfg files. Those imports have specific pathnames. Those pathnames will presumably be incorrect at the customer site.
Their API installation script, for the customer, will need to query the customer for the proper $APEX_BASE value. It can then edit the various .cfg files and substitute the customer's $APEX_BASE value everywhere their $APEX_BASE value appears.
The $APEX_BASE may be /rational/320b and their customer's $APEX_BASE might be /vendor/apex-latest and they would need to substitute /vendor/apex-latest everywhere that /rational/320b appeared in the Import .cfg files.
Then you will do an apex import -refresh on the installed API views. At that point if the two Apex versions are the same, the APIs should be functional.
Switch File
Since compilation is never performed in an API view, most compiler switches are irrelevant. When the API builder creates an API view, it creates a switch file containing the few switches that are relevant. Here are the switches that are relevant in API views. Because the requirements of switch files in API views are so different from those of source views, API views should not be used as models.
- BUILD_POLICY
This is set to API by the API builder. It should never be changed.
- CREATE_SHARED_LIBRARY
This is set to TRUE or FALSE to indicate whether a shared library is present. It should not be changed.
- SHARED_LIBRARY_NAME
If CREATE_SHARED_LIBRARY is set to TRUE, this switch contains the name of the shared library. If for some reason, the shared library is renamed, this switch should be updated to match the name of the library.
- COMPILER_KEY
This switch is copied from the source views. It may be changed by the Apex remodel command.
- LINK_CONTRIBUTION_DEFAULT_MODE
This switch is never set by the API builder, but may be added afterwards. It has the same effect as in other library views.
- LINK_CLOSURE_LANGUAGES
If an API contains any C++ code, any main program linked against the API must include a call to the _cinit_ routine to invoke the C++ static initializers. If the value of this option contains the substring "C++", such a call will be added. The API Builder sets this switch to C++ if there are any C views included in the command line. The API Builder can't actually tell the difference between C views and C++ views; the switch can be deleted when it is not actually necessary.
- NON_ADA_LINKAGE
When a main program is linked against an API containing this switch, the value of this switch is added to the linker line. The switch should contain linker directives and the absolute pathnames of libraries or object files. Apex keyword replacement is performed on the switch. For example, if the code in an API contained unresolved references to a library named libextra.a, one could put a copy of that library in the API view, and set this switch to the value <view>/libextra.a. The Apex C++ Build facilities allow C++ views to specify dependencies on external libraries and nonstandard linker switches. If a source view contains a C++ view with such dependencies, the API builder will create a NON_ADA_LINKAGE switch that contains these dependencies. Typically this value will have to be modified before the API is delivered to the client. The switch value must be updated to reflect where the referenced libraries will reside in the client area.
Rational Software Corporation http://www.rational.com support@rational.com techpubs@rational.com Copyright © 1993-2004, Rational Software Corporation. All rights reserved. |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |