64 bit Windows - Teil 8
Nach langer Pause jetzt mal wieder was über meine fortdauernde Beschäftigung mit 64-bit Windows. Diesmal will ich auf wiederkehrende Muster bei dem Problem, meinen Sourcecode 64-bit-tauglich zu machen, eingehen.
Die Sache mit dem size_t
An vielen Stellen ging mein Code in der Vergangenheit davon aus, dass die Größe eines size_t 32-bit ist, oder dasselbe ist wie die Größe eines DWORD oder long/LONG. Das stimmt unter 64-bit Windows nicht mehr, der size_t ist so groß wie ein Zeiger, also gilt folgendes:
sizeof(size_t)!=sizeof(DWORD)
Was hat das für praktische Konsequenzen? Also erstmal eine Menge casts, wo vorher keine nötig waren, wenn man warnungsfrei bleiben will.
Beispiele:
Aus
size_t stLen = _tcslen(szFoo); dwLen += stLen*sizeof(TCHAR);
wird jetzt
size_t stLen = _tcslen(szFoo); dwLen += (DWORD)stLen*sizeof(TCHAR);
oder aus
WriteFile(hFile, szPath, _tcslen(szPath), &dwWritten, NULL);
wird
WriteFile(hFile, szPath, (DWORD)_tcslen(szPath), &dwWritten, NULL);
und aus
RegSetValueExW( hKey, NULL, 0L, REG_SZ, (LPBYTE)szPath, wcslen(szPath)+1)*sizeof(wchar_t));
wird
RegSetValueExW( hKey, NULL, 0L, REG_SZ, (LPBYTE)szPath, (DWORD)(wcslen(szPath)+1)*sizeof(wchar_t));
oder aus
VERIFY(MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, (LPCSTR)name, -1, &szFullname[wcslen(szFullname)], dimof(szFullname)-wcslen(szFullname)));
wird
VERIFY(MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, (LPCSTR)name, -1, &szFullname[wcslen(szFullname)], (int)(dimof(szFullname)-wcslen(szFullname))));
Huh, das war aber einfach, oder? Hat man einen size_t aber als Teil einer Datenstruktur, die serialisiert wird, hat man jetzt ein größeres Problem.
Pointer und Handles sind jetzt 64-bittig - naja fast alle...
Das sollte man nun wirklich erwarten, dass Pointer und Handles 64-bit breit sind, schließlich wollten wir doch den großen Adreßraum, oder? Naja, aber was fangen nun 32-bittige GUI-Prozesse auf einem 64-bittigen Desktop mit 64-bit breiten Windows-Handles an? Was, wenn sie einen schlichten FindWindow machen wollen auf ein Fenster, das zu einem 64-bittigen Prozess gehört? Deswegen sind Window Handles für 64-bittige Prozesse zwar 64-bit breit, aber die oberen 32-bit davon bleiben ungenutzt (also Null). Daher funktioniert beispielsweise folgender Code (aus einer InstallShield Extension DLL) nach wie vor, egal, ob für 32-bit oder 64-bit uebersetzt:
__declspec(dllexport) LONG __cdecl FindWindowClass(HWND hwnd, LPLONG lpIValue, LPSTR lpszValue) { #pragma warning (disable: 4305 4311) Unreferenced(hwnd); Unreferenced(lpIValue); return (LONG)FindWindow(lpszValue, NULL); #pragma warning (default: 4305 4311) }
INVALID_HANDLE_VALUE und 0xFFFFFFFF
Aus irgendeinem dummen Grund habe ich an vielen Stellen in altem Code als Test fuer das Scheitern des CreateFile APIs auf die Rückgabe von 0xFFFFFFFF geprüft. Das ist auch soweit OK auf x86. Die eigentlich korrekterweise zu verwendende Konstante (der Wert INVALID_HANDLE_VALUE) ist auch als (HANDLE)-1 definiert. Dummerweise ist aber damit INVALID_HANDLE_VALUE auf x64-Systemen der numerische Wert 0xFFFFFFFFFFFFFFFF und nicht 0xFFFFFFFF. Also Moral von der Geschicht': Benutz´' die Zahlenwerte nicht, benutze die Makros aus den Headern, obwohl das erstmal gleich ausschaut.
Die Sache mit LONG_PTR und INT_PTR
Bei einer ganzen Reihe von Parametern zu APIs und MFC-Methoden wurden in den vergangenen Jahren still und heimlich die Prototypen leicht modifiziert. Das betrifft in erster Linie Parameter, über die traditionell ein Zeiger transportiert wurde, die aber als formalen Parameter einen LONG oder DWORD erwarten (die unter 64-bit Windows weiterhin 32-bit breit sind und deswegen keinen Pointer mehr transportieren können). Ein INT_PTR oder ein LONG_PTR etc.. sind also Integerwerte, die je nach Plattform so breit sind wie ein Zeiger auf der entsprechenden Plattform. Man sollte so einen Datentyp also in keinem Fall für persistentes und plattformübergreifendes Serialisieren von Daten verwenden.
Beispiele aus der Praxis:
Folgender Altcode wird nicht mehr sauber übersetzt mit einem x64-Compiler:
BOOL CCustomToolTip::AddAutoTool(CWnd *pWnd,LPCTSTR pszText) { TOOLINFO ti; ti.cbSize=sizeof(TOOLINFO); ti.uFlags=TTF_IDISHWND|TTF_SUBCLASS; ti.hwnd=pWnd->GetParent()->GetSafeHwnd(); ti.uId=(UINT)pWnd->GetSafeHwnd(); . .
Die letzte Zeile muß einfach mit einem korrekten cast versehen werden, weil der uId member der TOOLINFO Struktur jetzt ein UINT_PTR ist:
ti.uId=(UINT_PTR)pWnd->GetSafeHwnd();
Ähnliches bei der NEWCPLINFO Struktur für Control Panel Applets. Folgender Altcode wird nicht mehr sauber übersetzt mit einem x64-Compiler:
LRESULT CCPApplet::OnNewInquire(NEWCPLINFO& info) { info.dwSize = sizeof(NEWCPLINFO); info.dwFlags = 0; info.dwHelpContext = 0; info.lData = (LONG)this; . .
Die letzte Zeile muß auch hier mit einem korrekten cast versehen werden, weil der lData member der NEWCPLINFO Struktur jetzt ein LONG_PTR ist:
info.lData = (LONG_PTR)this;
Oder InsertMenu aus MFCs CMenu Klasse: Diese Methode erwartet als dritten Parameter entweder eine ID als UINT oder ein Menu-Handle wenn das Flag MF_POPUP im zweiten Parameter übergeben wird. Alter Code sieht daher so aus:
pSubMenu->InsertMenu(0,MF_BYPOSITION|MF_POPUP|MF_ENABLED, (UINT) hMenu, str);
was aber der x64-Compiler nicht akzeptiert. Es muß heißen:
pSubMenu->InsertMenu(0,MF_BYPOSITION|MF_POPUP|MF_ENABLED, (UINT_PTR)hMenu, str);
Weitere vergleichbare Probleme:
- SendMessageTimeout erwartet einen PDWORD_PTR statt eines PDWORD als letzten Parameter
- IContextMenu::GetCommandString erwartet einen UINT_PTR, keinen UINT
- SendMessage liefert einen LRESULT, keinen int. LRESULT ist jetzt 64-bittig
- Eine WNDPROC hat einen LRESULT, keinen BOOL oder int als Rückgabewert
- GetWindowLong/SetWindowLong sind obsolet um etwa eine WNDPROC zu bekommen für's subclassing. Stattdessen: GetWindowLongPtr/SetWindowLongPtr
- CPropertySheet :: DoModal() liefert einen INT_PTR zurueck, keinen int. Damit können erstmal komplete Ableitungsketten von Altcode zerstört werden oder können zumindest nicht auf Anhieb fehlerfrei übersetzt werden
- CList :: GetCount() oder CMap :: GetCount() liefern einen INT_PTR zurueck, keinen int.
Um alle diese Probleme erstmal zu sehen (denn nicht immer hilft ein cast - und unbesehen können hier Datenverlust oder Crashes auftreten - wenn etwa Pointer truncated werden) sollte man unbedingt den Compilerschalter /Wp64 verwenden, der solche Dinge zu erkennen hilft.
Ganz krass sind auch die Umstände zum Aufzählen von files in einem Directory, wenn das Ganze portabel sein soll. Aus folgendem Altcode:
struct _tfinddata_t data; _tcscat(szPath,"\\*cpl"); long handle = _tfindfirst(szPath, &data);
muß die letzte Zeile geändert werden zu:
intptr_t handle = _tfindfirst(szPath, &data);
Zusätzlich mußte noch folgende #ifdef-Orgie vorangestellt werden - damals war's noch ein Beta-SDK, ich hoffe, heute geht's auch ohne solchen Heckmeck:
#ifndef _INTPTR_T_DEFINED #ifdef _WIN64 typedef __int64 intptr_t; #else typedef _W64 int intptr_t; #endif #define _INTPTR_T_DEFINED #endif
Luna Manifeste
Die Manifeste für Luna waren für mich ein weiteres, größeres Hindernis. Hat man nämlich dann eine exe fuer x64 endlich erfolgreich portiert und übersetzt, können die Resultate selbst dann noch zu Problemen führen. Folgendes Manifest aus einem x86-Binary wird beispielweise problemlos in ein native x64 Binary eingebunden:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <assemblyIdentity version="1.0.0.0" processorArchitecture="x86" name=Nettosport.Cool.Product type="win32" /> <description> NettosportApplication </description> <dependency> <dependentAssembly> <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="x86" publicKeyToken="6595b64144ccf1df" language="*" . . .
Aber die native für x64 gebuildete exe startet leider um's Verrecken nicht auf einem x64-System. Es hat eine halbe Ewigkeit gedauert, bis ich rausgefunden hatte, daß ein Entfernen des Manifests einen problemlosen Start ermöglicht, aber mit genau diesem Manifest aus der x86-Version der Exe kein Start möglich war. Dann das Manifest genauer angeschaut und die Lösung ist simpel:
Überall da, wo in diesem Manifest die processorArchitecture mit "x86" festgelegt ist, ersetzt man einfach "x86" durch "*" und schon klappt's auch mit dem x64-Programmlader und ein- und dasselbe Manifest funktioniert für's Übersetzen desselben Sourcecodes unter x64 und x86. So einfach kann's sein.
"DESCRIPTION" in def files
Für den Linker ist die "DESCRIPTION" in def files für x64 obsolet. Drum: Einfach zukünftig weglassen, entfernen oder mit einer Linkerwarnung leben. Für kurze def-files in denen sich wenig ändert kann man sich auch eine zweite x64-Version machen, wo "DESCRIPTION" fehlt und die man statt über das dsp-file dann eben über die Linkerkommandozeile ändert. Damit "a jeda" weiß, wovon ich hier rede: in folgendem def file muß die Zeile mit "DESCRIPTION" einfach komplett raus:
LIBRARY "foo" DESCRIPTION 'Supercalifragilisticexpialidocius Nettosport DLL' EXPORTS ; Explicit exports can go here Bar PRIVATE Baz PRIVATE Oohdelallye PRIVATE
So, das wär's jetzt erstmal wieder. Hoffentlich vergeht nicht wieder ein Vierteljahr, bis ich wieder was über meine x64-Erfahrungen schreibe...
Trackback address for this post
1 trackback
Comments are closed for this post.