Windows SDK
K32GetModuleFileNameExW wird nicht gefunden
Nach der Umstellung vom Windows SDK für Vista auf das von Windows 7 zeigte das Installationsprogramm von SpeedCommander unter Windows XP und Vista beim Start plötzlich folgende Meldung an:
--------------------------- setup.exe - Einsprungpunkt nicht gefunden --------------------------- Der Prozedureinsprungpunkt "K32GetModuleFileNameExW" wurde in der DLL "KERNEL32.dll" nicht gefunden. --------------------------- OK ---------------------------
Der Grund dafür ist, dass mit Windows 7 alle Funktionen der PSAPI.DLL in den Kernel gewandert sind. Wenn man nun für Windows 7 kompiliert, dann werden alle Funktionsnamen auf die entsprechenden Kernelfunktionen gemappt. Die Implementierung findet man in der psapi.h:
// // Give teams a choice of using a downlevel version of psapi.h for an OS versions. // Teams can set C_DEFINES=$(C_DEFINES) -DPSAPI_VERSION=1 for downlevel psapi // on windows 7 and higher. We found that test code needs this capability. // #ifndef PSAPI_VERSION #if (NTDDI_VERSION >= NTDDI_WIN7) #define PSAPI_VERSION 2 #else #define PSAPI_VERSION 1 #endif #endif #if (PSAPI_VERSION > 1) #define EnumProcesses K32EnumProcesses #define EnumProcessModules K32EnumProcessModules #define EnumProcessModulesEx K32EnumProcessModulesEx #define GetModuleBaseNameA K32GetModuleBaseNameA #define GetModuleBaseNameW K32GetModuleBaseNameW #define GetModuleFileNameExA K32GetModuleFileNameExA #define GetModuleFileNameExW K32GetModuleFileNameExW ...... #endif
Die Kernelfunktionen existieren aber nur ab Windows 7. Auf älteren Systemen können sie beim Programmstart nicht aufgelöst werden und es erfolgt eine Fehlermeldung.
Zur Abhilfe definiert man einfach vor dem Einbinden der psapi.h den entsprechenden Kompatibilitätsschalter:
#define PSAPI_VERSION 1
Nun werden die Funktionen nicht mehr gemappt und wie bisher gewohnt in der PSAPI.DLL gesucht.
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:
BOOL RegisterCommonControls() { // Common Controls initialisieren INITCOMMONCONTROLSEX initCCex; initCCex.dwSize = sizeof(initCCex); initCCex.dwICC = ICC_WIN95_CLASSES | ICC_LINK_CLASS; // Common Controls initialisieren InitCommonControlsEx(&initCCex); // Erfolg? BOOL fSuccess = FALSE; // Fensterklasse abrufen WNDCLASSW wndLinkClass; if (!GetClassInfoW(NULL, L"SysLink", &wndLinkClass)) { // Registrierungsfunktion typedef BOOL (WINAPI* pfnLinkWindowRegisterProc)(); // Shell32-Dll laden HINSTANCE hModShell32 = LoadLibraryW(L"shell32.dll"); if (NULL != hModShell32) { // Funkion importieren pfnLinkWindowRegisterProc pfnLinkWindowRegister = reinterpret_cast<pfnLinkWindowRegisterProc>(GetProcAddress(hModShell32, MAKEINTRESOURCEA(258))); if (NULL != pfnLinkWindowRegister) { fSuccess = pfnLinkWindowRegister(); } // Dll freigeben FreeLibrary(hModShell32); } // Fensterklasse fuer "Link Window" registriert if (fSuccess) { // Fensterklasse abrufen if (GetClassInfoW(NULL, L"Link Window", &wndLinkClass)) { // Fensterklasse auf SysLink setzen wndLinkClass.lpszClassName = L"SysLink"; // Fensterklasse registrieren if (!RegisterClassW(&wndLinkClass)) { fSuccess = FALSE; } } // Fehler beim Abruf der Fensterklasse else { fSuccess = FALSE; } } } // Fensterklasse existiert bereits else { fSuccess = TRUE; } // Erfolg return fSuccess; }
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:
// IID_PPV_ARGS(ppType) // ppType is the variable of type IType that will be filled // // RESULTS in: IID_IType, ppvType // will create a compiler error if wrong level of indirection is used. // extern "C++" { template<typename T> void** IID_PPV_ARGS_Helper(T** pp) { // make sure everyone derives from IUnknown static_cast<IUnknown*>(*pp); return reinterpret_cast<void**>(pp); } } #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
... IShellLinkPtr pShellLink = NULL; if (FAILED(CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void**) &pShellLink))) return FALSE; ...
schreibt man daher besser
... IShellLinkPtr pShellLink = NULL; if (FAILED(CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pShellLink)))) return FALSE; ...
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:
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:
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.