winapi - Wie entscheide ich, ob ich ATL, MFC, Win32 oder CLR für ein neues C++Projekt verwende?




(5)

Ich beginne gerade mein erstes C ++ Projekt. Ich verwende Visual Studio 2008 . Es handelt sich um eine Windows-Einzelformularanwendung, die auf einige Datenbanken zugreift und eine WebSphere MQ-Transaktion initiiert. Ich verstehe im Grunde die Unterschiede zwischen ATL, MFC, Win32 (ich bin eigentlich ein bisschen verschwommen) und CLR, aber ich weiß nicht, wie ich wählen soll.

Ist einer oder mehrere davon nur aus Gründen der Abwärtskompatibilität vorhanden?

Ist CLR eine schlechte Idee ?

Irgendwelche Vorschläge geschätzt.

Edit: Ich habe C ++ für dieses Projekt aus Gründen ausgewählt, die ich nicht in der Post betrachte, die nicht ganz technisch sind. Also, unter der Annahme , dass C ++ die einzige / beste Option ist, welche sollte ich wählen?


Answers

Soweit C ++ geht, würde ich WTL verwenden. Es ist leicht und Sie werden wenige (wenn überhaupt) Abhängigkeiten haben, so dass es einfach zu versenden und zu installieren ist. Ich finde es sehr befriedigend, wenn meine App aus einer einzigen EXE-Datei besteht, die auf den meisten Windows-Versionen ausgeführt wird, aber das mag Sie nicht interessieren.

Wenn Sie sich stattdessen für .NET entscheiden, ist C # mit ziemlicher Sicherheit der richtige Weg.

Mehr in WTL hier:

http://www.codeproject.com/KB/wtl/wtl4mfc1.aspx


Mit CLR ist nichts falsch. Wie andere hier würde ich C # vorschlagen, aber da Sie Gründe haben, bei C ++ zu bleiben, ist die Verwendung des .NET-Frameworks mehrere Tausend Mal einfacher als das Mischen mit ATL / MFC, wenn Sie nicht bereits mit ihnen vertraut sind (IMO).

Es ist erwähnenswert, dass wenn Sie C ++ / CLR benutzen, Sie C ++ überhaupt nicht benutzen. C ++ / CLR kompiliert zu CIL genau wie C #. Ich habe es nie selbst benutzt, aber ich glaube, es ist sein Zweck, Ihnen zu ermöglichen, alten Code zu kompilieren und ihn für neuen .NET-Code leicht verfügbar zu machen, anstatt neuen Code mit alten ausführbaren C ++ - Dateien arbeiten zu lassen. Es gibt andere Methoden, um nativen Code von .NET aufzurufen, den Sie vielleicht untersuchen sollten.


Win32 ist die rohe Bare-Metal-Methode. Es ist mühsam, schwierig zu benutzen und hat viele kleine Details, an die man sich erinnern muss, sonst werden die Dinge auf relativ mysteriöse Weise scheitern.

MFC baut auf Win32 auf, um Ihnen eine objektorientierte Methode zum Erstellen Ihrer Anwendung bereitzustellen. Es ist kein Ersatz für Win32, sondern eine Verbesserung - es ist eine Menge Arbeit für Sie.

System.Windows.Forms (was ich unter CLR vermute) ist völlig anders, hat aber große Ähnlichkeiten mit MFC aus seiner Grundstruktur. Es ist bei weitem am einfachsten zu verwenden, erfordert aber das .NET-Framework, das in Ihrem Fall ein Hindernis sein kann oder nicht.

Meine Empfehlung: Wenn Sie .NET vermeiden müssen, dann verwenden Sie MFC, ansonsten verwenden Sie .NET (in der Tat würde ich in diesem Fall C # verwenden, da es viel einfacher ist, mit zu arbeiten).


Ich wäre sehr neugierig, warum Sie das überhaupt in C ++ machen würden. Basierend auf Ihrer kurzen Beschreibung klingt C # wie eine viel geeignetere Wahl.

Um etwas näher zu erläutern, schauen Sie sich den Link an, den Sie mit C ++ CLR beschrieben haben. Die am besten bewerteten Antwort-Notizen (genau, meiner Meinung nach), dass C ++ für "Kernel, Spiele, Hochleistungs- und Server-Apps" geeignet ist - von denen keiner zu beschreiben scheint, was Sie tun.

MFC, ATL, usw. werden in dem Sinne unterstützt, dass Sie Ihre App in zukünftigen Versionen von Visual Studio kompilieren und auf zukünftigen Windows-Versionen ausführen können. Aber sie werden nicht in dem Sinne unterstützt, dass in der API oder der Sprache nicht so viel neue Entwicklung stattfindet wie in CLR und C #.


WARNUNG!

Die folgenden sind mögliche Gründe für einen Segmentierungsfehler. Es ist praktisch unmöglich, alle Gründe aufzulisten . Der Zweck dieser Liste besteht darin, einen vorhandenen segfault zu diagnostizieren.

Die Beziehung zwischen Segmentierungsfehlern und undefiniertem Verhalten kann nicht genug betont werden! Alle folgenden Situationen, die einen Segmentierungsfehler verursachen können, sind technisch undefiniert. Das bedeutet, dass sie alles tun können , nicht nur segfault - wie jemand auf USENET einmal gesagt hat, " es ist legal für den Compiler, Dämonen aus der Nase fliegen zu lassen. " Zählen Sie nicht auf ein segfault-Ereignis, wenn Sie ein undefiniertes Verhalten haben. Sie sollten lernen, welche undefinierten Verhaltensweisen in C und / oder C ++ existieren, und vermeiden, Code zu schreiben, der sie hat!

Weitere Informationen zu nicht definiertem Verhalten:

Was ist ein Segfault?

Kurz gesagt, ein Segmentierungsfehler wird verursacht, wenn der Code versucht, auf Speicher zuzugreifen, auf den er keine Zugriffsberechtigung hat . Jedem Programm wird ein Arbeitsspeicher (RAM) zur Verfügung gestellt, und aus Sicherheitsgründen darf es nur auf Speicher in diesem Teil zugreifen.

Eine genauere technische Erläuterung zu einem Segmentierungsfehler finden Sie unter Was ist ein Segmentierungsfehler? .

Hier sind die häufigsten Gründe für einen Segmentierungsfehler. Auch diese sollten bei der Diagnose eines vorhandenen Segmentfehlers verwendet werden . Um zu lernen, wie man sie vermeidet, lernen Sie das undefinierte Verhalten Ihrer Sprache.

Diese Liste ist auch kein Ersatz für Ihre eigene Debugging-Arbeit . (Siehe den Abschnitt am Ende der Antwort.) Dies sind Dinge, nach denen Sie suchen können, aber Ihre Debugging-Tools sind die einzige zuverlässige Möglichkeit, das Problem zu beheben.

Zugreifen auf einen NULL- oder nicht initialisierten Zeiger

Wenn Sie einen Zeiger haben, der NULL ist ( ptr=0 ) oder der vollständig nicht initialisiert ist (er ist überhaupt noch gar nicht gesetzt), hat der Versuch, auf diesen Zeiger zuzugreifen oder ihn zu ändern, ein undefiniertes Verhalten.

int* ptr = 0;
*ptr += 5;

Da eine fehlgeschlagene Zuweisung (z. B. mit malloc oder new ) einen Nullzeiger zurückgibt, sollten Sie immer überprüfen, dass der Zeiger nicht NULL ist, bevor Sie damit arbeiten.

Beachten Sie auch, dass selbst das Lesen von Werten (ohne Dereferenzierung) von nicht initialisierten Zeigern (und Variablen im Allgemeinen) undefiniertes Verhalten ist.

Manchmal kann dieser Zugriff auf einen undefinierten Zeiger ziemlich subtil sein, beispielsweise wenn versucht wird, einen solchen Zeiger als eine Zeichenfolge in einer C-Druckanweisung zu interpretieren.

char* ptr;
sprintf(id, "%s", ptr);

Siehe auch:

  • Wie kann ich erkennen, ob Variable nicht initialisiert / gefangen ist in C?
  • Verkettung von String und int führt zu Seg-Fehler C

Auf einen freien Zeiger zugreifen

Wenn Sie malloc oder new , um Speicher zuzuordnen, und später diesen Speicher über Zeiger delete oder delete , wird dieser Zeiger jetzt als ein dangling Zeiger betrachtet . Dereferenzieren Sie es (und einfach seinen Wert zu lesen - vorausgesetzt, Sie haben ihm keinen neuen Wert wie NULL zugewiesen) ist ein nicht definiertes Verhalten und kann zu einem Segmentierungsfehler führen.

Something* ptr = new Something(123, 456);
delete ptr;
std::cout << ptr->foo << std::endl;

Siehe auch:

  • Was ist ein fliegender Zeiger?
  • Warum verursacht mein fliegender Zeiger keinen Segmentierungsfehler?

Paketüberfluss

[Nein, nicht die Seite, auf der du gerade bist, nach der du benannt wurdest.] Übertrieben vereinfacht, ist der "Stapel" wie die Spitze, auf der du deine Bestellung in einigen Restaurants festhältst. Dieses Problem kann auftreten, wenn Sie sozusagen zu viele Aufträge auf diese Spitze setzen. In dem Computer gehen jede Variable, die nicht dynamisch zugeordnet ist, und jeder Befehl, der noch von der CPU verarbeitet werden muss, auf den Stapel.

Eine Ursache dafür könnte eine tiefe oder unendliche Rekursion sein, etwa wenn eine Funktion sich selbst aufruft und nicht stoppen kann. Da dieser Stapel übergelaufen ist, beginnen die Auftragspapiere, "herunterzufallen" und anderen Platz einzunehmen, der nicht für sie bestimmt ist. Daher können wir einen Segmentierungsfehler erhalten. Eine andere Ursache könnte der Versuch sein, ein sehr großes Array zu initialisieren: Es ist nur eine einzige Reihenfolge, aber eine, die bereits groß genug ist.

int stupidFunction(int n)
{
   return stupidFunction(n);
}

Ein weiterer Grund für einen Stack-Überlauf wäre, dass zu viele (nicht dynamisch zugewiesene) Variablen gleichzeitig vorhanden wären.

int stupidArray[600851475143];

Ein Fall eines Stapelüberlaufs im freien Fall ergab sich aus einem einfachen Weglassen einer return Anweisung in einer Bedingung, die eine unendliche Rekursion in einer Funktion verhindern sollte. Die Moral dieser Geschichte, immer sicherstellen , dass Ihre Fehlerüberprüfungen funktionieren!

Siehe auch:

Wilde Zeiger

Das Erstellen eines Zeigers an einem zufälligen Ort im Speicher ist wie das Spielen von Russisch Roulette mit Ihrem Code - Sie könnten leicht einen Zeiger auf einen Ort verfehlen und erstellen, zu dem Sie keine Zugriffsrechte haben.

int n = 123;
int* ptr = (&n + 0xDEADBEEF); //This is just stupid, people.

In der Regel erstellen Sie keine Zeiger auf literale Speicherorte. Auch wenn sie einmal arbeiten, beim nächsten Mal nicht. Sie können nicht vorhersagen, wo der Programmspeicher bei einer bestimmten Ausführung sein wird.

Siehe auch:

Versuch, nach dem Ende eines Arrays zu lesen

Ein Array ist ein zusammenhängender Speicherbereich, in dem sich jedes nachfolgende Element an der nächsten Adresse im Speicher befindet. Die meisten Arrays haben jedoch keinen angeborenen Sinn dafür, wie groß sie sind oder was das letzte Element ist. Daher ist es einfach, über das Ende des Arrays hinauszuwehen und es nie zu kennen, besonders wenn Sie Zeigerarithmetik verwenden.

Wenn Sie über das Ende des Arrays hinauslesen, werden Sie möglicherweise in den Speicher gehen, der nicht initialisiert ist oder zu etwas anderem gehört. Dies ist technisch undefiniertes Verhalten . Ein Segfault ist nur eines dieser vielen möglichen undefinierten Verhaltensweisen. [Ehrlich gesagt, wenn Sie hier einen Fehler haben, haben Sie Glück. Andere sind schwerer zu diagnostizieren.]

// like most UB, this code is a total crapshoot.
int arr[3] {5, 151, 478};
int i = 0;
while(arr[i] != 16)
{
   std::cout << arr[i] << std::endl;
   i++;
}

Oder der häufig gesehene, der for mit <= anstelle von < (liest 1 Byte zu viel):

char arr[10];
for (int i = 0; i<=10; i++)
{
   std::cout << arr[i] << std::endl;
}

Oder sogar ein unglücklicher Tippfehler, der sich gut zusammensetzt ( here sehen) und nur 1 Element zuweist, das mit dim anstelle von dim Elementen initialisiert ist.

int* my_array = new int(dim);

Außerdem sollte beachtet werden, dass Sie nicht einmal einen Zeiger erstellen (oder von der Dereferenzierung ganz absehen) dürfen, der außerhalb des Arrays zeigt (Sie können einen solchen Zeiger nur erstellen, wenn er auf ein Element innerhalb des Arrays oder nach dem Ende zeigt). Andernfalls lösen Sie ein undefiniertes Verhalten aus.

Siehe auch:

Einen NUL-Terminator auf einer C-Zeichenfolge vergessen.

C-Strings sind Arrays mit einigen zusätzlichen Verhaltensweisen. Sie müssen null terminiert sein, was bedeutet, dass sie ein \0 am Ende haben, um zuverlässig als Strings verwendet zu werden. Dies geschieht in einigen Fällen automatisch und nicht in anderen.

Wenn dies vergessen wird, wissen einige Funktionen, die C-Strings behandeln, nie, wann sie anhalten sollen, und Sie können die gleichen Probleme bekommen wie beim Lesen über das Ende eines Arrays hinaus.

char str[3] = {'f', 'o', 'o'};
int i = 0;
while(str[i] != '\0')
{
   std::cout << str[i] << std::endl;
   i++;
}

Mit C-Strings ist es wirklich Hit-and-Miss, ob \0 wird einen Unterschied machen. Sie sollten davon ausgehen, dass ein undefiniertes Verhalten vermieden wird: also schreiben Sie besser char str[4] = {'f', 'o', 'o', '\0'};

Versuch, ein Zeichenfolgenliteral zu ändern

Wenn Sie einem Zeichen * ein Zeichenfolgenliteral zuweisen, kann es nicht geändert werden. Beispielsweise...

char* foo = "Hello, world!"
foo[7] = 'W';

... löst ein undefiniertes Verhalten aus und ein Segmentierungsfehler ist ein mögliches Ergebnis.

Siehe auch:

  • Warum verursacht dieser String-C-Code einen Segmentierungsfehler?

Mismatching Allocation and Deallocation Methoden

Sie müssen malloc und free zusammen verwenden, new und delete zusammen und new[] und delete[] zusammen. Wenn Sie sie mischen, können Sie Segfaults und anderes seltsames Verhalten erhalten.

Siehe auch:

  • Verhalten von malloc mit Löschen in C ++
  • Segmentierungsfehler (Core Dumped), wenn ich den Zeiger lösche

Fehler in der Toolchain.

Ein Fehler im Maschinencode-Backend eines Compilers ist durchaus in der Lage, einen gültigen Code in eine ausführbare Datei umzuwandeln. Ein Fehler im Linker kann dies auch tun.

Besonders beängstigend ist, dass dies nicht UB von Ihrem eigenen Code aufgerufen wird.

Das heißt, Sie sollten immer das Problem annehmen, bis Sie das Gegenteil beweisen.

Andere Ursachen

Die möglichen Ursachen für Segmentierungsfehler sind ungefähr so ​​zahlreich wie die Anzahl nicht definierter Verhalten und es gibt viel zu viele, um selbst die Standarddokumentation aufzulisten.

Ein paar weniger häufige Ursachen zu überprüfen:

Entschuldigung

Debugging-Tools helfen bei der Diagnose der Ursachen eines Segmentfehlers. Kompilieren Sie Ihr Programm mit dem Debug-Flag ( -g ), und führen Sie es dann mit Ihrem Debugger aus, um zu finden, wo der Segfault wahrscheinlich auftritt.

Neuere Compiler unterstützen das -fsanitize=address mit der -fsanitize=address , was normalerweise zu einem Programm führt, das etwa 2x langsamer läuft, aber Adressenfehler genauer erkennen kann. Andere Fehler (z. B. das Lesen von nicht initialisiertem Speicher oder das Lecken von Ressourcen ohne Arbeitsspeicher, z. B. Dateideskriptoren) werden von dieser Methode jedoch nicht unterstützt, und es ist nicht möglich, viele Debugging-Tools und ASan gleichzeitig zu verwenden.

Einige Speicherdebugger

  • GDB | Mac, Linux
  • valgrind (memcheck) | Linux
  • Dr. Memory | Windows

Darüber hinaus wird empfohlen, statische Analysetools zu verwenden, um undefiniertes Verhalten zu erkennen - aber auch hier handelt es sich um ein Tool, das Ihnen lediglich hilft, nicht definiertes Verhalten zu finden. Es wird nicht garantiert, dass alle Vorkommen von undefiniertem Verhalten gefunden werden.

Wenn Sie wirklich Pech haben, kann die Verwendung eines Debuggers (oder seltener nur das erneute Kompilieren mit Debug-Informationen) den Programmcode und Speicher ausreichend beeinflussen, so dass der Segfault nicht länger auftritt, ein Phänomen, das als heisenbug .





c++ winapi mfc clr atl