Erzeugung einer Graphikdatei mit C++
-
Wenn man das so pedantisch sieht hast du natürlich recht, aber bevor ich eine weitere Abhängigkeit in meine Projekte einführe benutze ich doch lieber das, was die Compilersuite von sich aus mitbringt.
Leider kann man nichts, was TObject als Basisklasse hat, auf dem Stack erzeugen, und leider gehört TBitmap auch zu den TObject Derivaten. Ist ein "Feature" der VCL, das ich über alles schätze... naja, immerhin hat mich dieser Sachverhalt auf boost aufmerksam gemacht.
-
Hallo DocShoe,
das sieht sehr nach etwas aus, das mir hilft. Dankeschoen!
Leider hab ich bisher kaum mit Bibliotheken und Templates gearbeitet und bin mir deshalb nicht ganz sicher, was dein Code-Vorschlag tut.
Ich hab mal versucht, in einzubauen, und ihn leicht veraendert.
Matrix ist die Klasse, in der die Bitmap-Erzeugungsroutine liegen soll, cols und rows sind Spalten und Zeilen der Matrix.
Die Pixel sollen dann entsprechend den Matrixeintraegen weiss oder schwarz gefaerbt werden.void Matrix::plot() const { boost::shared_ptr<Graphics::TBitmap> Bitmap(new Graphics::TBitmap); Bitmap->PixelFormat = pf32bit; Bitmap->Width = cols; Bitmap->Height = rows; for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { if (pv[i]->get(j)==0) { Bitmap->Canvas->Pixels[i][j] = clWhite; } else Bitmap->Canvas->Pixels[i][j] = clBlack; } } Bitmap->SaveToFile( "repgraph.bmp" ); }
Nun meckert der Compiler allerdings rum, er kenne den Qualifier 'Graphics' nicht. Wahrscheinlich muss ich noch irgendwas includen - aber was genau?
-
DocShoe schrieb:
Wenn man das so pedantisch sieht hast du natürlich recht, aber bevor ich eine weitere Abhängigkeit in meine Projekte einführe benutze ich doch lieber das, was die Compilersuite von sich aus mitbringt.
Wer sagt dir, dass er die VCL bereits verwendet? Nur weil man den Code Gear Kompiler verwendet, muss man doch nicht automatisch auch die VCL verwenden. Aber wenn er diese bereits verwendet, dann hast du natürlich schon recht, dass eine zusätzliche Abhängigkeit nicht unbedingt sinnvoll wäre. Kann TBitmap auch PNGs speichern oder sonst ein etwas besser komprimiertes Format als bmp?
Grüssli
-
Stellt sich die Frage, ob die Komprimierung für ihn wichtig ist...
Falls sie es nicht ist, hat er mit TBitmap eine sehr angenehme und leicht zu verwendende Klasse, die meines Wissens aber nur .bmp speichert.
Falls seine Bilder sehr groß sind, ist er mit libpng wohl besser beraten.
-
Soweit ich weiss kann TBitmap nur das Bitmap Format schreiben, aber es gibt noch eine Klasse TJPEGImage, die (oh Wunder!) Grafiken im JPG Format speichert.
@Daramos
Du musst den Header <graphics.hpp> und <boost/smart_ptr.hpp> inkludieren.PS:
Wenn du das ohne boost machen willst musst du dich selbst um das Zerstören der Bitmap kümmern:#include <vcl.h> #include <graphics.hpp> Graphics::Bitmap Bmp = new Graphics::TBitmap(); // Code wie oben delete Bmp;
-
DocShoe schrieb:
boost::shared_ptr<Graphics::TBitmap> Bitmap( new Graphics::TBitmap() ); Bitmap->PixelFormat = pf32bit; Bitmap->Width = Width; Bitmap->Height = Height; if( Bitmap->Canvas->Pixels[0][0] == clBlack ) { Bitmap->Canvas->Pixels[0][0] = clWhite; } Bitmap->SaveToFile( "c:/bitmap.bmp" );
Das ist wieder ein schönes Beispiel dafür, wie man
shared_ptr
nicht einsetzen sollte. Es gibt auch nochscoped_ptr
, wenn kein geteilter Besitz nötig ist. Der ist hier viel sinnvoller. Selbststd::auto_ptr
wäre noch geeigneter.
-
Nexus schrieb:
Das ist wieder ein schönes Beispiel dafür, wie man
shared_ptr
nicht einsetzen sollte. Es gibt auch nochscoped_ptr
, wenn kein geteilter Besitz nötig ist. Der ist hier viel sinnvoller. Selbststd::auto_ptr
wäre noch geeigneter.Genau! Bis man sich irgendwann entschliesst (bzw. die Notwendigkeit eintritt), die Grafik doch irgendwie anzeigen zu wollen oder irgendwie anders weiter benutzt werden soll. Und baut dann von scoped_ptr/auto_ptr doch auf smart_ptr um. So ganz ohne Kontext kann man an fast jedem Code irgendwas aussetzen... weisst du, ob der vollständige Code nicht aus mehreren Funktionen besteht, von denen eine die Grafik erzeugt und eine andere die Grafik speichert? Und eine dritte vielleicht sogar die Grafik ausdruckt? Oder es mehrere Matrizen gibt, zu der jeweils eine Grafik in einer std::map abgelegt wird?
Also manchmal glaube ich wirklich, dass hier nur gepostet wird, um irgendein Posting wegen einer Spitzfindigkeit anzuprangern...
-
Dieser Thread wurde von Moderator/in evilissimo aus dem Forum C++ in das Forum VCL (C++ Builder) verschoben.
Im Zweifelsfall bitte auch folgende Hinweise beachten:
C/C++ Forum :: FAQ - Sonstiges :: Wohin mit meiner Frage?Dieses Posting wurde automatisch erzeugt.
-
DocShoe schrieb:
Also manchmal glaube ich wirklich, dass hier nur gepostet wird, um irgendein Posting wegen einer Spitzfindigkeit anzuprangern...
Nur nicht gleich so empfindlich reagieren.
Ich wollte dich nur darauf hinweisen, dass
shared_ptr
oftmals nicht angebracht ist, auch wenn er "praktisch" zu sein scheint (im Sinne von, man muss nichts überlegen und bei möglichen Änderungen nichts umstellen). Für die Übergabe an weitere Funktionen braucht es keinenshared_ptr
, da reichen Referenzen. Im Übrigen kann es gut sein, dass man mitscoped_ptr
Fehler vermeidet, weil man den Zeiger nicht aus Versehen kopiert. Als klassischer RAII-Smartpointer ist er sicher zu bevorzugen.Wenn du dir dieser Thematik bewusst bist, okay. Aber es gibt eben einige Programmierer, die grundsätzlich einfach mal
shared_ptr
einsetzen, ohne dass sie ihn überhaupt wirklich brauchen. Ein weiteres Beispiel dafür sind Smart-Pointers in Containern, wenn Boosts Pointer-Container sich viel besser eignen würden.Und bevor sich Daramos so eine schlechte Technik noch abschaut, möchte ich das lieber erwähnen. Wie gesagt: Es war als konstruktive Kritik gedacht, nicht als Haarspalterei.
-
Nexus schrieb:
Und bevor sich Daramos so eine schlechte Technik noch abschaut, möchte ich das lieber erwähnen.
So eideutige Feindbilder möchte ich mal haben.
Ein smart_ptr<> ist nicht "so eine schlechte Technik", sondern lediglich gegenüber einem Scoped-Pointer ein kleiner Overhead, der angesichts des Overheads der einhergehenden Allokation eines Bitmaps vollkommen irrelevant ist.
Von allen Argumenten für Garbage Collection, die ich bisher gelesen habe, ist dieses Smart-Pointer-/Pointer-Container-/Move-Semantik-Chaos bei weitem das eindrücklichste
-
audacia schrieb:
Ein smart_ptr<> ist nicht "so eine schlechte Technik", sondern lediglich gegenüber einem Scoped-Pointer ein kleiner Overhead, der angesichts des Overheads der einhergehenden Allokation eines Bitmaps vollkommen irrelevant ist.
Hier vielleicht. Aber wie soll man in zeitkritischen Anwendungen auf einmal plötzlich das Richtige auswählen, wenn man es vorher nie gemacht hat? Abgesehen davon halte ich es nicht für verkehrt, auch bei kleineren Dingen das richtige Sprach- oder Bibliotheksmittel zu wählen. "Schlechte Technik" sagte ich im Hinblick auf ein ständiges Verwenden von
shared_ptr
als GC-Ersatz (das meine ich im Allgemeinen, nicht auf DocShoe bezogen).audacia schrieb:
Von allen Argumenten für Garbage Collection, die ich bisher gelesen habe, ist dieses Smart-Pointer-/Pointer-Container-/Move-Semantik-Chaos bei weitem das eindrücklichste
Ja, sich Besitzverhältnisse genau zu überlegen scheint auch für viele Leute eine ziemliche Herausforderung zu sein. Aber wenn man wenig denken will, nimmt man sowieso nicht C++.
-
Nexus schrieb:
in zeitkritischen Anwendungen
... benutzt man einen Profiler, um Bottlenecks zu finden. Wenn shared_ptr<> tatsächlich eines sein sollte, dann ist der Programmierer nur zu beglückwünschen - dann hat er auch die Kenntnisse, daraus einen scoped_ptr<> zu machen. Ein Anfänger hat in performancekritischen Anwendungen eher ganz andere Probleme.
Nexus schrieb:
audacia schrieb:
Von allen Argumenten für Garbage Collection, die ich bisher gelesen habe, ist dieses Smart-Pointer-/Pointer-Container-/Move-Semantik-Chaos bei weitem das eindrücklichste
Ja, sich Besitzverhältnisse genau zu überlegen scheint auch für viele Leute eine ziemliche Herausforderung zu sein. Aber wenn man wenig denken will, nimmt man sowieso nicht C++.
Was hat Garbage Collection jetzt mit "wenig denken" oder mit dem Bewußtmachen der Besitzverhältnisse zu tun?
-
audacia schrieb:
benutzt man einen Profiler, um Bottlenecks zu finden. Wenn shared_ptr<> tatsächlich eines sein sollte, dann ist der Programmierer nur zu beglückwünschen - dann hat er auch die Kenntnisse, daraus einen scoped_ptr<> zu machen.
Warum zuerst unnötigerweise
shared_ptr
einsetzen, Zeit messen und dann aufscoped_ptr
zurückgreifen? Wenn man die Funktionalität von geteiltem Besitz nicht benötigt, kann man gleichscoped_ptr
nehmen – und zwar völlig unabhängig von irgendwelchen Performance-Überlegungen. Das richtige Mittel auszuwählen hat doch nichts mit Premature Optimization zu tun. Solange man mitscoped_ptr
keine Nachteile hat, spricht auch nichts gegen ihn. Und dass man möglicherweise später einmal doch geteilten Besitz braucht, sehe ich nicht als Anlass, von Beginn anshared_ptr
einzusetzen. Mittypedef
ist eine Umstellung problemlos möglich, zumal die Smart-Pointers von Boost und der Standardbibliothek die gleiche Schnittstelle aufweisen.audacia schrieb:
Was hat Garbage Collection jetzt mit "wenig denken" oder mit dem Bewußtmachen der Besitzverhältnisse zu tun?
Der C++-Programmierer muss sich Gedanken darüber machen, wer ein Objekt besitzt und für dessen Freigabe verantwortlich ist (
shared_ptr
ist ja nicht der Normalfall). Es gibt besitzende und nur referenzierende Zeiger. Diese Unterscheidung existiert in Sprachen mit Garbage Collector nicht. Der GC kann ein Objekt aufräumen, sobald keine Referenz mehr darauf verweist. Das heisst zwar nicht, dass Zugehörigkeiten keine Rolle spielen, aber Shared-Ownership ist von der Speicherverwaltung her Standard.In C++ ist die Situation allerdings etwas komplizierter (du bezeichnest sie sogar als Chaos), man muss entsprechend etwas mehr überlegen und Fälle unterscheiden. Aber das trifft nicht nur auf Speicherverwaltung zu. Entweder man befasst sich mit der Materie und sieht die unterschiedlichen Smart-Pointer-Semantiken auch nicht als Problem an, oder man will sich das nicht antun und benutzt eine andere Sprache.
-
Nexus schrieb:
Warum zuerst unnötigerweise
shared_ptr
einsetzenWohlgemerkt fordere ich nicht, daß man es stets so machen solle. So ganz ohne Grund existieren auto_ptr<> und scoped_ptr<> ja nicht. Ich weise lediglich darauf hin, daß es nicht dramatisch, insbesondere in keiner Weise "so eine schlechte Technik" ist, wenn man das auf einen shared_ptr<> generalisiert.
Nexus schrieb:
In C++ ist die Situation allerdings etwas komplizierter (du bezeichnest sie sogar als Chaos)
Sag bloß, du empfindest rvalue-Referenzen als Bereicherung
Allein die Tatsache, daß man ein derart arrangiertes Konstrukt einführt - ich glaube, keine andere Mainstream-Sprache hat etwas vergleichbar Abgedrehtes -, deutet darauf hin, daß irgendwo etwas ganz gravierend schiefgelaufen ist. Aber das Thema hatten wir schonmal, das brauchen wir jetzt nicht auch noch ausführen.
Nochmal zu
Nexus schrieb:
Ja, sich Besitzverhältnisse genau zu überlegen scheint auch für viele Leute eine ziemliche Herausforderung zu sein. Aber wenn man wenig denken will, nimmt man sowieso nicht C++.
- ich sehe den Zusammenhang zum GC nicht. Ein GC nimmt dir nicht ab, über Besitzverhältnisse nachzudenken. Er verwaltet nur eine einzige Ressource, und das ist Speicher. Da es zufälligerweise die am häufigsten verwandte Ressource ist, nimmt er dir enorm viel Arbeit, Boilerplate-Code und die Konstruktion etwas herbeigeholter Besitztumsketten ab. Netter Nebeneffekt ist natürlich, daß der DAU keine Speicherlecks mehr produziert. Aber das wird natürlich nicht verhindern, daß er vergißt, das Datei-Handle, die Socket-Verbindung oder den Gerätekontext wieder freizugeben.
Wenn man wenig denken will, benutzt man nicht einen GC, sondern irgendeine Sandkastensprache.
Nexus schrieb:
Entweder man befasst sich mit der Materie und sieht die unterschiedlichen Smart-Pointer-Semantiken auch nicht als Problem an, oder man will sich das nicht antun und benutzt eine andere Sprache.
Wenn es nur immer so einfach wäre.
Im übrigen ist es, glaube ich, an der Zeit, den Thread zweizuteilen. Es wäre sehr nett, wenn einer der Moderatoren behilflich sein könnte
-
audacia schrieb:
Ich weise lediglich darauf hin, daß es nicht dramatisch, insbesondere in keiner Weise "so eine schlechte Technik" ist, wenn man das auf einen shared_ptr<> generalisiert.
Ich bin eben relativ skeptisch, was solche Generalisierungen anbelangt. Denn sie haben die Tendenz, dass man Dinge nicht mehr hinterfragt und selbstverständlich anwendet, ohne sich der Nachteile bewusst zu sein. Natürlich muss es nicht immer tragisch enden, man sollte die Möglichkeiten einfach massvoll einsetzen.
audacia schrieb:
Sag bloß, du empfindest rvalue-Referenzen als Bereicherung
Für das momentane C++ sicher. Oder ist es nicht gut, wenn z.B. Funktionsrückgabewerte nicht mehr unnötig kopiert werden? Das ist bisher nur im Rahmen von (N)RVO möglich. Ob jetzt C++ bisher sonst einiges falsch gemacht hat, ist wieder eine andere Frage, welche nicht zuletzt auch sehr subjektiv ist.
audacia schrieb:
Allein die Tatsache, daß man ein derart arrangiertes Konstrukt einführt - ich glaube, keine andere Mainstream-Sprache hat etwas vergleichbar Abgedrehtes
Könnte aber auch daran liegen, dass fast alle anderen Mainstream-Sprachen einen GC haben und Referenzen beliebig herumschubsen können.
audacia schrieb:
ich sehe den Zusammenhang zum GC nicht. Ein GC nimmt dir nicht ab, über Besitzverhältnisse nachzudenken.
Nicht komplett, aber zumindest was den Speicherverwaltungsteil anbelangt. In C++ entscheiden sich die Besitzverhältnisse fast ausschliesslich dadurch, wem der Speicher zugeteilt wird. In GC-Sprachen gibts so eine Zuteilung nicht, Speicher ist überall. Zum Beispiel kannst du plötzlich entscheiden, dass eine Referenz nicht mehr einzigartig ist und ihr Objekt geteilt wird. Oder dass ein Objekt einfach so übertragen wird.
In C++ musst du im Voraus wissen, welche Besitzstrategie du verfolgen willst. Normalerweise kommt man mit automatischen Objekten schon recht weit. Wenn man dann aber plötzlich vorhat, dass Zugriff von mehreren Orten benötigt wird und nicht klar ist, wer als letzter zugreift, braucht man Shared-Semantik und muss den Code anpassen. Das hat auch Vorteile, zum Beispiel kann man eine Strategie erzwingen und sieht oft bereits am Code, wie die Besitzverhältnisse aussehen.
class MyClass { OtherClass o; // besitzend OtherClass& p; // nicht besitzend scoped_ptr<OtherClass> q; // besitzend, nicht kopierbar shared_ptr<OtherClass> r; // geteilter Besitz auto_ptr<OtherClass> s; // transferierter Besitz };
class MyClass { OtherClass o; // Man kann nicht direkt sagen, ob o zu diesem Objekt // gehört oder nur eine Referenz auf ein Objekt "ausserhalb" ist // (oft spielt es auch gar keine Rolle <- genau das meine ich) }
Okay, bei Zeigern ist es in C++ auch nicht auf Anhieb eindeutig. Wenn man konsequent Smart-Pointers oder andere kapselnde Klassen für besitzende Zeiger einsetzt, allerdings schon.
audacia schrieb:
Wenn es nur immer so einfach wäre.
Natürlich ist es das nicht. Aber wenn man bereits damit überfordert ist, den richtigen Smart-Pointer auszuwählen, dann kommt in C++ noch einiges auf einen zu. Gerade Smart-Pointer nehmen einem ja viel Arbeit ab, unter anderem den fehleranfälligen Boilerplatecode manueller Speicherverwaltung.
Ich hoffe, ich konnte meine Aussagen einigermassen verständlich machen.
-
Nexus schrieb:
audacia schrieb:
Allein die Tatsache, daß man ein derart arrangiertes Konstrukt einführt - ich glaube, keine andere Mainstream-Sprache hat etwas vergleichbar Abgedrehtes
Könnte aber auch daran liegen, dass fast alle anderen Mainstream-Sprachen einen GC haben und Referenzen beliebig herumschubsen können.
Delphi hat auch keinen GC und kommt prima ohne rvalue-Referenzen aus. Primär deshalb, weil Klassen dort absolut nichts mit Strukturen aka Records gemein haben und konsequenterweise auch nicht auf dem Stack landen.
Nexus schrieb:
// (oft spielt es auch gar keine Rolle <- genau das meine ich)
Das meine ich auch.
-
audacia schrieb:
Delphi hat auch keinen GC und kommt prima ohne rvalue-Referenzen aus. Primär deshalb, weil Klassen dort absolut nichts mit Strukturen aka Records gemein haben und konsequenterweise auch nicht auf dem Stack landen.
Wie löst man das Problem in Delphi? Wenn eine Funktion ein Objekt zurückgibt, muss das der Aufrufer manuell freigeben?
-
Üblicherweise try-finally. Bei Komponenten allerdings gibt es ein Besitztumsmodell (eine Komponente kann einen Owner haben, der für die Freigabe verantwortlich zeichnet), so daß die explizite Freigabe nicht notwendig ist. Bei der Konstruktion muß der Owner explizit übergeben werden, so daß klar ist, was passiert.
RAII ist auch möglich, aber ungebräuchlich.
-
Gut, try-finally ist aber auch nur eine moderne Art der manuellen Speicherverwaltung.
Kommt halt etwas auf den Programmierer an, ich persönlich schätze den Umgang von C++ mit automatischen Objekten und RAII sehr. Dass man den Stack benutzen darf, ist für mich auch kein Nachteil – man muss sich dann eben Sprachmittel wie RValue-Referenzen überlegen, wenn man Wertsemantik mit Movesemantik kombinieren möchte.
-
Nexus schrieb:
Kommt halt etwas auf den Programmierer an, ich persönlich schätze den Umgang von C++ mit automatischen Objekten und RAII sehr. Dass man den Stack benutzen darf, ist für mich auch kein Nachteil – man muss sich dann eben Sprachmittel wie RValue-Referenzen überlegen, wenn man Wertsemantik mit Movesemantik kombinieren möchte.
Die beinahe unbegrenzte Flexibilität von C++ ist ja durchaus bewundernswert und als intellektuelle Herausforderung sehr reizvoll. Aber erkläre mal dem Praktikanten, was rvalue-Referenzen sind.
Anders gesagt (und um einer Tirade von "Wenn man nicht denken will, nimmt man auch nicht C++" vorzubeugen), manchmal ist es sinnvoll, zugunsten von Einheitlichkeit und Systematik andere Einbußen in Kauf zu nehmen.
Noch ein schönes Beispiel dazu: in C++ wählt ja praktisch jeder Nomenklatur, Einrückstil und nach eigenem Gusto. Es gibt zwar mit der Standardbibliothek in gewisser Weise eine einheitliche Stilvorlage, aber die wird gemeinhin recht wenig befolgt. Die einen verwenden Java-Klassennamen und Allman-Einrückung (etwa ich
), andere verwenden ungarische Notation (oder etwas, das sie dafür halten), wieder andere befolgen fast die Java-Nomenklatur, beginnen aber Klassennamen mit C etc. pp. Das ist vermutlich auch der wesentliche Grund, weshalb es keinen wirklich zufriedenstellenden Formatierer für C++-Code gibt.
Dementgegen steht die außergewöhnliche Homogenität, die etwa in Delphi üblich ist. Typen werden mit T, Interfaces mit I, Felder mit F, Funktionsargumente mit A, Konstanten mit C und (Ressourcen-)Strings mit S präfiziert, Bezeichner grundsätzlich mit einem Großbuchstaben begonnen, Blöcke mit zwei Leerzeichen eingerückt, Methoden, Properties und Felder von private bis published sortiert und dergleichen mehr. (Hier ausführlich.) Man kann sicherlich über die Sinnhaftigkeit der einzelnen Forderungen streiten, aber das Wesentliche ist, daß sich einfach jeder daran hält. (Hier eine der wenigen Ausnahmen.) Dieser einfache Sachverhalt macht fremden Code so unglaublich viel lesbarer.
Nebenbei ist das vielleicht ein Anhaltspunkt, wie wenig die Leistungsfähigkeit von Sprachfeatures tatsächlich Einfluß auf die relevanten Argumente für und wider eine Sprache hat (wobei es keineswegs so ist, daß Delphi hier im Hintertreffen wäre.
). Man betrachte nur Java und C#. Wenn sich DEvent nicht kurzfristig einmischt, können wir sicher einvernehmlich festhalten, daß C# sprachlich deutlich fortschrittlicher ist: es ist weit weniger dogmatisch (es gibt beispielsweise unsigned-Datentypen, keine Checked Exceptions, keine Beschränkung auf eine öffentliche Klasse per Datei), viele Dinge, die bei Java falsch liefen, wurden in C# richtig gemacht (etwa: die Einführung von Properties vs. die get*/set*-Konvention für JavaBeans, typsichere Generics mit JIT-Instantiierung vs. verkrüppelte Compile-time-Generics mit Type Erasure und Boxing, ordentliche Closures vs. anonyme Klassen, Operatorenüberladung vs.
myString.equals(yourString)
), und darüber hinaus gibt es auch echte Innovationen (allen voran LINQ, dann Extension Methods (die zuvor schon in Delphi als "class helpers" aufgetaucht waren), oder etwayield return
). Dem allen zum Trotz sind die Toolchains auf der Java-Seite erfahrungsgemäß deutlich überlegen. Gegen die durchschnittliche Java-IDE ist Visual Studio ein müder Scherz. Fast alle größeren, etablierten ORM- oder IoC-Frameworks sind für Java entstanden und bieten vielleicht mittlerweile einen .NET-Port. Ein normaler Java-Entwickler ist im Vergleich mit dem durchschnittlichen C#-Programmierer deutlich versierter im Arbeiten mit Patterns und größeren Frameworks*. Kurzum, C# kann sprachlich so überlegen sein wie es will, im Server-Umfeld sind diese Gründe dennoch oft ausschlaggebend geung, um Java vorzuziehen.Zugegebenermaßen ist auf der Java-Seite derzeit, insbesondere im direkten Vergleich, so wenig Innovation zu vernehmen, daß ich es nicht für unwahrscheinlich halte, daß sich das Bild in absehbarer Zeit zugunsten von .NET/C# wandelt.
(* Für die Bestätigung dieser letzten Aussage habe ich keine Zahlen; das ist nur ein bereits öfters von anderen bestätigter Erfahrungswert.)