Monday, November 15, 2004

Demand-loaded component packages in Delphi 2005

Delphi 2005 introduced a new mechanism for handling design-time component packages that allows them to now be demand-loaded.  This allows the IDE to be more intelligent about when and what packages to load and only when actually needed, thus decreasing overall startup time (since D2005 is now three products in one, every little bit helps).  In general the qualifications for demand-loading a package, or a group of packages (which I'll explain in a moment), is very deterministic.  When loading a package the IDE is always analysing the package as it is registering its contents in order to best determine whether or not it is a candidate for demand-loading.  There are, however, a few gotchas that you may encounter that can lead to confusion.  There are a few rare cases where the IDE is unable to properly determine the eligibility of a set of packages.  I'll try and outline and describe the logic which the IDE uses to qualify or disqualify a package's demand-load-ability.

Let's start with a few assumptions.  In Delphi Win32, when you boil it all down, a class reference is nothing more than a pointer to that class' virtual method table.  That VMT is located in the “text” or “code” segment of a given module (.exe, .dll or package).  This is important to understand because it provides crucial information about the location of implementation of a given class.  By using VirtualQueryEx, you can easily determine the actual module in which a class is defined.  Simply pass the class reference to that function and one of the values returned in the MEMORY_BASIC_INFORMATION structure.  The AllocationBase field happens to correspond to that module's HMODULE.  You can pass this value to GetModuleFileName in order to get the fully qualified path to the module that was loaded.

Next, I need to define what a “package group” is.  This is simply a given package and all its dependencies, both direct and indirect.  Say package A requires packages B and C.  The package group for package A contains A, B, and C, with A being the “root” package (ie. the package that caused all the others to be loaded).  Now what's the group for package B?  It's just B.  That is because B doesn't require A, nor does it require C.  Had B required C, then B's group would hav contained B and C.  (of course B or C cannot require A since that would be circular and that is not a possible situation to have).  Of course A, B and C can also exist in another root package's group as well.  For instance, say package D also requires packages B and C.  So now there are two groups, one containg A, B and C and another containing D, B and C with the group roots being A and D.

Now that you understand these two basics, lets start in on how the IDE analyses the design-time packages for demand-load eligibility.  When the IDE loads a design-time package, it will typically “require” various run-time packages that contain *only* those bits needed for run-time use of the components. (you have separated your run-time bits from the design-time bits haven't you?... If not.. that is a discussion for another day ;-)..  The design-time only package is where the actual component and property/component/selection editors are registered.  This is done by calling RegisterComponents, RegisterPropertyEditor, RegisterComponentEditor, etc from within various global procedures called “Register”.  It is important that this always be done within the “Register” procedure in order for the demand-loading to properly function.  See this blog entry.  What the IDE is basically looking for in order to determine the eligibility for demand-loading is that all component/property editor/ etc... registrations are restricted to that package's group only.  If a propertly editor were to be registered that could apply to any component outside the currently loading group, then that group is disqualified from being demand-loaded.  This is an important point.

Internally the IDE creates some datastructures that are filled in while all this “registering” is going on.  So as each component and property/component/selection editor is registered they are cross-checked against the package group.  Remember the VirtualQueryEx trick above?  This is used to determine what packages contain a given component or property/component/selection editor.  If the package is in the group then all is well.  If a reference were allowed to “leak” outside the group, then it could be possible that a component/property/selection editor would not be properly loaded and used when needed.  For instance, say you register a new propertly editor for any property of type TStrings on any TPersistent.  This is a perfectly legal registration, however it would cause your package to be disqualified from being demand-loaded.  Disqualification isn't nessesarily a bad thing.  It may be that you actually wanted to override the Borland supplied TStrings property editor with one of your own design.  It simply means that your package will now always be loaded either at startup or when a project that has it enabled is loaded.

Now what if you want your package to be demand-loaded but it isn't?  How can you tell why it was disqualified?  When implementing this feature, I asked the same questions.  So in addition to the package analysis logic, I also added extensive logging and reporting.  This functionality is also available to you as well.  There are two ways you can turn this on.  One is to enable it on a per-package basis.  You can do this by calling EnableDemandLoadReport(Detailed: Boolean) inDesignIntf from one of the Register procedures in your design-time package.  Pass False to simply get a report of why your package was disqualified and True to get a fully detailed report of what your package registered (which may be useful for many other uses as well).  Once your package is loaded and fully registered, the IDE will create a text file that is the same base name as the design-time package being loaded with the extension .rpt in the same folder containing your package.  You can also enable reporting globally by going to HKCUSoftwareBorlandBDS3.0Package Cache and adding one or both of the following values:  Report=1 or DetailedReport=1.  They correspond to EnableDemandLoadReport(False) and EnableDemandLoadReport(True), respectively.  The cool thing is that you can enable reporting in this manner and see detailed reports for *all* design-time packages that the IDE loads, including all the Borland supplied packages.

There are some cases where the IDE can get confused about what to demand-load.  For instance, if you have a design-time package that requires a run-time/design-time package.  The run-time/design-time package registers some components and the design-time package registers some property or component editors for the components in the run-time/design-time package.  In this case you can do one of two things.  The preferred method is to simply make the run-time/design-time package into a run-time only package and move the component registration into the design-time only package.  The other technique is to call ForceDemandLoadState(dlDisable) from the design-time only package.  Which brings up another new global function, ForceDemandLoadState().  This allows you to circumvent and override the descisions made by the IDE's automatic analysis code.  There may be cases where the IDE, for whatever reason disqualified your package from being demand-loaded, but you feel that it really should be demand-loaded.  You can call ForceDemandLoadState(dlEnable) from a Register procedure in your design-time package.  For more information on this you can see the various comments in DesignIntf.pas.

So your packages are now being demand-loaded, how and when does it actually get loaded?  When an instance of a registered component is created, the IDE will notice that the component is from a demand-loaded package and then load the package “on-demand.”  This can happen by either manually dropping the component onto a design-surface or loading a form/datamodule/frame that contains that component.  What about property/component editors?  All the property/component/selection editors are “component centric” meaning that they always require a live component instance in order to be selected.  Since they are also registered when the demand-loaded package is finally loaded, they too become available.

There is more that the demand-loading logic does, both in what it tracks and how it manages the state and palette filtering logic, but that will be left for another blog entry.