Schreiben von in C++ unterstütztem Delphi-Code

Aus RAD Studio
Wechseln zu: Navigation, Suche

Nach oben zu Delphi-Sprachreferenz - Index


C++ kann Delphi-Code verwenden. Der Delphi-Befehlszeilen-Compiler generiert mit den folgenden Optionen die Dateien, die C++ zum Verarbeiten von Delphi-Code benötigt:

Es werden jedoch nicht alle Delphi-Features von C++ unterstützt. Dieses Thema enthält die Regeln für Delphi-Laufzeitcode, den Sie mit C++ verwenden möchten.

Ausführen

Neu Deklarieren aller geerbten Konstruktoren

Im Gegensatz zu Delphi werden in C++ keine Konstruktoren vererbt. Der folgende Code ist zum Beispiel falsch:

class A
{
  public:
  A(int x) {}
};

class B: public A
{
};

int main(void)
{
  B *b = new B(5);  // Error
  delete b;
}

In der Logik des Delphi-Compilers für das Generieren der Header-Datei ist dieser Sprachunterschied berücksichtigt, und es werden fehlende geerbte Konstruktoren zu jeder abgeleiteten Klasse hinzugefügt. Diese Konstruktoren initialisieren aber auch Member-Variablen der Klasse. Dadurch werden Probleme verursacht, wenn eine Basisklasse eine virtuelle Methode aufruft, die bereits eine dieser Member-Variablen mit einem Nicht-Standardwert initialisiert hat. Es besonders wichtig, geerbte Konstruktoren neu zu deklarieren, wenn der Basiskonstruktor einen Member mit dem Typ delphireturn in der Klasse initialisieren kann.

Eindeutige Signaturen für alle Konstruktoren in einer Hierarchie

C++ unterstützt benannte Konstruktoren nicht. Daher dürfen überladene Konstruktoren weder identische noch ähnliche Parameter haben. Beispielsweise würde der folgende Code nicht mit C++ funktionieren:

MyPoint = class
public
  constructor Polar(Radius, Angle: Single);
  constructor Rect(X, Y: Single);

Dieses Beispiel führt zu folgendem C++-Code, der Compilierfehler im Zusammenhang mit doppelten Konstruktoren verursacht:

class PASCALIMPLEMENTATION MyPoint : public System::TObject
{
  public:
    __fastcall MyPoint(float Radius, float Angle);
    __fastcall MyPoint(float X, float Y);
};

Sie können dieses Problem auf verschiedene Weise umgehen:

  • Fügen Sie einem der Konstruktoren einen Dummy-Parameter mit einem Standardwert hinzu. Die Logik beim Generieren der Header-Datei lässt Standardwerte im Konstruktor absichtlich aus, sodass die beiden Konstruktoren in C++ unterschiedlich sind:
MyPoint = class
public
  constructor Polar(Radius, Angle: Single);
  constructor Rect(X, Y: Single; Dummy: Integer = 0);
class PASCALIMPLEMENTATION MyPoint : public System::TObject
{
  public:
    __fastcall MyPoint(float Radius, float Angle);
    __fastcall MyPoint(float X, float Y, int Dummy);
};
  • Verwenden Sie Named Constructor Idiom (EN). Bei dieser Technik werden anstatt von benannten Konstruktoren klassenstatische Generatoren-Member deklariert, wenn Konstruktoren mit identischen oder ähnlichen Parametern überladen sind. Dies ist besonders für die Record-Typen von Delphi wichtig. Das folgende Beispiel zeigt eine Lösung auf Basis dieser Technik:
class MyPoint {
public:
  static MyPoint Rect(float X, float Y);  // Rectangular coordinates
  static MyPoint Polar(float Radius, float Angle);  // Polar coordinates
private:
  MyPoint(float X, float Y);  // Rectangular coordinates
  float X_, Y_;
};

inline MyPoint::MyPoint(float X, float Y)
  : X_(X), Y_(Y) { }

inline MyPoint MyPoint::Rect(float X, float Y)
{  return MyPoint(X, Y); }

inline MyPoint MyPoint::Polar(float Radius, float Angle)
{  return Point(Radius*std::cos(Angle), Radius*std::sin(Angle)); }

Unterlassen

Überladen von Indexeigenschaften

In Delphi können Indexeigenschaften überladen werden, wie z. B.:

TTest = class
  function  GetPropI(Index: Integer): Longint; overload;
  procedure SetProp(Index: Integer; Value: Longint); overload;
  function  GetPropS(Index: Integer): String; overload;
  procedure SetProp(Index: Integer; Value: String); overload;
public
  property Props[Index: Integer] : Longint read GetPropI write SetProp;
  property Props[Index: Integer] : String read GetPropS write SetProp; default;
end;

Das resultierende Interface in der Header-Datei funktioniert jedoch nicht in C++, weil jede Eigenschaft einer Klasse eindeutig sein muss.

Aufrufen von virtuellen Methoden aus Konstruktoren

Dies bezieht sich auf die Neudeklaration aller geerbten Konstruktoren. Für Klassen im Delphi-Stil wird die V-Tabelle der am stärksten abgeleiteten Klasse festgelegt, wenn die Basiskonstruktoren aufgerufen werden. Dadurch kann der virtuelle Mechanismus aus Konstruktoren arbeiten. Dies impliziert jedoch ein merkwürdiges Verhalten in einer C++-Umgebung, wie z. B. dass eine virtuelle Methode einer Klasse aufgerufen wird, bevor der Konstruktor der Klasse ausgeführt wird; oder dass der Konstruktor einer Klasse die Initialisierung eines Members, die im Basiskonstruktor vorgenommen wurde, rückgängig macht.

Verwenden von Generics in Aliasen

C++ kann mit einem Delphi-Alias einen Template-Typ instantiieren. C++ kann aber keinen Delphi-Alias mit abhängigen Typen verwenden. Der folgende Code zeigt dies:

type
  GoodArray = TArray<Integer>;
  BadArray<T> = array of T;

GoodArray ist ein konkreter Typ, den C++ verwenden kann. BadArray enthält aber einen abhängigen Typ, daher kann C++ es nicht verwenden.

Verwenden von Generics in Closures

Die für published-Ereignisse generierte RTTI ermöglicht der IDE Ereignisbehandlungsroutinen zu generieren. Die Logik in der IDE kann keine für Generics generierte RTTI verarbeiten, wenn C++-Ereignisbehandlungsroutinen generiert werden. Daher sollten Sie die Verwendung von Generics in Closures vermeiden.

Verwenden von Records mit Konstruktoren

Ein varianter Record in Delphi entspricht einer C++-Union. Records mit Konstruktoren dürfen nicht in varianten Records vorkommen. Die C++-Regel ist allgemeiner: ein Typ mit einem benutzerdefinierten Konstruktor, Destruktor oder einer benutzerdefinierten Zuweisung darf kein Member einer Union sein. Der folgende Code enthält ein Beispiel, das für C++ nicht funktioniert:

type
  TPointD = record
    X: Double;
    Y: Double;
  public
    constructor Create(const X, Y: Double);
  end;

  TRectD = record
    case Integer of
      0:
        (Left, Top, Right, Bottom: Double);
      1:
        (TopLeft, BottomRight: TPointD);
  end;

Der resultierende C++-Code löst Compilerfehler aus:

struct DECLSPEC_DRECORD TRectD
{
  #pragma pack(push,1)
   union
    {
      struct
      {
        TPointD TopLeft;     // Error
        TPointD BottomRight; // Error
      };
      struct
      {
        double Left;
        double Top;
        double Right;
        double Bottom;
      };

    };
  #pragma pack(pop)
};

Verwenden von nicht leeren Standard-String-Parametern

Nicht leere Standard-String-Parameter generieren die folgende Warnung:

W8058 Vorcompilierter Header kann nicht erzeugt werden: initialisierte Daten im Header

Dieses Problem wirkt sich nur auf die C++-Compiler der vorherigen Generation (BCC32 und BCCOSX) aus, es hat keinen Einfluss auf durch Clang erweiterte C++-Compiler.

Siehe auch