Image components (ICs) and their benefits
An image component is a portion of an image. If a VAST development or reduced runtime image file were arbitrarily partitioned into separate files, the objects present in a particular file would not be able to perform computational processing by themselves, because they would quite likely be dependent on objects and classes that would be available only in another IC. For example, OrderedCollections require Arrays to perform their behavior. If an image were arbitrarily partitioned so that OrderedCollections were put in one IC and Arrays were put in another, then the IC containing the OrderedCollection could not operate unless the IC containing Arrays was also available in memory. In other words, one cannot arbitrarily divide an image into ICs; the ICs are related. This relationship between ICs takes the familiar form of prerequisite/dependent. An IC containing OrderedCollections needs an IC containing Arrays as a prerequisite to allow the behavior of the OrderedCollections to operate properly. In practice, ICs are used to decompose systems into finer grained functional components. It is therefore natural to see ICs representing characteristics of a system such as communication capabilities, database capabilities, national language support, external program invocation, and Lotus(R) Notes(TM) support.
An IC is created by the VAST packager by a process that is similar (but not identical) to that of producing reduced runtime images. For many ICs there might be a 1:1 correspondence between the IC and an Application or configuration map.
1 Why is splitting an image file into portions beneficial? The benefits of ICs are twofold:
1. They reduce the memory footprint required when running multiple VAST applications by allowing common code within the applications to be shared in memory and by allowing portions of applications to be dynamically loaded and unloaded as their function is needed.
"Footprint" refers to the amount of memory used by the application's code and data when run for its intended purpose.
2. They provide a mechanism that allows for incremental deployment of changes (patches/fixes) to a deployed application.
The sections that follow examine these benefits.
Reducing the memory footprint
Traditionally, a delivered VAST application consisted of a single image file used in conjunction with an executable (based on abt.exe).
Note:
The runtime executable might have been renamed or based on nodialog.exe, but for purposes of consistency this chapter assumes that abt.exe is always the executable used to invoke a VAST runtime application.
The deployed single image file is referred to as a reduced runtime image. The reduced runtime image was produced by the packager by statically analyzing code within one or more Applications, and applying an algorithm to determine which code and objects are truly required for the application to run. This resulted in an output image that was completely customized for that particular application. A custom image implies that it has been highly optimized for that particular application, but the trade-off is that the code within it cannot be easily shared, because the included code is only that which is truly required for that application and nothing more.
When a reduced runtime image file is loaded into memory by abt.exe, the memory is associated with the same operating system process that is running abt.exe. In other words, if two instances of an application are started, by invoking abt.exe twice (which in turn, causes two operating system processes to be started, one for each invocation), the memory used by each reduced runtime image when it is loaded is not seen nor shared by the other invocation of abt.exe. The in-memory images occupy completely different memory address spaces (ranges). The memory occupied by the reduced runtime image is process protected; an attempt to address a portion of a loaded reduced runtime image from another operating system process, usually results in a memory address fault at the operating system level.
ICs provide an alternative deployment mechanism that facilitates sharing of these common objects for multiple abt.exe invocations. This is accomplished by recognizing that certain objects within a deployed runtime image are common to all VAST applications and that these objects are unchanged by the application when it is running. Classes and CompiledMethod instances are not changed by runtime applications. Runtime applications do not alter class definitions or hierarchies and do not alter bytecodes in CompiledMethods. Objects that are unchanged by applications using them can be characterized as read-only objects or ROM objects. The term ROM object emphasizes the fact that these objects do not change and could theoretically be placed in Read Only Memory (ROM). Since the ROM objects are unchanged by the applications that use them, they can be shared by multiple VAST application invocations. To accomplish this, they need to be placed in special memory which can be shared between processes at the operating system level. All modern operating systems support the notion of shared memory spaces. The VAST virtual machine takes advantage of the operating system shared memory support to reduce the combined memory footprint of multiple invocations of a VAST application.
Conceptually, an IC can be envisioned as comprising two kinds of objects. ROM objects are the objects mentioned above: they are unchanged by the application that uses them. RAM objects are the objects that are subject to change within an application. An object is changed when its instance variables change. Typical RAM objects are instances of the above mentioned classes (such as OrderedCollection, Array, String, AbtAppBldrView), as well as instances of classes specific to the business application domain (such as Customer, Account, Policy). RAM objects cannot be shared by multiple application instances. A new in-memory copy of a RAM object must be created for each application instance that is invoked.
The maximum amount of object sharing that can take place for multiple uses of an IC is equivalent to the size of memory occupied by the ROM objects, as this is the only portion of the IC that is shareable. However, IC images are not reduced like reduced runtime images. For a given application, it is possible to determine the minimum set of objects required to produce a reduced runtime image, because the context in which the objects are used is fully determined by the VAST packager algorithm. For ICs, this reduction cannot be performed because the application context in which the IC code will be used is unknown at the time of packaging. This means that for an IC, all the objects present within the original Applications are packaged into the IC, either in ROM or RAM space. When the IC is loaded into memory, these objects will take up space whether they are needed by the application using the IC or not. Herein lies the basic memory trade-off when using ICs compared to reduced runtime images: ICs allow sharing of a subset of their objects (ROM) for multiple invocations of VAST runtime applications, but there is no guarantee that objects loaded into memory will be used. Reduced runtime images do not allow sharing of objects between multiple invocations, but any objects that are not used by the application have already been removed by the VAST packaging process. If you ever run a single invocation of a VAST built application on your workstation, the reduced runtime image will result in the smaller memory footprint, as only the objects actually being used by the application are loaded into memory.
The relative sizes of the ROM and RAM objects for a given IC are determined by the VAST packaging process when the IC is created. You have no direct control over the determination of whether an object is a ROM or RAM object. As an approximation, ROM objects represent code (CompiledMethods), literals, and symbols. RAM objects are everything else. Because the greater part of an IC is generally occupied by ROM objects, you can control memory footprint size by partitioning ICs in ways where the probability of most of the ROM objects being used for an application using the IC is very high.
Memory footprint reduction will be most evident in applications whose IC tree is wide (many leaf ICs) but not deep (only one layer of reusable ICs between the product supplied ICs and the leaf ICs) and whose leaf ICs are explicitly loaded/unloaded as needed. A call-center application is a typical example.
Facilitating incremental development
Reducing the in-memory application footprint for multiple application invocations is not the only use for ICs. Another use for partitioning a monolithic reduced runtime image into pieces is to facilitate incremental changes to a deployed application. Consider a scenario where a VAST 12.0.1 aplication has been deployed to 1500 different sites and the size of the reduced runtime image is 8 megabytes. Also, assume that the WAN that is used to electronically deploy application code to the sites has a bandwidth of 9600 baud. In most cases, providing a fix for the deployed application would require a complete repackaging of the image and a re-delivery of this image to all the sites. The download time alone of the fixed image to the client site would be more than two hours. This is a long time for a fix that might have required a change to only one or two methods!
Proper factoring of an application into ICs can help reduce the amount of data required to repair an application defect in the field. At a minimum, the VAST code does not have to be deployed again every time a small fix in the application code is required. If fixes are limited to an IC whose size is 1 megabyte for example, then deployment time (and some costs) would be reduced by a factor of 8. Further IC partitioning can help reduce this to the level of 100's of kbytes, which is in the same range as typical DLLs for C/C++ workstation based applications. It must be noted that not all kinds of code fixes can be handled by single IC replacement. The constraints on the types of fixes will be described in more detail later.
Once again, a wide shallow IC tree will be easier to manage than a narrow deep tree. This is because significant changes to a reusable IC in a deep tree may require restructuring the tree itself and repackaging all ICs above the change IC.
Image components as compared to DLLs
The main difference between ICs and DLLs is that DLLs have operating system level support while ICs are supported through the VAST virtual machine. The following table highlights some interesting differences between VAST ICs and operating system DLLs.
| | |
Shared/unshared portion | Yes | Yes |
Direct operating system support | No | Yes |
Demand loading | No | Yes |
Logical to file name mapping | Yes | Some OS's |
Platform portable Note: Assuming there is no platform specific code. | Often | No |
The structure of ICs within an application
IC dependencies
Like DLLs and VAST Applications, a particular IC operates within a context that is defined by its prerequisite ICs and its dependent ICs. Together, the prerequisite and dependent ICs form relationships characterized as a directed acyclic graph (no circular dependencies). A single IC might require multiple ICs as prerequisites. A single IC might also participate as a prerequisite for many other ICs. The prerequisite relationships are specified by you at IC creation (package) time. As with DLLs, you are not required to reuse objects already contained within existing ICs. You can choose to incorporate the objects directly into the IC being built, reducing the prerequisite dependencies, but also limiting the potential sharing of those objects in memory.
Relationship of ICs to applications
IC prerequisite relationships must satisfy Application prerequisite relationships. An IC may contain one or more Applications. If two Applications, say A and B, are related so that A is a prerequisite of B, then it follows that either A and B are packaged into the same IC or if they exist in different ICs, then the IC containing Application A will be a prerequisite IC of the IC containing Application B. This is because the Application prerequisite dependency implies a functional usage dependency: Application B needs objects in Application A to perform its function. This is the exact same relationship between ICs that have a prerequisite/dependency relationship. However, the relationships between ICs and Applications is not necessarily one to one. This is because Applications are partitioned along function (framework) boundaries but their partitioning is also influenced by development time constraints such as class ownership and Application management. ICs are primarily a runtime delivery mechanism that should not be subject to restrictions due to development time ownership issues. For this reason, ICs often contain several Applications that together comprise a complete set of functionality. ICs do not usually contain configuration management information.
IC usage architecture
The introduction of ICs provides great flexibility in how runtime applications are constructed. Below is a synopsis of various IC architectures.
Pure ICs
A runtime application can be constructed completely from ICs. You can accomplish this by creating an IC that represents the application code that is directly or indirectly dependent on all other ICs that it requires for operation. An example of running using only ICs is packaging an application as an IC file called myapp.ic.
You start the application with the following command:
abt -imyapp.ic
You need to specify only myapp.ic as the IC to use to start your application. You do not have to specify any of the other ICs. This is because myapp.ic knows about its prerequisite ICs and before it is loaded into memory, its prerequisite ICs are first loaded. Loading an IC into memory causes both its prerequisites and itself to be loaded. This automatic process of loading prerequisites is known as implicit binding. Implicit binding is not demand loading. Implicit binding takes placed either when an application is started for a specific initial IC or image file, or when an IC is explicitly loaded using code within application code.
An application might be constructed completely from ICs. Since saving an image is not a requirement for runtime applications, the starting state of the application is constant and can be delivered by means of an IC.
Image file with ICs
Delivering applications purely as a set of ICs provides for a conceptually clean deployment mechanism, but there is a trade-off. There is extra processing overhead involved in implicitly binding the ICs every time that the application is started. As an alternative, there exists a mechanism to allow an image to be created for runtime applications that will alleviate some of the overhead of performing the IC binding process every time. For newly packaged ICs, you can use a special VAST command line option -se<image file name>, which will save an .ICX file to the specified image file name. Subsequently, the <image file name>.icx can be used as the starting image for the application. For example:
abt -imyapp.ic -semyapp.icx
Then start the image with:
abt -myapp.icx
Using an .icx image file to launch the application results in a quicker startup time, but deploying changes to the application into the field is slightly more complex. A previously deployed .icx image will not work with changed application ICs, because the .icx image expects to run with the exact ICs from which it was created. In a deployed environment, the image file must first be re-created to reflect changes to application ICs, by using the -se command line option mentioned above. Invoking VAST with the -se command line option could be accomplished by using a command file that is supplied by the development team as part of the installation process. The extra steps involved in deploying the application are certainly worth the increase in performance obtained by using an .icx image file for launching the application.
Explicit binding and unbinding
VAST provides Smalltalk protocol for explicitly binding and unbinding ICs. Therefore, the loading and unloading of ICs can be controlled by an application. Application control of loading and unloading works well in an environment where several VAST applications are deployed and controlled by a desktop (or launcher). The desktop, which is itself a VAST application, might present icons for the various user applications. The code underlying each icon within the desktop would be minimal and would be responsible for binding the actual application through ICs. In this way, the amount of memory occupied by the VAST applications is a function of the number of icons that are activated (instantiated).
This deployment scenario would operate well where a consolidated corporate desktop is sent into the field, but individual users tend to focus on one application related to their area of expertise, with occasional use of the other applications. The trade-off for this flexibility is that it might take a little longer for the IC to bind, as opposed to incorporating all the application code into the same VAST application that contains the desktop.
The process of loading ICs
When an IC is built by the VAST packager, the names of its prerequisite ICs are contained within it. When the IC is loaded, how are the prerequisite IC files found and loaded? First it is important to know that just like DLLs, an IC will contain only the name of its prerequisite ICs, and not their directory location. Locating the IC with a given name is done during the load process by following a set of rules that determine the actual file name and directory location of the IC in question.
Up to three locations may be referenced when searching for an IC.
Specifying an explicit location in abt.ini
The first is an explicit location specified in the abt.ini file.
The actual name of the .ini file that is associated with the IC or image file name that starts the application. Because the default image is abt.icx, the corresponding .ini file is abt.ini. If you rename the image, then you should also rename the corresponding .ini file. For consistency, it is best to name the .exe, .ini, and startup .ic files that represent the application with the same file name.
Within the abt.ini file, you can specify a logical IC name to full directory and file name mapping so the actual IC that is loaded as a prerequisite can easily be changed by changing the abt.ini file and directory structure. This alleviates having to repackage a dependent IC only because one of its prerequisite IC files has changed name or directory location.
Using the path structure for application maintenance: By providing a logical-to-physical name mapping within the .ini file, VAST allows for great flexibility in delivering updated ICs into both production application environments and development environments. For example, a quality assurance team could switch testing between several environments (production, preproduction, development) simply by altering the .ini file that they use to start the application. Each .ini file maps logical IC names to a different set of ICs within different directories on the network. Each directory structure could contain the software for a different release. Since the physical file name of the IC can be different from the logical name, the physical file names can also embody version numbers within their names to help ease the diagnosis of application installation problems (kernel45.ic for example).
Specifying the directory where the application IC resides
The second location that is checked is the same directory where the application IC resides. For example, if an application is started by invoking abt -imyapp.ic where myapp.ic represents the application IC, the loading process will first search for prerequisite ICs in the same directory as myapp.ic. This is similar to the situation in VAST 12.0.1 running on Windows platforms, where the DLLs associated with the abt.exe executable were contained in the same directory as the abt.exe file. So, if myapp.ic has prereq.ic as a prerequisite, then the loading process would first search for prereq.ic in the same directory as myapp.ic. The full IC name of the prerequisite IC including the .ic suffix is contained within a dependent IC. However, the .ic suffix is a convention. You are free to use any file name.
To increase sharing of platform independent ICs among different platforms, use the more restrictive 8.3 DOS style of file names.
Using the default directory specified in abt.ini
The third and last location that is checked is a default directory specified in the
abt.ini file. Within the
abt.ini file you can also specify a single default directory location to be searched for ICs. Within the IC Path section of the
abt.ini file, you can specify an entry that identifies the default IC directory. This entry is provided in the
abt.ini file shipped with the product and identifies the location of all product ICs. For example, nearly any application IC you build will have one or more product ICs as prerequisites. You can use the
abt.ini file to identify the location of those prerequisite ICs. For information on specifying a default directory in the
abt.ini file, see
Image component (IC) mapping.
Explicit loading of ICs
ICs are explicitly bound by using the following protocol:
System loadComponent: icName
The above expression returns one of three values:
nil
The IC is already loaded (ICs can be loaded only once)
true
The IC has successfully been loaded
false
The IC could not be loaded
When an IC is explicitly loaded, that IC and all its prerequisite ICs (that are not already loaded) are loaded. The icName is the logical icName.
Removing ICs
An IC is unbound using the following protocol:
System removeComponent: icName
The above expression returns a Boolean value:
true
The component was successfully removed
false
The component was not removed
nil
The component was not present
To successfully remove an IC, there cannot be any instances of classes defined by that IC present in memory, nor can there be any ICs which depend on the IC being removed present in memory. Analogous to DLLs, it is much easier to manage selective loading of ICs rather than trying to determine when it is safe to unload ICs.
ICs do not load on demand as a result of references to classes or global (name space) references.
Also, loadComponent: and removeComponent: cannot be issued from a reduced runtime image, because the necessary prerequisite ICs cannot be loaded or are not available in this environment.
Initializing an IC
Once the name of an IC is resolved as part of the loading process and the actual IC file is loaded into memory, the loader must ensure that the IC is initialized so that it is ready to perform processing on behalf of its dependent ICs. When an IC is loaded, the actions that are performed are very similar to the actions that are performed when loading Applications from the VAST library.
Once the set of ICs are in memory, the following initialization sequence is performed.
For each IC in prerequisite order:
1. The initializeAfterLoad message is sent to all the newly loaded classes.
2. The loaded method is sent to the Application or SubApplication in their prerequisite order.
3. If the IC is being loaded as a result of an explicit binding through application code (as a result of System loadComponent: being run for one of its dependents), then the loadComponents: message is sent to the startUpClass.
4. If the IC is being loaded as a result of an explicit binding, then the startUp method is sent to each IC.
During the packaging process to create an IC, you can set the startUpClass for the IC. Since there can be only one startUpClass for an image, the startUpClass will be that of the "leaf" IC (the one that does not have any dependent ICs associated with it). In other words, for implicit binding of ICs, the startUpClass specified as part of packaging the IC can be overridden by a dependent IC. The startUpClass is ignored if an IC is being loaded as the result of an explicit bind (using System instance method loadComponent:).
Footnotes:
In this document, a capitalized Application implies an application as seen from the Application Manager. An uncapitalized application implies a software deliverable with which the end user interacts.