SysLink-Control unter Windows 2000

Mit Hilfe des SysLink-Controls lassen sich anklickbare Hypertext-Elemente in einem Fenster darstellen. Die Formatierung des Textes erfolgt mit Hilfe von <a>anklickbarer Text</a>-Tags. Bei Aktivierung durch Maus oder Tastatur wird das Elternfenster informiert und es kann die jeweilige Aktion durchführen. Leider ist das SysLink-Control erst ab der ComCtl32.dll V6 enthalten und lässt sich so nur ab Windows XP verwenden.

Unter Windows 2000 gibt es ebenfalls ein SysLink-Control. Es ist aber nicht Bestandteil der ComCtl32.dll, die Implementierung erfolgt in der Shell32.dll. Mit Hilfe der Funktion LinkWindow_RegisterClass lässt sich das Control registrieren und verwenden. Der hauptsächliche Unterschied zum SysLink-Control von Windows XP ist der Name der Fensterklasse. Unter Windows XP heißt das Control SysLink, unter Windows 2000 Link Window. Zudem unterstützt die Windows 2000-Implementierung keine HREF und ID-Attribute innerhalb des Anchor-Tags.

Die verschiedenen Fensterklassen-Namen erschweren die Verwendung von SysLink-Controls in Dialogvorlagen. Der Dialog kann nur erzeugt werden, wenn alle verwendeten Fensterklassen registriert sind. Der Dialogeditor von Visual Studio verwendet SysLink als Fensterklasse, das verhindert aber die Anzeige des Dialogs unter Windows 2000.

Mit einem kleinen Trick lässt sich das Problem recht einfach lösen. Beim Start der Anwendung wird geprüft, ob die Fensterklasse SysLink existiert. Wenn nicht, dann wird mit Hilfe von LinkWindow_RegisterClass die Fensterklasse Link Window registriert. Anschließend ruft man die Fensterklassen-Daten für Link Window ab, ändert den Namen auf SysLink und registriert die Klasse unter dem neuen Namen. Nun kann man auch unter Windows 2000 mit SysLink arbeiten.

Das ganze sieht dann so aus:

  1. BOOL RegisterCommonControls()
  2. {
  3. // Common Controls initialisieren
  4. INITCOMMONCONTROLSEX initCCex;
  5. initCCex.dwSize = sizeof(initCCex);
  6. initCCex.dwICC = ICC_WIN95_CLASSES | ICC_LINK_CLASS;
  7.  
  8. // Common Controls initialisieren
  9. InitCommonControlsEx(&initCCex);
  10.  
  11. // Erfolg?
  12. BOOL fSuccess = FALSE;
  13.  
  14. // Fensterklasse abrufen
  15. WNDCLASSW wndLinkClass;
  16. if (!GetClassInfoW(NULL, L"SysLink", &wndLinkClass))
  17. {
  18. // Registrierungsfunktion
  19. typedef BOOL (WINAPI* pfnLinkWindowRegisterProc)();
  20.  
  21. // Shell32-Dll laden
  22. HINSTANCE hModShell32 = LoadLibraryW(L"shell32.dll");
  23. if (NULL != hModShell32)
  24. {
  25. // Funkion importieren
  26. pfnLinkWindowRegisterProc pfnLinkWindowRegister = reinterpret_cast<pfnLinkWindowRegisterProc>(GetProcAddress(hModShell32, MAKEINTRESOURCEA(258)));
  27. if (NULL != pfnLinkWindowRegister)
  28. {
  29. fSuccess = pfnLinkWindowRegister();
  30. }
  31.  
  32. // Dll freigeben
  33. FreeLibrary(hModShell32);
  34. }
  35.  
  36. // Fensterklasse fuer "Link Window" registriert
  37. if (fSuccess)
  38. {
  39. // Fensterklasse abrufen
  40. if (GetClassInfoW(NULL, L"Link Window", &wndLinkClass))
  41. {
  42. // Fensterklasse auf SysLink setzen
  43. wndLinkClass.lpszClassName = L"SysLink";
  44.  
  45. // Fensterklasse registrieren
  46. if (!RegisterClassW(&wndLinkClass))
  47. {
  48. fSuccess = FALSE;
  49. }
  50. }
  51.  
  52. // Fehler beim Abruf der Fensterklasse
  53. else
  54. {
  55. fSuccess = FALSE;
  56. }
  57. }
  58. }
  59.  
  60. // Fensterklasse existiert bereits
  61. else
  62. {
  63. fSuccess = TRUE;
  64. }
  65.  
  66. // Erfolg
  67. return fSuccess;
  68. }

IID_PPV_ARGS

Beim Stöbern in den Beispielen zum neuen Platform SDK ist mir das Makro IID_PPV_ARGS aufgefallen, welches häufig bei QueryInterface- und CoCreateInstance-Aufrufen benutzt wird. Die Definition des Makros ist in der Headerdatei objbase.h zu finden und schaut wie folgt aus:

  1. // IID_PPV_ARGS(ppType)
  2. // ppType is the variable of type IType that will be filled
  3. //
  4. // RESULTS in: IID_IType, ppvType
  5. // will create a compiler error if wrong level of indirection is used.
  6. //
  7. extern "C++"
  8. {
  9. template<typename T> void** IID_PPV_ARGS_Helper(T** pp)
  10. {
  11. // make sure everyone derives from IUnknown
  12. static_cast<IUnknown*>(*pp);
  13.  
  14. return reinterpret_cast<void**>(pp);
  15. }
  16. }
  17.  
  18. #define IID_PPV_ARGS(ppType) __uuidof(**(ppType)), IID_PPV_ARGS_Helper(ppType)

Das Makro stellt sicher, dass QueryInterface und CoCreateInstance immer die zum angeforderten Objekt passende Schnittstellen-IID übergeben bekommen.

Anstatt

  1. ...
  2. IShellLinkPtr pShellLink = NULL;
  3. if (FAILED(CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void**) &pShellLink)))
  4. return FALSE;
  5. ...

schreibt man daher besser

  1. ...
  2. IShellLinkPtr pShellLink = NULL;
  3. if (FAILED(CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pShellLink))))
  4. return FALSE;
  5. ...

und verhindert so schon mögliche Fehler während des Quellcode-Schreibens.

DWORDLONG oder LONGLONG

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

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

Sichere Stringfunktionen

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)

GetMenuItemInfo und x64

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:

  1. typedef struct tagMENUITEMINFO
  2. {
  3. UINT cbSize;
  4. UINT fMask;
  5. UINT fType; // used if MIIM_TYPE (4.0) or MIIM_FTYPE (>4.0)
  6. UINT fState; // used if MIIM_STATE
  7. UINT wID; // used if MIIM_ID
  8. HMENU hSubMenu; // used if MIIM_SUBMENU
  9. HBITMAP hbmpChecked; // used if MIIM_CHECKMARKS
  10. HBITMAP hbmpUnchecked; // used if MIIM_CHECKMARKS
  11. ULONG_PTR dwItemData; // used if MIIM_DATA
  12. LPSTR dwTypeData; // used if MIIM_TYPE (4.0) or MIIM_STRING (>4.0)
  13. UINT cch; // used if MIIM_TYPE (4.0) or MIIM_STRING (>4.0)
  14. #if(WINVER >= 0x0500)
  15. HBITMAP hbmpItem; // used if MIIM_BITMAP
  16. #endif /* WINVER >= 0x0500 */
  17. } 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 0×0400, 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 0×0501 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:

  1. #define MENUITEMINFO_SIZE_VERSION_400A CDSIZEOF_STRUCT(MENUITEMINFOA, cch)
  2. #define MENUITEMINFO_SIZE_VERSION_400W CDSIZEOF_STRUCT(MENUITEMINFOW, cch)
  3. #define MENUITEMINFO_SIZE_VERSION_500A sizeof(MENUITEMINFOA)
  4. #define MENUITEMINFO_SIZE_VERSION_500W sizeof(MENUITEMINFOW)
  5.  
  6. #ifdef UNICODE
  7. #define MENUITEMINFO_SIZE_VERSION_400 MENUITEMINFO_SIZE_VERSION_400W
  8. #define MENUITEMINFO_SIZE_VERSION_500 MENUITEMINFO_SIZE_VERSION_500W
  9. #else
  10. #define MENUITEMINFO_SIZE_VERSION_400 MENUITEMINFO_SIZE_VERSION_400A
  11. #define MENUITEMINFO_SIZE_VERSION_500 MENUITEMINFO_SIZE_VERSION_500A
  12. #endif
  13.  
  14. #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:

  1. #define MENUITEMINFO_SIZE_VERSION_400A CDSIZEOF_STRUCT(MENUITEMINFOA, cch)
  2. #define MENUITEMINFO_SIZE_VERSION_400W CDSIZEOF_STRUCT(MENUITEMINFOW, cch)
  3. #define MENUITEMINFO_SIZE_VERSION_500A sizeof(MENUITEMINFOA)
  4. #define MENUITEMINFO_SIZE_VERSION_500W sizeof(MENUITEMINFOW)
  5.  
  6. #ifdef UNICODE
  7. #define MENUITEMINFO_SIZE_VERSION_400 MENUITEMINFO_SIZE_VERSION_400W
  8. #define MENUITEMINFO_SIZE_VERSION_500 MENUITEMINFO_SIZE_VERSION_500W
  9. #else
  10. #define MENUITEMINFO_SIZE_VERSION_400 MENUITEMINFO_SIZE_VERSION_400A
  11. #define MENUITEMINFO_SIZE_VERSION_500 MENUITEMINFO_SIZE_VERSION_500A
  12. #endif
  13.  
  14. #ifdef _M_IX86
  15. #define MENUITEMINFO_SIZE MENUITEMINFO_SIZE_VERSION_400
  16. #else
  17. #define MENUITEMINFO_SIZE MENUITEMINFO_SIZE_VERSION_500
  18. #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.