In Delphi, component packages are an important type of DLL. Packages allow you to bundle a group of components and then link the components either statically (adding their compiled code to the executable file of your application) or dynamically (keeping the component code in a DLL, the run-time package that you'll distribute along with your program, along with all other packages you will need). In Chapter 9, "Writing Delphi Components," you saw how to build a package. Now I want to underline some advantages and disadvantages of the two forms of linking for a package. You need to keep many elements in mind:
Using a package as a DLL makes the executable files much smaller.
Linking the package units into the program allows you to distribute only part of the package code. The size of the executable file of an application plus the size of the required package DLLs is always much bigger than the size of the statically linked program. The linker includes only the code used by the program, whereas a package must link in all the functions and classes declared in the interface sections of all the units contained in the package.
If you distribute several Delphi applications based on the same packages, you might end up distributing less code, because the run-time packages are shared. In other words, once the users of your application have the standard Delphi run-time packages, you can ship them very small programs.
If you run several Delphi applications based on the same packages, you can save some memory space at run time; the code of the run-time packages is loaded in memory only once among the multiple Delphi applications.
Don't worry too much about distributing a large executable file. Keep in mind that when you make minor changes to a program, you can use any of various tools to create a patch file, so that you distribute only a file containing the differences, not a complete copy of the files.
If you place a few of your program's forms in a run-time package, you can share them among programs. When you modify these forms, however, you'll generally need to recompile the main program as well, and distribute both of them again to your users. The next section discusses this complex topic in detail.
A package is a collection of compiled units (including classes, types, variables, routines), which don't differ at all from the units inside a program. The only difference is in the build process. The code of the package units and that of the units of the main program using them remains identical. This is arguably one of the key advantages of packages over DLLs.
A very important and often misunderstood element is the distribution of updated packages. When you update a DLL, you can ship the new version, and the executable programs requiring this DLL will still work (unless you've removed existing exported functions or changed some of their parameters).
When you distribute a Delphi package, however, if you update the package and modify the interface portion of any unit of the package, you may need to recompile all the applications that use the package. This step is required if you add methods or properties to a class, but not if you add new global symbols (or modify anything not used by client applications). There is no problem if you make changes affecting only the implementation section of the package's units.
A DCU file in Delphi has a version tag based on its timestamp and a checksum computed from the interface portion of the unit. When you change the interface portion of a unit, every other unit based on it should be recompiled. The compiler compares the timestamp and checksum of the unit from previous compilations with the new timestamp and checksum, and decides whether the dependent unit must be recompiled. For this reason, you must recompile each unit when you get a new version of Delphi that has modified system units.
In Delphi 3 (when packages were first introduced), the compiler added an extra entry function to the package library named with a checksum of the package, obtained from the checksum of the units it contained and the checksum of the packages it required. This checksum function was then called by programs using the package so that an older executable would fail at startup.
Delphi 4 and following versions up to Delphi 7 have relaxed the run-time constraints of the package. (The design-time constraints on DCU files remain identical, though.) The checksum of the packages is no longer checked, so you can directly modify the units that are part of a package and deploy a new version of the package to be used with the existing executable file. Because methods are referenced by name, you cannot remove any existing method. You cannot even change its parameters, because of name-mangling techniques that protect a package's method against changes in parameters.
Removing a method referenced from the calling program will stop the program during the loading process. If you make other changes, however, the program might fail unexpectedly during its execution. For example, if you replace a component placed on a form compiled in a package with a similar component, the calling program might still be able to access the component in that memory location, although it is now different!
If you decide to follow this treacherous road of changing the interface of units in a package without recompiling all the programs that use it, you should at least limit your changes. When you add new properties or nonvirtual methods to the form, you should be able to maintain full compatibility with existing programs already using the package. Also, adding fields and virtual methods might affect the internal structure of the class, leading to problems with existing programs that expect a different class data and virtual method table (VMT) layout.
Here I'm referring to the distribution of compiled programs divided between EXEs and packages, not to the distribution of components to other Delphi developers. In this latter case the versioning rules are more stringent, and you must take extra care in package versioning.
Having said this, I recommend never changing the interface of any unit exported by your packages. To accomplish this, you can add to your package a unit with form-creation functions (as in the DLL with forms presented earlier) and use it to access another unit, which defines the form. Although there is no way to hide a unit that is linked into a package, if you never directly use the class defined in a unit, but use it only through other routines, you'll have more flexibility in modifying it. You can also use form inheritance to modify a form within a package without affecting the original version.
The most stringent rule for packages is the following one used by component writers: For long-term deployment and maintenance of code in packages, plan on having a major release with minor maintenance releases. A major release of your package will require all client programs to be recompiled from source; the package file should be renamed with a new version number, and the interface sections of units can be modified. Maintenance releases of that package should be restricted to implementation changes to preserve full compatibility with existing executables and units, as is generally done by Borland with its Update Packs.