Lav et Xbox 360-spil på 8 timer

January 22nd, 2010

Min orlov går på hæld. Jeg har haft fri siden slutningen af november, men på mandag er jeg tilbage på pinden. Med så mange ugers fritid, og skulle man tro, at jeg ville have tid nok til at skrive et par indlæg, men tiden er gået med husprojekter, hygge med ungerne, to gange skoldkopper samt jul og nytår, så det er ikke blevet til særlig meget. Mine tekniske stunder de seneste uger har været få, og det er lang tid siden, jeg har haft gang i WinDbg.

Jeg har dog fået ny bærbar, en Dell Studio 15 med i7 CPU, hvilket får Process Explorer til at vise ikke mindre end otte kerner! Det, synes jeg stadig, er ret cool. Desværre er DDR3 RAM stadig ufattelig kostbar, så jeg nøjes indtil videre med 4 GB, men med 64 bit Windows 7 installeret er jeg klar til at opgradere, når prisen per GB ikke længere konkurrerer med prisen på Beluga kaviar.

Derudover har jeg fået læst Chris Smiths glimrende Programming F#. Der er meget at holde af ved F#: Kompakt syntaks, immutable variable som standard, fantastisk model for asynkron programmering, typer med enheder, glimrende udviklingsmiljø og let integration til andre .NET sprog.

Sidst men ikke mindst fik jeg lavet mit andet Xbox-spil til min søn, Asbjørn, og det vil jeg bruge dette indlæg på at dele med jer.

Rammerne for spillet

Jeg vidste, at jeg ikke havde tid til det helt store, så jeg satte en deadline for projektet på otte timer. På otte timer skulle jeg lave det hele: Grafik, lyd, kode og test. Det vil med andre ord sige, at vi på spilskalaen befinder os meget tættere på gamle bip-bip-spil end på nyeste inkarnation af Call of Duty. Jeg er sikker på, at de til sidstnævnte har brugt mere end otte timer på blot at lave teksturer til vægge.

Hvis man kan leve med det begrænsede ambitionsniveau, er det glade budskab til gengæld, at man faktisk snildt kan lave noget på blot otte timer.

Fra mit første forsøg med at lave et spil til Asbjørn vidste jeg, at det skulle være simpelt. Meget simpelt. En Xbox-kontroller er stor og har mange knapper, og når man blot er to år, er der grænser for hvor meget, man kan på en gang. Ergo skal styringen være ukompliceret. Ligeledes må der heller ikke foregå for meget på en gang, så jeg endte med et ”fang de æbler, der falder ned fra himlen”-spil. Den slags var sjovt i de tidlige 80ere, så mon ikke jeg kunne underholde en toårig for en stund.
Bambumspil

Indhold

Min søns yndlingsbamse er en Ugly Doll, som han af uvisse årsager har døbt Bambum. En oplagt kandidat til spillets hovedperson, så Bambum blev fotograferet og fritlagt i Photoshop. Jeg klippede derefter ben og ører af det stakkels tøjdyr og med de afklippede dele samt en nytegnet mund, kunne jeg lave et par simple animationer.

Derefter skulle jeg bruge en baggrund. Det var let. En blå gradient og lidt græs. Voila. Dertil kom et lille skilt til at vise point samt cifrene 0-9 i løs vægt.

Skyerne var lidt mere tricky. Jeg tegnede to skyer i Photoshop og endte med at kode min egen fattigmands partikeleffekt. Det vil sige, at jeg tager et par bitmaps og manipulerer dem, mens jeg tegner dem. Det giver en glimrende ”skyer, der driver hen over himlen”-effekt.

Sidst men ikke mindst skulle jeg have et æble, så Google, fritlæg, reducer størrelse og få minutter senere var det på plads. Alt i alt endte jeg med 21 grafikfiler.

Lydene var ligeledes lige ud ad landevejen: Windows Recorder og en mikrofon. Tre variationer over ”ups!” samt tre glædesudbrud, og så var den næsten i hus. Desværre optager Windows Recorder i WMA og XNA kræver WAV, så Google måtte igen komme til undsætning med en Free WMA Converter.

Og så skulle der kodes.

Forudsætninger

For at bruge XNA, skal man installere XNA Game Studio, som er en gratis udvidelse til Visual Studio. Hvis man kan klare sig med kun at lave Windows-spil, er der ikke brug for mere. Til Xbox 360-spil kræves desuden et komponent på Xboxen. Det kan også hentes gratis, men desværre skal man derudover have en Creator’s Club Premium-licens for at kunne overføre spil til konsollen.

Den koster ca. $100 per år, og det er i mine øjne en stupid ide at tage penge for den slags. Bevares, der følger også adgang til diverse ressourcer og mulighed for at uploade sine spil til Microsofts XNA community, så andre kan prøve det. Jeg kan til nøds forstå, hvorfor de vil have penge for rettigheden til at uploade til Arcade, men det er bare fjollet, at sætte den slags forhindringer for at folk kan lege med teknologien.

Når man så har betalt og bandet over det, går alting som en leg. Forbind Visual Studio til Xboxen via lokalnettet og kør og debug kode direkte. Det bliver ikke meget lettere. Jeg vil dog anbefale, at undgå Xboxens ustabile, trådløse netværk. Ikke alene giver det diverse problemer under udviklingen, men fordi Microsoft partout vil verificere, at man har føromtalte licens, kræver afvikling af spillet også konstant adgang til Xbox Live, hvilket bare ikke fungerer særlig godt med deres trådløse net. På et kablet net spiller det bare.

Kode

XNA kommer med en lang række brugbare typer. Game-klasse har som standard sat de væsentligste elementer op. Der er metoder til indlæsning af resourcer, opdatering af tilstand og tegning af skærmen. Det eneste, man skal gøre, er således at fylde metoderne ud efter behov. Det kræver ikke særlig meget kode, at lave mit lille spil, men der er et par elementer, der tåler kommentarer.

Når man tegner billeder på skærmen, skal man angive billedets centrum. For baggrundsbilleder og andre elementer, der ikke skal manipuleres, kan man blot bruge den simpleste overload af Draw-metoden. Den benytter øverste venstre hjørne af billedet som omdrejningspunkt. Billeder, der skal roteres, skaleres eller på anden måde manipuleres, er ofte bedre tjent med at have centrum i billedets centrum. Ved at sætte centrum på Bambum-billedet kan jeg således let skabe illusionen af bevægelse ved simpelthen at dreje billedet en anelse i flyveretningen. Den slags muligheder sparer en for at skulle lave en del bitmaps.

Desværre er der ingen direkte værktøjer i XNA til registrering af kollisioner, så det er man nødt til at kode selv. Der er masser indlæg på nettet om hvordan dette kan gøres, men jeg valgte en quick’n’dirty-løsning. Jeg udregner blot længden for en vektor mellem Bambums centrum og ditto for hvert æble. To grænseværdier afgør hvorvidt Bambum åbner munden for at spise et æble, og om han faktisk har spist æblet. Det er ikke så nøjagtig som det kunne være, men i praksis fungerer det fint, og det var let at implementere.

For at mindske udviklingstiden er det lettest at udvikle til Windows og så lave et Xbox-projekt, når man har noget, der fungerer. Sidstnævnte klarer XNA Studio for en, men følger man den strategi, er der et par udfordringer med at håndtere input. Hvis man ikke lige har en kontroller på sin PC, kommer man til at skrive kode, der kan håndtere input fra både keyboard og Xboxens controller. Det er lidt trælst, og det er ikke helt nemt at få de to til at opføre sig nogenlunde konsistent, så det kræver som regel lige et par forsøg.

Hvorfor 2D?

Med en samlet udviklingstid på blot otte timer er 2D-spil et indlysende valg, men selv hvis man har mere tid til rådighed, er det et godt valg for hobbyprojekter. Logistikken i 2D-spil er ret overskuelig. De kræver kun en samling bitmaps. De kan findes på nettet eller produceres uden de store sværdslag i et væld af programmer. Jeg brugte Photoshop, men mindre kostbare applikationer kan klare opgaven lige så godt. Flere indiespil har tilmed med stor succes brugt håndtegnede figurer og baggrunde. Der er altså mange muligheder, og forhindringerne er overkommelige.

Går man fra 2D til 3D, ændrer forudsætningerne for spillet sig radikalt. Der er stadig brug for et væld af bitmaps til teksturer, men derudover skal man kunne frembringe 3D-modeller. Det er langt mere omstændeligt end at lave bitmaps og værktøjskassen bliver mange gange mere kompliceret og kostbar. De nødvendige applikationer koster i de fleste tilfælde en formue, og indlæringskurven for eksempelvis 3D Studio Max eller Maya er lang og stejl sammenlignet med, hvad der skal til for at komme i gang med XNA.

Holder man sig til 2D, er det en overkommelig opgave, at lave alle elementerne selv.

Indtryk

Jeg vil ikke prale af koden til mit spil. Jeg har taget en del smutveje, og jeg ved, at det kan laves pænere og mere vedligeholdelsesvenligt. Min eneste undskyldning er, at hverken jeg eller andre skal arbejde videre med koden. Desværre er jeg langt fra den eneste, der koder XNA på den måde. En overvejende del af de eksempler, jeg har set, har desværre været udført forfærdelig rodet.

Heldigvis synes selve XNA framework-koden at være skruet væsentlig mere elegant sammen, men da dokumentationen endnu er ret sparsom, ender man ofte med at sidde og læse andres sla^h^h^hikke så pæne kode.

Selv med disse forbehold in mente, må jeg indrømme, at jeg er ret begejstret for XNA. Der er enkelte elementer, jeg gerne så udvidet og forbedret, men det ændrer ikke ved, at det overordnede indtryk er rigtig godt. Det er utrolig let at komme i gang med, og man kan hurtig få skabt synlige resultater. Sidstnævnte finder jeg særdeles motiverende. Det er let at afprøve ideer, så ofte sidder man bare og leger sig frem til nye features og ideer. Der går meget hurtigt, ”jeg skal liiiige” i den, og inden man får set sig om, er klokken blevet alt for mange.

Prøv det. Man behøver ikke engang at have børn for at synes, at det er sjovt.

Hjerneblødninger og crash dumps

November 26th, 2009

Da jeg holdt indlæg om WinDbg for ONUG, var der en, der spurgte, om man ikke kunne sætte applikationen til selv at lave et memory dump i tilfælde af fejl. Det er et meget relevant spørgsmål, og timingen var lidt pudsig, for jeg skulle have møde med en kollega den efterfølgende dag om netop dette emne.

Jeg svarede, at jeg endnu ikke havde gransket mulighederne og begrænsningerne, men at min mavefornemmelse fortalte mig, at det næppe ville være mulig at gøre det på en brugbar måde i alle tilfælde. Det viste sig, at holde stik. De interesserede kan læse John Robbins detaljerede gennemgang af sit mislykkedes forsøg på at implementere denne feature. Den vil jeg ikke gentage, men blot konstatere at hvis han ikke kan, er vilkårene ikke særlig gunstige for os andre. I stedet vil jeg prøve at belyse noget af problematikken med en analogi.

Hjerneblødning

Forestil dig, at du får en hjerneblødning. Det er en forfærdelig situation, og jo før en læge får set på det desto bedre. Du har to muligheder. Enten kan du lade lægen stå på spring, så hun er klar til at slå til, så snart symptomerne konstateres, eller også kan du bide smerterne i dig, hoppe ned i bilen, drøne mod hospitalet, sidde i kø på motorvejen, fortsætte i endnu højere fart mod hospitalet, parkere ulovligt på en nærliggende mark, fordi parkeringspladsen ved hospitalet allerede er fuld, løbe hele vejen til receptionen, vente de obligatoriske tre kvarter eller mere for derefter at blive tilset af en læge.

I første tilfælde har lægen alle muligheder for at inspicere ikke alene den konkrete blødning men også tilstanden af andre vitale organer. Jeg ved ikke, hvad der er relevant at se på i den situation, men det ved lægen forhåbentlig. Pointen er, at relevante detaljer risikerer at forsvinde med tiden, og derfor er det ideelt at kunne komme til med det samme. Givet omstændighederne har hun altså de bedste muligheder for at finde ud af, hvad der er galt og hvorfor.

Det er ikke tilfældet i vores andet tilfælde. Blødningen kan have bredt sig, og det kan derfor være svært at se, hvor den opstod. Der kan være følgevirkninger. Måske har trykket påvirket dit syn, så du er løbet ind i samtlige dørkarme og faldet ned af alle trapperne på vej til hospitalet. Lægens første indtryk kan derfor meget vel være en meget forslået m/k, og derfor kan det være, at hun indledningsvis forsøger med plaster og bandager. Jeg kan gudskelov ikke tale af erfaring her, men ikke desto mindre står det klart, at lægen kommer til at arbejde under langt fra ideelle forhold i andet tilfælde.

Lad os prøve at se på, hvordan min lettere morbide analogi hænger sammen med debugging. Men måske er det på sin plads at lægge ud med at understrege, at jeg ikke anser applikationsnedbrud som en livstruende situation.

Gør det selv

Hvad skal der til, for at få vores applikation til at tage et memory dump i tilfælde af en uhåndteret exception? I første omgang er vi nødt til at sørge for, at der ikke er en uhånderet exception. Vi er jo nødt til at kunne reagere, og vi er således ikke interesseret i, at Windows overtager styringen på dette punkt. Ergo, er vi nødt til at fange alle exceptions på det yderste niveau for alle tråde. Det er ikke uden sine udfordringer, men lad os antage, at det kan lade sig gøre. Når vi så står med en exception, er det sådan set bare, at kalde de relevante metoder i dbghelp.dll, men det er ikke her problemet ligger.

Generering af dumpet svarer til lægens indledende undersøgelse af patienten. Som illustreret er det perioden fra blødningens opståen til lægens undersøgelse, der er problemet. Jo længere tid der går, desto dårligere bliver mulighederne for indsamling af brugbar information.

For det første har vores applikation jo fanget den uhåndterede exception, og derfor er der per definition ikke en uhåndteret exception! Det betyder, at der ikke ligger en exception på den fejlende tråds stak, og relevante stakvariable vil højst sandsynlig ligeledes være væk. Vi vil muligvis kunne finde den relevante Exception-instans på heapen, men allerede her er der en forskydning mellem fejlsituationen og rapporteringen, og relevante detaljer er muligvis gået tabt.

Håndtering af exceptions sker som bekendt per tråd. Det vil sige, at mens vi er ved at konstatere, om vi har en – under andre forhold – uhåndteret exception på en tråd, fortsætter alle andre tråde i applikationen ufortrødent. Vi kan forsøge at stoppe dem tidligt i forløbet, men det vil i bedste fald kun reducere men ikke eliminere omfanget af skaderne.

Vi kan ikke med sikkerhed sige, om de andre tråde sletter vigtige spor i forhold til undersøgelse af problemet. Når vi inspicerer det genererede dump, vil disse tråde være i en vilkårlig tilstand i forhold til problemet. Deres kaldestakke vil være ændret mange gange, og de kan have påvirket heapen, finalizerkøen, indlæsning af moduler og meget andet.

Ganske som i analogien er vores muligheder for at undersøge problemet altså begrænset i forhold til det, vi kunne ønske os.

Lægen på spring

Hvordan ser det ud, hvis vi har lægen stående klar? Det svarer til, at vi f.eks. sætter adplus op til at tage et crash dump i tilfælde af en uhåndteret exception. Håndtering af exceptions i .NET bygger på Windows’ SEH, som er en mekanisme i selve operativsystemet til håndtering af exceptions. Det vil sige, at fra Windows’ synspunkt er der intet specielt ved en .NET exception. Det er blot en exception af en bestemt type, men håndteringen er ikke specifik for .NET.

Så når vi sætter adplus til at lave et crash dump, fortæller vi blot Windows, at vi ønsker at bruge CDB som JIT debugger. Ydermere sørger adplus for, at bruge denne debugger til at generere det ønskede dump, så snart Windows konstaterer problemet.

Hvordan er det forskelligt fra ovenstående situation? Som analogien illustrerer, træder lægen til så snart problemet opstår, og det er præcis, hvad der sker, når vi bruger adplus eller lignende. Når Windows konstaterer, at applikationen har en exception, der ikke vil blive håndteret, sætter den processen på hold og overlader kontrollen af denne til den registrerede debugger.

Det vil altså sige, at CDB kommer til i det øjeblik problemet opstår (altså ”just in time”). Ydermere er vores applikation, kaldet debuggee processen stoppet, og det er debuggeren, der har kontrollen. Kaldestakken for den fejlende tråd er korrekt i forhold til den ikke uhåndterede exception, og alle andre tråde er sat på hold. De ødelægger altså ikke noget, når problemet først er konstateret. Det vil sige, at vi har de bedst mulige forhold til at lave et retvisende dump af processens hukommelse. Det er stadig mulighed for at information er gået tabt, men det er de bedste forhold, vi kan håbe på.

Adplus er tilmed smart nok til ikke blot at tage et dump i tilfælde af et crash. Med mindre den instrueres i at skippe first chance exceptions, laver adplus faktisk både et dump, når vores exception smides (first chance), og derefter når den konstaterer, at den ikke er blevet håndteret (second chance). Derved kan vi inspicerer begge de aktuelle tilstande, hvilket vi ikke har mulighed for med gør det selv-metoden, eftersom at vi ikke kan få afviklet kode, hver gang en vilkårlig exception bliver smidt.

Som min analogi forhåbentlig har illustreret, er der således stor forskel på vores muligheder for at skabe brugbare dumpfiler afhængig af, om vi gør det via debugging grænsefladen eller ej, og derfor er svaret på det indledende spørgsmål i bedste fald, ”det er næsten mulig”. Da dump debugging allerede er en noget kryptisk affære, er det næppe tilrådeligt, at gøre det sværere ved at lave næsten brugbare dumpfiler.

Kodegolf: Resultater og vinderne

November 23rd, 2009

Jeg er imponeret! Ikke alene modtog jeg hele 24 bidrag i konkurrencen, men flere af jer sendte også adskillige forsøg, og der var rigtig mange gode forslag imellem de indkomne svar. Tak for det.

Jeg har modtaget bidrag fra: Jacob Eisenberg, Kristian Vinther, Mark Seemann, Jan Jensen, Janus Egholm, Claus Christensen, Christian Rysgaard, Morten Gejl, Mads Mau Pedersen, ”lajensen”, Dennis Riis, Søren Larsen, Troels Thomsen, Jakob Gade, Carsten Hess, Hakon Baunsgaard, Jacob Tjørnholm, Rune Juhl-Petersen, David Thomas, Allan Thraen, Jesper Harder, Mads Hedegaard, Mads Lützhøft og Niels Rasmussen.

Tusind tak fordi I ville være med.

Ifølge mit optællingsscript ser resultaterne ud som følger.

129 : Hakon Baunsgaard
129 : Jesper Harder
129 : ”lajensen”
129 : Mads Mau Pedersen
129 : Troels Thomsen
132 : Kristian Vinther
137 : Mads Hedegaard
138 : Christian Rysgaard
139 : Niels Rasmussen
141 : Morten Gejl
144 : Jacob Tjørnholm
149 : Jan Jensen
149 : Rune Juhl-Petersen
150 : Søren Larsen
151 : Dennis Riis
152 : Carsten Hess
152 : Jacob Eisenberg
164 : Claus Christensen
168 : Mads Lützhøft
177 : David Thomas
196 : Janus Egholm
228 : Jakob Gade
281 : Allan Thraen
348 : Mark Seemann

Det vil sige, at der ikke er en, men ikke mindre end fem vindere! Tillykke til Hakon Baunsgaard, Jesper Harder, ”lajensen”, Mads Mau Pedersen og Troels Thomsen.

129 tegn er altså godt gået.

Jeg har talt med Microsoft, og jeg har fået lov at indløse min trøstepræmie for fire yderligere præmier, så det betyder, at alle vinderne får en præmie. Stort tillykke til vinderne!

Jeg har lavet en zip-fil med alle besvarelserne, så I kan nyde de andres påfund, men her kommer lige en tilfældig udvalgt blandt de fem vindere.

namespace Kodegolf {
    class MMP {
        public static void MAIN() {
            for (int t = 0; ++t < 66; )
                System.Console.WriteLine(t < 65 ? t + ":" + (1ul << t - 1) : "=" + ~0ul);
        }
    }
}

Læg desuden mærke til hvordan flere af vinderne for alle praktiske formål er nået frem til den samme løsning. Det er åbenbart måden at løse denne opgave.

Jeg er imponeret over, at fem personer kom frem til en løsning med så få anslag, og jeg er ligeledes imponeret over det meget tætte løb mellem deltagerne. Hatten af for jer, tak til jer alle sammen fordi I ville være med og endnu en gang tillykke til vinderne!

Kodegolf og rigtig mange ris

November 18th, 2009

Første gang, jeg hørte om eksponentielle funktioner, var i min folkeskoletid. Jeg vil tro, at det var omkring fjerde eller femte klasse. Begrebet blev naturligvis ikke omtalt som sådan, og mine klassekammerater og jeg havde ikke den fjerneste anelse om den underliggende matematik, men jeg husker tydelig, hvordan vores lærer berettede en variant af fortællingen om en kejser, et stakbræt og en masse ris.

Der er en glimrende oversigt over anekdoten på Wikipedia, men i korte træk går historien ud på, at en kejser vil belønne opfinderen af skak, så han spørger ham, hvad han vil have som tak. Manden svarer, at han vil have et riskorn på første felt, to på andet, fire på tredje og så fremdeles indtil han har fået ris svarende til alle felterne på brættet.

Det bliver til ganske mange ris, og jeg husker stadig fascinationen over, hvordan denne lille konstruktion kunne resulterer i et tal, der var langt større, end hvad lærerens lommeregner kunne håndtere. I den alder er det let at blive imponeret over, at teknologi har sine begrænsninger.

Så som en hyldest til ris, skak og matematik holder jeg endnu en gang kodegolf, og Microsoft har venligst stillet et par præmier til rådighed. Det er naturligvis en glimrende lejlighed til at få leget lidt med Visual Studio 2010 beta2, så jeg håber, I vil være med.

Opgaven

Lav et program, der udskriver antal riskorn per felt samt summen for alle 64 felter.
Feltlinjerne skal se ud som følger F:R, hvor F er nummeret på feltet, og R er antal ris på feltet.

Summen skal udskrives på en linje som =S, hvor S er summen. Altså:

1:1
2:2
3:4
4:8

64:xx
=yy

Reglerne

  • Programmet skal som en console application og implementeres i managed C#.
    Alle assemblies fra VS2010 eller tidligere må bruges.
  • Nedenstående skabelon, der skal bruges. Koden i den må ikke ændres. Den sikrer, at default namespace skal hedde ”kodegolf”, og applikationsklassen skal navngives med tre bogstaver baseret på dit navn, så hvis du hedder Ole Hansen kunne den f.eks. hedde OHA. Med samme namespace og klasser med på tre bogstaver bliver optællingen lettere.
namespace Kodegolf {
    class XXX { // Ret XXX til dine initialer (tre bogstaver) og slet denne kommentar
        public static void Main() {

        }
    }
}
  • White space der ikke har betydning for oversættelsen tæller ikke med. Så tomme linjer, indrykninger af koden og så videre tæller ikke med, men mellemrum i tekster gør.
  • Det er ikke tilladt at læse data eller kode fra filer, nettet eller andre steder. Al koden til programmet skal være indeholdt i ovennævnte klasse.
  • Update: Alle tal udskrives som heltal.

Deadline

Deadline er mandag den 23. november klokken 6 (som i om morgenen). Send løsningen til brian@kodehoved.dk.

Vinderen er den korrekte besvarelse med færrest anslag. Vinderen får en eller anden lækker præmie fra Microsoft. Jeg trækker desuden lod om en trøstepræmie blandt alle besvarelserne.

Er der spørgsmål til konkurrencen, så smid en kommentar.

Optælling

Optælling foregår som følger:

  • Først sikrer jeg mig, at Main() er public. Er den ikke det, indsætter jeg public.
  • Jeg fjerner string[] args fra Main(), hvis den del er der.
  • Da jeg har alle løsningerne i samme solution, omdøber jeg Main() til MAIN().
  • Hvis der er mellemrum, der har betydning for korrektheden af løsningen, ændrer jeg disse til underscore i kildeteksten.
  • Derefter kører jeg nedenstående kode for at tælle og det tal, den spytter ud, er resultatet for den pågældende fil.
using System;
using System.IO;
using System.Text.RegularExpressions;

namespace Count {
    class Program {
        static void Main(string[] args) {
            if (args.Length < 1) {
                Console.WriteLine("Syntax: Count.exe sourcefile");
                return;
            }

            var file = args[0];
            if (!File.Exists(file)) {
                Console.WriteLine("{0} not found", file);
                return;
            }

            var code = File.ReadAllText(file);
            Console.WriteLine(Regex.Replace(code, @"\s", "").Length);
        }
    }
}

Mit tal vil derfor muligvis være forskellig fra det, I får, men det sikrer, at alles bidrag bliver talt på den samme måde.

TechEd Europe 2009 i bullet points og billeder

November 17th, 2009

TechEd er ovre, og jeg er langt om længe hjemme igen. Her følger et par indtryk og observationer fra TechEd og Berlin.

  • Med over 7000 deltagere er TechEd ikke det mest oplagte sted at netværke, men jeg var heldig at løbe på en masse mennesker, jeg gerne ville hilse på. Tak til Harry Pierson, Tess Ferrandez, Don Syme, Martin Esmann og Daniel Pearson for nogle gode stunder.
  • Dette var min første TechEd, så jeg kan ikke sammenligne den med tidligere år, men almindeligvis er TechEd delt i to: en for IT professionals og en for udviklere. I år var de slået sammen til en konference, hvilket så ud til at ramme begge lejre med færre interessante sessions. Således var det lidt af en udfordring at fylde programmet op med udviklerrelaterede emner.
  • Gode sessions: Der var forbløffende få skuffelser på mit program, og dem er der vist ingen grund til at fremhæve, så i stedet kommer her et par af de gode: Mark Russinovich om debugging og opdateringer i kernen, Tess Ferrandez ditto om debugging, Daniel Pearson om kernel debugging, Udi Dahan om Command Query Responsibility Segregation, Don Syme om asynkron programmering i F#, Harry Pierson om dynamiske sprog om DLRen og Roy Osherove om unit test (desværre uden guitar).
  • Faciliteterne var generelt gode, men der var kun lokaler i to størrelser, hvilket gav arrangørerne begrænsede muligheder for at tilpasse lokalerne til mængden af mennesker. Det betød desværre, at man meget let kom til at gå forgæves, og i praksis blev man nødt til at gå direkte fra en session til den næste, hvis man ville gøre sig håb om få en plads. Jeg løb ind i Tess en af dagene, og hun var så uheldig, at gå glip af de eneste to sessions hun ville se på den dag. Det er bare ikke godt nok, når nu antallet af relevante sessions var så begrænset.
  • Betalingskort er altså ikke et modefænomen. De er sgu nok kommet for at blive. Desværre har berlinerne ikke fanget den, så der er mange restauranter, taxaer med videre, der ikke tager plastik. Det havde jeg måske forventet ville være tilfældet i Angola men altså ikke i Berlin. Kom lige ind i kampen.
  • Det mest overraskende, jeg så, var en præsentation fra et firma, der havde integreret hele deres Cobol-udviklingsmiljø ind i VS2010. De havde brugt det nye framework til at lave en editor, der kunne håndtere Cobols finurligheder og blandt andet vise sammenhænge mellem diverse GOTOs i koden ved hjælp af pile. De kunne sågar afvikle deres Cobol unit tests fra Visual Studio. Det er på ingen måde noget, der er relevant for mig, men det illustrerer på glimrende vis, de mange muligheder for tilpasninger VS2010 giver.
  • Sammen med Tess Ferrandez og hendes irske kollega Finbar Ryan blev jeg interviewet til et spansk .NET-magasin (jeg skal nok smide et link, når jeg ved mere). Emnet var selvfølgelig debugging, og jeg synes, at det gik fint, så jeg glæder mig til at se resultatet.
  • Visual Studio 2010 ser ud til at blive en helt fantastisk udgave. Der er rigtig mange cool features som f.eks. parallel stacks, task debugging og mulighederne for at tilpasse hele udviklingsmiljøet.

Udvalgte billeder

Messe Berlin

Messe Berlin. Det store samlingssted.

Kloge hoveder

Kloge hoveder.

Tess og Magnus

Tess og Magnus.

Et lidt for almindelig syn på konferencen

Et desværre lidt for almindelig syn på konferencen.

Performanceproblem

En ikke så overbevisende demo af PLINQ. Læg mærke til det grønne tal. Heldigvis blev det meget bedre i andet forsøg.

Google Chrome

Og mens vi er ved upserne: Må man godt have Google Chrome, når man arbejder for Microsoft?

Madkøen

7000+ nørder spiser.

Roy Osherove

Roy O uden guitar.

Tess Ferrandez

Tess optræder.

DRY handler om mere end blot copy/paste

November 6th, 2009

For nogle år siden havde vi en lille, intern arbejdsgruppe, der havde til formål at komme med forslag til, hvordan vi kunne forbedre den måde, vi skriver kode på. Som oplæg læste vi blandt andet The Pragmatic Programmer, der er en fantastisk bog spækket med fornuftige tanker og anbefalinger. Har du ikke læst den, vil jeg opfordre dig til at sætte den højt på din to do-liste.

Et af de mange gode råd, der omtales i bogen, er DRY-princippet. DRY er en forkortelse for Don’t Repeat Yourself, og det går ud på, at vi ikke skal gentage os selv, når vi skriver kode. Ikke overraskende var det et af de råd, alle kunne tilslutte sig, for uanset programmeringssprog synes de fleste udviklere at være enige om, at copy/paste-kode er en rigtig dårlig ide. Strengt taget omhandler DRY dog mere end blot kodegentagelser. Ideen bag DRY er at undgå gentagelser i enhver form, men lad os holde os til kodegentagelser i denne omgang.

Desværre er problemet omkring copy/paste-kode ikke så interessant. Vi kan blive enige om, at det er en dårlig ide, og vi behøver sjældent at debattere hvorfor, det forholder sig sådan.

Den virkelige udfordring består i at identificere alle de steder, gentagelser optræder i mere subtile forklædninger. Lad os se på et forsimplet eksempel. Lad os forestille os, at vi har en klasse, Logger, som vi kan udskrive diverse driftsrelaterede beskeder til. Logger har altså en LogMessage(), der tager en string, så almindeligvis vil den blive kaldt som følger:

logger.LogMessage("Here is a message");

Det er der næppe noget odiøst i. Andre gange er beskeden dog mere omfattende, som i dette eksempel:

logger.LogMessage(string.Format("Current status {0}", GetStatus()));

I dette tilfælde har vi et yderligere metodekald knyttet til selve logging-operationen, og i princippet kunne der være et vilkårligt antal yderligere kald. Vi ved ikke, hvor lang tid det tager at udføre GetStatus() (samt eventuelle andre kald), men hvis det er en langvarig affære, er det ikke noget, vi er interesseret i at gøre, i fald logging er slået fra. Det spidsfindige er, at vi med den nuværende konstruktion kommer til at betale prisen for kaldet til GetStatus() uanset hvad, der end sker i LogMessage().

For at komme dette til livs, kan vi jo bare undersøge, om logging er slået til, inden vi laver noget. Så kunne det f.eks. se ud som følger.

if (logger.IsLoggingEnabled) {
   logger.LogMessage(string.Format("Current status {0}", GetStatus()));
}

Så undgår vi at kalde GetStatus() i de tilfælde, hvor det er overflødigt. Vi betaler godt nok en lille merpris som følge af vores tjek, så logging er blevet en anelse dyrere, men vi slipper for det overflødige arbejde.

Desværre har vi også netop introduceret en afhængighed, der gør, at vi kommer til at gentage os selv igen og igen. Vi har med andre ord brudt DRY-princippet!

Hver gang vi vil begrænse afviklingen af kode i henhold til det aktuelle niveau af logging, er vi nødt til at have denne IsLoggingEnabled / LogMessage()-konstruktion. Det er selvfølgelig kun to linjer, men det kan let blive til rigtig mange steder, og hvad sker, hvis kravene, til hvornår disse kald skal udføres, ændrer sig? Hvis vi er heldige, kan det lægges ind under vores IsLoggingEnabled abstraktion, men der er grænser for, hvor meget vi kan feje ind under det gulvtæppe. I værste fald må vi rundt og rette mange, mange steder, og det vil vi jo gerne undgå.

Problemet og en mulig løsning

Hvad er problemet? Vores API udstiller en afhængighed mellem to kald og tvinger således brugeren til at kæde disse sammen. Denne sammenkædning skal ske hver gang, vi ønsker at begrænse udskriften af log-beskeder og dermed får vi mange gentagelser. En sådan afhængighed bør håndteres af APIet og ikke af brugeren. Gør vi det, kan vi slippe af med gentagelserne.

Der er flere måder, at komme uden om dette på. Jeg vil dog nøjes med at se på en mulig løsning, for pointen er ikke at løse problemet i mit eksempel, men blot at illustrere problematikken.

For at begrænse de potentielt dyre kald, er vi nødt til at kunne slå afviklingen af disse fra, så vi har brug for en if-konstruktion, men da vi ikke ved, hvad det er, vi forsøger, at begrænse afviklingen af, er vi nødt til at indkapsle dette. Her kommer delegates os til undsætning. En delegate er blot en reference til et stykke kode, og denne reference kan vi naturligvis give som input til en metode. Med introduktionen af lambdaudtryk er det tilmed blevet meget nemt at gøre dette.

Vi kan altså implementere en LogMessage()-variant, som tager en delegate, der indkapsler alle de dyre kald, som input. Herefter kan vi lave det nødvendige tjek. Hvis og kun hvis logging er slået til, kan vi afvikle den kode, vores delegate peger på og logge den generede besked. Metoden kunne se ud som følger:

public void LogMessage(Func<string> func) {
   if (IsLoggingEnabled) {
      LogMessage(func());
   }
}

Vi kan f.eks. kalde LogMessage() med en delegate, der indkapsler det tunge arbejde således:

logger.LogMessage(() => string.Format("Current status {0}", GetStatus()))

På den måde skal den kaldende kode ikke længere tage stilling til, om logging er slået til eller ej, hvilket ikke alene gør koden simplere. Vi slipper også for gentagelserne. Hvis vi senere vil tilføje flere betingelser, har vi også et og kun et sted at gøre det.

Opgaven her var ikke at illustrere, hvordan et logging-API kan eller skal skrues sammen. Dertil er eksemplet for overfladisk, men forhåbentlig kan det illustrere, hvordan vi let kommer til at introducere gentagelser i koden, samt hvad vi kan gøre ved det.

Der er mange varianter af problemer i forhold til DRY, og dette indlæg har kun berørt en lille del. Hver gang vi bryder princippet, risikerer vi at gøre vores kode dyrere at vedligeholde, så derfor er det en god ide at komme gentagelserne til livs, inden de vokser sig store.

WinDbg Q&A: Hvorfor er der så mange instanser af string og object[] på heapen?

October 28th, 2009

Når vi debugger .NET applikationer med WinDbg, vil vi almindeligvis have brug for at inspicere heapen med !dumpheap-kommandoen. Den viser antallet af instanser af de forskellige typer på heapen. Listen er sorteret efter summen af størrelsen for instanserne af de enkelte typer på heapen. Dog skal det bemærkes, at der er tale om selve typernes størrelse, så størrelsen af en type med referencer inkluderer altså ikke størrelsen på de refererede objekter.

Det vil typisk sige, at typer med mange instanser vil være at finde mod slutningen af listen, og blandt dem finder vi meget ofte string og object[], men hvorfor er der så mange af disse i stort set alle .NET applikationer?

String

Lad os begynde med string, for her er forklaringen oplangt. For det første opretter CLRen en hel del strings til diverse konstanter, stier og så videre og jo flere assemblies vi refererer, desto flere af disse får vi med i bagagen. Dertil kommer at instanser af string som bekendt er immutable, så hvis applikationen laver en eller anden form for string-behandling, skaber det hurtig mange instanser. Husk at alle konstante tekster samt alle konstruerede tekster havner på heapen, så derfor løber det hurtig op. Med mindre der er tale om meget store instanser, er det sjældent værd at bekymre sig om.

Object[]

Antallet af string-instanser kan virke overvældende, men når vi tænker over det, er det i virkeligheden ikke så underligt, men hvad med object[]?

Var vi ikke lige blevet enige om, at generics og typestærke collections er sådan en god ide? Hvorfor skulle vi så bruge object[]? Og hvis det ikke er vores applikation, der bruger alle disse forældede collections, er det så Microsofts klasser, der endnu ikke er blevet opdateret?

Forklaringen er faktisk mere subtil end som så. Den underliggende type af List<T> er faktisk object[]. Hvordan kan det passe? Når vi fyrer op under Reflector, kan vi jo tydelig se, at List<T> benytter T[] til opbevaring af elementerne. Så hvis vi har List<string>, får vi vel string[] som den underliggende type. Ja, og nej.

Den underliggende type af List<string> er ganske rigtig string[], men måden CLRen implementerer arrays for referencetyper er ved hjælp af object[]. Lad os se et eksempel. Hvis vi har følgende i Main(), kan vi let finde den aktuelle List<string>-instans ved hjælp af !dso.

var Simpsons = new List<string> {
   "Homer", "Marge", "Bart", "Lisa", "Maggie"
};
0:000> !dso
OS Thread Id: 0x1390 (0)
ESP/REG  Object   Name
0029ed28 01ea2b78 Microsoft.Win32.SafeHandles.SafeFileHandle
0029ed38 01ea2b78 Microsoft.Win32.SafeHandles.SafeFileHandle
0029ed6c 01ea42ac System.Byte[]
0029ed70 01ea2b8c System.IO.__ConsoleStream
0029ed90 01ea4034 System.IO.StreamReader
0029ed94 01ea4034 System.IO.StreamReader
0029edac 01ea4034 System.IO.StreamReader
0029edb0 01ea45c4 System.IO.TextReader+SyncTextReader
0029edd0 01ea45c4 System.IO.TextReader+SyncTextReader
0029ede0 01ea2af4 System.Collections.Generic.List`1[[System.String, mscorlib]] <=== HER!
0029eea4 01ea2a38 System.Object[]    (System.String[])
0029f050 01ea2a38 System.Object[]    (System.String[])
0029f078 01ea2a38 System.Object[]    (System.String[])

Vores instans findes på adressen 01ea2af4, så lad os se nærmere på den.

0:000> !do 01ea2af4
Name: System.Collections.Generic.List`1[[System.String, mscorlib]]
MethodTable: 5f5f58c8
EEClass: 5f3abf0c
Size: 24(0x18) bytes
 (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
5f5c4eec  40009d8        4      System.Object[]  0 instance 01ea2b3c _items
5f5eab0c  40009d9        c         System.Int32  1 instance        5 _size
5f5eab0c  40009da       10         System.Int32  1 instance        5 _version
5f5e84dc  40009db        8        System.Object  0 instance 00000000 _syncRoot
5f5c4eec  40009dc        0      System.Object[]  0   shared   static _emptyArray

WinDbg reporterer korrekt, at vi har fat i List<string>, men læg også mærke til, at vi blandt Fields finder _items af typen Object[]. Er det vores data? Lad os se nærmere på indholdet af _items.

0:000> !dumparray -details -nofields 01ea2b3c
Name: System.String[]
MethodTable: 5f5c4eec
EEClass: 5f3aa8a0
Size: 48(0x30) bytes
Array: Rank 1, Number of elements 8, Type CLASS
Element Methodtable: 5f5e88c0
[0] 01ea2a64
    Name: System.String
    MethodTable: 5f5e88c0
    EEClass: 5f3aa498
    Size: 28(0x1c) bytes
     (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
    String:     Homer
[1] 01ea2a80
    Name: System.String
    MethodTable: 5f5e88c0
    EEClass: 5f3aa498
    Size: 28(0x1c) bytes
     (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
    String:     Marge
[2] 01ea2a9c
    Name: System.String
    MethodTable: 5f5e88c0
    EEClass: 5f3aa498
    Size: 26(0x1a) bytes
     (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
    String:     Bart
[3] 01ea2ab8
    Name: System.String
    MethodTable: 5f5e88c0
    EEClass: 5f3aa498
    Size: 26(0x1a) bytes
     (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
    String:     Lisa
[4] 01ea2ad4
    Name: System.String
    MethodTable: 5f5e88c0
    EEClass: 5f3aa498
    Size: 30(0x1e) bytes
     (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
    String:     Maggie
[5] null
[6] null
[7] null

Der er vist ingen tvivl om, at det object[] indeholder navnene på vores gule familie, men hvor er sammenhængen mellem List<string> og det underliggende object[]? Hvordan ved CLRen, at vores array indeholder instanser af string?

Lad os først få en ting på det rene. Referencer er referencer, så der er ingen forskel på en reference til en instans af object og en reference til en instans af string. Det er blot referencer, så der er intet galt i, at CLRen bruger et object[] til at gemme referencerne, men hvordan holder den styr på det faktiske indhold?

Ovenstående dump giver ikke nogen svar på dette spørgsmål. SOS kan give indtrykket af, at vi ser det hele, men faktisk pakker SOS et par implementeringsdetaljer ind og viser os dem på anden vis. Vi er altså nødt til at gå uden om SOS, for at få svar på det spørgsmål.

Dumper vi hukommelsen for vores object[], får vi de manglende brikker.

0:000> dd 01c62b3c -4 01c62b3c -4 +0x30 -1
01c62b38  00000000 5f5c4eec 00000008 5f5e88c0
01c62b48  01c62a64 01c62a80 01c62a9c 01c62ab8
01c62b58  01c62ad4 00000000 00000000 00000000

Den noget kryptiske kommando kalder vist på en forklaring. Den instansadresse SOS reporterer, er faktisk forskudt med et enkelt word. Det vil sige, at data for en instans begynder 4 bytes før den viste adresse (på 32 bit forstås). Så for at udskrive indholdet af instansen er vi nødt til at gå 4 bytes tilbage og udskrive til og med instansadressen, minus de fire bytes, plus størrelsen af instansen minus en. Det giver os data for vores object[].

Det første word er instansens SyncBlock, derefter følger instansen egen Method Table, så kommer længden at arrayet (8 i dette tilfælde), og herefter kommer det interessante: Hvad gemmer der sig bag 5f5e88c0? At dømme på værdien kunne det være en Method Table, så lad os se om det skulle være tilfældet.

0:000> !dumpmt 5f5e88c0
EEClass: 5f3aa498
Module: 5f381000
Name: System.String
mdToken: 02000024  (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
BaseSize: 0x10
ComponentSize: 0x2
Number of IFaces in IFaceMap: 7
Slots in VTable: 196

Det er en Method Table for string og dermed har vi kædet vores object[] sammen med den aktuelle type. De efterfølgende otte words er referencerne til vores fem string-instanser, samt tre tommer pladser til yderligere string-referencer.

Det efterlader blot et spørgsmål: Hvis object[] i realiteten er T[], hvad indeholder det mystiske felt så for et rigtig object array? Method Table for object naturligvis og så passer pengene.