Writing C++-friendly Delphi Code

From RAD Studio
Jump to: navigation, search

Go Up to Delphi Language Guide Index


C++ can consume Delphi code. The Delphi command-line compiler uses the following switches to generate the files that C++ needs to process Delphi code:

However, not all Delphi features are C++-friendly. This topic lists the DOs and DON'Ts for Delphi run-time code that you want to consume from C++.

DOs

Redeclaring All Inherited Contructors

Unlike Delphi, C++ does not inherit constructors. For example, the following is incorrect:

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

class B: public A
{
};

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

The header file generation logic of the Delphi compiler is aware of this language difference and adds the missing inherited constructors to each derived class. However, these constructors also initialize member variables of the class. This causes problems if a base class invokes a virtual method that already initialized one of these member variables to a non-default value. It is particularly important to redeclare inherited constructors if the base constructor can initialize a member of a delphireturn type in the class.

Ensuring Distinct Signature for Each Constructor in a Hierarchy

C++ does not support named constructors. For this reason overloaded constructors must not have identical nor similar parameters. For example, the following code does not work for C++ consumption:

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

This example results in the following C++ code that arises compilation errors associated with duplicated constructors:

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

You can workaround this issue in different ways:

  • Add a dummy parameter with a default value to one of the constructors. The header file generation logic intentionally leaves out the default value on the constructor so that the two constructors are distinct in C++:
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);
};
  • Use the Named Constructor Idiom. This technique declares class static factory members instead of named constructors when constructors are overloaded with identical or similar parameters. This is particularly relevant for the Delphi record type. The following example depicts a solution based on this technique:
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)); }

DON'Ts

Overloading Index Properties

Delphi allows overloading index properties, such as:

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;

However, the resulting interface in the header file does not work in C++, since each property of a class must be unique.

Calling Virtual Mehtods from Constructors

This is related to Redeclaring All Inhereted Contructors. For Delphi-style classes, the vtable of the most-derived class is set when the base constructors is invoked. This allows the virtual mechanism to work from constructors. However, this implies a strange behavior in a C++ environment, such as a virtual method of a class that is invoked before the constructor of the class executes; or the constructor of a class that undoes the initialization of a member that was performed from a base constructor.

Using Generics in Aliases

C++ can use a Delphi alias to an instantiated template type. However, C++ cannot use a Delphi alias with dependent types. The following code illustrates this fact:

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

GoodArray is a concrete type that C++ can use. In contrast, BadArray contains a dependent type, thus C++ cannot use it.

Using Generics in Closures

RTTI generated for published events allows the IDE to generate event handlers. The logic in the IDE is unable to process RTTI generated for Generics when C++ event handlers are generated. Thus it is recommended that you avoid using Generics in closures.

Using Records with Constructors

In Delphi, a variant record is equivalent to a C++ union. Records with constructors cannot be in a variant record. The C++ rule is actually more generic: a type with a user-defined constructor, destructor, or assignment cannot be a member of a union. The following code illustrates a case that does not work for C++:

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;

The resulting C++ code triggers compiler errors:

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)
};

Using Non-Empty Default String Parameters

Non-empty default string parameters generate the following warning:

W8058 Cannot create pre-compiled header: initialized data in header

Note that this issue only affects previous-generation C++ compilers (BCC32), it does not affect Clang-enhanced C++ compilers.

See Also