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?
Asked Answered
C

6

10

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:

  1. My main form uses Action list.
  2. 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.

Cornelius answered 2/9, 2011 at 17:35 Comment(1)
The nil only signals to ReadComponent to instantiate a new instance instead of reading into an existing one.Diminutive
T
11

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;
Throstle answered 2/9, 2011 at 18:40 Comment(0)
G
3

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.

Gotama answered 10/5, 2019 at 14:29 Comment(0)
B
2

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.

Bookstack answered 2/9, 2011 at 19:29 Comment(2)
So in the case of a frame that is inheriting from another frame, probably this explains why Classes.RegisterClass was never called, because the .dfm of the frame contains only an "inherited x" entry, not the "object x" entry.Cornelius
There is something fishy... if the project didn't contain the parent frame, then how come that the streaming system looked for the TActionList class? Because the dfm for the descendant frame were empty (ie doesn't contain the ActionList), right?Bookstack
C
1

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.

Cornelius answered 2/9, 2011 at 19:52 Comment(0)
C
1

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.

Clarenceclarenceux answered 11/11, 2018 at 20:44 Comment(5)
This hapens when your label doesn't have a name. The IDE adds declarations to the class only if the control has got a name (how else should it declare it?). So clearing the names of some components can remove clutter if you don't access them in the code. But at least one of each type must have a name otherwise you get this error.Crelin
@Crelin - Hi. I don't understand. That label is a standard TLabel. It is not a custom component. It is on the form. And you cannot delete the name of a control from the IDE, from the Object Inspector (you can do it if you edit the DFM manually, but this is not the case). Update: I looked into that "bugged" project and my "broken" label HAS a name.Clarenceclarenceux
I think my question is: how would you make a control without name appear on your form?Clarenceclarenceux
just drop it on the form and then in the object inspector clear the name property.Crelin
@Crelin - also see this: stackoverflow.com/questions/53278644Clarenceclarenceux
A
0

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.

Aberrant answered 29/7, 2022 at 7:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.