Komfortabler Dateimanager mit vielen Funktionen

Entwicklung

Warum noch SP1?

By Sven on 20.06.2006 - 15:22 in Entwicklung with Keine Kommentare

Im Kommentar zu Windows-Version ermitteln fragte Thomas, warum ich immer noch XP SP1 einsetze. Der Grund liegt in der Kombination von VC6 und den Windows-Debugsymbolen. Debugsymbole sind Zusatzinformationen des Compilers bzw. Linkers. Sie werden benötigt, um sich beim Debuggen bequem durch die verschiedenen Funktionen eines Programms bewegen zu können. Mit Hilfe der Debuginformationen kann der Debugger viel mehr Informationen anzeigen als ohne. Beim Erstellen der eigenen Programme werden die Debuginformationen normalerweise automatisch erstellt und verwendet. Sobald man aber eine Windows-Funktion aufruft, sieht man nur noch den Assemblercode und im Aufrufstack werden hexadezimale Adressen angezeigt. Zur Verdeutlichung habe ich einmal zwei Screenshots gemacht. Im ersten wird der Aufrufstack beim erstellen eines Fensters gezeigt. Man sieht nur, dass der Aufruf aus der USER32.DLL kommt:

Aufrufstack ohne Debugsymbole

Der zweite Screenshot zeigt das Disassembler-Fenster im Debugger, wenn man in die Windows-Funktion LoadLibrary hineingeht:

LoadLibrary ohne Debugsymbole

Bei Verwendung von Debugsymbolen ist der Aufrufstack sehr viel aussagekräftiger. Man sieht genau, wo der Aufruf einer Funktion herkommt und welchen Weg er durchlaufen hat:

Aufrufstack mit Debugsymbolen

Im Disassembler-Fenster sind die Auswirkungen der Debugsymbole aber noch gravierender. Die Assemblerwüste hat sich zu einer Oase von nützlichen Informationen entwickelt. Nun ist z.B. zu sehen, dass die Datei twain_32.dll beim Laden eine Sonderbehandlung erfährt:

LoadLibrary mit Debugsymbolen

Mit dem SP2 und der damaligen Sicherheitsinitiative hat Microsoft zum besseren Schutz vor Pufferüberläufen den Compiler gewechselt. Ab dem SP2 werden alle Dateien mit dem VC7 kompiliert, bis zum SP1 wurde noch der VC6 verwendet. Alle Patches für SP1 werden übrigens auch noch mit dem VC6 erstellt. Leider hat sich mit dem VC7 aber das Format der Debuginformationen geändert, der VC6 kann mit dem neuen Format nichts mehr anfangen. Man sieht so nur noch die Informationen, die in den ersten beiden Screenshots dargestellt werden. Abhilfe würde nur ein kompletter Umstieg auf Visual Studio 2005 schaffen, allerdings gibt es einige Gründe, warum dies derzeit keine Alternative ist.

DWORDLONG oder LONGLONG

By Sven on 02.05.2006 - 11:18 in Windows SDK with 2 Kommentare

DWORDLONG und LONGLONG sind die Typbezeichner für vorzeichenlose und vorzeichenbehaftete 64-bit-Werte. Bei 32-bit-Werten ist es schon wichtig, ob man mit oder ohne Vorzeichen arbeitet, gerade wenn man den Wertebereich voll ausschöpfen muss. Daher bin ich da immer recht konsequent und nehme für Zahlen, die nur positiv sein sollen, immer ein DWORD bzw. ein UINT.

Bit Typenbezeichner Vorzeichen Untere Grenze Obere Grenze
32 int
UINT oder DWORD
signed
unsigned
-2147483648
0
2147483647
4294967294
64 LONGLONG
DWORDLONG
signed
unsigned
-9223372036854775808
0
9223372036854775807
18446744073709551615

Bei 64-bit-Werten scheint es im Moment aber noch nicht auf das letzte Bit anzukommen. In der Dokumentation zu SetFilePointer wird auch explizit nur von vorzeichenbehafteten 64-bit-Werten gesprochen. Immerhin ist 9.223.372.036.854.775.807 das 2.147.483.649-fache des Maximums eines vorzeichenlosen 32-bit-Wertes. Eigentlich sollte das für die nächsten 20 Jahre doch dicke ausreichen, oder?

Spalten für alle Ansichten

By Sven on 22.03.2006 - 12:39 in Windows SDK with Keine Kommentare

Beim Durchstöbern des Vista-SDKs habe ich eine nette Funktion gefunden. Mit dem Flag LVS_EX_HEADERINALLVIEWS zeichnet das ListView-Control die Spaltenheader auch für alle anderen Ansichten, bisher erfolgte dies nur für die Detailansicht. Das ganze sieht dann so aus:

Spaltenheader für alle Ansichten

Developer Security Training mit Tücken

By Sven on 20.02.2006 - 09:39 in Entwicklung with 2 Kommentare

Bis vor kurzem hat Microsoft kostenlos eine DVD verschickt, in der Frank Fischer einige für den Programmierer wichtige Sicherheitsaspekte bei der Entwicklung beschreibt. Ein Schwerpunkt ist die Beschreibung von Pufferüberläufen und Strategien zu ihrer Vermeidung. In kleinen Codebeispielen wird demonstriert, was bei Pufferüberläufen passiert und was der Entwickler dagegen tun kann.

Pufferüberläufe treten sehr häufig bei Stringfunktionen auf, weil beim Kopieren von Strings oft nicht geprüft wird, ob der Zielpuffer groß genug für die Aufnahme des Strings ist. Abhilfe schaffen hier die sicheren Stringfunktionen, welche die Zielpuffergröße immer beachten und sicherstellen, dass der Zielstring explizit mit einem NULL-Zeichen abgeschlossen wird. Zur Demonstration der sicheren Stringfunktionen wird folgendes Beispiel verwendet:

void DisplayResult(HRESULT hr);

int _tmain(int argc, TCHAR *argv[])
{
    TCHAR pszDest[10];

    // String size OK
    HRESULT hr = StringCchCopy(pszDest, sizeof(pszDest), "Hello");
    DisplayResult(hr);

    // String size TOO big
    hr = StringCchCopy(pszDest, sizeof(pszDest), "This is a rather long string");
    DisplayResult(hr);

    TCHAR pszFullName[25] = "Oliver";
    TCHAR *pszSrc = " Hughes";

    // Resultant string less than 25
    hr = StringCchCat(pszFullName, sizeof(pszFullName), pszSrc);
    DisplayResult(hr);

    // Now we'll add more to the string
    hr = StringCchCat(pszFullName, sizeof(pszFullName), " James Hughes ");
    DisplayResult(hr);

    printf("\nPress a key to continue\n");
    getch();

    return 1;
}

Der jeweils zweite Parameter bei StringCchCopy und StringCchCat gibt die Größe des Puffers an. Die Funktion DisplayResult ermittelt die Beschreibung für die von den Stringfunktionen zurückgegebenen Fehlercodes und gibt diese in einem Konsolenfenster aus:

Der Vorgang wurde erfolgreich beendet.
Der an einen Systemaufruf übergebene Datenbereich ist zu klein.
Der Vorgang wurde erfolgreich beendet.
Der an einen Systemaufruf übergebene Datenbereich ist zu klein.

Bis hier sieht alles ganz vernünftig aus, richtig gefährlich wird es aber, wenn man das vorgestellte Beispiel mit Unicode kompiliert. Hierfür müssen lediglich alle Strings als Unicode gekennzeichnet werden, zusätzlich wird printf durch das Makro _tprintf ersetzt. Durch den konsequenten Einsatz der Wrapper-Makros aus TCHAR.H ist es sehr einfach, Ansi- und Unicode-Version einer Anwendung in einem Quelltext zu verwalten. Anhand der Verwendung von _tmain als Hauptfunktion und TCHAR als universellen Datentyp für Ansi-/Unicode-Zeichen sieht man, dass der Autor des Beispiels eine Verwendung für Unicode bereits angedacht hat.

void DisplayResult(HRESULT hr);

int _tmain(int argc, TCHAR *argv[])
{
    TCHAR pszDest[10];

    // String size OK
    HRESULT hr = StringCchCopy(pszDest, sizeof(pszDest), _T("Hello"));
    DisplayResult(hr);

    // String size TOO big
    hr = StringCchCopy(pszDest, sizeof(pszDest), _T("This is a rather long string"));
    DisplayResult(hr);

    TCHAR pszFullName[25] = _T("Oliver");
    TCHAR *pszSrc = _T(" Hughes");

    // Resultant string less than 25
    hr = StringCchCat(pszFullName, sizeof(pszFullName), pszSrc);
    DisplayResult(hr);

    // Now we'll add more to the string
    hr = StringCchCat(pszFullName, sizeof(pszFullName), _T(" James Hughes "));
    DisplayResult(hr);

    _tprintf(_T("\nPress a key to continue\n"));
    getch();

    return 1;
}

Führt man das Programm nun aus, dann werden folgende Meldungen ausgegeben:

Der Vorgang wurde erfolgreich beendet.
Der an einen Systemaufruf übergebene Datenbereich ist zu klein.
Der Vorgang wurde erfolgreich beendet.
Der Vorgang wurde erfolgreich beendet.

Anschließend erscheint ein Hinweis von Windows:

—————————
test.exe – Fehler in Anwendung
—————————
Die Anweisung in “0x00680074” verweist auf Speicher in “0x00000001”. Der Vorgang

“written” konnte nicht auf dem Speicher durchgeführt werden.

Klicken Sie auf “OK”, um das Programm zu beenden.
Klicken Sie auf “Abbrechen”, um das Programm zu debuggen.
—————————
OK Abbrechen
—————————

Nun haben wir das, was eigentlich durch die Verwendung von sicheren Stringfunktionen vermieden werden sollte – einen waschechten Pufferüberlauf. Schaut man sich die Definition von StringCchCopy und StringCchCat nämlich genauer an, dann sieht man, dass diese Funktionen die Größe des Puffers in Zeichen und NICHT in Bytes haben möchten. Umso verwunderlicher ist das ganze, da in einem späteren Kapitel des Vortrags zu möglichen Unicode-Problemen mehrfach explizit darauf hingewiesen wird, dass Puffergrößen bei Unicode-Strings immer mit sizeof(WCHAR) berechnet werden sollen.

Im obigen Beispiel soll den beiden sicheren Stringfunktionen zugesichert werden, dass 10 beziehungsweise 25 Zeichen für den Text zur Verfügung stehen. In der Ansi-Version funktioniert das auch wunderbar, da hier ein Zeichen nur ein Byte groß ist und der sizeof-Operator 10 bzw. 25 zurückgibt. Unicode-Zeichen sind aber immer zwei Bytes groß, so dass der sizeof-Operator im Beispiel 20 bzw. 50 Bytes zurückgibt. Die beiden Stringfunktionen kopieren daher in Anbetracht des großen Puffers munter drauflos. Da aber in Wirklichkeit nur 10 bzw. 25 (und nicht 20 bzw. 50) Zeichen zur Verfügung stehen, kommt es zu einem Pufferüberlauf.

Vermeiden lässt sich das ganze, indem man bei Pufferangaben von Zeichenketten statt sizeof konsequent das Makro countof verwendet. Hierbei wird die von sizeof zurückgegebene Größe durch die Größe des ersten Elements geteilt, welches bei Ansi-Strings ein Byte und bei Unicode-Strings zwei Byte beträgt.

void DisplayResult(HRESULT hr);

#define countof(array) (sizeof(array)/sizeof(array[0]))

int _tmain(int argc, TCHAR *argv[])
{
    TCHAR pszDest[10];

    // String size OK
    HRESULT hr = StringCchCopy(pszDest, countof(pszDest), _T("Hello"));
    DisplayResult(hr);

    // String size TOO big
    hr = StringCchCopy(pszDest, countof(pszDest), _T("This is a rather long string"));
    DisplayResult(hr);

    TCHAR pszFullName[25] = _T("Oliver");
    TCHAR *pszSrc = _T(" Hughes");

    // Resultant string less than 25
    hr = StringCchCat(pszFullName, countof(pszFullName), pszSrc);
    DisplayResult(hr);

    // Now we'll add more to the string
    hr = StringCchCat(pszFullName, countof(pszFullName), _T(" James Hughes "));
    DisplayResult(hr);

    _tprintf(_T("\nPress a key to continue\n"));
    getch();

    return 1;
}

Die sicheren Stringfunktionen sind eine feine Sache und helfen bei richtiger Anwendung enorm, mögliche Pufferüberläufe schon im Ansatz zu bekämpfen. Es ist allerdings sehr schade, dass gerade dieses Beispiel auf der DVD einen solchen Patzer enthält. Entwickler, die beim Anschauen zum ersten Mal mit sicheren Stringfunktionen in Kontakt geraten, werden sich vermutlich am gezeigten Beispiel orientieren und damit unbeachsichtigt wieder potentielle Pufferüberläufe einbauen, die sie eigentlich vermeiden wollten.

Auf der Rückseite der DVD steht übrigens:

Start training to become our Security Champion! Pay close attention to the contents of this DVD and you’ll be ready to tackle almost any security question in the future. Have fun honing your skills!

Hoffentlich verlässt sich der eine oder andere Entwickler nicht zu sehr darauf.

Sichere Stringfunktionen

By Sven on 19.12.2005 - 11:48 in Windows SDK with Keine Kommentare

Seit SpeedCommander 10.1 verwende ich selbst nur noch sichere Stringfunktionen. Im Gegensatz zu den herkömmlichen Funktionen lstrcpy und lstrcpyn muss hier immer die Größe des Zielpuffers angegeben werden. Somit wird vermieden, dass über den Puffer hinaus geschrieben wird. Außerdem wird sichergestellt, dass der Zielpuffer in jedem Fall mit einem NULL-Zeichen abgeschlossen wird.

Nach der von Secunia entdeckten Schwachstelle in den Packfunktionen habe ich nun auch den von Rainer übernommenen Quellcode komplett auf die sicheren Stringfunktionen umgestellt, damit so etwas nicht noch einmal passiert. Bei der 64bit-Version der CxCab60u.dll gab es jedoch ein kleines Problem:

1>Linking...
1>fci_x64.lib(buildcab.obj) : error LNK2005: StringCopyWorkerA already defined in strsafe.lib(strsafe.obj)
1>fci_x64.lib(buildcab.obj) : error LNK2005: StringLengthWorkerA already defined in strsafe.lib(strsafe.obj)
1>fci_x64.lib(buildcab.obj) : error LNK2005: StringCchCopyA already defined in strsafe.lib(strsafe.obj)
1>fci_x64.lib(buildcab.obj) : error LNK2005: StringCatWorkerA already defined in strsafe.lib(strsafe.obj)
1>fci_x64.lib(buildcab.obj) : error LNK2005: StringCchCatA already defined in strsafe.lib(strsafe.obj)
1>fdi_x64.lib(fdi.obj) : error LNK2005: StringCopyWorkerA already defined in strsafe.lib(strsafe.obj)
1>fdi_x64.lib(fdi.obj) : error LNK2005: StringLengthWorkerA already defined in strsafe.lib(strsafe.obj)
1>fdi_x64.lib(fdi.obj) : error LNK2005: StringCchCopyA already defined in strsafe.lib(strsafe.obj)
1>fdi_x64.lib(fdi.obj) : error LNK2005: StringCatWorkerA already defined in strsafe.lib(strsafe.obj)
1>fdi_x64.lib(fdi.obj) : error LNK2005: StringCchCatA already defined in strsafe.lib(strsafe.obj)
1>   Creating library .\objs\x64\Unicode Release\CxCab60u.lib and object .\objs\x64\Unicode Release\CxCab60u.exp
1>..\..\lib\x64\CxCab60u.dll : fatal error LNK1169: one or more multiply defined symbols found

Die Bibliotheken für die CAB-Funktionen aus dem Platform SDK (fci.lib und fdi.lib) enthalten bereits einige Funktionen aus der strsafe.lib. Um den Linker doch noch zu einer Zusammenarbeit zu überreden, muss man als zusätzliche Linkeroption “/FORCE:MULTIPLE” angeben. Damit wird der Linker angewiesen, die zweite Definition zu ignorieren:

1>Linking...
1>fci_x64.lib(buildcab.obj) : warning LNK4006: StringCopyWorkerA already defined in strsafe.lib(strsafe.obj); second definition ignored
1>fci_x64.lib(buildcab.obj) : warning LNK4006: StringLengthWorkerA already defined in strsafe.lib(strsafe.obj); second definition ignored
1>fci_x64.lib(buildcab.obj) : warning LNK4006: StringCchCopyA already defined in strsafe.lib(strsafe.obj); second definition ignored
1>fci_x64.lib(buildcab.obj) : warning LNK4006: StringCatWorkerA already defined in strsafe.lib(strsafe.obj); second definition ignored
1>fci_x64.lib(buildcab.obj) : warning LNK4006: StringCchCatA already defined in strsafe.lib(strsafe.obj); second definition ignored
1>fdi_x64.lib(fdi.obj) : warning LNK4006: StringCopyWorkerA already defined in strsafe.lib(strsafe.obj); second definition ignored
1>fdi_x64.lib(fdi.obj) : warning LNK4006: StringLengthWorkerA already defined in strsafe.lib(strsafe.obj); second definition ignored
1>fdi_x64.lib(fdi.obj) : warning LNK4006: StringCchCopyA already defined in strsafe.lib(strsafe.obj); second definition ignored
1>fdi_x64.lib(fdi.obj) : warning LNK4006: StringCatWorkerA already defined in strsafe.lib(strsafe.obj); second definition ignored
1>fdi_x64.lib(fdi.obj) : warning LNK4006: StringCchCatA already defined in strsafe.lib(strsafe.obj); second definition ignored
1>   Creating library .\objs\x64\Unicode Release\CxCab60u.lib and object .\objs\x64\Unicode Release\CxCab60u.exp
1>..\..\lib\x64\CxCab60u.dll : warning LNK4088: image being generated due to /FORCE option; image may not run
1>Embedding manifest...
1>CxCab - 0 error(s), 11 warning(s)

Verkürzte Baumansicht

By Sven on 26.10.2005 - 09:50 in Entwicklung with 1 Kommentar

Windows bietet für die Anzeige von Elementen in der Baum- und Listenansicht zwei Möglichkeiten an. Die anzuzeigenden Informationen können entweder gleich beim Einfügen festgelegt werden oder später durch Nachfrage, wenn Windows die Informationen zum Anzeigen oder Sortieren benötigt. Zweiteres ist deutlich effektiver, wenn viele Elemente in die Ansicht eingefügt werden und die Informationen nicht sofort zur Verfügung stehen und erst ermittelt werden müssen.

Zur Abfrage von Name und Symbol verschickt die Baumansicht eine TVN_GETDISPINFO-Nachricht an das Elternfenster und dieses füllt dann die mitgelieferte TVITEM-Struktur mit den gewünschten Daten. Relevant für den Namen sind die Einträge pszText und cchTextMax:

pszText: Pointer to a null-terminated string that contains the item text if the structure specifies item attributes. If this member is the LPSTR_TEXTCALLBACK value, the parent window is responsible for storing the name. In this case, the tree-view control sends the parent window a TVN_GETDISPINFO notification message when it needs the item text for displaying, sorting, or editing and a TVN_SETDISPINFO notification message when the item text changes. If the structure is receiving item attributes, this member is the address of the buffer that receives the item text. Note that although the tree-view control allows any length string to be stored as item text, only the first 260 characters are displayed.

cchTextMax: Size of the buffer pointed to by the pszText member, in characters. If this structure is being used to set item attributes, this member is ignored.

Die Baumansicht kann durch Setzen des Flags TVIF_DI_SETITEM veranlasst werden, die abgefragte Information zu speichern. In diesem Fall erfolgt keine weitere Abfrage, wenn der Eintrag wieder angezeigt werden soll, stattdessen wird die gespeicherte Information verwendet.

Ein Anwender hat nun festgestellt, dass die Ordnernamen in der Baumansicht in SpeedCommander 11 nach ca. 70 Zeichen abgeschnitten werden. Im Debugger sah ich dann auch schnell den Grund dafür. Beim ersten Eintrag war der von der Baumansicht zur Verfügung gestellte Puffer 260 Zeichen groß, ab dem zweiten aber nur noch 64 Zeichen. Somit können auch nur die ersten 63 Zeichen des Ordnernamens in den Puffer kopiert und angezeigt werden.

Doch warum beschränkt die Baumansicht den Puffer ab dem zweiten Eintrag auf 64 Zeichen? Das ganze muss etwas mit der Einfügeposition in der TVINSERTSTRUCT-Struktur zu tun haben. SpeedCommander verwendet hier TVI_SORT für hInsertAfter, die Einträge werden so gleich beim Einfügen alphabetisch sortiert. Wenn ich den Wert nun auf TVI_LAST ändere, dann ist der Textpuffer in der TVN_GETDISPINFO-Nachricht bei allen Elementen plötzlich 260 Zeichen groß.

Wenn man sich das nun noch einmal in Ruhe durch den Kopf gehen lässt, dann ist die Erklärung für das Abschneiden der Ordnernamen auch recht einfach. Um die Elemente gleich beim Einfügen nach dem Namen zu sortieren, muss die Baumansicht auch die Namen der Elemente kennen. Diese werden über die TVN_GETDISPINFO-Nachricht ermittelt, die Baumansicht erachtet hier anscheinend einen Puffer von 64 Zeichen als ausreichend, um die Elemente sortieren zu können. Durch Setzen des Flags TVIF_DI_SETITEM werden die für die Sortierung ermittelten Einträge auch gleich gespeichert und die Anzeigefunktion greift nun auf den auf 63 Zeichen begrenzten Text zurück.

Beheben lässt sich das ganze, indem man auf die automatische Sortierung beim Einfügen verzichtet. Der eigentliche Fehler liegt aber eher in der Baumansicht selbst, der Puffer für die Sortierung sollte in der nächsten Windows-Version auf die üblichen 260 Zeichen vergrößert werden.

FormatMessage und Ausrufezeichen

By Sven on 09.09.2005 - 08:34 in Entwicklung with 1 Kommentar

Während des Debuggens der x64-Version von SpeedCommander 11 wurde mir beim Start eine Fehlermeldung “Out of memory” angezeigt. Der Auslöser der Meldung war eine FormatMessage-Anweisung, mit deren Hilfe Argumente in Strings eingefügt werden können. Der Vorteil von FormatMessage gegenüber dem bekannteren printf liegt darin, dass sich bei FormatMessage die Reihenfolge der Argumente im Text variabel gestalten lässt. Jeder Platzhalter beginnt mit %n, so dass man z.B. bei mehrsprachigen Anwendungen flexibel bei der Übersetzung ist und auch mal ein %2 vor ein %1 setzen kann. Bei printf muss dagegen die Reihenfolge der Argumente stets eingehalten werden.

Die Fehlermeldung kam bei folgendem Text:

Thank you for evaluating %1!

%1 wird hier durch den aktuellen Programmnamen ersetzt. Warum Windows hier die Arbeit verweigert wird erst deutlich, wenn man sich die erweiterte Syntax für die Platzhalter näher anschaut. In der Regel benutzt man als Argumente Zeichenketten, so dass hier die Angabe von %1%99 ausreicht. Will man jedoch andere Datentypen benutzen, so muss man die Platzhalter entsprechend formatieren. Dies geschieht durch %n!format!, die Typbezeichner sind äquivalent zur Funktion wsprintf.

Windows erwartete also statt es normalen Ausrufezeichens einen Typbezeichner. Der Sonderfall eines direkt einem Platzhalter folgenden Ausrufezeichens wird in der Dokumentation übrigens auch erwähnt:

%! – A single exclamation point in the formatted message text. This format string can be used to include an exclamation point immediately after an insert without its being mistaken for the beginning of a printf format string.

Der Vorlagentext muss also korrekterweise so lauten:

Thank you for evaluating %1%!

Bei den bisherigen Tests auf deutschen Systemen trat der Fehler übrigens nicht auf, weil sich hier zwischen Platzhalter und Ausrufezeichen mindestens ein anderes Zeichen befindet:

Vielen Dank dafür, dass Sie %1 ausprobieren!

GetMenuItemInfo und x64

By Sven on 22.08.2005 - 20:59 in Windows SDK with 2 Kommentare

Das Platform SDK enthält alle (dokumentierten) Definitionen der Windows-API, die für die Entwicklung von Windows-Anwendungen nötig sind. Im Laufe der Zeit und der vielen Windows-Versionen kommen immer wieder ein paar neue Definitionen hinzu, häufig werden ältere Definitionen auch erweitert. Damit die Windows-Version, auf dem die Anwendung dann läuft, auch weiß, wie groß die Strukturdaten sind, die bei einem Aufruf einer API-Funktion übergeben werden, enthält eine Strukturdefinition in den meisten Fällen als ersten Wert die Größe der Struktur. So kann das Betriebssystem entscheiden, welche Daten in der Struktur gültig sind und von welchen Daten es zwecks Vermeidung eines Zugriffsfehlers die Finger lassen sollte.

In der Regel läuft das alles auch problemlos, eine recht heikle Funktion ist aber GetMenuItemInfo im Zusammenhang mit der MENUITEMINFO-Struktur:

typedef struct tagMENUITEMINFO
{
    UINT     cbSize;
    UINT     fMask;
    UINT     fType;         // used if MIIM_TYPE (4.0) or MIIM_FTYPE (>4.0)
    UINT     fState;        // used if MIIM_STATE
    UINT     wID;           // used if MIIM_ID
    HMENU    hSubMenu;      // used if MIIM_SUBMENU
    HBITMAP  hbmpChecked;   // used if MIIM_CHECKMARKS
    HBITMAP  hbmpUnchecked; // used if MIIM_CHECKMARKS
    ULONG_PTR dwItemData;   // used if MIIM_DATA
    LPSTR    dwTypeData;    // used if MIIM_TYPE (4.0) or MIIM_STRING (>4.0)
    UINT     cch;           // used if MIIM_TYPE (4.0) or MIIM_STRING (>4.0)
#if(WINVER >= 0x0500)
    HBITMAP  hbmpItem;      // used if MIIM_BITMAP
#endif /* WINVER >= 0x0500 */
}   MENUITEMINFO, FAR *LPMENUITEMINFO;

Diese Struktur wurde für Windows 2000 und Windows ME erweitert, wie man an

#if(WINVER >= 0x0500) 
    HBITMAP hbmpItem; // used if MIIM_BITMAP 
#endif /* WINVER >= 0x0500 */ 

sieht. Durch das Makro WINVER lässt sich steuern, ab welcher Windows-Version die Anwendung laufen soll. Dieses und einige weitere Makros sind über die ganzen API-Definitionen verstreut. Definiert man WINVER z.B. mit 0x0400, damit die Anwendung auch unter Windows 95 und NT 4 lauffähig ist, werden sämtliche zusätzliche Definitionen ausgeblendet. Dies hat aber den Nachteil, dass man neue Funktionen und Flags überhaupt nicht mehr benutzen kann.

Daher setze ich WINVER immer auf 0x0501 und importiere Funktionen, die erst ab einer bestimmten Windows-Version verfügbar sind, immer dynamisch. Somit laufen meine Anwendungen alle ab Windows 95/NT4, sofern zumindestens der Internet Explorer 5.5 installiert ist. Dieser enthält einige wichtige Systemerweiterungen, die von Entwicklerseite nicht einzeln redistributierbar sind.

Bei der Funktion GetMenuItemInfo gibt es aber einen kleinen Stolperstein. Übergibt man hier als Strukturgröße in cbSize die neue Größe, dann verweigern Windows 95 und NT4 mit der Fehlermeldung 87 (Ungültiger Parameter) die Arbeit. Beide Systeme verlangen explizit die alte und ihnen bekannte Strukturgröße ohne das zusätzliche hbmpItem.

Als Workaround hatte ich mir daher vor langer Zeit einmal ein paar Definitionen zusammengestellt, mit deren Hilfe dieses Problem umgangen wird:

#define MENUITEMINFO_SIZE_VERSION_400A  CDSIZEOF_STRUCT(MENUITEMINFOA, cch)
#define MENUITEMINFO_SIZE_VERSION_400W  CDSIZEOF_STRUCT(MENUITEMINFOW, cch)
#define MENUITEMINFO_SIZE_VERSION_500A  sizeof(MENUITEMINFOA)
#define MENUITEMINFO_SIZE_VERSION_500W  sizeof(MENUITEMINFOW)

#ifdef UNICODE
    #define MENUITEMINFO_SIZE_VERSION_400  MENUITEMINFO_SIZE_VERSION_400W
    #define MENUITEMINFO_SIZE_VERSION_500  MENUITEMINFO_SIZE_VERSION_500W
#else
    #define MENUITEMINFO_SIZE_VERSION_400  MENUITEMINFO_SIZE_VERSION_400A
    #define MENUITEMINFO_SIZE_VERSION_500  MENUITEMINFO_SIZE_VERSION_500A
#endif

#define MENUITEMINFO_SIZE		MENUITEMINFO_SIZE_VERSION_400

Da ich die zusätzliche Strukturvariable hbmpItem fast nie benötige, verwende ich für die Größe der Struktur in cbSize immer MENUITEMINFO_SIZE, somit funktioniert die Funktion problemlos auf allen Systemen ab Windows 95.

Beim Testen der x64-Version vom SpeedCommander musste ich aber leider feststellen, dass nun Windows XP x64 an dieser Stelle herumzickt. Alle Aufrufe von GetMenuItemInfo wurden wieder mit Fehler 87 zurückgegeben. Ändert man die Strukturgröße jedoch auf MENUITEMINFO_SIZE_VERSION_500, dann funktioniert es unter Windows XP x64 wieder wie erwartet. Eine kleine Erweiterung der obigen Definition schafft Abhilfe:

#define MENUITEMINFO_SIZE_VERSION_400A  CDSIZEOF_STRUCT(MENUITEMINFOA, cch)
#define MENUITEMINFO_SIZE_VERSION_400W  CDSIZEOF_STRUCT(MENUITEMINFOW, cch)
#define MENUITEMINFO_SIZE_VERSION_500A  sizeof(MENUITEMINFOA)
#define MENUITEMINFO_SIZE_VERSION_500W  sizeof(MENUITEMINFOW)

#ifdef UNICODE
    #define MENUITEMINFO_SIZE_VERSION_400  MENUITEMINFO_SIZE_VERSION_400W
    #define MENUITEMINFO_SIZE_VERSION_500  MENUITEMINFO_SIZE_VERSION_500W
#else
    #define MENUITEMINFO_SIZE_VERSION_400  MENUITEMINFO_SIZE_VERSION_400A
    #define MENUITEMINFO_SIZE_VERSION_500  MENUITEMINFO_SIZE_VERSION_500A
#endif

#ifdef _M_IX86
    #define MENUITEMINFO_SIZE		MENUITEMINFO_SIZE_VERSION_400
#else
    #define MENUITEMINFO_SIZE		MENUITEMINFO_SIZE_VERSION_500
#endif

Beim Kompilieren für 32bit-Systeme wird wie bisher immer die Größe der alten Struktur verwendet, beim Kompilieren für 64bit-Systeme dagegen die Größe der neuen Struktur.

Grenzgänge

By Sven on 02.08.2005 - 19:21 in Entwicklung

Unter Windows XP x64 erfolgt eine konsequente Trennung zwischen 32bit-Anwendungen und nativen 64bit-Anwendungen. Eine 64bit-Anwendung kann keine 32bit-Dll laden – damit haben momentan alle Entwickler zu kämpfen, die ihre 32bit-Module in das Kontextmenü des 64bit-Explorers einbinden möchten. Die einzige Lösung hierfür ist, die Shellerweiterung als 64bit-Version zu kompilieren.

Auf ein ähnliches Problem stößt man, wenn man eine native 64bit-Anwendung ausliefern möchte und einige Module nur als 32bit-Dll zur Verfügung stehen. Wenn die Module nicht überlebenswichtig sind, kann man sie vorerst auch ausklammern. Ein Beispiel hierfür ist Squeez, der in der 64bit-Ausgabe keine Anzeige von Bildern in der Schnellansicht erlaubt und auch keine ACE-Archive entpacken kann. Von den WinACE-Entwickler war bisher leider noch nicht zu erfahren, ob bzw. wann eine x64-Fassung der UnAceV2.dll zu erwarten ist. Mit der zunehmend immer geringeren Verbreitung von ACE-Archiven lässt sich die fehlende UnACE-Unterstützung aber sicher verschmerzen.

Ganz anders sieht es mit der Schnellansicht von Bildern aus, die im SpeedCommander noch eine sehr viel größere Bedeutung hat. Hierfür verwende ich seit einigen Jahren die PictView-Dll von Jan Patera. Diese Dll kann über 50 verschiedene Bildformate lesen und ist gerade einmal 191KB groß. Auf meine Anfrage zu einer x64-Version teilte mir Jan leider mit, dass er vorerst keine Umsetzung auf 64bit plane. Die Dll selbst benutzt viel Assemblercode, so dass eine Portierung auch nicht ganz einfach ist.

Beim Stöbern in der MSDN-Library entdeckte ich folgenden Satz:

On 64-bit Windows, a 64-bit process cannot load a 32-bit dynamic-link library (DLL). Additionally, a 32-bit process cannot load a 64-bit DLL. However, 64-bit Windows supports remote procedure calls (RPC) between 64-bit and 32-bit processes (both on the same computer and across computers). On 64-bit Windows, an out-of-process 32-bit COM server can communicate with a 64-bit client, and an out-of-process 64-bit COM server can communicate with a 32-bit client. Therefore, if you have a 32-bit DLL that is not COM-aware, you can wrap it in an out-of-process COM server and use COM to marshal calls to and from a 64-bit process.

“Gesagt, tun getan” pflegte Onkel Hotte immer zu sagen. Mit externen COM-Servern hatte ich bisher noch keinerlei Erfahrung, aber glücklicherweise bietet das Visual Studio 2005 mit der ATL dafür eine umfangreiche Unterstützung dafür. Ein COM-Server ist mit wenigen Handgriffen erstellt. Zusätzlich benötigt man noch eine Proxy-Dll, deren Projekt auch gleich generiert wird.

Das COM-Objekt definiert eine einfache Schnittstelle für den Zugriff auf die PictView-Dll. Es bekommt den Namen für die anzuzeigende Datei übergeben, lädt die Dll und bekommt dann von der Dll ein Bitmap (HBITMAP) zurück. Dieses wird in ein DIB (Device Independent Bitmap) konvertiert und an den Aufrufer zurückgegeben. Die Konvertierung in ein DIB ist notwendig, da das Marshaling zwischen dem externen COM-Server und dem Client bei einigen Grafiken nicht korrekt funktioniert, wenn ein HBITMAP übertragen wird. Vermutlich gibt es da noch einen kleinen Fehler im Windows-Marshaling-Code für HBITMAPs.

Damit ist nun auch die letzte große Hürde für die 64bit-Version von SpeedCommander genommen, die nun bis auf die fehlende UnACE-Unterstützung keinerlei Einschränkungen gegenüber der 32bit-Version mehr hat.

Quellcodestatistik

By Sven on 04.06.2005 - 19:46 in Entwicklung with 7 Kommentare

Es ist immer wieder interessant, wieviel Zeilen Quellcode sich hinter einem Projekt verstecken; es ist aber recht mühsam, dies Datei für Datei zusammenzurechnen. Für das Visual Studio gibt es ein AddIn, das einem diese Arbeit abnimmt. Ich habe den LineCounter mal auf den sich in Entwicklung befindlichen SpeedCommander 11 inklusive aller enthaltenen Module (aber ohne MFC) angesetzt. Folgende Resultate kamen dabei heraus:

Projekttyp Zeilen Code Kommentare Gemixt Leer
Anwendungen & MxLibs 647349 376359 (58%) 172428 (26%) 18161 (2%) 116723 (18%)
FxLibs (Sync & Search) 31064 21578 (69%) 5756 (18%) 1178 (3%) 4908 (15%)
CxLibs (Packer) 165323 113445 (68%) 27087 (16%) 4367 (2%) 29158 (17%)
  843736 511382 (61%) 205271 (24%) 23706 (3%) 150789 (18%)

In der ersten Zeile ist mein Code aufgelistet, die anderen beiden Zeilen zeigen die von Rainer übernommenen Quellen. Wenn man sich überlegt, dass Windows XP aus rund 38 Millionen Quellcodezeilen besteht, dann ist der SpeedCommander mit einer knappen Million mittlerweile doch schon ein ziemlich großes Projekt geworden.

Top