Visual Studio 6
Unwohl
Ich hätte ja nie geglaubt, dass es einmal so weit kommen würde. Gerade musste ich für ein paar Minuten wieder mit dem VC 6 arbeiten, weil ich einem Problem in SpeedCommander 11 auf den Grund gehen wollte. Die letzte Begegnung mit dem VC 6 liegt schon mehr als zwei paar Monate zurück.
Während der ganzen Zeit habe ich mich dabei ziemlich unwohl gefühlt und kam mir richtiggehend verloren vor. Also den VC 6 ganz schnell wieder schließen und zurück zum geliebten VS 2008.
Windows Vista SDK im VC 6 einbinden
Das letzte Platform SDK, welches noch anstandslos mit dem VC 6 funktioniert, ist das für Windows Server 2003 vom Februar 2003. Alle nachfolgenden SDKs bringen LIB-Dateien in einem neueren Format mit (VS 2003), welche der Linker vom VC 6 nicht mehr verwenden kann.
Will man also weiter mit dem VC 6 arbeiten und trotzdem nicht auf die neuen Definitionen für Vista verzichten, so muss man diese aus den Include-Dateien des Vista-SDKs zusammenklauben und in eigene Headerdateien stecken. Auf Dauer ist dies aber recht mühsam, da es teilweise mehrstufige Abhängigkeiten gibt. Daher habe ich nach einer Möglichkeit gesucht, dieses Gefrickel zu umgehen.
Die Lösung ist denkbar einfach: Man verwendet die Headerdateien vom Vista-SDK und die LIB-Dateien vom W2k3-SDK. Die alten LIB-Dateien sind kein großer Nachteil, da neuere Funktionen, die nur ab Vista verfügbar sind, sowieso dynamisch eingebunden werden sollten. Ansonsten versagt die Anwendung auf älteren Systemen ihren Dienst.
Hier nun die Schritt-für-Schritt-Anleitung:
- Das include-Verzeichnis im alten PSDK auf „include.old“ umbenennen.
- Das include-Verzeichnis vom Vista-SDK in das alte PSDK kopieren
- Die strsafe.h im neuen include-Verzeichnis durch die aus „include.old“ ersetzen.
- Alle .idl-Dateien im neuen include-Verzeichnis löschen und durch die aus „include.old“ ersetzen.
Beim ersten Kompilieren werden bei höchster Warnstufe (W4) noch vier Warnungen in der WinGDI.h und WinNT.h ausgegeben. Die in der WinGDI.h werden durch ein dem VC 6 unbekanntes pragma ausgelöst, eine Auskommentierung der beiden Zeilen hilft. In der WinNT.h werden zwei Funktionen ohne Rückgabewert angemeckert, Abhilfe schafft hier eine temporäre Deaktivierung der Meldung 4035 für die beiden Funktionen.
Das Ersetzen der .idl-Dateien ist nötig, da der alte MIDL-Compiler das neue Schlüsselwort „annotation“ nicht versteht und das Kompilieren an dieser Stelle abbricht. Die neue strsafe.h benutzt teilweise neue Funktionskennungen und ist daher mit der alten strsafe.lib nicht mehr kompatibel.
Neue CLSID- bzw. IID-Wert für Vista findet der Linker in der Regel in den LIB-Dateien, diese stehen uns hier aber nicht zur Verfügung. Daher muss man sich diese selbst definieren. Der Aufwand ist aber recht gering, wie das folgende Beispiel zeigt:
#define MFX_DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ EXTERN_C const GUID DECLSPEC_SELECTANY name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } MFX_DEFINE_GUID(CLSID_FileOpenDialog, 0xDC1C5A9C, 0xE88A, 0x4dde, 0xA5, 0xA1, 0x60, 0xF8, 0x2A, 0x20, 0xAE, 0xF7); MFX_DEFINE_GUID(CLSID_FileSaveDialog, 0xC0B4E2F3, 0xBA21, 0x4773, 0x8D, 0xBA, 0x33, 0x5E, 0xC9, 0x46, 0xEB, 0x8B);
MFC 8.0 für Visual C++ 6
Damit auch andere VC6-Entwickler in den Genuss der MFC 8.0 kommen können, habe ich mit Hilfe des Patch Makers vom Clickteam die Änderungen zusammengefasst und als Download zur Verfügung gestellt. Voraussetzung für die Installation des Patches ist eine Kopie des Original-atlmfc-Verzeichnisses von Visual Studio 2005 und das Platform SDK vom Februar 2003. Neuere Versionen des Platform SDKs funktionieren nicht, da der VC6 mit den Lib-Dateien nichts mehr anfangen kann.
Alle Änderungen sind mit Ausnahme der Ersetzungen von throw(…) und __interface mit dem Kürzel SR: dokumentiert und insgesamt auch noch einmal in der Datei changelog.txt aufgelistet.
Exemplarische Vorgehensweise:
- Originales atlmfc-Verzeichnis aus VS2005 nach [VS6]\VC98 kopieren
- Patch ausführen und [VS6]\VC98\atlmfc als Produktverzeichnis auswählen
- Pfadangaben in [VS6]\VC98\atlmfc\src\vcvars32.bat anpassen (VSCommonDir, MSDevDir, MSVCDir, PSDK)
- Modulnamen MyMfc80 für die eigene MFC in [VS6]\VC98\atlmfc\src\makemfx.bat, [VS6]\VC98\atlmfc\include\afx.h und [VS6]\VC98\atlmfc\src\nolib.cpp anpassen
- Eingabeaufforderung öffnen und nach [VS6]\VC98\atlmfc\src wechseln
- Umgebungsvariablen initialisieren (vcvars32.bat)
- Statische ATL erstellen (makeatl.bat)
- MFC erstellen (makemfc.bat)
- Lib-Dateien aus [VS6]\VC98\atlmfc\lib\Intel nach [VS6]\VC98\atlmfc\lib kopieren
- Pfadangaben zur MFC im Developer Studio anpassen
Hinweise
- Die ATL steht nur als statische Bibliothek zur Verfügung, die ATL-Server-Klassen sowie alle Headerdateien in [VS6]\VC98\atlmfc\include\! können nicht verwendet werden.
- Die WinForms-Erweiterungen können natürlich auch nicht verwendet werden.
- Die Klassen für den Zugriff auf Datenbanken (OLEDB und DAO) wurden von mir nicht getestet.
- Die Benutzung geschieht auf eigene Gefahr, ich übernehme keine Verantwortung für eventuelle Probleme.
- Die Verwendung der MFC 8.0 für Visual C++ 6 ist lizenzrechtlich nur Entwicklern gestattet, die auch die entsprechende Lizenz für Visual Studio 2005 erworben haben.
Volle Kraft voraus
Das gedankliche Hin und Her in den letzten Wochen zwischen dem altbewährten VC6 und dem VS2005 mit der neuen MFC 8.0 ist endlich beendet. Mit einigen kleinen Compileranpassungen kann ich die MFC 8.0 nun auch im VC6 verwenden. So brauche ich die gewohnte Umgebung nicht verlassen und kann zumindestens bis zur nächsten Version von Visual Studio die Vorteile der aktuellen MFC nutzen.
Nun ist der Kopf wieder frei für neue Funktionen im SpeedCommander. Seid gespannt.
Nichts ist unmöglich
Der wohl größte Nachteil beim VC6 ist die mitgelieferte MFC in der Version 6.0. Der Technikstand der MFC 6.0 ist eigentlich Windows 98, in den späteren Servicepacks kamen keine Neuerungen mehr hinzu. Für eine zeitgemäße Unterstützung aktueller Windows-Versionen ist man daher auf eigene Erweiterungen angewiesen. Alternativ kann man auch versuchen, die aktuelle MFC mit dem VC6 zu kompilieren. Mit der Zeit wird das aber immer schwieriger, da die neueren Versionen natürlich auch gerne die jeweils aktuelle Compilertechnologie verwenden.
Die MFC 7.2 vom Visual Studio.NET 2003 hatte ich schon Ende 2003 adoptiert, letzte Woche ist mir das nun auch mit der MFC 8.0 vom Visual Studio 2005 gelungen. Ein ziemliches KO-Kriterium waren anfangs die umfangreichen Templates der ATL, die den VC6-Compiler ziemlich ins Schwitzen gebracht haben. An den String-Klassen hat er sich dann aber mit einem stetigen
fatal error C1001: INTERNER COMPILER-FEHLER
(Compiler-Datei „msc1.cpp“, Zeile 1794)
Bitte wählen Sie im Menü „?“ von Visual C++ den Befehl „Software Service“, oder
öffnen Sie die Hilfedatei für den Software Service, um weitere Informationen zu erhalten.
Fehler beim Ausführen von cl.exe.
vergeblich die Zähne ausgebissen. Als Workaround kam dann wieder meine Lösung von 2003 zum Einsatz. Die ATL-Stringklassen wurden komplett entfernt und durch die CString-Implementation der MFC 6.0 ersetzt. Da die MFC 8.0 nun vermehrt auch CStringA und CStringW einsetzt, habe ich die CString-Klasse einfach verdoppelt und eine Ansi-Fassung (CStringA) sowie eine Unicode-Fassung (CStringW) implementiert. Per typedef wird dann in einer Ansi-Anwendung aus CStringA der normale CString, bei einer Unicode-Anwendung entspricht CString dann CStringW.
Dazu habe ich für CStringA und CStringW noch einmal separat eine ATL-Implementation (CAtlStringA und CAtlStringW) eingebaut, so dass man auch die eine oder andere ATL-Klasse verwenden kann.
Weitere größere Fallstricke sind u.a.
- einige Definitionen aus der VC8-Laufzeit, die man zusammentragen muss
- fehlende Laufzeitunterstützung für time64-Funktionen, welche sich aber aus der VC8-CRT ausleihen lassen
- einige sichere Stringfunktionen der VC8-Laufzeit, welche entweder durch die alten Stringfunktionen oder durch sichere Stringfunktionen der strsafe.h ersetzt werden müssen
- das Schlüsselwort throw(…), welches in verschiedenen Dateien durch throw() ersetzt werden muss
- Statische Konstanten in Templates, die entweder in #defines umgewandelt oder als Klassenmember im Konstruktor initialisiert werden müssen
- declspec(selectany) für einige globale Variablen
- das Schlüsselwort __interface, welches durch struct ersetzt werden kann
- die OLEDB-Templates in atldbcli.h, welche durch die atldbcli.h aus der MFC 6.0 zzgl. der Definition für CVBufHelper und CVirtualBuffer aus der atlbase.h (auch MFC 6.0) ersetzt werden können
- die Implementation in dllmodul.cpp, welche beim Linken von mit _USRDLL kompilierten Dlls zwei fehlende Funktionsimporte bemängelt und durch die Fassung aus der MFC 7.2 ersetzt werden kann
Nachdem dann alles ohne Fehlermeldungen kompiliert wird, müssen nur noch die vier DEF-Dateien für die MFC-Dlls angepasst werden. Das ging diesmal bedeutend schneller als bei der MFC 7.2, da alle Referenzen auf die ATL-Klassen durch Suchen/Ersetzen auf die CStringA/W-Klassen korrigiert werden können. VS2005 behandelt und exportiert wchar_t als eigenen Typ, während der VC6 hier noch mit unsigned short arbeitet. Dementsprechend müssen die Exportbeschreibungen für Unicode-Funktionen, die Variablen mit wchar_t enthalten, auch noch angepasst werden. Aber auch hier geht ein globales Ersetzen von „_W“ durch „G“ sehr schnell vonstatten. Dazu kommen noch die nötigen Exporte für die CStringA/W-Klassen, die sich mit Hilfe der MAP-Dateien zusammenstellen lassen.
Ich hatte schon überlegt, ob ich hier einen genauen Portierungsablauf dokumentieren sollte, aber das ist ziemlich trocken und wohl für die meisten Leser auch eher uninteressant. Ein komplettes Downloadangebot der geänderten Quelltexte ist vermutlich lizenzrechtlich nicht möglich, alternativ könnte man aber zwischen der originalen VS2005-Version und den angepassten Quelltexten ein Diff erstellen, mit dem sich dann aus der VS2005-Version die angepasste Version erstellen lässt. Tips zu einem einfach zu benutzenden Diff-Tool (möglichst ohne Cygwin-Voraussetzung) sind willkommen.
Inside Visual C++ 6.0
Auf der MSDN-Seite habe ich heute einen außergewöhnlichen Buch-Tipp entdeckt:
Buch-Tipp
Bei Microsoft Press erscheint dieser Tage als Sonderausgabe „Inside Visual C++ 6.0 – Das Standardwerk zur Programmierung mit Visual C++: MFC, ATL und Co.“ (Autor: David Kruglinski u.a.; 1104 Seiten; 29 €).
Posted on Wed, 14 Sep 2005 07:46:10 GMT
Erst dachte ich an einen Scherz, aber es stimmt wirklich. Sieben Jahre nach der Erstausgabe gibt es nun für 29 Euro eine neuaufgelegte Sonderausgabe. Anscheinend gibt es wohl doch noch mehr als genug Entwickler, die noch immer auf den VC6 schwören. Eigentlich nur allzu verständlich.
Das Buch ist übrigens absolut empfehlenswert.
Benannte Threads
Beim Debuggen von Anwendungen mit mehreren Threads fragt man sich mitunter, in welchem Thread man sich gerade durch den Debugger quält. Im VC6 gibt es ein Thread-Fenster, was aber nur den Namen der Startfunktion des Threads bzw. den der aktuellen Funktion anzeigt. Sobald man mehrere Threads mit den gleichen Funktionen zu debuggen hat, wird die Unterscheidung schwer, welcher Thread gerade aktiv ist:
In der MSDN-Library ist beschrieben, wie man durch das Auslösen einer bestimmten Exception die einzelnen Threads benennen kann. Hierzu wird eine Struktur definiert, die u.a. mit dem Namen und der entsprechenden Thread-ID initialisiert wird. Anschließend wird eine Exception ausgelöst. Erfolgt der Aufruf zum Setzen des Namens für den aktuellen Thread, so kann als Thread-ID auch -1 übergeben werden. Zu beachten ist, dass der Name des Threads immer in Ansi (und nicht in Unicode) angegeben wird.
typedef struct tagTHREADNAME_INFO { DWORD dwType; // Typ (muss 0x1000 sein) LPCSTR pszName; // Zeiger auf den Threadnamen (ANSI) DWORD dwThreadID; // Thread-ID (-1 fuer den aufrufenden Thread) DWORD dwFlags; // Reserviert (muss 0 sein) } THREADNAME_INFO; void Lwl_SysSetThreadName(DWORD dwThreadID, LPCSTR pszThreadName) { // Struktur initialisieren THREADNAME_INFO info; info.dwType = 0x1000; info.pszName = pszThreadName; info.dwThreadID = dwThreadID; info.dwFlags = 0; // Exception generieren __try { RaiseException(0x406D1388, 0, sizeof(info)/sizeof(DWORD), (DWORD_PTR*) &info); } // __except(EXCEPTION_CONTINUE_EXECUTION) { } }
Beim Aufruf des Thread-Fensters werden die Threads nun mit ihrem Namen angezeigt. Beim VC6 besteht leider eine Limitierung des Namens auf 9 Zeichen, die im aktuellen Visual Studio 2003 aber aufgehoben ist.
Auch in der .netten Welt mag es manchmal hilfreich sein, wenn man mehrere Threads anhand des Namens unterscheiden kann. In der Regel präsentiert sich das Thread-Fenster wie folgt:
Der Threadname für den Debugger lässt sich direkt über die Eigenschaft Name der Thread-Klasse des .NET-Frameworks festlegen.
Public Class Form1 : Inherits Form Class Needle ' This method will be called when the thread is started Sub Baz() Console.WriteLine("Needle Baz is running on another thread") End Sub End Class Public Shared Sub Main() Console.WriteLine("Thread Simple Sample") Dim oNeedle As New Needle() ' Create a Thread object Dim oThread As New Thread(AddressOf oNeedle.Baz) ' Set the Thread name to "MainThread" oThread.Name = "MainThread" ' Starting the thread invokes the ThreadStart delegate oThread.Start() End Sub End Class
Der Zugriff auf das Thread-Objekt des Prozesses erfolgt über Thread.GetCurrentThread. Nach dem Setzen der Threadnamen sieht das Fenster im Debugger nun so aus: