vrijdag 11 december 2009

"External exception C0000006" bij app op WLAN

Als je een applicatie maakt dat moet draaien via een WLAN (Wifi), pas dan even op en lees het onderstaande door:

Windows laadt namelijk niet de gehele executable in het geheugen, maar alleen wat hij op dat moment nodig heeft. Als je bijv. op een knop in je applicatie drukt, dan *kan* Windows een nieuw blok bitjes van je applicatie via het netwerk moeten ophalen (of voor een resource (bijv. een icon) bij een nieuw scherm dat je aanmaakt, etc).
Echter, als je via een WLAN werkt, dan kan natuurlijk net je verbinding weg zijn...
Wat er dan gebeurt? Een "External exception C0000006" :-(
Oplossing? Je moet een flag in je exe zetten, die aangeeft dat je vanaf een removeable medium werkt (USB, Wifi, etc) zodat Windows wel alles inlaadt. In Delphi kun je dit in de .dpr doen:
{$SetPEFlags IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP}

Dit heeft bij mijn huidige klant nogal voor wat hoofdbrekens gezorgd: we kregen deze exceptions op de meest vreemde plaatsen, zonder aanwijsbare bug in de code. Na wat googlen was de oorzaak gauw gevonden en de fix snel gemaakt :-) Zie originele discussie + oplossing:

woensdag 9 december 2009

Windows tijd: in stapjes van 15ms!

Soms wordt je onaangenaam verrast: je meet de tijd van alle SQL queries in je programma met "MillisecondsBetween" en je krijgt als resultaat: 0, 0, 0, en 15ms...

Hmmm, dit is te mooi om waar te zijn, zo snel is MS SQL Server nou ook weer niet! :-)
Er ging bij mij toen wel een lampje branden, ik eens eerder zoiets gezien met "Sleep" en "GetTickCount".

Thread time slices
In Windows (en andere systemen) worden threads 1 voor 1 op een CPU uitgevoerd. Elke thread heeft een "time slice" van 15ms, waarin hij de volledige CPU tot zijn beschikking heeft. Na 15ms breekt Windows de thread af, slaat de inhoud van de CPU registers e.d. op, en laad en start een andere thread ("context switch"). Een "time slice" kun je afbreken door een wait function aan te roepen, zoals "Sleep", "WaitForSingleObject", etc. Op deze manier kan een andere thread de kostbare CPU tijd gebruiken.


Consequenties
De default time slice van 15ms heeft nogal wat consequenties voor het programmeren: zo gaat een "Sleep(1)" niet 1ms wachten, maar 10 - 15ms! (afhankelijk van andere threads etc). Maar ook andere tijd functies zoals "GetTickCount", "Now" (Delphi) of "GetLocalTime" (Windows) werken in stapjes van 15ms!

Nauwkeurige tijdmeting
Gelukkig zijn er andere manieren om het aantal ms te bepalen:
  • hardware timer gebruiken: met QueryPerformanceFrequency + QueryPerformanceCounter kun je met micro en nano secondes werken!
  • kortere "time slice" instellen met "timeBeginPeriod", bijv een time slice van 1ms met "timeBeginPeriod(1)". Dit heeft echter wel nadelen: veel meer context switches, dus meer overhead!
Zelf heb ik andere "Now" functie in Delphi geschreven, die op basis van QueryPerformanceCounter etc werkt (dan werkt alles in 1x op de ms nauwkeurig, of zelfs op de micro seconde).
Ik vervang ("detour") de oude "Now" functie door mijn "NowExact" door middel van "KOLDetours.pas":
OldNow := KOLDetours.InterceptCreate(@Now, @NowExact);
Anekdote
Ik heb eens bij een bedrijf de RS232 communicatie met een PLC flink versneld: in een loopje werd na elke ontvangen character een "Sleep(1)" aangeroepen. Dit heb ik met een factor 10 versneld door pas na een lege character een "Sleep(1)" te doen, en die door middel van "timeBeginPeriod(1)" ook echt 1ms wacht ipv 10 to 15ms!