delphiexceptiondelphi-xeobject-persistence

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:

  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.


Solution

  • 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;