Embarcadero C++ Builder, TDataModule und Vererbung

Nach ein paar kleiner Umbauten und Optimierungen im Programmcode erhielt ich beim Versuch ein Fenster zu öffnen ziemlich unerwartet den folgenden Fehler:

Im Projekt Project.exe ist eine Exception der Klasse EReadError mit der Meldung 'Eigenschaft ClientHeight existiert nicht' aufgetreten.

2016-03-24 09_10_59-Rational - C++Builder 10 Seattle - UnitFormMain.h [Angehalten - Thread 16168] [E

Dabei war interessant, dass der Fehler nicht wirklich in Zusammenhang mit meinen zuletzt vorgenommenen Code-Änderungen stehen konnte.

Die Fehlermeldung im Debugger war nicht sehr Aufschlussreich – die Ausgabe in der Laufzeit (nachdem ich “Fortsetzen” gewählt hatte) gab mir allerdings den Zielführenden Hinweis: “Fehler beim Lesen von Invoice.ClientHeight”.

2016-03-24 09_12_08-Rational

Ich wusste, dass “Invoice” ein auf TDataModule basierendes Objekt innerhalb des aktuellen Fensters (TFormInvoice) ist: TDataModule / TInvoice. Aber weder meine Klasse TInvoice noch deren Basis (TDataModule) verfügen über eine Eigenschaft namens ClientHeight. Das haben gewöhnlich nur Formulare.

Nach einigen Überlegungen fiel mir ein, dass ich wenige Builds zuvor eine Ebene “TDataModuleBaseRecord” zwischen TDataModule und TInvoice einschob: Die neue Klasse sollte Erbtante für bestimmte ähnliche Kindklassen werden.

Im SVN konnte ich folgende Änderungen finden:

TInvoice.h

///////////////////////////////////
// ALT:
///////////////////////////////////
#include "DataChangedObserver.h"
#include "DataSavedObserver.h"

class TInvoice : public TDataModule
{
    __published:
    // Programmcode...
}

//////////////////////////////////
// NEU:
//////////////////////////////////
#include "DataModuleBaseRecord.h"
#include "DataChangedObserver.h"
#include "DataSavedObserver.h"

class TInvoice : public TDataModuleBaseRecord
{
    __published:
    // Programmcode...
}

TInvoice.cpp

/////////////////////////////
// ALT:
/////////////////////////////
#pragma hdrstop

#include "Invoice.h"

#pragma package(smart_init)
#pragma classgroup "Vcl.Controls.TControl"
#pragma resource "*.dfm"

__fastcall TInvoice::TInvoice(
    TComponent* Owner,
    long AInvoiceId,
    TCustomer *ACustomer
)
    :
    TDataModule(Owner),
    InvoiceId( AInvoiceId ),
    Customer( ACustomer )
{
}

//////////////////////
// NEU:
//////////////////////
#pragma hdrstop

#include "Invoice.h"

#pragma package(smart_init)
#pragma classgroup "Vcl.Controls.TControl"
#pragma resource "*.dfm"

__fastcall TInvoice::TInvoice(
    TComponent* Owner,
    long AInvoiceId,
    TCustomer *ACustomer
)
    :
    TDataModuleBaseRecord(Owner),
    InvoiceId( AInvoiceId ),
    Customer( ACustomer )
{
}

Die neu eingeführte “Erbtante” hatte auch nichts ungwöhnliches an sich:

//////////////
// Header:
//////////////
#ifndef UnitDataModuleBaseRecordH
#define UnitDataModuleBaseRecordH

#include <System.Classes.hpp>

class TDataModuleBaseRecord : public TDataModule
{
    __published:
    private:
    public:
        __fastcall TDataModuleBaseRecord(TComponent* Owner);

        bool IsLoaded;
};

#endif

//////////////
// CPP:
//////////////
#pragma hdrstop

#include "UnitDataModuleBaseRecord.h"

#pragma package(smart_init)
#pragma classgroup "Vcl.Controls.TControl"
#pragma resource "*.dfm"

__fastcall TDataModuleBaseRecord::TDataModuleBaseRecord(TComponent* Owner)
    :
    TDataModule( Owner ),
    IsLoaded( false )
{
}

/////////////
// DFM:
/////////////
object DataModuleBaseRecord: TDataModuleBaseRecord
  OldCreateOrder = False
  Height = 501
  Width = 505
end

Bisher nichts ungewöhnliches, doch dann der Blick auf die Invoice.dfm ließ mich etwas staunen:

///////////////////////////
// ALT:
///////////////////////////
object Invoice: TInvoice
  OldCreateOrder = False
  OnCreate = DataModuleCreate
  Height = 626
  Width = 672
  object FDTableInvoice: TFDTable
    Connection = Database.FDConnection
    UpdateOptions.UpdateTableName = 'Invoice'
    TableName = 'Invoice'
    Left = 92
    Top = 133
  end

/////////////////
// NEU:
/////////////////
object Invoice: TInvoice
  Left = 0
  Top = 0
  ClientHeight = 588
  ClientWidth = 656
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  OnCreate = DataModuleCreate
  PixelsPerInch = 96
  TextHeight = 13
  object FDTableInvoice: TFDTable
    Connection = Database.FDConnection
    UpdateOptions.UpdateTableName = 'Invoice'
    TableName = 'Invoice'
    Left = 92
    Top = 133
  end

TInvoice ist einfach so von einem Kind von TDataModule zu einem von TForm mutiert. Die Frage ist: Warum?

Die Antwort ist: Weil im DFM der Hinweis “Inherited” fehlt.

Lösung: Bei der nachträglichen Änderung der Vererbungshierarchie hätte diese neue Vererbung im DFM bekannt gemacht werden sollen.

Aus

object Invoice: TInvoice
  Left = 0

hätte

inerited Invoice: TInvoice
  Left = 0

werden sollen.

Das ist eine sehr gemeine, schwerwiegende und nicht leicht zu findende Stolperfalle für C++ Builder Programmierer: Erstens kümmern sich C++ Entwickler nur ungern um den von der IDE fabrizierten Delphi-Code und zweitens tauchen die Probleme mitunter erst Tage später auf. Dadurch fängt man gerne an der falschen Stelle zu suchen an.

Warum die Folgen erst Tage später aufscheinen können weiß ich ebenso wenig wie warum die IDE hier keine elegante Möglichkeit bietet das Problem zu beheben und der Betroffene zum Texteditor greifen muss.

Kennt hier jemand eine bessere Lösung?