Alle meine Programme sind auf Robustheit ausgelegt. Ein Programm ist robust, wenn es ohne zu murren und ohne Installiererei einfach lĂ€uft (und lĂ€uft und lĂ€uft). Auf möglichst jeder Zielplattform. (Das gilt sinngemÀà auch fĂŒr Webseiten!) Wichtig ist dabei die UnabhĂ€ngigkeit von nicht-immer-vorhandenen Zusatzbibliotheken. Etwa den rechts gelisteten, manchen geplagten Anwender sattsam bekannten, mit Updates nervenden Softwarebomben.
Es gibt nur noch recht wenige AusfĂŒhrungen anderswo, mit Win32 zu programmieren. Der typische AnfĂ€nger beginnt mit .NET und C#. Oder, noch schlimmer, mit Python.
Was die Fachliteratur als âEinfachstes Windows-Programmâ so anbietet ist schlichtweg abschreckend! Hier sind die wirklich einfachen Programme.
#include <stdio.h>int main() { printf( "Hallo Welt!\n");return 0 ;}
Wer kennt das nicht? (Quelle: Kerninghan+Ritchie: C-Programmierung)
Mit den richtigen Projekteinstellungen entsteht ein Konsolenprogramm, welches âHallo Weltâ ausspuckt und mit ca. 32 Kilobyte ziemlich fett ist. Hier den Speck zu entfernen ist fĂŒr AnfĂ€nger etwas knifflig, daher werde ich im folgenden nicht darauf eingehen. AuĂerdem sieht es nicht wie ein Windows-Programm aus. Sondern wie ein Fremdkörper.
#include <windows.h>int WinMain(HINSTANCE, HINSTANCE, LPTSTR, int) { return MessageBox( 0 ,"Hallo Welt!","Nr. 2",0 );}
Das ist ein einfaches Windows-Programm!
Beinahe selbsterklĂ€rend erscheint ein (kleines) Fenster in der Mitte des Bildschirms mit dem Text âHallo Welt!â und einem âOKâ-Knopf zum Beenden des Programms.
Mit etwa 20 Kilobyte ist es aber immer noch Bloatware. Diese lassen sich ganz einfach entfernen, siehe nÀchstes Beispiel.
#include <windows.h>EXTERN_C int WinMainCRTStartup(void) { return MessageBox( 0 ,"Hallo Welt!","Nr. 3",MB_OK);}
Dieses Programm macht genau dasselbe wie Nr. 2, die EXE-Datei ist mit den richtigen Projekteinstellungen nur noch rund 2 Kilobyte groĂ. Kleiner geht's nicht, die fĂŒr Windows erforderlichen Verwaltungsinformationen im EXE-Header kosten ihren Fixpreis. Der eigentliche Kode und die Stringkonstanten bedĂŒrfen nur weniger als 100 Bytes und wĂŒrde in den kleinsten Mikrocontroller passen: An der Intel-Architektur liegt's jedenfalls nicht.
Vergleicht man die drei Beispiele mit dem Dependency Walker, wird man feststellen, dass dieses genau eine externe AbhĂ€ngigkeit aufweist, nĂ€mlich nach USER32:MessageBoxA. Die Bloatware vorher ruft noch Dutzende anderer Funktionen auf, die ĂŒberhaupt nicht benötigt werden.
Unter Windows 10 und neuer bleibt das Programm als Leiche im Task-Manager liegen,
nachdem man in der Messagebox âOKâ anklickt und das Fenster verschwindet.
Merkt man daran, dass eine erneute Kompilierung fehlschlÀgt,
weil der Linker keinen Schreibzugriff auf die Echse erhÀlt.
Ursache ist ein âRemote-Threadâ der noch lĂ€uft.
Dieser entsteht in der Regel durch SetWindowsHookEx(WH_CBT,dllHookProc,dllInstance,0)
einer injizierten DLL.
Das Programm muss mit ExitProcess()
beendet werden.
return
beendet nur den Thread
â und den Prozess nur wenn's der letzte Thread ist.
#include <windows.h>EXTERN_C __declspec(noreturn) void WinMainCRTStartup(void) { ExitProcess(MessageBox( 0 ,"Hallo Welt!","Nr. 3.1",MB_OK));}
Leider ist diese Echse etwas gröĂer wegen Import von KERNEL32:ExitProcess.
__declspec(noreturn)
sollte den
ret
-Assemblerbefehl wegoptimieren.
ExitProcess()
sollte gefÀlligst selbst mit
__declspec(noreturn)
deklariert sein.
Im Normalfall bindet der C/C++-Compiler zwischen âŠ
⊠Kode ein der in etwa folgendes macht:
Win32-GUI | Win32-Konsole | avr-gcc | PIC xc8 |
---|---|---|---|
â | Stapelzeiger initialisieren | â | |
â | R1 nullsetzen: clr r1 | â | |
Kommandozeile beschaffen: GetCommandLine() | Initialisierte statische Daten (.data) vom Flash in RAM kopieren | ||
â | Kommandozeile zerlegen: _getmainargs(); oder CommandLineToArgvW() | Uninitialisierte statische Daten (.bss) nullsetzen | |
Den Heap vorbereiten | |||
Nur C++: Statische Konstruktoren aufrufen | |||
WinMain() aufrufen | main() aufrufen
| ||
Prozess beenden mit ExitProcess() (return geht nicht!) | âProzessendeâ mit Endlosschleife simulieren und so auf Reset warten |
Und was zur Hölle braucht das 20 Kilobyte?? Auch bei avr-gcc ist die Stapelzeiger-Initialisierung bei modernen ATtiny und ATmega ĂŒberflĂŒssig (also Code Bloat) und fĂŒhrt zu ungeahnten Problemen, wenn man bspw. das Flash-Image eines ATtiny25 auf einem ATtiny85 ausfĂŒhrt.
SchlieĂlich der Ausgangspunkt fĂŒr 95 % aller praktischen Programmieraufgaben.
Programme die wie Dialoge aussehen sind das Butterbrot des Programmierers.
Der Erfolg von VisualBasic und Delphi bestÀtigt dies.
Denn oftmals sind es die kleinen Probleme,
die ein kleines Programm benötigen.
Sogar âgroĂeâ Programme mit gröĂenverĂ€nderlichem Hauptfenster lassen sich
ganz ohne CreateWindow()
und RegisterClass()
schreiben,
nur ist's dann nicht mehr so vorteilhaft.
#include <windows.h>static INT_PTR WINAPI MainDlgProc(HWND Wnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_INITDIALOG: { }return TRUE; case WM_COMMAND: switch (wParam) { case MAKELONG(IDCANCEL,BN_CLICKED): EndDialog(Wnd,wParam); break; }break; } return FALSE; } EXTERN_C void WinMainCRTStartup() { ExitProcess(DialogBox( 0 ,MAKEINTRESOURCE(100 ),0 ,MainDlgProc));}
Wie man am Quelltext âsiehtâ, fehlt jegliche Ausgabe, etwa Zeichenketten. Diese befinden sich nunmehr in einer Ressource: (Maus darĂŒberhalten fĂŒr ErklĂ€rungen!)
1 24 "manifest" 100 DIALOG 0,0,220,100 STYLE DS_3DLOOK|DS_NOFAILCREATE|DS_CENTER|WS_MINIMIZEBOX|WS_VISIBLE|WS_CAPTION|WS_SYSMENU CAPTION "Nr. 4" FONT 8, "Helv" { CTEXT "Hallo Welt!",-1,10,10,200,8 DEFPUSHBUTTON "Ende",2,60,50,100,14 }
So eine Ressourcendatei (*.rc) sieht aus wie C, ist aber kein C, und wird vom Resource Compiler (rc.exe) entweder in eine BinĂ€rressource (*.res) oder gleich in die EXE-Datei eingebunden. Ressourcen sind (u.a. fĂŒr Dialoge) standardisiert und mehrsprachig, sie können auch nachtrĂ€glich mit geeigneten Editoren korrigiert, ĂŒbersetzt und hinzugefĂŒgt werden. So hat der Kunde die Möglichkeit der Anpassung und Ăbersetzung ohne Quelltext! Das ist die Idee hinter den Ressourcen! Keine Idee von Microsoft sondern von Apple. Mit .NET oder Qt geht das nicht.
Die EXE-Datei ist nun gröĂer geworden (4 Kilobyte), weil eine weitere âSectionâ (die Ressource nĂ€mlich) mit hineingewandert ist. DafĂŒr ist dieses Programm nun beliebig ausbaufĂ€hig.
Wie man sieht, kommt man fĂŒr lange Zeit
um RegisterClass()
und CreateWindow()
herum!
Wer so etwas als âErstes Windows-Programmâ anbietet,
will wohl nur davon abschrecken.
(Schade, Herr Petzold.)
Ein unterschĂ€tztes Problem der (nicht nur Windows-)Programmierung ist das Merken der Fensterposition sowie weiterer âkleinerâ Konfigurationseinstellungen wie:
GetOpenFileName()
TatsĂ€chlich kĂŒmmern sich viele Windows-Programme in der freien Wildbahn nicht die Bohne um das Speichern â und sind dadurch elend unkomfortabel!
Am praktischsten hat es sich erwiesen, immer zu speichern und gar nicht erst den Benutzer zu fragen! Also kein MenĂŒpunkt âKonfiguration speichernâ oder so anzulegen.
Zum GlĂŒck gibt es unter Windows den perfekten Speicherort dafĂŒr: Die Systemregistrierung (Programmierer-Jargon: Registry) unter HKEY_CURRENT_USER\Software\Firma_oder_Programmierersignet\Programmname. Das elend lange HKEY_CURRENT_USER wird mit HKCU abgekĂŒrzt. Und hier kann man problemlos BinĂ€rdaten speichern!
Hier habe ich folgende Lösung fĂŒr die beste herausgefunden:
struct
gesteckt:
âą Fensterposition, GröĂe, Erscheinung â siehe GetWindowPlacement()
, sowie âą Sonstige Einstellungen.
struct
-Elemente (âMemberâ) halte ich minimal klein
(short
fĂŒr Fensterposition und -GröĂe,
char
und BYTE
fĂŒr nahezu alles andere,
um die Registry nicht mit Nullen zu ĂŒberfrachten.
struct
âin einem Rutschâ beim Starten geladen
und beim Beenden gespeichert.
So ist's schön aufgerÀumt, bei hervorragender
User Experience.
Gibt man schon seit Windows 3.1 mit OutputDebugString()
aus.
Diese erscheinen im Output-Fenster der Entwicklungsumgebung.
Oder mittels Mark Russinovichs
DebugView.
Neu: Da das Vorhandensein von DebugView nicht immer sichergestellt
werden kann ist die alternative Ausgabe auf eine Konsole wĂŒnschenswert:
Die Funktion AttachConsole(-1)
verwendet das Konsolenfenster des Aufrufers.
Mit anderen Worten: Es verwendet die Konsole beim Aufruf aus einem Konsolenfenster
(d.h. aus cmd.exe oder command.com) wie ein Konsolenprogramm
aber öffnet keine Konsole beim Aufruf vom StartmenĂŒ, Explorer
oder Total Commander
im Gegensatz zu einem Konsolenprogramm.
Ergo: Als Windows-Programm compilieren (SUBSYSTEM:WINDOWS).
Gelingt AttachConsole(-1)
dann mit WriteConsole()
ausgeben
sonst wie gehabt mit OutputDebugString()
.
Zudem lassen sich so âbimodaleâ Programme generieren: Konsole und Windows.
Gut fĂŒr alles was man auch mal von einem
Makefile starten möchte.
Das gute alte MSVC6 aus dem Jahr 1998 (1 CD) ist bei mir immer noch die beste Grundlage, um derartig kleine Programme zu schreiben. Dazu passt am besten die Online-Hilfe âMSDN April 2001â. Bei mir auf 3 CDs. Leider macht die Installation Probleme ab Windows XP:
Was man mit MSVC6 (und allen C++-Dialekten vor C++11) nicht tun sollte
ist die Verwendung von std::vector
mit richtigen Objekten!
Dadurch, dass der Raubkopierkonstruktor und std::vector::emplace()
fehlt,
wird das Einbetten von Handles und deren automatischer Destruktoraufruf
zu einer elend windigen Angelegenheit.
Man tut besser daran, keine Objekte sondern nur einfache Datentypen hineinzutun.
Bei Strukturen, die Handles enthalten, muss man diese zu FuĂ freigeben.
Oder man tut Zeiger auf Objekte in den Vektor.
(Bei virtuellen Objekten geht das ohnehin nur so.)
Nun, das macht das Iterieren umstÀndlicher.
Weil die Iteratoren nun Zeiger auf Zeiger sind.
Normalerweise gibt es keinen Grund, den Gnu-C-Compiler mingw zu benutzen. AuĂer man folgt einen gewissen Fetisch. Denn der Compiler compiliert noch schlechter als der von Microsoft, den Microsoft-Compiler gibt's in einer freien Version ohne EinschrĂ€nkungen, und die Microsoft-Lizenz macht keine EinschrĂ€nkungen zur Verwendung des entstehenden Kompilats.
Die folgenden Kommandozeilenoptionen sind nötig, um die Laufzeitbibliothek loszuwerden und die Echse kleinzukriegen:
try..except
bzw. C __try..__except
)
__chkstk
, lokale Variablen muss man pro Funktion auf 4096 Byte begrenzen
push ebp; mov ebp,esp; sub esp,local_variables_size
zu etwas kĂŒrzerem
printf()
, strlen()
,
memcpy()
und new
muss man konsequent verzichten.
FĂŒr alles auĂer Gleitkomma gibt es Win32-API-Funktionen.
Auf den fehlenden Ressourcen-Editor kann man verzichten, wenn man von einer vorhandenen Ressource ausgeht und die .RC-Datei als Text editiert. Nur das Gestalten der Dialoge ist damit sehr mĂŒhevoll. Die Aufrufzeile geht (typisch Gnu-GlĂ€ubige) mal wieder nicht ohne Pflichtoption: windres -o ziel.res -O coff quelle.rc
Ich habe mal mingw64 auf die wirklich benötigten Dateien eingedampft. Dabei kam diese Dateiliste heraus. Immer noch reichlich 40 Megabyte. UnsÀglich wirr verstreut. Das war zu Zeiten von Borland C++ die ganze Festplatte!
Fast alle meiner Win32-Programme sind ohne Laufzeitbibliothek geschrieben. Die nachfolgende Ăbersicht gibt einige Hinweise auf die Unterschiede der Programmierung.
Prinzipiell gibt es da zwei Wege:
Ganz ohne Laufzeitbibliothek | Verwendung der msvcrt.dll | |
Vorteile: |
|
|
Nachteile: |
|
|
Vorgehen: |
|
|
Dem Leser sollte Win32-Programmierung bekannt sein, wenn nicht, dann bitte den âPetzoldâ lesen!
Ich bin mit Windows-Programmierung in Pascal groĂ geworden, und habe Objektbibliotheken zu hassen gelernt, weil sie eben nur so tun, als ob sie KomplexitĂ€t verbergen könnten. Deswegen wird der Leser bei mir kein einziges MFC-Programm finden. Auch wenn es nach auĂen so (modern) aussehen möge, schon die DateigröĂe verrĂ€t, dass es kein MFC sein kann.
Generelle Unterschiede:
mit Laufzeitbibliothek | ohne Laufzeitbibliothek | |
---|---|---|
Minimale KodegröĂe | 20 KB | 2,5 KB |
Kopfdateien |
oder Àhnliches |
|
Eintrittspunkt | WinMain
| WinMainCRTStartup
|
Kommandozeilen-Argumente | Argument von WinMain , Programm-Name entfernt
Unicode-Problem! GetCommandLine wird empfohlen
| GetCommandLine benutzen, mit PathGetArgs
zerstĂŒckeln
|
Programmende | return retval
| ExitProcess(retval)
Nur return killt nicht die durch GetOpenFileName erzeugten Threads!
|
Compiler-Schalter | Projekt-Einstellungen funktionieren, die vorcompilierten Header machen aber meistens nur Ărger |
|
Linker-Schalter | Projekt-Einstellungen, auf die OLE-Importbibliotheken kann man getrost
verzichten, einige andere Importbibliotheken werden des öfteren benötigt:
olepro32.lib
(typischerweise fĂŒr
OleLoadPicture()
ist richtig fĂŒr Windows 95, aber falsch fĂŒr Win7/64.
FĂŒr 64-Bit-Plattform zu oleaut32.lib wechseln!
oleaut32.dll gibt's sicher ab Windows 98 SE.
|
#pragma -Anweisungen ist nicht portabel
hin zum neueren MSVC9! Deshalb besser im Projekt setzen.
|
Diese Tabelle spiegelt nur den Unterscheid bei Microsoft Visual C++ (ab Version 6 geprĂŒft) wider. Bei Borland C++ gelten andere Regeln!
Statt mit msvcrt-light.lib herumzuhampeln kann man den Compiler des Windows-DDKs benutzen. Dieser linkt automatisch konservativ zur alten MSVCRT.DLL mit folgender Kommandozeile, aus einer Build-Umgebung heraus:
Man erspart sich Probleme mit stdout
, __try
, __chkstk
und sicherlich auch der STL.
Ohne /entry= wird der Code gröĂer, aber statische Konstruktoren
und Kommandozeilenargumente werden kompatibel behandelt.
Konstanten im Kodesegment (/GF) ersparen initialisierte statische Variablen.
(Alle nicht initialisierten statischen Variablen werden vom EXE-Loader
stets mit Null initialisiert, muss man wissen.
Es ist ohnehin unschöner Programmierstil, mit Nicht-Null initialisierte
statische Variablen nicht const
zu setzen.
Bei der Verwendung von
unicows.lib
bzw.
libunicows.lib
kommt man um solche Variablen nicht herum.)
Den Erfolg der MaĂnahme kann man mittels dumpbin /headers
prĂŒfen: das Datensegment (.data
) muss in der EXE-Datei leer sein.
SECTION HEADER #3 .data name 6F4 virtual size 8000 virtual address (00408000 to 004086F3) 0 size of raw data 0 file pointer to raw data 0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers C0000040 flags Initialized Data Read Write
Ohne Standardbibliothek kann man nicht âportabelâ im Sinne des ANSI-Standards
programmieren. Aber kein Windows-Programm ist jemals ANSI-konform.
Es gibt kein main
, kein printf
. (Wohin denn?)
Muss ich weiter argumentieren? (Ich rede hier nicht von Konsolen-Programmen.)
Also, PortabibiltĂ€t ist etwas fĂŒr den Elfenbeinturm (Informatiker, Lehre)
oder fĂŒr Unix (Linux - da kommt man nicht herum!).
FĂŒr Windows: da sage ich einfach TschĂŒss und winke-winke!
Jetzt muss man nur noch wissen, was man statt der gewohnten Funktionen
verwenden muss. Ganz nebenbei wird das Programm unicode-fÀhig, ohne jedesmal
den TCHAR-PrÀfix (bspw. _tprintf
statt printf
)
verwenden zu mĂŒssen (was ja auch nicht ganz portabel ist).
Hier eine kurze Ăbersicht
mit Laufzeitbibliothek | ohne Laufzeitbibliothek | |
---|---|---|
Kommandozeilen-âArgumente | via strchr zerstĂŒckelt | PathGetArgs einsetzen!
|
String-âManipulation | Vorwort: Es gibt einen kleinen aber feinen Unterschied
bei der Behandlung von PuffergröĂen bei String-Funktionen der Laufzeitbibliothek
und von Windows!
elemof(array) (siehe unten) leistet hier gute Dienste.
| |
strlen | lstrlen (ist Unicode-fÀhig auch unter Win9x)
| |
strcpy , strncpy | lstrcpy , besser lstrcpyn ; fĂŒr Win9x-Unicode StrCpy , besser StrCpyN
Beachte: strncpy und StrCpyN arbeiten unterschiedlich bei Pufferknappheit!
StrCpyN garantiert eine terminierende Null, kopiert also maximal N-1 Zeichen; strncpy fĂŒllt den Puffer bis zum Rand.
| |
strcat | lstrcat, StrCat , besser StrCatBuff
| |
strchr bzw. strrchr | StrChr bzw. StrRChr
| |
strstr bzw. strrstr | StrStr bzw. StrRStr
| |
strpbrk | StrPBrk
| |
strspn | StrSpn
| |
strdup | StrDup
| |
s{n}printf , itoa | wsprintf , besser wnsprintf (siehe Anmerkungen nÀchste Zeile)
| |
vsprintf , _vsnprintf | wvsprintf , besser wvnsprintf (kĂŒmmerlich (keine variablen Feldbreiten mit * ), kein Gleitkomma;
letztere Funktion ist Win9x-Unicode-fĂ€hig, aber erst mit installiertem Internet Explorer 3.0 oder höher verfĂŒgbar â Windows 95 wurde mit IE 2.0 ausgeliefert) Beachte: {v}snprintf und w{v}nsprintf arbeiten unterschiedlich bei Pufferknappheit!
w{v}nsprintf garantiert eine terminierende Null, schreibt also maximal N-1 Zeichen; {v}snprintf fĂŒllt den Puffer bis zum Rand.
Selbst schreiben ist eine gute Option! Bei dieser Gelegenheit Dezimalkomma-Ausgabe beim Formatstring "5,2i" vorsehen. TODO: Beispiel verlinken
| |
sscanf | kein Ersatz! Leider. Mit StrToInt ersetzen
| |
atoi | StrToInt, StrToIntEx (fĂŒr Hexadezimalzahlen "0x" davorsetzen!)
| |
shlwapi.h bietet noch eine Reihe weiterer nĂŒtzlicher String-Funktionen,
insbesondere zur Behandlung von Dateinamen.
Deren Unicode-Funktionen (Suffix âŠW ) sind auch unter Windows 9x funktionsfĂ€hig,
im Gegensatz zu fast allen Funktionen der USER32.DLL.
(Leider arbeitet PathFindExtension bei Leerzeichen
sowie bei Unix-Dateinamen wie .htaccess fehlerhaft: selber schreiben!)
| ||
Speicher | malloc, calloc, new | LocalAlloc , HeapAlloc (C++-Operator new kann ĂŒberladen werden: inline void*operator new(size_t sz) {return HeapAlloc(hHeap,HEAP_GENERATE_EXCEPTIONS,sz);} )
|
free, delete | LocalFree (C++-Operator delete kann ĂŒberladen werden: inline void operator delete(void*p) {HeapFree(hHeap,0,p);} )
| |
memset, ZeroMemory, FillMemory | Selber schreiben:
| |
oder Windows-Funktionen verwenden (lassen):
| ||
oder durch Intrinsics ersetzen: __stosb() , __stosw() oder __stosd() (bei AMD64 auch _stosq() ) je nach DatengröĂe
| ||
memcpy, CopyMemory, | Selber schreiben:
| |
oder Windows-Funktionen verwenden (lassen):
Hinweis: Einige Funktionen mehr sind in der kernl32p.lib verfĂŒgbar,
statt der standardmĂ€Ăigen kernel32.lib .
| ||
oder âinlinenâ lassen:
| ||
memchr, wmemchr | Selber schreiben:
| |
memcmp, wmemcmp | Selber schreiben:
Die Àhnliche Kernel-Funktion RtlCompareMemory ist bei Win32 nicht in kernel32.lib enthalten.
| |
Formatierte Datei- | fprintf | zusammensetzen aus wvnsprintf ,
CharToOemBuff und WriteFile
(oder WriteConsole )
Niemals printf mit Umlauten
oder sonstigen lokalisierbaren Strings verwenden!
|
fscanf | ReadFile benutzen
| |
Gleitkomma | sin, cos ⊠| Inlinen lassen: #pragma intrinsic(sin,cos,atan,sqrt,fabs)
oder selber schreiben: Koprozessor nutzen Irgendwo braucht es im Programm ein __fltused
(EXTERN_C void _declspec(naked) _cdecl _fltused(void) {} ),
um den Linker ruhig zu stellen.
Die genannte Quelltext-Zeile erzeugt nur das notwendige Symbol, keinen Kode.
|
Hier könnte man noch beliebig fortsetzen.
Bei Einsatz von Gleitkomma bei Ein- und Ausgabe ist es besser,
ein funktionierendes sprintf()
und sscanf()
zu haben, diese beschafft man sich wie folgt:
EXTERN_C int (_cdecl*_stprintf)(PTSTR,int,PTSTR,...); EXTERN_C int (_cdecl*_stscanf)(PTSTR,PTSTR,...); #ifdef UNICODE # define __IMP__STPRINTF "_snwprintf"# define __IMP__STSCANF "swscanf"#else # define __IMP__STPRINTF "_snprintf"# define __IMP__STSCANF "sscanf"#endif // irgendwo in der Programminitialisierung: HINSTANCE hLibMSVCRT=LoadLibraryA( "MSVCRT.DLL");(FARPROC)_stprintf=GetProcAddress(hLibMSVCRT,__IMP__STPRINTF); (FARPROC)_stscanf=GetProcAddress(hLibMSVCRT,__IMP__STSCANF);
(das funktioniert auch mit mingw) oder, durch eine spezielle Importbibliothek. Denn die MSVCRT.DLL ist seit Windows 95 standardmĂ€Ăig installiert, die braucht man weder mitzuliefern noch deren Funktionen statisch einzubinden.
Oftmals will der Linker eine Funktion __ftol oder __ftol2_sse. Siehe hier: Bei Linker-Fehlern mit sse ist die Architektur beim Compilieren âfalschâ eingestellt; das Programm wĂŒrde auf Ă€lteren Prozessoren nicht laufen. Schalter: /arch:IA32 (Microsoft C++). Die Funktion _ftol lieĂe sich mit der Kommandozeilenoption /QIfist inlinen. Das generiert Kode wie diese Ersatzfunktion:
Das Problem: Diese Funktion rundet standardmĂ€Ăig. (Die echtelong _declspec(naked) _cdecl _ftol(void){ _asm{ push eax fistp dword ptr [esp] pop eax ret }}
__ftol
-Funktion rundet zur Null.)
Meistens will man sowieso runden, und deshalb ist es besser, sÀmtliche
automatischen Typecasts zu entfernen und durch eine eigene Funktion
zu ersetzen, etwa:
statt dem langweiligen, aber ĂŒblichen__forceinline __int64 llrint(double f) { __int64 i; _asm fld f _asm fistp i return i; }
i=(int)floor(x+0.5)
.
Das Rundungsergebnis ist dabei nicht ganz gleich,
bei x=0.5
rundet diese Zeile zu 1
(KaufmÀnnisches Runden),
wÀhrend llrint(0.5)
zur geraden Zahl 0
rundet (Banker's Rounding).
X86-Assembler | AMD64-Assembler | Visual C (#include <intrin.h> ) | Gnu C | Funktion | |||
---|---|---|---|---|---|---|---|
Bytetausch (Endian-Konvertierung) | |||||||
xchg ah,al | _byteswap_ushort | __builtin_bswap16 | htons() auf Little-Endian | ||||
bswap eax | bswap rax | _byteswap_ulong | _byteswap_uint64 | __builtin_bswap32 | __builtin_bswap64 | htonl() auf Little-Endian | |
Bitsuche, BitzÀhlen, Bitrotieren | |||||||
bsf eax,ecx | bsf rax,rcx | _BitScanForward | _BitScanForward64 | __builtin_ctz | __builtin_ctzll | nicht identisch! | |
bsr eax,ecx | bsr rax,rcx | _BitScanReverse | _BitScanReverse64 | __builtin_clz | __builtin_clzll | =(int)lb(x) (log2) | |
bt [ecx],edx setnz al | bt [rcx],rdx setnz al | _bittest | _bittest64 | - | (a&1<<b) | Ăberspannt Speicherbereiche | |
btr [ecx],edx setnz al | btr [rcx],rdx setnz al | _bittestandreset | _bittestandreset64 | __sync_fetch_and_nand | (m=1<<b,r=a&m,a&=~m,r) | ||
bts [ecx],edx setnz al | bts [rcx],rdx setnz al | _bittestandset | _bittestandset64 | __sync_fetch_and_or | (m=1<<b,r=a&m,a|=m,r) | ||
btc [ecx],edx setnz al | btc [rcx],rdx setnz al | _bittestandcomplement | _bittestandcomplement64 | __sync_fetch_and_xor | (m=1<<b,r=a&m,a^=m,r) | ||
popcnt eax,ecx | popcnt rax,rcx | __popcnt16, __popcnt, __popcnt64 | __builtin_popcount | __builtin_popcountll | ZĂ€hlt 1-Bits | ||
or ecx,ecx setp al | or rcx,rcx setp al | - | __builtin_parity | __builtin_parityll | ParitÀt, Anzahl 1-Bits modulo 2 | ||
rol eax,cl | rol rax,cl | _rotl | _rotl64 | (a<<b|a>>32-b) | (a<<b|a>>64-b) | Linksrotieren mit umlaufenden Bits | |
ror eax,cl | ror rax,cl | _rotr | _rotr64 | (a<<32-b|a>>b) | (a<<64-b|a>>b) | Rechtsrotieren mit umlaufenden Bits | |
Interlocked | |||||||
cmpxchg [ecx],edx | cmpxchg [rcx],rdx | _InterlockedCompareExchange | __sync_val_compare_and_swap | SMP-sichere Austauschoperation | |||
lock inc dword[ecx] | lock inc qword[rcx] | _InterlockedIncrement | ? | SMP-sicheres Inkrementieren einer Speicherstelle | |||
lock dec dword[ecx] | lock dec qword[rcx] | _InterlockedIncrement | ? | SMP-sicheres Dekrementieren einer Speicherstelle | |||
Sonstiges | |||||||
int 3 | __debugbreak | ? | Debugger-Aufruf | ||||
nop | __noop | ? | Leerbefehl |
C-Compiler ab dem Jahr 2005 bringen unglaublich viele Intrinsics fĂŒr MMX, 3DNow, SSE, SSE2, SSE3, SSE4, AVX2011 und AVX5122015 mit.
FĂŒr leistungshungrige Programme mit massiv-parallelem Gleitkomma-Einsatz lohnt sich der Einsatz von SSE2.
SSE2 ist implizit vorausgesetzt fĂŒr 64-Bit-Programme, da x64-Prozessoren immer SSE2 haben.
MMX und 3DNow (64 Bit Parallel-Integer aus der Pentium-Ăra) kann man heutzutage (2024) ignorieren
und den zugehörigen Datentyp __mm64 als âgiftigâ (= lahm, nie verwenden!) betrachten.
Die SSE-Befehle bauen auf 128 Bit breiten Registern auf (__mm128).
Darin passen vier float
-Gleitkommazahlen.
AVX und AVX512 verdoppeln jeweils die Registerbreite.
In meinen Programmen âfehltâ die Datei resource.h.
Ich halte es fĂŒr ĂŒberflĂŒssig, numerischen Konstanten per #define
eine Zeichenkettenkonstante zuzuordnen, die:
Zur Kompensation muss bei Verwendung numerischer Konstanten deren Bedeutung im Quelltext-Kommentar aufgefĂŒhrt werden. Zum Beispiel:
switch (msg) { case WM_COMMAND:{ // GĂ€ngiger, von Microsoft und Petzold propagierter Stil switch (GET_WM_COMMAND_ID(wParam,lParam)) { case ID_BUTTON1: TueIrgendwas(GET_WM_COMMAND_HWND(wParam,lParam)); break; // Nun, was ist "BUTTON1"? } // Mein Stil switch (LOBYTE(wParam)) { // KĂŒrzerer Kode durch Bytezugriff, Win16 ignorierend case 44 : TueIrgendwas(HWND(lParam)); break; // Button "TuWas!" â unverzichtbarer Kommentar} }break; }
Kaum ausgenutzt wird, dass auf der X86-Architektur Zahlen im Bereich -128 .. +127
(der Umfang von char
) mit deutlich kĂŒrzeren Opcodes gehandhabt werden als gröĂere Zahlen.
Mithin, die Verwendung von kleinen Konstanten wird durch kleinere Echsen honoriert.
Ich achte bei meinen Programmen darauf, IDs in diesem Bereich zu halten.
GĂŒnstig etwa fĂŒr Dialoge und MenĂŒs.
FĂŒr alles, worauf verglichen werden muss.
Die Ressourcen selbst werden dadurch leider nicht kleiner, schade.
Mit jeder neuen Version von Visual Studio lauern neue Ăberraschungen. Zum einen wurden Stringfunktionen als potenziell unsicher eingestuft:
sprintf()
: Recht so! ZielpufferĂŒberwachung gehört heutzutage dazu!
_sntprintf()
: Nö! Man kann und muss die ZielpuffergröĂe angeben.
Nur die terminierende "\0"
ist nicht sichergestellt.
Wenn man den RĂŒckgabewert der Funktion benutzt, braucht man das Stringende nicht,
etwa fĂŒr GDI TextOut()
.
Zum anderen werden die printf-Funktionen zusammengefasst
zu __stdio_common_*printf
.
Ein Schritt, der die Verwendung der msvcrt.dll
in Verbindung mit msvcrt-light.lib bzw. msvcrt-light-x64.lib unmöglich macht.
Zum GlĂŒck gibt es dafĂŒr RĂŒckfall-Makros, die am besten fĂŒr das gesamte Projekt festgelegt werden:
#define _NO_CRT_STDIO_INLINE // Visual Studio 2017+: Verhindert __stdio_common_vswprintf #define _CRT_SECURE_NO_WARNINGS // Visual Studio 2008+: verhindert sinnlose (ja, falsche) Warnungen wegen drohender PufferĂŒberlĂ€ufe
Neuere Visual Studios erlauben die Quelltexterfassung in UTF-8. Beim MSVC6 geht allenfalls UTF-16. FĂŒr Kommentare gedacht sind die Rahmensymbole zum 'Rauskopieren. Ebenfalls fĂŒr Kommentare hĂ€ufig benötigte mathematische Symbole. Griechisch und Kyrillisch lĂ€sst sich ab C++17 fĂŒr Bezeichner verwenden. Also nicht nur fĂŒr MSVC sondern fĂŒr jedes Programmierprojekt. Nach Möglichkeit sollten griechische und kyrillische Buchstaben, die wie Lateinbuchstaben aussehen, nicht einzeln verwendet werden.
ââââŹâââââ ââââŠâââââ ââââ€âââââ ââââ„âââââ â â â â â â â â â â â â ââââŒââââ†â âââŹââââ⣠ââââȘââââ⥠ââââ«âââââą ââââŽâââââ ââââ©âââââ ââââ§âââââ ââââšâââââ ÂĄÂżâŒÂŠÂ§Â©Âźâčâș«»Â¶âââââŸâââââââ âĄ ÂŒÂœÂŸâ â â â âŠâ°âŠ ââââââ⚠±ĂĂ·â ââââââąâââââ©â« ââ âĄâ€â„ ââââââââ â âŹâČâșâŒâââââ âșâ»âŒâââ âŁâ„âŠâȘâ« ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎ ÎĄÎŁ ΀΄ΊΧΚΩ αÎČγΎΔζηΞÎčÎșÎ»ÎŒÎœÎŸÎżÏÏÏÏÏÏ ÏÏÏÏ ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐРХйУЀЄЊЧКЩĐȘĐ«ĐŹĐПЯ абĐČĐłŃĐŽĐ”ŃŃжзОŃŃĐčĐșĐ»ĐŒĐœĐŸĐżŃŃŃŃŃŃ ŃŃŃŃŃŃŃŃŃŃ
FĂŒr diese gibt es die MSVCRT.DLL-Funktion _initterm
.
Diese bekommt den Zeiger auf eine Liste von Funktionszeigern zum FraĂ
sowie deren Endadresse. Von den einzelnen Modulen werden diese Adressen
in das Datensegment ".CRT$XCC"
geworfen.
Der Linker pappt diese Datensegmente in eher zufÀlliger Reihenfolge
hintereinander und baut so eine Tabelle auf, die (wegen der nicht ganz
sicheren Segmentausrichtung des Linkers) Nullen enthalten kann;
_initterm()
ĂŒbergeht Nullen automatisch.
Siehe auch: Wie ein Linker arbeitet.
Und so kommt man an die Anfangs- und Endadresse dieser Tabelle, siehe auch Microsoft-CRT-Quelle:
#include <windows.h>#pragma bss_seg( ".CRT$XCA")static void* Anfang; // Keine Initialisierung #pragma bss_seg( ".CRT$XCZ")static void* Ende; #pragma bss_seg() // zurĂŒckschalten zu ".bss"
Es wird schlichtweg darauf vertraut, dass der Linker
die Segmente in alphabetischer Reihenfolge zusammensetzt!
Damit stehen diese beiden Tabellenelemente am Anfang und am Ende der Tabelle,
und man kann _initterm()
aufrufen:
extern "C"_CRTIMP void _cdecl _initterm(void*&,void*&);_initterm(Anfang,Ende); // ruft alle statischen Konstruktoren
Die Konstruktoren enthalten auch Kode, um die statischen Destruktoren
in die Liste fĂŒr _onexit
einzutragen.
Das gesonderte Aufrufen der Destruktoren ist daher nicht notwendig.
__$ReturnUdt
?âIn C sieht man extrem selten die RĂŒckgabe einer struct.
Ein klassisches Beispiel dafĂŒr ist div_t
der Standardbibliotheksfunktion div()
.
Dabei sind derartige Funktionen so gemacht,
dass die struct in ein Register oder notfalls in ein Doppelregister passt.
Bei X86 wÀre das eax
bzw. edx:eax
.
Bei X64 entsprechend rax
bzw. rdx:rax
.
Der C-Standard schreibt vor, dass bei RĂŒckgabe
zu groĂer
oder nichttrivialer
struct/union/class
der Aufrufer einer solchen Funktion
einen versteckten Zeiger auf einen
(ĂŒblicherweise auf dem Stapel frei gehaltenen)
Speicherbereich ĂŒbergeben muss.
Logisch also, warum das so selten zu sehen ist und nicht zur Win32-API
bzw. zu DLL-Aufrufen passt:
Wie dieser Zeiger ĂŒbergeben wird ist dem C/C++-Compiler freigestellt.
So steht zusÀtzlich zum Klassenzeiger (this
, nur bei nicht-statischer Memberfunktion)
dem Unterprogramm ein weiterer Zeiger zur VerfĂŒgung.
Bei MSC hat dieser einen Namen:
__$ReturnUdt
.
Diesen Namen sieht man im Debugger.
Die einzige Möglichkeit in C oder C++, eine Struktur zurĂŒckzugeben ist das Anlegen einer lokalen Variable derselben Struktur, deren Elemente zu fĂŒllen und die Struktur zurĂŒckzugeben. (Abgesehen vom Aufruf einer weiteren Funktion, die dieselbe Struktur liefert.)
MyStruct funktion(const char*s) { MyStruct ret; // Normale lokale Variable, kann auch ein hier konstruiertes Objekt sein // Tue irgendwas und fĂŒlle die Struktur return ret; // faktisch ein Kopierkonstruktor, bei C++11 ein Verschiebekonstruktor, kopiert alle Bytes // Wenn ret ein Objekt mit Destruktor ist, wird an dieser Stelle ret->~Mystruct() aufgerufen }
Normalerweise muss man sich nicht um den versteckten Zeiger kĂŒmmern,
der Optimizer vermeidet den allfÀlligen
Kopierkonstruktor
(C++11:
Verschiebekonstruktor
wenn vorhanden, siehe Dreierregel) bei der return-Anweisung,
indem er statt der lokalen Strukturkopie den ĂŒbergebenen Zeiger nutzt.
Bei MSVC6 muss man dem Optimizer allerdings unter die Arme greifen.
Dabei sieht man, wie modernere Compiler optimieren:
MyClass funktion(const char*s) { MyClass&ret = *__$ReturnUdt; // Der (kĂŒnftige) RĂŒckgabeparameter wird auf den vom Aufrufer bereitgestellten Platz gedacht // Tue irgendwas und fĂŒlle die Referenz // Konstruktoraufruf geht hier mit Placement-new, der in MSVC6 ebenfalls noch deklariert werden muss. return ret; // Das ruft den Kopierkonstruktor auf den Plan, der Zeigergleichheit feststellt und keine Bytes kopiert. // C++ fĂŒgt hier keinen Destruktoraufruf ein, weil Referenzen genauso wie Zeiger nicht automatisch aufgerĂ€umt werden. }
Artikel zum Placement new.
Die Klasse std::string
baut auf das Feature der RĂŒckgabe von Strukturen auf.
Dabei ist die innere Struktur von std::string
nicht festgelegt
und unterscheidet sich von Compiler zu Compiler sowie von Version zu Version.
Somit ebenfalls nicht geeignet fĂŒr publizierbare DLL-Aufrufe.
Im einfachsten Fall enthÀlt std::string
einen Zeiger auf einen nullterminierten String.
WĂ€re also dasselbe wie std::unique_ptr<char*>
.
Wirklich schade dass C++ solch eine zeigergroĂe Struktur nicht in ein Register packt.
BloĂ weil's dafĂŒr einen Destruktor gibt bzw. geben muss.
Sonst wĂ€re das der ideale Kandidat fĂŒr DLL-Funktionen wie GetWindowText()
â mit dem Destruktor LocalFree()
dunnemals.
Der simple Zeiger wĂ€re auĂerdem kompatibel zu printf()
, mit Augenzwinkern.
Ăblich ist hingegen eine Implementierung in eine gröĂere struct
,
die kurze Strings ohne Heap speichert
sowie âcounted stringsâ (= Zeichenarray und irgendeine, oftmals sehr geschickt gemachte LĂ€ngenangabe) verwendet.
Oft sieht man die Implementierung fĂŒr bis zu knapp 32 Zeichen,
wie im C-Beispiel oben,
weil da ein Datumsstring hineinpasst.
FĂŒr KompatibilitĂ€t mit anderen Compilern verwende ich ein Makro. Jene âanderen Compilerâ verwenden hoffentlich Copy Elision als Teil der âReturn Value Optimizationâ, kurz RVO.
// Definition in einer projektglobalen Kopfdatei (hier: C++) #ifdef _MSC_VER # define UDT(x) &x=*__$ReturnUdt #else # define UDT(x) x #endif // Anwendung im C++-Programm MeinTyp funktion() { MeinTyp UDT(ret); TuWas(ret); return ret; }
Manchmal habe ich den Eindruck, dass Microsoft einige immer wieder benötigte
Funktionen und Makros verschlÀft zu implementieren.
Die 1997 aufkommende shlwapi.dll
war da schon ein kleiner
Durchbruch, lieferte diese doch endlich die fehlenden String-Funktionen nach.
Immer noch fehlen Funktionen fĂŒr Gleitkomma-SetWorldTransform
) und bei GDI+ fĂŒr exakte Positionierung
kantengeglĂ€tteter Figuren Ă€uĂerst nĂŒtzlich ist.
Kopfdateien und define s
| |
---|---|
WIN32_LEAN_AND_MEAN | lÀsst einigen unnötigen Windows-3.0-Ramsch weg. Nicht verwenden bei Verwendung von GDIplus! |
STRICT | Schaltet die strenge TypprĂŒfung auch bei Win16 ein.
Wichtig fĂŒr den klassischen Bug bei ReleaseDC : Man konnte
HWND und HDC vertauschen, und irgendwann (viel spÀter)
krachte es im GDI, weil die Handles alle waren.
|
<windowsx.h> | NĂŒtzliche Makros, ersparen eine Unmenge
SendMessage , und man kann kombinierte Win16/Win32-Quelltexte schreiben
|
Makros | |
T("string") | wie TEXT("string") ,
erzeugt je nach UNICODE "string" oder L"string"
|
elemof(array) | liefert Anzahl der Elemente eines Arrays
(Feldes) bekannter LĂ€nge, bspw. die Anzahl der Zeichen eines TCHAR-Feldes
|
nobreak | expandiert zu nichts und dient zur Kennzeichnung
absichtlich âvergessenerâ break bei switch + case
|
Funktionen | |
InitStruct | FĂŒllt eine Struktur mit Nullen, aber das erste UINT
mit der LĂ€ngenangabe. ZweckmĂ€Ăig fĂŒr eine groĂe Zahl von Windows-Funktionen.
|
EnableDlgItem | VermÀhlung von EnableWindow und GetDlgItem
|
ShowDlgItem | VermÀhlung von ShowWindow und GetDlgItem
|
GetRadioCheck | Das Gegenteil von CheckDlgButton
|
SetCheckboxGroup | Bitfeld in eine Reihe von Auswahlfeldern setzen
|
GetCheckboxGroup | Bitfeld aus einer Reihe von Auswahlfeldern lesen
|
SetEditFocus | sollte aufgerufen werden, wenn bei WM_COMMAND - IDOK
ein fehlerhaftes Eingabefeld vorgefunden wird: der Text wird markiert
|
MoveRectIntoRect | (Fenster-)Rechteck
in ein anderes (Bildschirm-)Rechteck schieben, zur Verhinderung deplatzierter Fenster.
(Wer kennt nicht die Programme mit klassischen Bugs? Win3.1 CLOCK.EXE, um mal anzufangen, WinAmpâŠ) |
Limit | Integer-Zahl zwischen zwei Grenzen eingrenzen |
iitrafo | Integer-Zahl âtransformierenâ, bspw. Messwert nach Zweipunktabgleich |
bsf,bsr | âbit scan forwardâ, âbit scan backwardâ,
so heiĂen die Assemblerbefehle, die hier nachgebildet werden:
nieder- bzw. höchstwertigstes Bit suchen
|
bt,btc,btr,bts | âbit testâ, âbit test and complementâ,
âbit test and resetâ, âbit test and setâ, so heiĂen die Assemblerbefehle, die nachgebildet werden.
NĂŒtzlich zu wissen: Der Bittest funktioniert in Speicherbereichen bis 512 MByte GröĂe! |
vMBox, MBox | MessageBox mit String aus Resource und statischem Titel
|
GetFileNameExt | Zeiger auf Datei-Erweiterung liefern,
funktioniert auch bei Unix-Dateien wie .login (keine Erweiterung!)PathFindExtension() ist leider unbrauchbar.
|
Das fehlende typeof
-Makro kann ich leider nicht nachliefernâŠ
Hier hat Microsoft eine Menge unterdokumentierter Fallstricke in die Kopfdateien eingebaut. Dies beginnt schon damit, dass man fĂŒr manche noch so kleine Funktion oder GUID das DDK benötigt. Auch wenn's nur um das Auflisten verfĂŒgbarer serieller Schnittstellen geht.
Hier zÀhle ich nur die wichtigsten Funktionen auf.
Diese Funktionen sind ab Windows 95 und Windows NT 4 verfĂŒgbar.
SetupDiGetClassDevs()
erzeugt einen Schnappschuss der aktuellen
Konfiguration und liefert davon ein Handle.
Dieser wird fĂŒr alle nachfolgenden Funktionen benötigt.
Der gemachte Schnappschuss kann (und wird im Allgemeinen) durch die Angabe einer
klassenspezifischen GUID (Class-GUID) eingeschrÀnkt, etwa GUID_DEVCLASS_PORTS.
Diese GUID und die englische Klassenbezeichnung findet sich im
[Version]-Abschnitt von gerÀtespezifischen INF-Dateien wieder.
SetupDiEnumDeviceInfo()
, typischerweise im Kopf einer for-Schleife,
liefert davon ein Handle zu einem bestimmten GerÀt dieser Klasse,
etwa ein COM-Port. Mit dem Handle geht es dann mit CfgMgr-Funktionen weiter.
SetupDiDestroyDeviceInfoList()
zerstört diesen Schnappschuss.
Im Laufe der Entwicklung hat Microsoft aber entschieden, dass bspw. parallele und serielle Schnittstellen doch etwas unterschiedlich sind (wer wĂ€re nicht vorher drauf gekommen?), und hat neben den GerĂ€teklassen (nicht unterhalb der GerĂ€teklassen!) die Interfaces eingefĂŒhrt. Diese haben andere GUIDs und benötigen z.T. auch andere Zugriffsfunktionen.
Die UnterstĂŒtzung des Interface-Bezugs beginnt mit Windows 98 (IMHO Zweite Ausgabe), ist dort noch etwas hakelig (funktioniert bspw. bei seriellen Schnittstellen nicht), und ist ab Windows 2000 einigermaĂen vollstĂ€ndig implementiert.
Die hauptsĂ€chliche Anwendung ist das Finden und Ansprechen von USB-GerĂ€ten, fĂŒr die es fĂŒr jede USB-GerĂ€teklasse (nach USB-IF-Spezifikation) ein eigenes Interface mit eigener GUID gibt.
Um mit Interfaces zu arbeiten, ist beim Aufruf von SetupDiGetClassDevs()
das Flag DIGCF_DEVICEINTERFACE anzugeben, und es sind statt
folgende Funktionen zu verwenden:SetupDiEnumDeviceInfo()
SetupDiEnumDeviceInterfaces()
liefert ein fĂŒr die nachfolgende
Funktion geeignetes Handle fĂŒr jedes GerĂ€t im Schnappschuss.
SetupDiGetDeviceInterfaceDetail
liefert einen ziemlich kryptischen
aber systemweit eindeutigen Dateinamen fĂŒr die weitere âUnterhaltungâ mit dem GerĂ€t
via CreateFile()
und dateibezogener Ein/Ausgabe.
Die Funktionsnamen beginnen mit CM_ und bestehen allesamt aus mit Unterstrichen zusammengesetzten AbkĂŒrzungen: FĂŒr Windows eher untypisch, war aber so ganz typisch fĂŒr VxDs.
Es gibt sie ab Windows 95 sowie Windows NT 4 in einer 32-Bit-DLL namens cfgmgr32.dll.
Ab Windows 2000 sind die Funktionen in der setupapi.dll implementiert, und die noch vorhandene cfgmgr32.dll leitet die Aufrufe âĂ€ltererâ Programme einfach durch. Dies ist auch bei den 64-Bit-Windows-Versionen und -DLLs so, obwohl es da gar keine âaltenâ Programme geben kann.
Einige hÀufiger benutzte Funktionen sind:
CM_Open_DevNode_Key()
liefert einen RegistrierungsschlĂŒssel
in den âParametersâ-Teil des betreffenden GerĂ€tes.
Hier kann man gerÀtespezifische Einstellungen vornehmen
(wenn man den Treiber kennt!) bzw. den (symbolischen DOS-) Namen der Schnittstelle erfahren.
SetupDiOpenDevRegKey()
.
Diese macht genau dasselbe.
Allerdings ist der Parameter Flags unterdokumentiert.
CM_Get_First_Log_Conf()
, CM_Get_Next_Res_Des()
,
CM_Get_Res_Des_Data()
, CM_Free_Res_Des_Handle()
,
um die Portadresse(n) einer Schnittstellenkarte zu erfragen.
CM_Get_First_Log_Conf()
unter Windows 8 nicht!
Wichtig: Die meisten mit CM_
beginnenden Funktionen
funktionierten bei einem 64-Bit-Windows nur aus einem
64-Bit-Programm heraus!
Das betrifft IMHO auch einige SetupDi
-Funktionen.
Um mitzubekommen, dass neue GerÀte dazugekommen sind oder etwas entfernt wurde,
genĂŒgt es, den Rundruf WM_DEVICECHANGE mitzubekommen und dann
die Neuauflistung von GerÀten zu bewerkstelligen.
Da bisweilen mehrere dieser Rundrufe vorbeikommen, ist das Verwenden
eines Windows-Timers (SetTimer()
) sinnvoll, damit die Auflistung
verzögert erfolgt, vor allem, wenn die Auflistung lÀnger dauert.
(SetTimer()
setzt vorherige SetTimer()
-Aufrufe mit gleicher ID zurĂŒck!)
1,5 Sekunden sind ein gutes MaĂ.
Der hÀufigste Fall ist ein Programmschnipsel zur Auflistung
aller COM-Schnittstellen. Im Internet findet man auch viel Unsinn.
Auf bis zu 256 COM-Ports CreateFile()
anwenden dauert zu lange.
Dieses hier lÀuft ab Windows 98 Zweite Ausgabe
und ist dabei rasend schnell.
Hier klicken â
#include <setupapi.h>⊠case WM_DEVICECHANGE: { HWND hCombo; int i; hCombo=GetDlgItem(Wnd,ID_MEINER_COMBOBOX); ComboBox_ResetContent(hCombo); HDEVINFO devs; devs=SetupDiGetClassDevs(&GUID_DEVCLASS_PORTS, 0 ,0 ,DIGCF_PRESENT);// devs=SetupDiGetClassDevs(&GUID_DEVINTERFACE_COMPORT,0,0,DIGCF_PRESENT|DIGCF_DEVICEINTERFACE); funktioniert nicht unter Win98! if (devs!=INVALID_HANDLE_VALUE) { SP_DEVINFO_DATA devinfo; devinfo.cbSize=sizeof devinfo; for (i= 0 ; SetupDiEnumDeviceInfo(devs,i,&devinfo); i++) {TCHAR s[ 16 ];DWORD size=sizeof s; int k; HKEY hKey; if ((hKey=SetupDiOpenDevRegKey(devs,&devInfo,DICS_FLAG_GLOBAL, 0 ,DIREG_DEV,KEY_READ))==INVALID_HANDLE_VALUE) continue; if (!RegQueryValueEx(hKey,TEXT( "PortName"),NULL,NULL,(LPBYTE)s,&size)&& _stscanf(s,TEXT( "COM%d"),&k)==1 ) { // LPTs herauswerfenint idx=ComboBox_AddString(hCombo,s); // PortName-Wert, so wie er ist ComboBox_SetItemData(hCombo,idx,k); // numerischen Wert extra setzen, erspart spÀteres sscanf() if (k==MEINE_GERADE_AKTIVE_SCHNITTSTELLE) ComboBox_SetCurSel(hCombo,idx); } RegCloseKey(hKey); } SetupDiDestroyDeviceInfoList(devs); } }break;
Der Kode kommt ohne AbhÀngigkeit zu cfgmgr32.dll aus. Das vermeidet etwaige Probleme unter Windows 98.
Es sollte sogar unter Windows NT 4 laufen.
(Nur SetupDiOpenDevRegKey()
habe ich nicht getestet.)
Obwohl NT 4 gar kein richtiges Plug-and-Play-System hat.
Die Eintrittspunkte sind auch unter Windows 95 verfĂŒgbar; allerdings
liefert die Funktion SetupDiEnumDeviceInfo()
FALSE,
und GetLastError()
liefert 0x103 == ERROR_NO_MORE_ITEMS.
Von Vorteil bei der Liste der seriellen Schnittstellen ist:
Gelöst ist dies mittels Icons in einer ComboBoxEx. Damit lÀsst sich gezielt eine serielle Schnittstelle auffinden.
TODO: Verlinken
MSVC enthÀlt eher einen RC-Zerstörer als einen -Editor.
Verglichen mit Borland Resource Workshop, den es aber nur bis BC5 gab. Den Quelltext kann man jedenfalls nach Angriff mit MSVC keinem Menschen anbieten.Mehrsprachen-Ressourcen scheinen nie ordentlich zu funktionieren. So beschweren sich Amerikaner, dass meine zweisprachigen (englisch+deutsch) Programme selbst unter XP ĂŒberraschend in Deutsch erscheinen. Ich habe hierzu keine gute Lösung. Vielleicht Windows Commander zum Vorbild nehmen und nur Texte ĂŒbersetzen? Das Problem hierbei sind Dialoge im Rechts-nach-Links-Stil (arabisch).
Das Problem hat sich inzwischen gelegt, bei mir funktioniert die automatische Sprachselektion immer korrekt. Viele meiner neueren Programme sind nunmehr zweisprachig ohne viel Federlesen.
Hier ein AWK-Skript, diese Dateien wieder menschenlesbar zu machen. FĂŒr meine Web-Seiten, und fĂŒr kleinere .RC-Dateien: nice_rc.zip
Ersetze \t\t\t<Tool\n\t\t\t\tName="[^"]*"\n\t\t\t/>\n
durch Nichts.
OFN_âLONGNAMES
â Ă€h, 0x200000L
â in die Flags bei
GetOpenFileName
nachzureichen.
Und DS_â3DLOOK
â Ă€h, 0x0004
â in jede
DialogÂresource. Und so weiter.
FĂŒr alle, die nicht wissen, worum es geht:
GewöhnÂliche, nicht speziell fĂŒr Windows XP geschrieÂbene ProÂgramme bekommen
bei aktiÂvierter âTeleÂtubbie-âOptikâ zwar einen schicken FensterÂrahmen
(ĂŒber Geschmack lĂ€sst sich streiten), aber das Dialog-âInnere sieht altbacken
aus.
Es sei denn, man legt neben die Echse eine âdateiname.exe.âmanifestâ-âDatei
mit maÂgiÂschem Inhalt. Oder steckt diese in die Ressource:
Typ: RT_âMANIFEST = 24
(nicht als Text stehen lassen!),
ID: CREATEPROCESS_âMANIFEST_âRESOURCE_âID = 1
.
Womit die Echse zwangsÂlĂ€ufig gröĂer wird.
EigentÂlich nur fĂŒr ein einÂziges Bit: âMach mich schick.â
Zeile in .RC-Datei
1 24 .manifest
Der Inhalt der Manifest-Datei ist immer der gleiche:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <dependency> <dependentAssembly> <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" /> </dependentAssembly> </dependency> </assembly>Den sonst ĂŒblichen Programmnamen und die Programmbeschreibung kann man weglassen. Mit
processorArchitecture="*"
hÀlt man sich 64-bit-Probleme vom Hals.
Was auch immer Microsoft mit Manifest-Dateien eigentlich wollte (irgendÂwas
mit C# und Microsoft Forms, ich habe es nicht verstanden), ich stecke jetzt immer
eine Manifest-Datei ins Projekt und stecke sie in die ResÂsourÂce.
Damit sind die Echsen etwas gröĂer, sehen aber modern aus.
FĂŒr jeÂmanÂden, der TeleÂtubbie-âOptik nie eingeschaltet hat
(oder wie ich WinÂdows 2000 oder noch Ă€lÂter beÂnutzt), nur sinnÂlose Luft.
BeÂsonÂders unÂverÂstĂ€ndÂlich fĂŒr mich ist die AusÂwirÂkung der NameÂspace-âEleÂmente (xmlns
).
Kann man den Quatsch vielÂleicht wegÂlasÂsen, oder spinnt dann irÂgendÂeins der MiÂcroÂsoft-âBeÂtriebsÂsysÂteÂme?
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"><security><requestedPrivileges><requestedExecutionLevel level="asInvoker" uiAccess="false"/></requestedPrivileges></security></trustInfo> <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"><application><supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/><supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/></application></compatibility> <asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3"><asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings"><dpiAware>true</dpiAware></asmv3:windowsSettings></asmv3:application> <dependency><dependentAssembly><assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" /></dependentAssembly></dependency></assembly>
Manifest-Ressource, auch zum nachtrĂ€glichen Einbau in Programme, die unscharf aussehen; Anleitung fĂŒr MSVC6:Diese MaĂnahme funktioniert gegenĂŒber dem Windows-Rezept (Rechte Maustaste auf EXE-Datei, Eigenschaften, Hohe DPI-Einstellungen usw.) auch dann, wenn die Echse ĂŒber eine VerknĂŒpfung (.lnk oder DateiverknĂŒpfung) geöffnet wird. Etwa bei der Keil-”Vision-IDE uv2.exe und der verknĂŒpften Projektdatei *.uv2.
- Betreffende EXE-Datei ausfindig machen und mit MSVC6 âals Ressourceâ öffnen, das startet den eingebauten BinĂ€rressourcen-Editor
- Ressource âbenutzerdefiniertâ anlegen, Name = Nummer = 24. Falls diese mit Nummer 1 bereits existiert, öffnen
- Inhalt der o.a. Datei einfĂŒgen bzw. ĂŒberschreiben
- Eigenschaften dieser Ressource anzeigen, Nummer von 101 auf 1 Àndern
- Speichern; bei Schreibschutz âSpeichern alsâ in eine temporĂ€re Datei und mit Admin-Rechten ĂŒberbĂŒgeln
Der beliebte Registry-âSchlĂŒssel HKEY_âLOCAL_âMACHINE
ist praktisch tabu.
Dieser SchlĂŒssel wird fĂŒr Programme mit normalen Benutzerrechten weg-âvirtualisiert,
und die Daten gelangen an eine unschöne, schwer aufÂzuÂfindende Stelle.
FĂŒr Ordnung in der Registrierung sollte man daher alle EinÂstellungen schön brav
nach HKEY_âCURRENT_âUSER
speichern.
MaschinenÂspezifische Einstellungen wie etwa die PortÂadresse einer
SchnittÂstellenÂkarte lassen sich faktisch nur sehr umstĂ€ndÂlich
(in einer INI-Datei; das Programme
-Verzeichnis kann aber schreibÂgeschĂŒtzt sein) speichern,
fĂŒr Ordnung und Einfachheit tue ich's ebenfalls zu HKEY_CURRENT_USER
.
Diese Einstellungen sind
âumziehendâ,
was bspw. fĂŒr Portadressen Quatsch ist.
Hier hilft eventuell das (missbrÀuchliche?) Speichern nach HKEY_CLASSES_ROOT
als benutzerÂspezifische, aber nicht âumziehendeâ Daten.
Manifest-Ressourcen fĂŒr DLLs, genauer: Eigenschaftsseiten-Lieferanten (âProperty Sheet Providerâ)â
Im konkreten Fall ging es darum, eine Erweiterung fĂŒr MMC.EXE, genauer, dem GerĂ€temanager (Aufruf einfach mitdevmgmt.msc
) modern aussehen zu lassen. Laut Microsoft-Dokumentation muss man wie folgt vorgehen:LÀsst man auch nur einen Schritt weg, bleibt alles altbacken. Warum eigentlich dieses Mehr an Aufwand? Ist mir total unverstÀndlich.
- Nimm ein genĂŒgend neues SDK zum Compilieren (also XP-SDK)
- Lege die
RT_MANIFEST
-Ressource mit dem numerischen BezeichnerISOLATIONAWARE_MANIFEST_RESOURCE_ID
= 2 an- Schreibe an den Anfang des Quelltextes
#define ISOLATION_AWARE_ENABLED 1
Das
Alt und neu zusammen in einem Fenster sieht eben nicht toll aus! Man beachte vor allem die unterschiedlichen KnöpfeISOLATION_AWARE_ENABLED
bewirkt ein AufblĂ€hen des Kodes und den Zwang zur Laufzeitbibliothek! Dies wird seitens Microsoft ĂŒber*.inl
-Dateien erreicht, das sind Kopfdateien mit einer Menge Inline-Code. Dieser Inline-Code ist absichtlich kryptisch gehalten und damit kaum lesbar! AuĂerdem werden so unsinnige Sachen gemacht wieUNICOWS.DLL
zu laden.UNICOWS.DLL
ist die Hauptdatei fĂŒr Unicode-UnterstĂŒtzung unter Windows 98/Me, aber diese Systeme haben mit Manifest-Ressourcen eh' nichts am Hut!Der Schalter
ISOLATION_AWARE_ENABLED
ist also fĂŒr mich völlig inakzeptabel, und so habe ich mich so lange durch die kryptischen*.inl
-Dateien gewurstelt, bis nur noch der wirklich benötigte Kode ĂŒbrig blieb. Das ist nun erfreulich wenig.Einschalten der Teletubbie-Optikâ
Irgendwie braucht man dazu immer einen AktivierungskontexthActCtx
.Beschaffen des Aktivierungskontextesâ
Der dynamische Einsprungpunkt viaHANDLE hActCtx; // âAktivierungskontextâ, das Ding mit der Teletubbie-Optik BOOL APIENTRY _DllMainCRTStartup(HANDLE hModule, DWORD reason, LPVOID lpReserved) { switch (reason) { case DLL_PROCESS_ATTACH: { ACTIVATION_CONTEXT_BASIC_INFORMATION actCtxBasicInfo; BOOL (WINAPI*pQueryActCtxW)(DWORD,HANDLE,PVOID,ULONG,PVOID,SIZE_T,SIZE_T*); HANDLE hKernel; hInst=(HINSTANCE)hModule; DisableThreadLibraryCalls(hModule); InitCommonControls(); hKernel=GetModuleHandle(T( "KERNEL32.dll"));(FARPROC)pQueryActCtxW=GetProcAddress(hKernel, "QueryActCtxW");if (pQueryActCtxW && pQueryActCtxW( // ziemlich undokumentiert QUERY_ACTCTX_FLAG_ACTCTX_IS_ADDRESS|QUERY_ACTCTX_FLAG_NO_ADDREF, &hActCtx, // irgendeine Adresse NULL, ActivationContextBasicInformation, &actCtxBasicInfo, sizeof(actCtxBasicInfo), NULL) && actCtxBasicInfo.hActCtx!=INVALID_HANDLE_VALUE) hActCtx=actCtxBasicInfo.hActCtx; // <hActCtx> nie freigeben, da ohne AddRef! }break; } return TRUE; } GetProcAddress()
wird benötigt, damit die DLL auch unter Windows 2000 ladbar ist.Der Trick bei Win16, mitWo (und ob?)UndefDynLink
zur Laufzeit zur vergleichen, klappt bei Win32 nicht.InitCommonControls()
aufgerufen wird, ist irrelevant. Beim Debuggen sieht man, dassInitCommonControls()
zu nichts weiter als einemret
-Befehl fĂŒhrt.Bei einer 64-bit-DLL kann man sich das Beschaffen des Einsprungpunktes zur Laufzeit ersparen, da es 64-bit-Editionen von Windows erst ab Windows XP gibt.
(Ja, gibt es! Wenn auch nur in englisch. Die meisten kennen das erst ab Windows Vista.)Aktivieren der Teletubbie-Optikâ
Normalerweise kann man die Aktivierung mittelsActivateActCtx()
wĂ€hrend des Programmlaufs bewirken. Das klappt aber nicht mit Eigenschaftsseiten (âProperty Sheetsâ)! Da verreckt irgendwoMMC.exe
, der GerÀtemanager.Eigenschaftsseiten verlangen ein besonderes Vorgehen:
Die ĂŒbrigen Fenster, wie Unter-Dialoge oder Messageboxen bekommen automatisch Teletubbie-Optik, da braucht man sich nicht mehr zu kĂŒmmern. Keine Wrapper mitHPROPSHEETPAGE hpage; PROPSHEETPAGE page; ⊠// die normale Initialisierung page.dwFlags = ⊠|PSP_USEFUSIONCONTEXT; page.hActCtx = hActCtx; // Die erweiterte PROPSHEETPAGE-Struktur steckt in der Kopfdatei des XP-SDKs oder -DDKs hpage = CreatePropertySheetPage(&page); ActivateActCtx()
undDeactivateActCtx()
. Und, das ist sehr nett, keine weiteren dynamischen Einsprungpunkte.Zu bedenken ist, dass das Hervorheben bestimmter Dialogelemente durch Farbe, insbesondere bei Checkboxen, nicht mehr mittels
SetTextColor()
innerhalb vonWM_CTLCOLORSTATIC
möglich ist. Die einfachste Lösung ist es, derartige Dialogelemente mittelsSetWindowTheme()
in ihr altes Aussehen zurĂŒckzuverwandeln.
Inzwischen ist mir der Sinn von Manifests (zumindest der <dependency>
-Abschnitt) etwas klar geworden:
WINDOWS\SYSTEM32
)
und mindestens eine âneueâ fĂŒr XP-Stil (in â festhalten â
WINDOWS\WinSxS\x86_âMicrosoft.âWindows.âCommon-âControls_â6595b64144ccf1df_â6.0.0.0_âx-ww_1382d70a
CreateâProcess()
und LoadâLibrary()
) entÂscheiÂdet
anÂhand der MaÂniÂfest-ResÂsourÂce oder -Datei, die in bzw. neben der
zu laÂdenÂden .EXE/.DLL liegt, welche VerÂsion von COMCTL32.dll geÂlaÂden und dyÂnaÂmisch geÂlinkt wird.
CREATEâPROCESS_âMANIFEST_âRESOURCE_âID
.
PSP_âUSEâFUSIONââCONTEXT
und MemÂber hActCtx
nicht geÂsetzt).
hActCtx
).
WahrÂscheinÂlich könÂnen DLLs TeleÂtubbie-âOptik beÂnutÂzen, auch wenn das
WirtsÂproÂgramm ohne ausÂgeÂstatÂtet ist; dazu muss CreateâActâCtx()
aufÂgeÂruÂfen werden.
MMC.exe
) mit TeleÂtubbie-âOptik
ausÂgeÂstatÂtet ist, darf CreateâActâCtx()
nicht
aufÂgeÂruÂfen werÂden (Absturz!!).
StattÂdesÂsen muss QueryâActâCtxW()
mit unÂdoÂkuÂmenÂtierÂten
PaÂraÂmeÂtern und SchalÂtern aufÂgeÂruÂfen werÂden.
(Betrifft nur Programme, bei denen nicht einmal der Bezug zur msvcrt.dll
erwĂŒnscht ist.)
intâfloatâdoubleâlong doubleâ__int64
âint
sind nicht intrinsisch,
es sei denn, man benutzt die Kommandozeilen-Option /QIfist.
Dann gilt das Rundungsverhalten von _controlfp()
.
StandardmĂ€Ăig gilt âBanker's Roundingâ (mathematisches Runden),
wÀhrend eine C-konforme (int)
-Typumwandlung gegen Null rundet.
fabs(x)
: 1 x87-Befehl
sin(x), cos(x), atan2(y,x), fmod(y,x)?, sqrt(x)
: 1 x87-Befehl
tan(x), atan(x), log(x), log10(x)
: einige x87-Befehle
float
-Formen (etwa sinf(x)
) sind blödsinnigerweise niemals intrinsisch!
_CI
an):exp(x), fmod(x,y), pow(x,y)
; Beispielkode, um die Laufzeitbibliothek zu vermeiden: double _cdecl _CIexp() { _asm{ fldl2e fmulp st( 1 ),st // st = f * log2 efld st // duplizieren frndint // st = ganzzahliger Teil (Rundung mathematisch) fsub st( 1 ),st // st(1) = gebrochener Teil (± 0,5)fxch st( 1 )f2xm1 // st = 2^gebrochener_Teil-1 (f2xm1 kann nur im Intervall ±1 arbeiten!) fld1 faddp st( 1 ),st // 1 addieren: st = 2^gebrochener_Teilfscale // st = 2^gebrochener_Teil * 2^ganzzahliger_Teil fxch st( 1 )fstp st // ganzzahligen Teil entfernen } } double _cdecl _CIfmod() { _asm{ fxch st( 1 ) // Operanden vertauschenfprem // eigentliches fmod fxch st( 1 )fstp st( 0 ) // Divisor verwerfen}}
acos(x), asin(x)
= _copysign(atan(sqrt(x*x/(1-x*x))),x)
cosh(x), sinh(x), tanh(x)
_CI
anfangen.
Logischerweise erĂŒbrigt sich damit das Vorhalten von Gleitkomma-Funktionen fĂŒr den einfach genauen Datentyp float
;
der 80x87 rechnet sowieso mit long double
(80 Bit).
_CI
an; wozu sollte das gut sein, wenn es intrinsische Funktionen gibt?)
floor(x), ceil(x), _copysign(x,y), _hypot(x,y), modf(x,ip)
_j0(x), _j1(x), _jn(n,x), _y0(x), _y1(x), _yn(n,x)
Da SSE2 keine transzendenten Funktionen anbietet, fĂŒhren alle diese Funktionen zur Laufzeitbibliothek, die das Ergebnis trickreich ausschlieĂlich per SSE2-Befehlssatz berechnet.
Trotzdem ist es möglich, derartige Funktionen mit der x87-Einheit ausrechnen zu lassen. Um den betreffenden Kode zu generieren, benötigt man einen alternativen Assembler. Ich habe kurzerhand YASM, ein Ableger von NASM ausprobiert.
Und so sieht eine selbstgeschriebene Sinusfunktion aus, die von C aus aufrufbar ist:
sin : movq rax,xmm0 ;Der double-Parameter kommt in xmm0; dieser muss erst mal in den x87-Stapelpush rax ;Dazu muss der Operand speicheradressierbar sein, da bietet sich der Stack an fld qword[rsp] ;Nun kann st(0) mit dem Winkelargument geladen werden fsin ;Die eigentliche Berechnung (fwait braucht man nicht mehr) fstp qword[rsp] ;Jetzt der ganze SpaĂ in der anderen Richtung: Auf den Stack pop rax ;runter vom Stack movq xmm0,rax ;wieder ins SSE2-Register ret ;fertig
All die WarÂnunÂgen, dass der x87-âReÂgisÂterÂsatz KonÂtextÂwechÂsel nicht ĂŒberÂlebt, kann man vorÂerst geÂtrost igÂnoÂrieÂren. Denn das stimmt schlichtÂweg nicht. ZuÂminÂdest im User Mode. Denn daÂmit 32-âBit-âProÂgramÂme unÂter 64-âBit-âWinÂdows lauÂfen, muss WinÂdows den x87-âZuÂstand beim KonÂtextÂwechÂsel siÂchern. Da wĂŒrÂde es kaum nutÂzen, wenn man das nur fĂŒr 64-âBit-âProÂzesÂse nicht tĂ€Âte. Erst wenn 32-âBit-âProÂgramÂme ausÂgeÂstorÂben sind (schĂ€tÂzungsÂweiÂse im Jahr 2030), könnÂte sich das Ă€nÂdern, aber woÂmögÂlich tauÂchen vorÂher schon x64-âProÂzesÂsoÂren ohÂne x87 und ohÂne 32-âBit-âKomÂpaÂtiÂbiÂliÂtĂ€tsÂmoÂdus auf.
_USE_MATH_DEFINES
benutzen!
(Nicht unter MSVC6 verfĂŒgbar.)
HĂ€ufig | Konstantenwert | |
---|---|---|
10x | exp(x*M_LN10) | 2.30258509299404568402 |
2x | exp(x*M_LN2) | 0.693147180559945309417 |
log2 x | log(x)/M_LN2
| |
Seltener, etwa bei selbst geschriebenen Gleitkomma-Routinen via 80x87 | ||
log10 x | log(x)/M_LN10 | 2.30258509299404568402 |
arcsin x | _copysign(atan(sqrt(x*x/(1-x*x))),x)
| |
sinh x | (exp(x)-exp(-x))*0.5
| |
cosh x | (exp(x)+exp(-x))*0.5
| |
arsinh x | ln(x+sqrt(x*x-1))
| |
arcosh x | ln(x+sqrt(x*x+1))
|
Der C-Standard unterstĂŒtzt die Operatoren
<<
, >>
sowie %
und ^
nicht.
Warum eigentlich nicht?
In der Standardbibliothek sind entsprechende (naheliegende) Funktionen ja bereits vorhanden.
Eine kleine Hilfsklasse schafft Abhilfe in C++:
Schade, dass C++ nicht das Ăberladen undefinierter Operatoren fĂŒrstruct Double{ Double(double v) : val(v) {} operator double() const {return val;} Double& operator <<= (int exp) {val=ldexp(val,exp); return *this;} Double operator << (int exp) const {Double o(*this); return o<<=exp;} Double& operator >>= (int exp) {return *this<<=-exp;} Double operator >> (int exp) const {return *this<<-exp;} Double& operator %= (double n) {val=fmod(val,n); return *this;} Double operator % (double n)const {Double o(*this); return o%=n;} Double& operator ^= (double n) {val=pow(val,n); return *this;} double operator ^ (double n)const {return pow(val,n);} double operator & (double n)const {return hypot(val,n);} // Betrag des Vektors Double operator | (Double n)const {return ~(~*this+~n);} // Parallelschaltung (von WiderstĂ€nden) Double operator ~ () const {return Double( 1 /val);} // Inversionprivate: double val; };
double
(klein!) unterstĂŒtzt.
StandardmĂ€Ăig unterstĂŒtzt Windows 98 (gleiches gilt fĂŒr Windows 95 und Windows Me) nur einige wenige Unicode-Funktionen:
Es reicht immerhin, um Unicode-Sonderzeichen zu malen. Steuerelemente mit Unicode sind hingegen eher aufwĂ€ndig, am ehesten geht es mittels CustomDraw- bzw. OwnerDraw-Malereien. Die Titelzeile eines Fensters sowie Tooltips sind fĂŒr erweiterte Unicode-Zeichen jedoch eher taub; da mĂŒsste man alles selber malen. (Tooltips selber machen ist nicht allzu schwierig.) KernelEx löst keines dieser Probleme.
Das Microsoft-Paket unicows hilft da nicht wirklich weiter, denn:
EnumPrinters
.
Fehlt jetzt eigentlich nur noch die Funktion LoadStringW
,
um eine Zeichenkette aus der ohnehin in Unicode
(genauer: UTF-16)
vorliegenden String-Ressource zu laden:
Die eigene Funktion kann man nichtint MyLoadStringW(HINSTANCE hInst, UINT id, PWSTR buf, int len) { HRSRC r=FindResource(hInst,MAKEINTRESOURCE((id>>4)+ 1 ),RT_STRING);PCWSTR p=(PCWSTR)LoadResource(hInst,r); if (!p) return 0 ;for (id&= 15 ; id; id--) {p+=*p+ 1 ;} len--; if (len<0) return *p+ 1 ; // AllokationsgröĂe in Zeichenif (len>*p) len=*p; p++; __movsw(buf,p,len); // inklusive etwaiger Nullen! buf[len]= 0 ;return len; }
LoadStringW
nennen,
deshalb der PrÀfix My
.
__movsw
ist ein intrinsischer Befehl ab Visual Studio 2003.
Bei MSVC 6 geht da ersatzweise:
__forceinline void __movsw(void*d, const void*s, DWORD count) { _asm mov edi,d _asm mov esi,s _asm mov ecx,count _asm rep movsw }
AnsiToOem()
(auĂer fĂŒr Dateinamen von der Kommandozeile),
sondern verwendet ausschlieĂlich OEM-Strings in seinem ANSI-Programm.
Nun muss man nur noch OEM-Strings aus der Ressource laden:
int LoadStringOem(HINSTANCE hInst, UINT id, PSTR buf, int len) { HRSRC r=FindResource(hInst,MAKEINTRESOURCE((id>>4)+ 1 ),RT_STRING);PCWSTR p=(PCWSTR)LoadResource(hInst,r); if (!p) return 0 ;for (id&= 15 ; id; id--) {p+=*p+ 1 ;} len=WideCharToMultiByte(CP_OEMCP, 0 ,p+1 ,*p,buf,len-1 ,NULL,NULL);buf[len]= 0 ;return len; }
Sieht logischerweise so Àhnlich aus wie die MyLoadStringW()
-Routine oben.
Die Ressourcendatei selbst muss in UTF-16 abgespeichert werden und kann nur mit RC.EXE ab Visual Studio 2005 compiliert werden; MSVC6 geht hierfĂŒr nicht.
Sollten auf dem Zielsystem keine Rahmensymbole zur VerfĂŒgung stehen
wandelt WideCharToMultiByte()
diese in optisch entsprechende
ASCII-Zeichen um.
Daher braucht man beim Export eines solchen Programms in exotische LĂ€nder
keine Angst zu haben.
Windows hat sich lange Zeit geweigert, UTF-8 als 8-Bit-Zeichensatz anzunehmen. Standard ist es nur bei der Hindi-Version von Windows.
Seit Windows 10 gibt es die Möglichkeit, UTF-8 als ANSI-Zeichensatz festzulegen. Als âexperimentelles Featureâ. Leider ist die Nebenwirkung, dass Notepad++ und Programmer's Notepad ANSI-Dateien von MSVC6 fehlerhaft darstellen.