You may wonder whether it is possible to know if a unit has been linked in the executable file or if it's part of a run-time package. Not only is this possible in Delphi, but you can also explore the overall structure of an application. A component can use the undocumented ModuleIsPackage global variable, declared in the SysInit unit. You should never need this variable, but it is technically possible for a component to have different code depending on whether it is packaged. The following code extracts the name of the run-time package hosting the component, if any:
var fPackName: string; begin // get package name SetLength (fPackName, 100); if ModuleIsPackage then begin GetModuleFileName (HInstance, PChar (fPackName), Length (fPackName)); fPackName := PChar (fPackName) // string length fixup end else fPackName := 'Not packaged';
Besides accessing package information from within a component (as in the previous code), you can also do so from a special entry point of the package libraries, the GetPackageInfoTable function. This function returns some specific package information that Delphi stores as resources and includes in the package DLL. Fortunately, you don't need to use low-level techniques to access this information, because Delphi provides some high-level functions to manipulate it.
You can use two functions to access package information:
GetPackageDescription returns a string that contains a description of the package. To call this function, you must supply the name of the module (the package library) as the only parameter.
GetPackageInfo doesn't directly return information about the package. Instead, you pass it a function that it calls for every entry in the package's internal data structure. In practice, GetPackageInfo will call your function for every one of the package's contained units and required packages. In addition, GetPackageInfo sets several flags in an Integer variable.
These two function calls allow you to access internal information about a package, but how do you know which packages your application is using? You could determine this information by looking at an executable file using low-level functions, but Delphi helps you again by supplying a simpler approach. The EnumModules function doesn't directly return information about an application's modules; but it lets you pass it a function, which it calls for each module of the application, for the main executable file, and for each of the packages the application relies on.
To demonstrate this approach, I've built a program that displays the module and package information in a TreeView component. Each first-level node corresponds to a module; within each module I've built a subtree that displays the contained and required packages for that module, as well as the package description and compiler flags (RunOnly and DesignOnly). You can see the output of this example in Figure 10.6.
In addition to the TreeView component, I've added several other components to the main form but hidden them from view: a DBEdit, a Chart, and a FilterComboBox. I added these components simply to include more run-time packages in the application, beyond the ubiquitous Vcl and Rtl packages. The only method of the form class is FormCreate, which calls the module enumeration function:
procedure TForm1.FormCreate(Sender: TObject); begin EnumModules(ForEachModule, nil); end;
The EnumModules function accepts two parameters: the callback function (in this case, ForEachModule) and a pointer to a data structure that the callback function will use (in this case, nil, because you don't need this). The callback function must accept two parameters— an HInstance value and an untyped pointer—and must return a Boolean value. The EnumModules function will, in turn, call your callback function for each module, passing the instance handle of each module as the first parameter and the data structure pointer (nil in this example) as the second:
function ForEachModule (HInstance: Longint; Data: Pointer): Boolean; var Flags: Integer; ModuleName, ModuleDesc: string; ModuleNode: TTreeNode; begin with Form1.TreeView1.Items do begin SetLength (ModuleName, 200); GetModuleFileName (HInstance, PChar (ModuleName), Length (ModuleName)); ModuleName := PChar (ModuleName); // fixup ModuleNode := Add (nil, ModuleName); // get description and add fixed nodes ModuleDesc := GetPackageDescription (PChar (ModuleName)); ContNode := AddChild (ModuleNode, 'Contains'); ReqNode := AddChild (ModuleNode, 'Requires'); // add information if the module is a package GetPackageInfo (HInstance, nil, Flags, ShowInfoProc); if ModuleDesc <> '' then begin AddChild (ModuleNode, 'Description: ' + ModuleDesc); if Flags and pfDesignOnly = pfDesignOnly then AddChild (ModuleNode, 'Design Only'); if Flags and pfRunOnly = pfRunOnly then AddChild (ModuleNode, 'Run Only'); end; end; Result := True; end;
As you can see in the preceding code, the ForEachModule function begins by adding the module name as the main node of the tree (by calling the Add method of the TreeView1.Items object and passing nil as the first parameter). It then adds two fixed child nodes, which are stored in the ContNode and ReqNode variables declared in the implementation section of this unit.
Next, the program calls the GetPackageInfo function and passes it another callback function, ShowInfoProc, which I'll discuss shortly, to provide a list of the application's or package's units. At the end of the ForEachModule function, if the module is a package the program adds more information, such as its description and compiler flags (the program knows it's a package if its description isn't an empty string).
Earlier, I mentioned passing another callback function (the ShowInfoProc procedure) to the GetPackageInfo function, which in turn calls the callback function for each contained or required package of a module. This procedure creates a string that describes the package and its main flags (added within parentheses), and then inserts that string under one of the two nodes (ContNode and ReqNode), depending on the type of the module. You can determine the module type by examining the NameType parameter. Here is the complete code for the second callback function:
procedure ShowInfoProc (const Name: string; NameType: TNameType; Flags: Byte; Param: Pointer); var FlagStr: string; begin FlagStr := ' '; if Flags and ufMainUnit <> 0 then FlagStr := FlagStr + 'Main Unit '; if Flags and ufPackageUnit <> 0 then FlagStr := FlagStr + 'Package Unit '; if Flags and ufWeakUnit <> 0 then FlagStr := FlagStr + 'Weak Unit '; if FlagStr <> ' ' then FlagStr := ' (' + FlagStr + ')'; with Form1.TreeView1.Items do case NameType of ntContainsUnit: AddChild (ContNode, Name + FlagStr); ntRequiresPackage: AddChild (ReqNode, Name); end; end;