How to debug or fix "Module has open descendants or linked modules" error? - delphi

I've had this long time problem that I can't view as text the main form for a project I inherited. Even if no other forms are open.
How can I debug the cause of this error message? What options do I have to fix it?
I found one related newsgroup post http://embarcadero.newsgroups.archived.at/public.delphi.ide/200906/0906193960.html but this only addresses the form inheritance cause, and doesn't explain anything about linked modules. I don't believe I'm using form inheritance.
I do have a DM (data module) for the project, and the form does load a couple of images from the dm through properties of a TTreeView on the form--does having a data module automatically mean I can never view as text a form in Delphi (aside from viewing the form as text in notepad)? It doesn't seem to matter whether my DM is open or closed in the IDE.
I also found one SO question with a related title (Module %s has open descendants or linked modules. can not reload) but the question itself and it's answer is not particularly relevant.

This is sometimes caused by a form that inherits from another form in your project (or the gallery) (known as Visual Form Inheritance in the documentation, IIRC). The IDE doesn't know how to find the base class for the form; it needs that opened before the descendant form. For instance, this can cause the same error if the unit containing TMyBaseForm isn't opened first in the IDE, particularly if the base (ancestor) unit is not included in the project first:
unit SpecialForm;
interface
uses
Forms, { all the other usual stuff }, BaseForm;
type
TMySpecialForm = class(TMyBaseForm)
private
public
end;
You can tell if this is the case by looking at your form's class declaration - if it descends from anything other than TForm, this is probably the cause of the error.
(Another instance of it happening is often when using a datamodule, because the base TDataModule .DFM isn't available. Attempting to view the datamodule .DFM as text will cause this error every time; the solution is to close your project and use an external editor such as Notepad or Notepad++ to edit the .dfm for your datamodule.)

I've had this issue occasionally, perhaps when I've used Frames, but my latest instance didn't involve Frames nor Data Modules nor inherited forms.
After an enormous amount of work creating a copy of the form (which copy didn't have the problem) and renaming the original unit and the form itself (which initially seemed to solve the problem), it turned out to be a live binding between forms.
Specifically, in my FMX application Form A has an options page with a TSpinBox that allows the user to set the minimum value for a TTrackBar on Form B (which was the form giving me grief). So the TSpinBox.Value was set to update the TTrackBar.Min field by means of a live binding. Closing Form A, or removing that live binding (and replacing it with an event handler to do the same thing) solved the problem.

I'd like to call upon the answer of Phillip J. Rayment and ADD that you don't have to have live binding to have this problem occur. It's sufficient to have custom control (class) of which you have an instance in another form. Then the RLink32 problem can appear and won't be solved until you close the form where you have the instance. The problem I experienced gave the following messages:
-RLink32 (during building)
-Access violation in module designide160.bpl` (if I made a modification to the problematic form)
-The module has open descendants or linked modules” error (if I tried to 'View as Form')

Related

How to add a property to TTabSheet such that it can be used at design time with TPageControl

I would like to add "MyProperty" to TTabSheet and work with it at design time. However if I subclass it I fear I will need to also subclass TPageControl, since it internally creates/manages the TTabSheets.
I believe this would require duplicating the TTabSheet management code to reference TMyTabSheet instead of TTabSheet, since it will need to invoke TMyTabSheet.create. This feels like something I will regret when a new version of Delphi updates TPageControl and I forget to update TMyPageControl.
I am also researching "class helpers" as another option.
Does anyone have advice on how to best accomplish this?
Thanks!
Type Helpers are merely compile-time sugar, they would cause no effect over the already compiled code, that stock VCL is together with TPageControl and TForm. That is a dead-end.
However you have a slight misconception here that TPageControl... internally creates... the TTabSheets. Indeed, when you create the sheets by the means of TPageControl itself, like right-clicking it and selecting "New Tab" that si what happens. But when you create the living form object out of DFM file (or DFM resource in your compiled EXE) that is TForm itself that creates ALL the components, including both page control and its tabsheets.
Just see my answer at How to efficiently let a `ParentFont = False` child control to use same font name as parent? - that would show you how far it is about the owner - TForm, not about TPageControl or other parent components when it comes about selecting specific classes for the actual tabs or other elements..
So you are free to pursue subclassing tabsheets only.
Create the TMyTabsheet = class(TTabSheet) component
Create and install into IDE the design-time package that would introduce new subclassed tab sheet to Delphi Form Editor
In your form put the stock TPageControl and create all the needed tabs regular way
in IDE form Editor right-click over the form free space and in the menu do the "View As Text Alt-F12" command - you would see the text content of your form's DFM file
in those DFM sources find your tab sheets and change their stock TTabSheet class to be your derived sub-class
right-click the text editor and choose "View As Form Alt-F12" command
If all was done correct then Delphi would recreate the form with your new-class sheets now. Find your new properties in Object Inspector and change them.
Now switch to .Pas sources of your form and find the declarations of those tab sheets and change their type too. That is only required if you would have to access your new properties from the Delphi sources. If not you can leave their declaration as TTabSheet in pas-file as your class is direct descendant from it. You may leave those declarations as they were - but then you would have to add RegisterClass(TMyTabSheet); call into the very initialization section at the bottom of your unit, so when the form would construct itself out of the DFM it would be able to find the class implementation by the name. If you would change the declaration (at least one of those) then your form would automagically call all needed RegisterClass before streaming out of DFM. Choose any option you like.
Optionally and later, extend your design-time package to find and hijack IDE Form Editor's right-click menu for TPageControl and add "New My Subclassed Tab" command there. Just to avoid manual post-factum DFM editing. if you would do it often
This feels like something I will regret when a new version of Delphi updates TPageControl
After you created and tuned the form and saved it into DFM - it would be TForm that creates all the components out of the saved DFM-data, and that includes your tabs too. Unless very improbable event EMBT would kill the whole VCL streaming (made back in Delphi 1 in 1995) and redesign it from scratch (killing all the compatibility with existing Delphi sources at once), there should be no problem with forward compatibility with specifying your class in DFM. It is just the standard way VCL is designed - to get specific component types from the DFM.

When, the code that create the components on the form and the code that set their properties, is called?

If I put a component on the form I don't see any code like MyComp:=TMyComponent.Create in unit code. I think the component is created automatically, but when ? And the same happends with the properties that I configured after I put the component on the form. When they are applied at runtime ?
The properties for a form and all the design time components that live on it are streamed in by the framework during the construction of the form. That process is triggered from the form's constructor, in TCustomForm.Create. The pertinent code in there looks like this:
Include(FFormState, fsCreating);
try
if not InitInheritedComponent(Self, TForm) then
raise EResNotFound.CreateFmt(SResNotFound, [ClassName]);
finally
Exclude(FFormState, fsCreating);
end;
The key is the call to InitInheritedComponent. That is a function defined in the Classes unit that does the heavy lifting. In a very broad overview it does the following:
Finds the name of the form class and looks for an RT_RCDATA resource of that name. That resource is the .dfm file.
Once the .dfm resource has been located it is parsed.
The .dfm parsing handles the assignment of properties that you set at design-time in the Object Inspector. For instance the parsing might encounter a line like this: Caption = 'My main form' and it turns that into an assignment of the string 'My main form' to the form's property Caption.
The .dfm file is hierarchical. It contains the properties for the various components and controls that are defined at design-time.
As well as setting the properties of the form's design-time components, the .dfm streaming process also instantiates those components.
In order for all of this to work, the streaming framework relies on RTTI. It knows nothing at all at compile time of your classes and components. Hence the need for RTTI. The streaming framework uses the old style RTTI and in fact that is the reason for the existence of old style RTTI. If ever you wonder about why old style RTTI is the way it is, try to view it from the perspective of having been designed to support streaming.
Information about controls and components as well as their properties that you edit while designing in IDE will be stored in your form .dfm file. Creation of that form in run-time will trigger the process of automatic loading of that .dfm file and all controls and components will be initialized at that time.
This is rather simple explanation of what exactly happens, you can start debugger at form creation line and follow what is happening there, but it is quite longish process and you can easily get lost if you are still learning.
You can find form creation code that Delphi automatically writes in .dpr file of your project.
Application.CreateForm(TForm1, Form1);

Published interface properties bug and workarounds

I wrote a set of components that link to each other via published interface properties. They are registered and installed in a design package.
Using published interface properties is not that common in Delphi, and thus, unsurprisingly, doesn't seem to work that well.
It works fine when components reside on the same form, however interface property links between components on different forms cause issues.
Unlike object links to components on another form, interface links don't seem to be recognized by IDE. What I mean is best described by an example, when you have 2 forms open in IDE, and have links between components on them, then trying to switch to form view as text (Alt+F12) would cause IDE to correctly complain that:
Module 'UnitXXX.pas' has open descendents or linked modules. Cannot close.
But if the property is an interface then this does not happen, what happens instead is that the link is severed (and that's the best case scenario when you use Notification mechanism to clear references, otherwise you're left with an invalid pointer)
Another problem, likely as a consequence of the same bug is that when you open a project in IDE, the order in which forms will be reopened is undefined, so IDE can try to open a form that contains components that have interface links to components on another form, but that other form is not recreated yet. So this effectively results in either AV or severed links.
Back in 90s while I used Datasets and Datasources I remember similar issues with links between forms disappearing, so this is somewhat similar.
As a temp workaround I added duplicate published properties, for each Interface property I added another that is declared as TComponent. This makes Delphi aware there is a link between forms, but is an ugly workaround to say the least.
So I wonder if there is something I can do to fix this issue ? It's an IDE bug and likely not fixable directly, but perhaps I can override something or otherwise hook in to streaming mechanism to more effectively workaround this bug.
I haven't ever gone so deep into streaming mechanism, but I suspect the Fixup mechanism is supposed to deal with this. There is a csFixups TComponentState so I hope a workaround is possible.
Edit: Using D2007.
Update:
New updated reproducible example uploaded to http://www.filedropper.com/fixupbugproject2
Added property ComponentReference: TComponent so that it's easy to compare and trace interface vs component streaming.
I narrowed the problem down to assembler level which is a bit out of my depth.
In procedure GlobalFixupReferences in classes unit it calls:
(GetOrdProp(FInstance, FPropInfo) <> 0)
which eventually executes:
function TInterfacedComponent.GetInterfaceReference: IInterface;
begin
// uncomment the code bellow to avoid exception
{ if (csLoading in ComponentState) and (FInterfaceReference = nil) then
// leave result unassigned to avoid exception
else
}
result := FInterfaceReference; // <----- Exception happens here
end;
As you can see from the comment, the only way I found to avoid the exception is to leave the result unassigned, but that breaks the functionality since comparison above in GlobalFixupReferences fails due to GetOrdProp <> 0, which severes the link.
tracing deeper the more exact location of exception is in
procedure _IntfCopy(var Dest: IInterface; const Source: IInterface); in system unit
This line in particular raises an read of address 0x80000000
{ Now we're into the less common cases. }
##NilSource:
MOV ECX, [EAX] // get current value
So, why MOV fails and what's wrong with ECX or EAX I have no idea.
To summarize, the problem happens only with published interface properties that have a getter method, and the property points to component on another form/module (and that form/module is not recreated yet). In such case restoring form DFM causes an AV.
I'm pretty sure the bug is in the ASM code in GetOrdProp, but it's beyond my ability to fix, so the
easiest workaround is to use a Field instead of a getter method and read it directly in the property. This is, fortunately good enough for my case currently.
Alternatively, you can declare the property as TComponent instead of interface, then write a TComponentProperty descendant, override ComponentMayBeSetTo to filter component that don't support the required interface. And of course register it using RegisterPropertyEditor

Unable to open designer on form in C# 2010

I have generated a solution in C# 2010 by way of the Artinsoft conversion from VB6.
When I open the new solution in C# I right-click on the forms but there is no "View Designer" option. I assume this is because something is failing to compile. What could be the problem and how should I fix it?
Try a clean and rebuild
Make sure you have proper designer.cs file
Make sure the initialize method is called from the constructor of your Forms.
Make sure if there are any custom dlls they are in right places.
Better create a new app in Windows forms and check how the things are arranged. so that you can compare the conversion.
I would not expect that from a failure to compile, but rather a failure to convert properly. Even if the class doesn't compile, if it's a form you shouldbe able to see and edit the form portion.
The reason should be marked in the error list if it's failing to compile. It's more likely that it's created it as just a class (that doesn't inherit from Form).
Check that there's a MyForm.Designer.cs (where your form name is MyForm) and also check that the declaration of the class in MyForm.cs inherits from form
public partial class MyForm : Form
If this isn't the case, it hasn't converted properly, so you may have to inherit from form, drop on the UI controls again and hook them up to any events
Have you tried running Visual Studio as Administrator (elevated rights)?
Can you try Shift+F7 key combination?

What does a EClassNotFound raised at runtime really mean when the class in question is there at compile and link time, and there explicitly in code?

I have a runtime error happening in the rtl Streaming in of a form, causing an exception EClassNotFound to be raised, while doing TReader.ReadRootComponent. The particular error message is "Class not found TActionList".
What is odd is:
My main form uses Action list.
For fun, I added ActnList.pas (from the VCL source folder) to my project, to try to fix it.
This happens to me when instantiating a form that I had working until a few minutes ago. The change that I made was in some sub-frame code: I removed all its implementation section code with an ifdef marker, because I am mocking up some frames, for unit testing and prototypes.
I tried adding the action list class to the project, and I tried with and without various compiler and link options, and yet, I still get this exception. Obviously something weird is up. There must be another weird way to get this problem.
In fact, it seems there is something really weird going on. When this error is raised, I get the following call stack:
rtl.Classes.ClassNotFound('TActionList')
rtl.Classes.TReader.FindComponentClass(???)
rtl.Classes.FindExistingComponent
rtl.Classes.TReader.ReadComponent(nil) /// NIL!? WHAT!!!!!
rtl.Classes.TReader.ReadDataInner(???)
rtl.Classes.TReader.ReadData(???)
rtl.Classes.TComponent.ReadState(???)
vcl.Controls.TControl.ReadState(???)
vcl.Controls.TWinControl.ReadState($60B9CF0)
vcl.Forms.TCustomForm.ReadState(???)
rtl.Classes.TReader.ReadRootComponent($606EB90)
rtl.Classes.TStream.ReadComponent($606EB90)
rtl.Classes.InternalReadComponentRes(???,???,$606EB90)
rtl.Classes.InitComponent(TComplexFormContainingFrames)
It seems the nil is intentional, in TReader.ReadDataInner(Instance:TComponent):
while not EndOfList do ReadComponent(nil);
Update: I believe the answer to this question is to understand "serialization contexts" as Mason has mentioned. And, it's time to admit my own Stupidity: I removed the parent of the frame from the project, not realizing it was the parent of the frame. I worked around it being missing by stubbing the type declaration for TMyFrameParent as TMyFrameParent = class(TFrame), and this in turn lead to the condition in question. I am leaving the question here because I think it might be really handy in future to note when this exception occurs in arcane cases, and how to fix it. In particular, Mason has a really interesting bit of information about "serialization contexts" and how they apply to class-name-finding.
It means that the class wasn't found in the current deserialization context. Not all existing classes are registered for all loading. Each form class has RTTI containing references to the components it uses. To get this to work, make sure that your form (or frame, if this is a frame) declares at least one TActionList before the private tag:
TMyForm = class(TForm)
ActionList: TActionList;
OtherComponent: TSomeComponent;
private
//whatever
public
//whatever
end;
Use Classes.RegisterClass to register classes you want to use with the streaming system. Quote from the doc
Form classes and component classes that are referenced in a form declaration (instance variables) are automatically registered. Any other classes used by an application must be explicitly registered by calling RegisterClass if instances are to be saved. Once classes are registered, they can be loaded or saved by the component streaming system.
It seems that this happens when you copy a frame from one project to another project, and that frame inherits from something, and you fake the inheritance, but leave the "inherited" item descriptions in the frame dfm, items like this:
inherited ActionList: TActionList
Left = 520
Top = 576
end
This in turn results in the "current deserialization context" that Mason talked about, not containing the class. One fix is to change Inherited to object in all the above cases.
There is another way to get this error: put 'public' at the top of a form definition class. By default class members are 'published'. I accidentally added 'public' to the top of a form declaration and it produces multiple 'Class not found' exceptions at run-time.
I got a "EClassNotFound" error when I had a TLabel declaration present in my DFM file but there was no declaration for it in the corresponding PAS file. Somehow the form editor screwed up.
The error was not visible until I deleted all labels from my form except that particular "corrupted" one. It was difficult to hunt it down because that label was hidden under a panel.
One easy fix is to cut (ctrl+x) that label (once you find it) from the form and paste it back. This time the form editor will correctly insert a declaration for it in the PAS file.
There is yet another way to get this error: I have put 'private' at the top of a form definition class (because none of the elements were used outside the form).
So same like in previous answer (by Server Overflow): by default class members are 'published' and if you change visibility of a form declaration it produces multiple 'Class not found' exceptions at run-time.

Resources