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.
| mit Laufzeitbibliothek | ohne Laufzeitbibliothek | |
|---|---|---|
| Minimale Kodegröße | 20 KB | 2,5 KB |
| Kopfdateien | #include <stdafx.h> oder ähnliches | #define WIN32_LEAN_AND_MEAN #include <windows.h> // Windows eben #include <windowsx.h> // Nützliche Makros #include <shlwapi.h> // String-Funktionen #include <comdlg32.h> // Datei-öffnen-Dialog #include <comctl32.h> // erweiterte Dialogelemente ... |
| 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.
|
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
| |||||||||||||||||||||||||||||||||||
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);
Oftmals will der Linker eine Funktion __ftol oder __ftol2_sse. Siehe hier:
long _declspec(naked) _cdecl _ftol(void){ _asm{
push eax
fistp dword ptr [esp]
pop eax
ret
}}
Das Problem: Diese Funktion rundet standardmäßig.
(Die echte __ftol-Funktion rundet zur Null.)
Man könnte auch die Kommandozeilenoption /QIfist für den gleichen Zweck benutzen.
Meistens will man sowieso runden, und deshalb ist es besser, sämtliche
automatischen Typecasts zu entfernen und durch eine eigene Funktion
zu ersetzen, etwa:
__forceinline __int64 RoundInt(double f) {
__int64 i;
_asm fld f
_asm fistp i
return i;
}
statt dem langweiligen, aber üblichen 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 RoundInt(0.5) zur geraden Zahl 0 rundet (Banker's Rounding).
_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.
Und so kommt man an die Anfangs- und Endadresse dieser Tabelle, siehe auch Microsoft-CRT-Quelle:
#include <windows.h>
#pragma data_seg(".CRT$XCA")
LONG_PTR Anfang = 0; // Initialisierung ist wichtig, sonst geht's nach .bss!
#pragma data_seg(".CRT$XCZ")
LONG_PTR Ende = 0; // irgendetwas
#pragma data_seg() //zurückschalten
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(LONG_PTR*,LONG_PTR*); _initterm(&Anfang,&Ende); // ruft alle statischen KonstruktorenDie Konstruktoren enthalten auch Kode, um die statischen Destruktoren in die Liste für
_onexit(?) einzutragen.
Das gesonderte Aufrufen der Destruktoren ist daher nicht notwendig.
shlwapi.dll war da schon ein kleiner
Durchbruch, lieferte diese doch endlich die fehlenden String-Funktionen nach.
Immer noch fehlen Funktionen für Gleitkomma-
Das fehlende
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.
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
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:
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 MSVC enthält eher einen RC-Zerstörer als einen -Editor.
Deshalb ein
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.
Für alle, die nicht wissen, worum es geht:
Gewöhnliche, nicht speziell für Windows XP geschriebene Programme bekommen
bei aktivierter „Teletubbie-Optik“ zwar einen schicken Fensterrahmen
(ü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 magischem Inhalt. Oder steckt diese in die Ressource:
Typ:
Zeile in projektname.RC-Datei
Der Inhalt der Manifest-Datei ist immer der gleiche:
SetWorldTransform) und bei GDI+ für exakte Positionierung
kantengeglätteter Figuren äußerst nützlich ist.
Kopfdateien und defines
WIN32_LEAN_AND_MEANlässt einigen unnötigen Windows-3.0-Ramsch weg.
Nicht verwenden bei Verwendung von GDIplus!
STRICTSchaltet 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"
#define T(x) TEXT(x)
elemof(array)liefert Anzahl der Elemente eines Arrays
(Feldes) bekannter Länge, bspw. die Anzahl der Zeichen eines TCHAR-Feldes
#define elemof(x) (sizeof(x)/sizeof(*(x)))
nobreakexpandiert zu nichts und dient zur Kennzeichnung
absichtlich „vergessener“ break bei switch + case
#define nobreak
Funktionen
InitStructFüllt eine Struktur mit Nullen, aber das erste UINT
mit der Längenangabe. Zweckmäßig für eine große Zahl von Windows-Funktionen.
void _fastcall InitStruct(LPVOID p, UINT len) {
__stosd(p,0,len>>2);
*(UINT*)p=len;
}
EnableDlgItemVermählung von EnableWindow und GetDlgItem
void EnableDlgItem(HWND Wnd, UINT id, BOOL state) {
Wnd=GetDlgItem(Wnd,id);
if (Wnd) EnableWindow(Wnd,state);
}
ShowDlgItemVermählung von ShowWindow und GetDlgItem
void ShowDlgItem(HWND Wnd, UINT id, int state) {
Wnd=GetDlgItem(Wnd,id);
if (Wnd) ShowWindow(Wnd,state);
}
GetRadioCheckDas Gegenteil von CheckDlgButton
SetCheckboxGroupBitfeld in eine Reihe von Auswahlfeldern setzen
void SetCheckboxGroup(HWND Wnd, UINT u, UINT o, UINT v) {
for (; u<=o; u++,v>>=1) CheckDlgButton(Wnd,u,v&1);
}
GetCheckboxGroupBitfeld aus einer Reihe von Auswahlfeldern lesen
UINT GetCheckboxGroup(HWND Wnd, UINT u, UINT o) {
UINT v,m;
for (v=0,m=1; u<=o; u++,m+=m) if (IsDlgButtonChecked(Wnd,u)==1) v|=m;
return v;
}
SetEditFocussollte aufgerufen werden, wenn bei WM_COMMAND - IDOK
ein fehlerhaftes Eingabefeld vorgefunden wird: der Text wird markiert
MoveRectIntoRect
MoveRectIntoFullScreen(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…)
LimitInteger-Zahl zwischen zwei Grenzen eingrenzen
iitrafoInteger-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
#if _MSC_VER < 1400 // Visual C++ 6, nur X86
__forceinline char _BitScanForward(DWORD*Index, DWORD mask) {
_asm bsf eax,mask
_asm mov ecx,Index // ohne ECX einzusauen geht es hier nicht
_asm mov [ecx],eax
_asm setnz al
}
__forceinline char _BitScanReverse(DWORD*Index, DWORD mask) {
_asm bsr eax,mask
_asm mov ecx,Index
_asm mov [ecx],eax
_asm setnz al
}
#endif
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, MBoxMessageBox mit String aus Resource und statischem Titel
HINSTANCE ghInstance; // kann je nach Ressourcen-DLL gesetzt werden, sonst 0 = aus EXE-Datei
TCHAR MBoxTitle[64]; // typischerweise mit LoadString() am Programmstart vorbelegen
int vMBox(HWND Wnd, LONG_PTR id, UINT style, va_list arglist) {
TCHAR buf2[1024];
if (IS_INTRESOURCE(id)) { // String aus Ressource (kein Zeiger)
TCHAR buf1[256];
LoadString(ghInstance,(UINT)id,buf1,elemof(buf1));
wvnsprintf(buf2,elemof(buf2),buf1,arglist);
}else wvnsprintf(buf2,elemof(buf2),(PCTSTR)id,arglist); // id ist Zeiger
return MessageBox(Wnd,buf2,MBoxTitle,style);
}
int _cdecl MBox(HWND Wnd, UINT id, UINT style,...) {
return vMBox(Wnd,id,style,(va_list)(&style+1));
}
GetFileNameExtZeiger auf Datei-Erweiterung liefern,
funktioniert auch bei Unix-Dateien wie .login (keine Erweiterung!)
PathFindExtension() ist leider unbrauchbar.
typeof-Makro kann ich leider nicht nachliefern…
Realisierung von Plug-and-Play
Für die Programmierung einigermaßen gesitteter, hardwarenaher Programme ist eine
PnP-Unterstützung erforderlich.
Hierbei gibt es immer wieder Reinfälle bezüglich der Kompatibilität
der Windows-Versionen untereinander.
SetupDi- und CfgMgr-Funktionen
Das moderne Arbeitspferd bei PnP sind SetupDi-Funktionen.
Sie fangen alle mit SetupDi an und sind in der setupapi.lib implementiert.
Diese gibt es in der „alten“ Ausführung mit Klassenbezug
und in der „neuen“ mit zusätzlichem Interface-Bezug.
SetupDi mit Klassenbezug
Unter Klasse ist die Geräteklasse wie im Geräte-Manager zu verstehen.
Also bspw. „Anschlüsse“ (englisch „Ports“), „Netzwerkadapter“ usw.
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.
Diese Funktion funktioniert unter Windows 95 nicht so recht.
SetupDiDestroyDeviceInfoList() zerstört diesen Schnappschuss.
SetupDi mit Interface-Bezug
Unter Interface ist offenbar eine detailliertere Schnittstelle als bei den Geräteklassen gemeint.
Klar, Geräteklassen verbindet eine gemeinsame Schnittstelle.
Auf Ports (COM oder LPT) kann man Daten ausgeben,
Daten lesen (vielleicht) und irgendetwas einstellen.
SetupDiGetClassDevs()
das Flag DIGCF_DEVICEINTERFACE anzugeben, und es sind statt
folgende Funktionen zu verwenden:SetupDiEnumDeviceInfo()
Unter Windows 95 und Windows NT 4 sind diese Funktionen
nicht in der setupapi.dll enthalten!
SetupDiEnumDeviceInterfaces() liefert ein für die nachfolgende
Funktion geeignetes Handle für jedes Gerät im Schnappschuss.
SetupDiGetDeviceInterfaceDetailliefert einen ziemlich kryptischen
aber systemweit eindeutigen Dateinamen für die weitere „Unterhaltung“ mit dem Gerät
via CreateFile() und dateibezogener Ein/Ausgabe.
Man findet im Internet eine Unmenge sehr umständlich programmierter Schleifen
mit irrwitziger Speicherverwaltung;
eins meiner Beispiele
zeigt ab Zeile 42, wie es auch ohne geht.
CfgMgr-Funktionen
Die Konfigurations-Manager-Funktionen sind eine recht alte Erfindung.
Es gab sie bei den ersten VxDs
im Kernel Mode sowie eine 16-Bit-Implementierung unter Windows 95.
Vermutlich würde niemand die o.g. SetupDi-Funktionen benötigen,
wäre hier alles sauber dokumentiert.
Microsoft hat anscheinend entschieden, diese Unterdokumentierung beizubehalten.
So als ob diese aussterben sollen.
Dann ist es trotzdem verwunderlich, dass man sie in vielen Situationen zwingend benötigt.
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.
Sieht man eigentlich nur wegen des in der
Microsoft-Dokumentation fehlenden Verweises auf 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.
Aus unerklärlichen Gründen funktioniert CM_Get_First_Log_Conf()
unter Windows 8 nicht!
Rückrufe (Callbacks)
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!)
2 Sekunden sind ein gutes Maß.
Beispiel
Das ist natürlich 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
(zumindest Zweite Ausgabe).
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 herauswerfen
int 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;
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.
Ressourcen - ein ewiges Drama mit MSVC
AWK-Skript,
diese Dateien wieder menschenlesbar zu machen.
Für meine Web-Seiten, und für kleinere .RC-Dateien:
nice_rc.zip
Manifest-Dateien und -Ressourcen
Damit hat es Microsoft wieder mal geschafft, gestandene Programmierer
so richtig zu ärgern. Und alte Programme alt aussehen zu lassen.
Wie damals schon beim Übergang von 16-bit-Programmen nach Windows95.
Als Heerscharen von Programmieren damit beschäftigt waren,
OFN_LONGNAMES – äh, 0x200000L – in die Flags bei
GetOpenFileName nachzureichen.
Und DS_3DLOOK – äh, 0x0004 – in jede
Dialogresource. Und so weiter.
RT_MANIFEST = 24 (nicht als Text stehen lassen!),
ID: CREATEPROCESS_MANIFEST_RESOURCE_ID = 1.
Womit die Echse zwangsläufig größer wird.
Eigentlich nur für ein einziges Bit: „Mach mich schick.“
1 24 projektname.manifest
<?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 (irgendwas
mit C# und Microsoft Forms, ich habe es nicht verstanden), ich stecke jetzt immer
eine Manifest-Datei ins Projekt und stecke sie in die Ressource.
Damit sind die Echsen etwas größer, sehen aber modern aus.
Für jemanden, der Teletubbie-Optik nie eingeschaltet hat
(oder wie ich Windows 2000 oder noch älter benutzt), nur sinnlose Luft.
Besonders unverständlich für mich ist die Auswirkung der Namespace-Elemente (xmlns).
Kann man den Quatsch vielleicht weglassen, oder spinnt dann irgendeins der Microsoft-Betriebssysteme?
<?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>
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 aufzufindende Stelle.
Für Ordnung in der Registrierung sollte man daher alle Einstellungen schön brav
nach HKEY_CURRENT_USER speichern.
Maschinenspezifische Einstellungen wie etwa die Portadresse einer
Schnittstellenkarte lassen sich faktisch nur sehr umständlich
(in einer INI-Datei; das Programme-Verzeichnis kann aber schreibgeschü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 benutzerspezifische, 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 1Das
![]()
![]()
Alt und neu zusammen in einem Fenster sieht eben nicht toll aus! Man beachte vor allem die unterschiedlichen KnöpfeISOLATION_AWARE_ENABLEDbewirkt 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.DLLzu laden.UNICOWS.DLList 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_ENABLEDist 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
HANDLE 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; }Der dynamische Einsprungpunkt viaGetProcAddress()wird benötigt, damit die DLL auch unter Windows 2000 ladbar ist.Der Trick bei Win16, mitWo (und ob?)UndefDynLinkzur 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:
HPROPSHEETPAGE 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);Die übrigen Fenster, wie Unter-Dialoge oder Messageboxen bekommen automatisch Teletubbie-Optik, da braucht man sich nicht mehr zu kümmern. Keine Wrapper mitActivateActCtx()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_CTLCOLORSTATICmö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
CreateProcess()
und LoadLibrary()) entscheidet
anhand der Manifest-Ressource oder -Datei, die in bzw. neben der
zu ladenden .EXE/.DLL liegt, welche Version von COMCTL32.dll geladen und dynamisch gelinkt wird.
CREATEPROCESS_MANIFEST_RESOURCE_ID.
PSP_USEFUSIONCONTEXT und Member hActCtx nicht gesetzt).
hActCtx).
Wahrscheinlich können DLLs Teletubbie-Optik benutzen, auch wenn das
Wirtsprogramm ohne ausgestattet ist; dazu muss CreateActCtx() aufgerufen werden.
MMC.exe) mit Teletubbie-Optik
ausgestattet ist, darf CreateActCtx() nicht
aufgerufen werden (Absturz!!).
Stattdessen muss QueryActCtxW() mit undokumentierten
Parametern und Schaltern aufgerufen werden.
(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 e
fld 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_Teil
fscale // st = 2^gebrochener_Teil * 2^ganzzahliger_Teil
fxch st(1)
fstp st // ganzzahligen Teil entfernen
}
}
double _cdecl _CIfmod() {
_asm{ fxch st(1) // Operanden vertauschen
fprem // 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-Stapel push 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 Warnungen, dass der x87-Registersatz Kontextwechsel nicht überlebt, kann man vorerst getrost ignorieren. Denn das stimmt schlichtweg nicht. Zumindest im User Mode. Denn damit 32-Bit-Programme unter 64-Bit-Windows laufen, muss Windows den x87-Zustand beim Kontextwechsel sichern. Da würde es kaum nutzen, wenn man das nur für 64-Bit-Prozesse nicht täte. Erst wenn 32-Bit-Programme ausgestorben sind (schätzungsweise im Jahr 2030), könnte sich das ändern, aber womöglich tauchen vorher schon x64-Prozessoren ohne x87 und ohne 32-Bit-Kompatibilitätsmodus 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))
| |
MessageBox).
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.)
Das Microsoft-Paket unicows hilft da nicht wirklich weiter, denn:
EnumPrinters.
LoadStringW,
um eine Zeichenkette aus der ohnehin in Unicode
(genauer: UTF-16)
vorliegenden String-Ressource zu laden:
int 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 Zeichen
if (len>*p) len=*p;
p++;
__movsw(buf,p,len); // inklusive etwaiger Nullen!
buf[len]=0;
return len;
}
Die eigene Funktion kann man nicht 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
}
Inwieweit man das Problem unter Vorhandensein von
KernelEx überhaupt
angehen muss, muss ich noch untersuchen.
Womöglich erübrigt sich ja das Erstellen spezieller Win9x-Kompilate …
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.
Auf der Wunschliste steht ja zumeist:
Siehe auch http://chmspec.nongnu.org/
[OPTIONS] Binary TOC=Yes Compiled file=gwbasic.chm Default Window=main Contents file=gwbasic.hhc Index file=gwbasic.hhk Default topic=default.html Full-text search=Yes Language=0x409 Englisch (USA) Title=GW-BASIC User's Guide [WINDOWS] main=,"gwbasic.hhc","gwbasic.hhk","default.html","default.html",,,,,0x62520,,0x70384E,,,,,,,,0 popup=,,,,,,,,,0,,0x80,,,,,,,, [FILES] gwbasic.hhp chapter1.html …Die Angabem bei main generieren alle erdenklichen (sinnvollen!) Extra-Knöpfe, nämlich Browse-Buttons, History zurück und vorwärts, Rückwärtssuche sowie Einstellen der Schriftgröße.
Das 0x80 bei popup generiert ein ToolWindow mit schmaler Titelzeile.
Wichtig zu wissen:
<HTML><HEAD><!-- Sitemap 1.0 --></HEAD><BODY><UL> <LI><OBJECT type="text/sitemap"><param name="Name" value="Chapters"><param name="Local" value="default.html"></OBJECT> <UL> <li><object type="text/sitemap"><param name="Name" value="1. Welcome"><param name="Local" value="Chapter1.html"></object> … </UL> <LI><OBJECT type="text/sitemap"><param name="Name" value="Appendicies"></OBJECT> <UL> <li><object type="text/sitemap"><param name="Name" value="A. Error Codes and Messages"><param name="Local" value="AppendixA.html"></object> </UL> <li><object type="text/sitemap"><param name="Name" value="Glossary"><param name="Local" value="Glossary.html#1"><param name="New" value="1"></object> </UL></BODY></HTML>Nicht jeder Knoten muss eine dahinter stehende URL haben. Leider gibt's da keinen Automatismus, mit dem Windows bspw. ein anderes Icon benutzt.
Alles in allem ziemlich geschwätzig; ein Texteditor mit regulären Ausdrücken und Wortersetzung ist hier zweckmäßig. Es kommt der Eindruck auf, das Microsoft die Programmierer quälen möchte. Eine simple ASCII-Datei mit verschiedenen Einrücktiefen und gewöhnlichen HTML-Links hätte es ja auch getan.
Hochkommas kann man weglassen, wo kein Leerzeichen eingeschlossen ist.
Wichtig zu wissen:
<OBJECT type="text/site properties"><param name="ImageType" value="Folder"></OBJECT>direkt nach
<BODY>, aber eben nur bei ausgeschaltetem „Binary TOC“!
#-Ankern.
<HTML><HEAD><!-- Sitemap 1.0 --></HEAD><BODY><UL> <li><object type="text/sitemap"><param name="Name" value="ABS"><param name="Local" value="ABS.html"></object> <li><object type="text/sitemap"><param name="Name" value="ASC"><param name="Local" value="ASC.html"></object> … </UL></BODY></HTML>Auch hier wieder viel Blabla. Reguläre Ausdrücke helfen, eine Wortliste entsprechend umzuformen.
Wichtig zu wissen:
<title>-Tag
[Options] Title=)
<object type="application/x-oleobject" classid="clsid:1e2a7bd0-dab9-11d0-b93a-00c04fc99f9e"> <param name="New HTML file" value="USB-Funktionen.htm"> <param name="New HTML title" value="USB-Funktionen"> </object>Möchte man das seitenweise HTML-Ausdrucken unterstützen, passt da auch ein
<br clear=all style='page-break-before:always'>davor.
Die Links im Inhaltsverzeichnis und Index müssen sich auf die zersägten Dateinamen beziehen!
Womöglich kann man sogar Inhaltsverzeichnis und Index mit hineinstecken.
Das beißt sich allerdings mit der Möglichkeit, HTML-Dateien zu zersägen.