Schreiben von in C++ unterstütztem Delphi-Code
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:
- Die Option
-JL
generiert .lib-, .bpi-, .bpl- und .obj-Dateien aus einer .dpk-Datei und Header-Dateien für alle Units in dem Package. - Die Option
-JPHNE
führt das gleiche aus einer.pas
-Unit durch.
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.
Inhaltsverzeichnis
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.