Meinung zu Delphi/FreePascal



  • Habt ihr Erfahrung mit Delphi/FreePascal sammeln können? Wie schnell ist heutiges Pascal im Vergleich zu C/C++ oder auch Java? Welche Vor- und Nachteile hat die Sprache Pascal/ObjektPascal?



  • Wenn du mich fragst, ich würde heute eher C# nehmen. Ich habe vor über 15 Jahren mit Delphi angefangen und habs auch lange benutzt. Damals war die Sprache ziemlicher Schrott, keine Templates, Unicodeunterstützung usw., aber das hat mich damals nicht so gestört 😉 Mit der damaligen Version würde ich heute nicht mehr arbeiten wollen, mittlerweile sind aber viele Features dazugekommen. Im Detail kenn ich mich da aber nicht aus.
    Mittlerweile sehe ich aber auch keinen Grund mehr, Delphi zu verwenden. Entweder nimmt man C++, oder wenn man etwas "einfacheres" haben will, dann eher .NET und C#.
    Die Vorteile der "Sprache" waren glaub ich nie so wirklich ausschlaggebend. Wie gesagt, früher war Object Pascal ziemlich eingeschränkt. Aber halt auch einfacher zu verwenden als C++, man konnte mehr oder weniger einfach anfangen und sofort Ergebnisse sehen, ohne sich monatelang mit der Sprache zu beschäftigen. Ok, das wird wohl auch ein Grund gewesen sein. Wichtiger war aber denke ich, dass es so viele Komponenten gab, die einfach funktioniert haben. Die GUI konnte man ganz einfach erstellen, viel einfacher als mit MFC Zeugs damals (und heute wahrscheinlich auch), Datenbankverbindungen hinzufügen, verschiedene Netzwerkprotokolle waren als Komponenten verfügbar, COM, später Office Automation, Reports... D.h., auch engagierte Laien (und vor 20 Jahren gabs viel weniger professionelle Entwickler als jetzt), die Experten in ihrem jeweiligen Fachbereich waren, konnten damit schnell relativ mächtige Software erstellen, mit lauter "Gimnicks", z.B. eben Report anzeigen und Ausdrucken, Infos per Mail versenden usw. Und da die Alternativen damals Java, C++ und VB hießen, konnte sich Delphi halbwegs am Markt etablieren. Und nicht wegen Sprachvorteilen von Object Pascal.



  • Pascal taugt imho primär als Lehrsprache. Wurde bei uns auch so gehalten.
    Die Syntax ist schrecklich verbose, das Stringkonzept fraglich und inkompatibel zu so ziemlich allen APIs, höchstens etwas mächtigere Compilermagie als C (zb der builtin shared_ptr ist ganz lustig), gegenüber C++ keine Vorteile.

    Zum Anfangen aber echt nett. Habe damit nicht angefangen, das Konzept bei Mitstudenten aber als sinnvoll erachtet.



  • Danke für eure Einschätzung. Wie sieht es mit der Performance aus im Vergleich zu C++, Java, Python oder so.


  • Mod

    PPascal schrieb:

    Danke für eure Einschätzung. Wie sieht es mit der Performance aus im Vergleich zu C++, Java, Python oder so.

    Ist halt eine relativ statische, compilierte Sprache mit einem nicht ganz schlechten Compiler, aber ein paar Behinderungen, wo sie noob-freundlich auf Kosten von Performance ist. Also so ungefähr wie Java, C# und ähnliche Sprachen. Merkbar langsamer als die High-Performance-Sprachen wie Fortran oder C++, aber natürlich noch um Welten schneller als interpretierte Sprachen mit viel Dynamik, wie Python (das ist aber auch kein Kunststück).



  • SeppJ schrieb:

    PPascal schrieb:

    Danke für eure Einschätzung. Wie sieht es mit der Performance aus im Vergleich zu C++, Java, Python oder so.

    Ist halt eine relativ statische, compilierte Sprache mit einem nicht ganz schlechten Compiler, aber ein paar Behinderungen, wo sie noob-freundlich auf Kosten von Performance ist. Also so ungefähr wie Java, C# und ähnliche Sprachen. Merkbar langsamer als die High-Performance-Sprachen wie Fortran oder C++, aber natürlich noch um Welten schneller als interpretierte Sprachen mit viel Dynamik, wie Python (das ist aber auch kein Kunststück).

    Wenn man sich die Mikrobenchmarks im Computer Language Benchmarks Game anschaut, dann spielt Pascal nur selten in der gleichen Liga wie Java.


  • Mod

    Gregor schrieb:

    SeppJ schrieb:

    PPascal schrieb:

    Danke für eure Einschätzung. Wie sieht es mit der Performance aus im Vergleich zu C++, Java, Python oder so.

    Ist halt eine relativ statische, compilierte Sprache mit einem nicht ganz schlechten Compiler, aber ein paar Behinderungen, wo sie noob-freundlich auf Kosten von Performance ist. Also so ungefähr wie Java, C# und ähnliche Sprachen. Merkbar langsamer als die High-Performance-Sprachen wie Fortran oder C++, aber natürlich noch um Welten schneller als interpretierte Sprachen mit viel Dynamik, wie Python (das ist aber auch kein Kunststück).

    Wenn man sich die Mikrobenchmarks im Computer Language Benchmarks Game anschaut, dann spielt Pascal nur selten in der gleichen Liga wie Java.

    Wenn man noch genauer hinguckt, sieht das so aus, als läge das eher da dran, dass da niemand vernünftige Programme für Pascal eingestellt hat. Die meisten sind ja nicht einmal parallelisiert. Es gibt doch keinen Grund gegen Parallelisierung in Pascal, oder? Das wird also vermutlich eher an der sehr geringen Zahl von talentierten Pascalprogrammierern liegen, die sich die Mühe geben, für irgendeine Internetseite hochoptimierte Programme zu schreiben. Bei Java dürften es hingegen sehr viele sein. Bei den trivialen Aufgabenstellungen, bei denen eine (nahezu) optimale Lösung recht direkt zu finden ist, sieht man hingegen, dass Pascal hier ungefähr in der gleichen Liga wie Java und C# spielt.

    Dabei zähle ich Laufzeitunterschiede von einem Faktor 2-3 als "ungefähr in der gleichen Liga". Wenn man mal mit den anderen Sprachen vergleicht, sind die Pascalprogramme (wie Java & Co auch), trotz fehlender Optimierung, allesamt noch im Bereich 5-30 Sekunden, während Scriptsprachen wie Python bei den gleichen Problemen eher bei 30 Minuten(!) landen. C, C++ und Fortran sind dabei zwar auch nur jeweils ungefähr 2x schneller als Java, dafür aber konsequent immer unter den ersten Plätzen. Das ist dann auch noch eine gesonderte Kategorie wert.



  • Ich habs mir schon sehr lang nicht mehr angeschaut, aber ich geh davon aus, dass "Delphi" zumindest mit Java auf jeden Fall mithalten können sollte. Als ich das früher programmiert habe (was im Grafikbereich), habe ich teilweise auch auf die Performance geachtet, und es war immer schnell genug oder man konnte was machen. Und man konnte auf jeden Fall einfach Inline Assembler verwenden, hab ich auch ab und zu gemacht. Das geht in Java nicht so einfach 😉



  • Vorab: mein Post bezieht sich auf Delphi, weil das unter den noch lebenden Pascal-Dialekten der relevanteste ist. Außerdem sei angemerkt, daß es mir nicht um die IDE, die Libraries, das Geschäftsmodell des Anbieters oder andere Faktoren geht, die bei einer Entscheidung sicher in Erwägung gezogen werden sollten. Es wurde nur nach der Sprache gefragt.

    PPascal schrieb:

    Welche Vor- und Nachteile hat die Sprache Pascal/ObjektPascal?

    Delphi ist eine recht alte Sprache und wurde in den letzten Jahren nur zögerlich und leider nicht besonders konsequent modernisiert. Man stößt also immer mal wieder auf inkonsistente Konzepte, halbgare .NET-inspirierte Sprachfeatures und leider auch Compilerfehler. Für die meisten praktischen Belange ist C# wahrscheinlich heute die bessere Wahl.

    Das heißt aber nicht, daß Delphi gegenüber anderen Sprachen keine Vorteile hätte. (Wer sowas behauptet, findet wahrscheinlich auch, daß Java eine tolle Programmiersprache ist und die JVM überhaupt keine Sicherheitsprobleme hat.) Es gibt eine ganze Reihe an Kleinigkeiten, die in C++ fehlen oder kaputt sind und die Delphi schon längst elegant und ordentlich gelöst hatte, als C# erfunden wurde. Und Delphi kann ein paar Dinge, die es bei der Konkurrenz auch heute noch nicht gibt.

    Die Aufteilung von Units in interface - und implementation -Abschnitt ist ähnlich zu den Headerdateien in C++, mit dem Unterschied, daß Delphi ein ordentliches Modulsystem hat und deshalb z.B. bereits der Compiler darauf hinweisen kann, wenn eine Funktionsimplementierung fehlt und nicht erst der Linker.

    Delphi hat ein natürliches Verhältnis zu Strings, die als built-in-Datentyp unterstützt werden. Es gibt also keine Probleme mit dem Verketten von Stringliteralen mit +, mit der Speicherverwaltung o.ä.:

    const
      Message = 'Out of memory';
      ErrStr = ' Error: ' + Message + '. ';
    

    Strings sind referenzgezählt und copy-on-write.

    Delphi hat keinen Garbage-Collector. Die Lebenszeit von Klassen wird vom Benutzer bestimmt. Es wird unterschieden zwischen class (Referenzsemantik) und record (Wertsemantik), analog zu class / struct in C#.

    Was generische Typen, Delegates, Klassen, Interfaces, Vererbung, Properties und Events, RTTI, Attribute, und dynamische Casts angeht, ist Delphi sehr ähnlich zu C#. Ein paar Unterschiede im Komfort gibt es aus historischen Gründen: Events können nur einen Handler haben, es wird zwischen Delegate-Typen und gebundenen Methodenzeigern unterschieden, Property-Accessors sind manchmal etwas umständlich, as hat eine andere Semantik etc.

    RAII-Konstrukte sind nicht so üblich wie in C++, stattdessen wird meist try/finally verwendet. Es ist aber, anders als in C#, durchaus möglich, Smartpointer analog zu unique_ptr<> oder shared_ptr<> zu definieren.

    Anstelle allgemeiner integraler Datentypen können sehr leicht "sprechende" Typen verwendet werden, die etwas über die zulässigen Werte aussagen:

    type
      TDateRec = record
        Year: Integer;
        Month: (Jan, Feb, Mar, Apr, May, Jun,
                Jul, Aug, Sep, Oct, Nov, Dec);
        Day: 1..31;
      end;
    

    Delphi unterstützt offene Arrays für Funktionen mit variabler Argumentzahl (analog zu initializer lists in C++):

    function Max(Values: array of Double): Double;
    var
      Value: Double;
    begin
      Result := Math.MinDouble;
      for Value in Values do
        Result := Math.Max(Value, Result);
    end;
    
    constructor TForm1.FormResize(Sender: TObject);
    begin
      ListBox.Width := Max([0, ListBox.Width, Width - 12]);
    end;
    

    Überhaupt ist die Array-Syntax in Delphi sehr viel natürlicher als in C++. Es ist außerdem möglich, Arrays mit anderen ganzzahligen Typen wie z.B. Enums zu indizieren:

    type
      TCursor = (Normal, HourGlass, Hand);
    
    const
      CursorToWin32CursorId: array[TCursor] of Cardinal = (
        IDC_ARROW, IDC_WAIT, IDC_HAND
      );
    
    procedure TMyComponent.SetCursor(NewCursor: TCursor);
    begin
      Windows.SetCursor(Windows.LoadCursor(HInstance,
        CursorToWin32CursorId[NewCursor]));
    end;
    

    Konstante Records können mit Feldnamen initialisiert werden:

    type
      TShopItem = record
        Name: String;
        Price: Currency;
      end;
    
    const
      Items: array of TShopItem = (
        (Name: 'Clock'; Price: 20.99),
        (Name: 'Pencil'; Price: 15.75),
        (Name: 'Board'; Price: 42.96)
      );
    

    Auch hier zu sehen: der Currency -Datentyp, ähnlich zu decimal in C#, für die Verwaltung von fixed-point-Zahlen wie den namensgebenden Geldbeträgen.

    Sehr elegant ist das Verhältnis von Enums und Bitsets gelöst:

    type
      TVegetable = (Tomato, Aubergine, Pepper, Potato, Parsnip, Onion, Garlic);
      TVegetables = set of TVegetable;
    
    const
      Ratatouille = [Tomato..Pepper, Onion, Garlic];
    
    function GoingOutTonight: Boolean; forward;
    
    procedure PrepareMeal(Inventory: TVegetables);
    var
      Ingredients: TVegetables;
    begin
      Ingredients := Inventory * Ratatouille; { Machen wir doch einfach Ratatouille mit dem, was noch da ist. }
    
      if Garlic in Ingredients and GoingOutTonight then
        raise Exception.Create('not recommended');
      ...
    end;
    

    Das geht auch mit anderen integralen Typen:

    const
      Numeric = ['0'..'9'];
      Alpha = ['A'..'Z', 'a'..'z'];
      AlphaNum = Alpha + Numeric;
    
    function IsAlNum(const C: AnsiChar): Boolean;
    begin
      Result := C in AlphaNum;
    end;
    

    Die Mengensemantik (Elementabfrage mit in , Vereinigung mit +, Differenz mit -, Schnittmenge mit 😉 ist eigentlich nur für Mengen über integrale Typen gedacht. Aber man kann sich ohne weiteres einen Typen namens THashSet<T> definieren und die entsprechenden Operatoren überladen.

    Durch eine geniale Designentscheidung ist die naive Implementierung von Konstruktoren bereits exceptionsicher:

    type
      TLogger = class
      private
        FMessageLog, FErrorLog: TStream;
      public
        constructor Create(AMessageLogFile, AErrorLogFile: String);
        destructor Destroy;
        ...
      end;
    
    constructor TLogger.Create(AMessageLogFile, AErrorLogFile: String);
    begin
      FMessageLog := TFileStream.Create(AMessageLogFile, fmCreate);
      FErrorLog := TFileStream.Create(AErrorLogFile, fmCreate);
    end;
    
    destructor TLogger.Destroy;
    begin
      FMessageLog.Free;
      FErrorLog.Free;
    end;
    

    Man vergleiche das mit dem folgenden, identisch aussehenden C#-Code:

    class Logger : IDisposable
    {
        private Stream errorLog, messageLog;
    
        public Logger(String messageLogFile, String errorLogFile)
        {
            this.messageLog = File.Create(messageLogFile);
            this.errorLog = File.Create(errorLogFile);
        }
    
        public override void Dispose()
        {
            errorLog.Dispose();
            messageLog.Dispose();
        }
    
        ...
    }
    

    Der Delphi-Code ist exceptionsicher, der C#-Code nicht. In C# muß ich sowas hier machen:

    public Logger(String messageLogFile, String errorLogFile)
        {
            bool success = false;
            try
            {
                this.messageLog = File.Create(messageLogFile);
                this.errorLog = File.Create(errorLogFile);
                success = true;
            }
            finally
            {
                if (!success)
                {
                    if (this.messageLog != null) this.messageLog.Dispose();
                    if (this.errorLog != null) this.errorLog.Dispose();
                }
            }
    
        }
    

    Der Unterschied liegt darin, daß Delphi automatisch den Destruktor aufruft, wenn im Konstruktor eine Exception auftritt. Weil Klasseninstanzen mit 0 initialisiert werden und Obj.Free; implizit einen Null-Check vornimmt, passiert automatisch das Richtige.

    Für Format-Strings und ähnliche Anwendungen gibt es außerdem dynamisch typisierte offene Arrays, die natürlich leicht an andere Funktionen weitergegeben werden können:

    type
      TLogger = class
        ...
        procedure LogError(const AMessage: String);
        procedure LogErrorFmt(const AFmtMessage: String; const AArgs: array of const);
      end;
    
    procedure TLogger.LogErrorFmt(const AFmtMessage: String; const AArgs: array of const);
    begin
      LogError(SysUtils.Format(AFmtMessage, AArgs));
    end;
    

    Die Verwendung geht analog zu typisierten offenen Arrays:

    resourcestring
      SInvalidDayOfWeek = 'The week has only 7 days, so ''%d'' is not a valid day';
    
    procedure LogInvalidDayOfWeekError(Logger: TLogger, Day: Integer);
    begin
      Logger.LogErrorFmt(SInvalidDayOfWeek, [Day]);
    end;
    

    Für Stringliterale, die in die jeweiligen Landessprachen übersetzt werden sollen, gibt es das hier benutzte resourcestring . Resourcestrings funktionieren so ähnlich wie ein implizites gettext() , allerdings vermeiden sie das offensichtliche Problem, daß gettext() nur ein Stringliteral und keinen Kontext sieht, so daß gettext("File") je nach Tagesform der Implementierung entweder "Datei" oder "Abheften" ergeben kann.

    Was es in keiner vergleichbaren Sprache gibt, sind Metaklassen:

    type
      TNumArray = class abstract
        ...
      protected
        class function GetNativeFormatFileExt: String; virtual; abstract;
      public
        class property NativeFormatFileExt: String read GetNativeFormatFileExt; { Dateinamenserweiterung des nativen binären Formats }
        class constructor Create(AShape: array of Cardinal); virtual; abstract;
        class constructor CreateFromFile(AFileName: String); virtual; abstract;
        procedure SaveToFile(AFileName: String); virtual; abstract;
        property Shape[DimIndex: Cardinal]: Cardinal read GetShape;
        ...
      end;
      TNumArrayClass = class of TNumArray; { Metaklassentyp für TNumArray }
    

    Hier wurde eine abstrakte Klasse TNumArray definiert. Diese hat virtuelle Konstruktoren und Klassenmethoden und -eigenschaften, die von abgeleiteten Klassen implementiert werden:

    type
      TNumPyArray = class(TNumArray)
      protected
        class function GetNativeFormatFileExt: String; override; { gibt '.npy' zurück }
      public
        class constructor Create(AShape: array of Cardinal); override; { erzeugt ein NumPy-Array, das an Python-Code übergeben werden kann }
        class constructor CreateFromFile(AFileName: String); override; { lädt ein NumPy-Array aus einer .npy-Datei }
        procedure SaveToFile(AFileName: String); override; { speichert ein NumPy-Array in eine .npy-Datei }
      end;
    
      TMATLABArray = class(TNumArray)
      protected
        class function GetNativeFormatFileExt: String; override; { gibt '.mat' zurück }
      public
        class constructor Create(AShape: array of Cardinal); override; { erzeugt ein MATLAB-Array, das an MATLAB-Routinen übergeben werden kann }
        class constructor CreateFromFile(AFileName: String); override; { lädt ein MATLAB-Array aus einer .mat-Datei }
        procedure SaveToFile(AFileName: String); override; { speichert ein MATLAB-Array in eine .mat-Datei }
      end;
    

    So wie es Klasseninstanzen gibt, über die man Instanzmethoden aufrufen und auf Instanzeigenschaften zugreifen kann, gibt es nun Klassenreferenzen (= Instanzen der Metaklasse), über die Klassenmethoden und die virtuellen Konstruktoren aufgerufen werden können:

    function FrobnicateArrays(A, B: TNumArray; ArrayClass: TNumArrayClass): TNumArray;
    begin
      Result := ArrayClass.Create([A.Shape[0], A.Shape[1], B.Shape[1]]);
      ... { in-place frobnizieren }
    end;
    
    function LoadAndTransmogrifyArray(Filename: String; ArrayClass: TNumArrayClass): TNumArray;
    begin
      Result := ArrayClass.CreateFromFile(Filename);
      ... { in-place transmogrifizieren }
    end;
    
    MyArray := LoadAndTransmogrifyArray('myfile.npy', TNumPyArray); { eine Klasse ist eine Instanz einer Metaklasse }
    

    Im vorhergehenden Beispiel ebenfalls zu sehen ist die Annotation von überschriebenen virtuellen Methoden mit override . Wenn man stattdessen eine Methode verstecken will, benutzt man reintroduce . Wenn man keines von beiden benutzt, gibt es eine Warnung:

    type
      TBase = class
        procedure Foo;
        procedure Bar;
      end;
      TDerived = class;
        procedure Foo; { Warnung vor versteckter Methode in Basisklasse }
        procedure Bar; reintroduce; { Warnung wird unterdrückt }
      end;
    

    Ebenso müssen Überladungen ausdrücklich gekennzeichnet werden:

    function ToString(Arg: Integer); overload;
    function ToString(Arg: Double); overload;
    

    Delphi unterstützt benannte Konstruktoren. Der Nutzen sollte für den C++-Programmierer offensichtlich sein:

    type
      TVector<T> = class
        constructor CreateWithSize(ASize: Integer);
        constructor CreateCopy(ARhs: TVector<T>);
        constructor CreateFrom(AItems: array of T);
        ...
      end;
    ...  
    Result := TVector<Double>.CreateFrom([42, 13.37]);
    

    In C++ wünsche ich mir das jedes Mal, wenn ich STL-Klassen mit nichttrivialen Konstruktoren konstruieren muß.

    So, mehr fällt mir im Moment nicht ein.

    Edit: Kleinigkeiten

    Edit 2: Doch, eine Sache noch. Und zwar hat Delphi hervorragende Sprachunterstützung für COM. (Den gleichfalls herausragenden IDE-Support für RIDL [= Reduced IDL] und Typbibliotheken lasse ich mal außen vor.) Wieder mit gewissen Ähnlichkeiten zu C#, aber mit dem distinkten Vorteil, daß man sich nicht mit den Seiteneffekten beim Kombinieren zwei verschiedener lifetime management-Modelle (GC und Referenzzählung) herumschlagen muß. Delphi nimmt einem das Exception-Marshaling mit HRESULT und SetErrorInfo() ab, bildet BSTR auf einen vernünftigen Werttyp ab und hat vor allem native Unterstützung für COM-Interfaces.

    In C++ ist der Umgang mit COM ja relativ umständlich:

    class __declspec(uuid("{5580D8FE-4550-46E4-AEF0-855848360D7A}")) IFoo : public IUnknown
    {
    public:
        HRESULT __stdcall Bar(BSTR arg, BSTR* result);
    };
        // InterfacedObject<> ist eine Basisklasse, die QueryInterface(), AddRef() und Release() implementiert
    class Foo : public InterfacedObject<IFoo>
    {
        ...
    public:
        HRESULT __stdcall Bar(BSTR arg, BSTR* result)
        {
            try
            {
                ...
                *result = SysAllocString(...);
                if (*result == nullptr)
                    return E_OUTOFMEMORY;
                return S_OK;
            }
            catch (const std::exception& e)
            {
                    // setExceptionInfo() ist eine Helferfunktion, die SetErrorInfo() aus oleaut32.dll mit der
                    // Exception-Meldung aufruft und ein HRESULT zurückgibt
                return setExceptionInfo(e);
            }
            catch (...)
            {
                return E_UNEXPECTED; 
            }
        }
    };
    
    std::wstring tryDoBarWithFoo(IUnknown* intf, const std::wstring s)
    {
            // COMSmartPointer<> ist ein COM-Smartpointer
        COMSmartPointer<IFoo> foo;
        if (intf->QueryInterface(__uuidof(IFoo), (void**) foo.getRef()) == S_OK)
        {
            BSTR arg = SysAllocString(s.empty() ? nullptr : s.c_str());
            if (arg == nullptr && !s.empty())
                throw std::bad_alloc();
            BSTR result;
            HRESULT hr = foo->Bar(arg, &result);
            SysFreeString(arg);
    
                // checkCOMResult() ist eine Helferfunktion, die im Fehlerfall mit GetErrorInfo() aus
                // oleaut32.dll die Fehlermeldung ausliest und eine Exception wirft
            checkCOMResult(hr);
    
            try
            {
                std::wstring resultStr(result ? result : L"");
                SysFreeString(result);
            }
            catch (...)
            {
                SysFreeString(result);
                throw;
            }
        }
        else
            return L"";
    }
    

    Delphi übernimmt die ganze Arbeit für uns:

    type
      IFoo = interface
      ['{5580D8FE-4550-46E4-AEF0-855848360D7A}']
        function Bar(Arg: WideString): WideString; safecall;
      end;
    
    type
      TFoo = class(TInterfacedObject, IFoo)
        function Bar(Arg: WideString): WideString; safecall;
      end;
    
    function TFoo.Bar(Arg: WideString): WideString; safecall;
    begin
      Result := ...;
    end;
    
    function TryDoBarWithFoo(const Intf: IInterface; const S: String): String;
    var
      Foo: IFoo;
    begin
      if Supports(Intf, IFoo, Foo) then
        Result := Foo.Bar(S);
    end;
    

    Außerdem gibt es explizite Unterstützung für Interface-Delegation (mit implements und TAggregatedObject ) und für das Auflösen von Namenskonflikten:

    type
      TNestedFoo = class(TAggregatedObject, IFoo) { wir leiten von TAggregatedObject ab, damit QueryInterface() richtig implementiert wird }
        function MyBar(Arg: WideString): WideString; safecall;
        function IFoo.Bar = MyBar; { wir sind nicht an die Namensgebung in IFoo gebunden }
      end;
      TAnotherFoo = class(TInterfacedObject, IFoo)
      private
        FFoo: TNestedFoo;
      public
        property RealFoo: TNestedFoo read FFoo implements IFoo; { dieses Objekt implementiert IFoo für uns }
      end;
    

    Es gibt den Variant -Typ, der u.a. late binding zu COM-Komponenten ermöglicht, ähnlich wie dynamic in C#:

    procedure TForm1.Button1Click(Sender: TObject);
    var
      Doc, Word: Variant;
    begin
      Word := CreateOleObject('Word.Application');
      Doc := Word.Documents.Add();
      Doc.Content.InsertAfter('This is some sample Text');
      Word.ActiveWindow.Visible := True;
    end;
    

    (Codebeispiel hier geklaut; praktischer Hinweis: für die Office-Automation bitte die early binding-Interfaces benutzen).
    Die late binding-Schnittstelle von Variant ist dabei nicht an COM gebunden und kann ebensogut für eigene Zwecke implementiert werden (cf. http://alex.ciobanu.org/?p=152).



  • @audacia: sehr gute Antwort! Ist mir jetzt auch selber so ein bisschen wieder eingefallen, warum ich früher gern Delphi programmiert habe 😉



  • FreePascal ist ja weiter in der aktiven Entwicklung, jedenfalls sind die letzten Builds von 2016. Kann Delphi was Entscheidenes was FreePascal + Lazerus nicht kann?



  • PPascal schrieb:

    Kann Delphi was Entscheidenes was FreePascal + Lazerus nicht kann?

    http://wiki.freepascal.org/delphi_language_features_which_fpc_does_not_have



  • Ok, scheint ja nicht viel zu sein. Die Liste ist auch nicht wirklich aktuell, da mittlerweile schon FreePascal 3.0.0 draußen ist und dort geht es nur bis 2.7



  • hier zu FreePascal 3.0, die 3.1 ist in der Mache. Das wird alles sehr aktiv weiterentwickelt.

    The language syntax has excellent compatibility with TP 7.0 as well as with most versions of Delphi (classes, rtti, exceptions, ansistrings, widestrings, interfaces). A Mac Pascal mode, largely compatible with Think Pascal and MetroWerks Pascal, is also available. Furthermore Free Pascal supports function overloading, operator overloading, global properties and several other extra features.



  • Wenn du weitere Details zu Delphi und FPC haben möchstest, kannst du mal explizit in einem Delphi-Forum, z.B. Entwickler-Ecke/Delphi-Forum nachfragen.


Anmelden zum Antworten