Archive for the ‘C#’ Category

Tabt i detaljerne

Tuesday, September 7th, 2010

Sådan vasker Microsoft hænderHosstående opslag hænger på herretoilettet hos Microsoft i Hellerup. Det er en detaljeret, illustreret gennemgang af, hvordan man vasker hænder. Hvis vi ser bort fra den reelle risiko for en TL;DR-reaktion, må den minutiøse beskrivelse være tilstrækkelig til at tilgodese selv den mest nidkære bureaukrats behov for information, og ingen skulle således være i tvivl om, hvordan man vasker hænder i Hellerup.

Opslaget er mere underholdende end gavnligt i mine øjne, men det er desværre også et billede på den kode, de fleste af os til tider skriver. Jeg har læst og skrevet masser af kode, der i detaljeringniveau kan måle sig med ovenstående. Jo tættere vi kommer på metallet, desto tyndere bliver vores abstraktioner, og dermed bliver vi nødt til at redegøre for flere detaljer.

Vi kan således ikke helt undgå denne problematik, men jo mere vi kan styre uden om den slags desto bedre.

Problemet er, at det kan være svært at se, hvad målet er. Vi har specificeret, hvad der skal gøres, men det kan være svært at se, hvad det egentlig er, vi gerne vil opnå, og dermed kan det være svært at sige noget konkret om implementeringen. Fungerer den efter hensigten? Er der bivirkninger? Kan den optimeres om nødvendig? Kan den paralleliseres?

Hvad er formålet med instruktionerne i opslaget? Eller sagt på en anden måde: Hvad er det ønskede slutresultat, hvis vi følger proceduren? Er det rene hænder, reduktion af sæbebeholdning eller en sindrig form for håndaerobics i vand? Er det, vi ønsker at opnå, blot en delmængde af det samlede resultat? Hvordan skelner vi i så fald mellem de nødvendige og overflødige trin i processen for at opnå det ønskede?

Det kan være svært at adressere disse spørgsmål, da svarene ligger gemt i detaljerne.

Lad os se på et eksempel mere. Betragt følgende kode.

var numbers1 = new[] { 2, 4, 6, 7, 8, 9 };
var numbers2 = new[] { 1, 2, 3, 4, 5, 6, 9 };
var numbers3 = new[] { 1, 5, 7, 9 };

var result = new List<int>();

foreach (var n1 in numbers1) {
   foreach (var n2 in numbers2) {
      if (n1 == n2) {
         bool found = false;
         foreach (var n3 in numbers3) {
            found |= n2 == n3;
         }
         if (!found) {
            result.Add(n2);
         }
         break;
      }
   }
}

Hvad laver ovenstående? Med mindre man har set konstruktionen et utal af gange, skal de fleste af os nok lige tænke en gang eller to for at gennemskue, hvad formålet med koden er. Problemet er, at der er mange detaljer, så vi er næsten tvunget til at løbe et lille eksempel igennem, før vi kan gennemskue resultatet af koden. Det er ikke et stort problem, men vi skal bruge flere ressourcer på at forstå koden, end vi behøver.

Der er andre nærliggende spørgsmål, der ligeledes kan være svære at svare på her: Kan vi optimere koden, hvis behovet opstår? Hvor mange uafhængige operationer er der i koden? Kan disse paralleliseres?

Som du sikkert har gennemskuet, er indholdet af result alle de elementer, der er i både numbers1 og numbers2, men ikke i numbers3.

Det lugter lidt af mængdeoperationer, og med den erkendelse kan vi pludselig tale om problemet i domænespecifikke termer. Vi kan tilmed omskrive koden til at bruge .NETs mængdeoperationer. Ovenstående kan således udtrykkes som:

result = numbers1.Intersect(numbers2).Except(numbers3).ToList();

Vi kan selvfølgelig glæde os over, at dette er meget kortere, men det er kun en af gevinsterne her. Ved at bruge domænespecifikke abstraktioner kan vi kommunikere i et sprog, der giver mening i forhold til problemet, og vi er fri for at bekymre os om, hvad der skal til for at opnå det ønskede resultat. Vores kode er således mere deklarativ. Vi specificerer, hvad vi vil have, fremfor hvordan vi vil finde frem til resultatet i et sprog, der relaterer til vores problemdomæne.

Vi kan også let identificere antallet af nødvendige operationer, og derved har vi bedre mulighed for at identificere overflødige trin. I og med at vi arbejder med abstraktioner, er det derimod svært at sige noget om hvorvidt disse er implementeret optimalt, men med mindre vores målinger viser, at koden ikke kører optimalt, er vi sikkert bedre tjent med at bruge kode, der er testet og dokumenteret fremfor vores egen hjemmebryggede kode.

Hvis vi forestiller os, at result i stedet repræsenterer listen af kunder, der skal have et specielt tilbud. Således kunne numbers1 repræsentere de kunder, der interesserer sig for hi-fi, numbers2 kunne være listen af kunder, der interesserer sig for vvs-artikler, og numbers3 kunne holde styr på de kunder, der ikke har betalt til tiden. I så fald kunne vi bruge vores mængdeoperationer til at sende brev til relevante kunder omkring et nyt spabad med surround sound (hvis det ikke findes, skal det nok komme).

Koden ville essentielt være den samme, men den nuværende navngivning er helt i skoven. Koden taler om tal og mængder, og vi har således ingen forbindelse mellem koden og domænet. Så selvom .NET tilbyder os de nødvendige operationer, ville vi med fordel kunne indkapsle mængdeoperationerne i en eller flere metoder, hvis navne kan bygge bro til domænet.

Så hvad er lektien her?

Undgå udpenslende kode. Se om problemet kan udtrykkes på en måde, så vi kan bruge eksisterende funktionalitet. Hvis det ikke er tilfældet, så skab de nødvendige abstraktioner. I begge tilfælde bør abstraktioner navngives (evt. indpakkes) i konstruktioner, der giver mening i problemdomænet.

Følger vi disse trin, får vi kode, der er lettere at læse. Vi får muligvis mere kode, men hver del bliver kort, præcis og formuleret i et sprog, der passer til problemet. Vi får lettere ved at ræsonnere om kodens egenskaber, da den er lettere at overskue. Kode, der er let at forstå, er også lettere at vedligeholde og udvide.

Abstraktioner som typer og metoder er blandt af de vigtigste værktøjer i vores værktøjskasse, fordi de giver os mulighed for at opsplitte kompleksitet i overkommelige størrelser og navngive disse i et sprog, der afspejler vores problemdomæne. Brug dem!

Nyheder i C# 4 – syvende del: Varians

Thursday, August 19th, 2010

Det er blevet tid til sidste indlæg i min serie om nyheder i C# 4. Denne gang skal det handle om varians eller mere specifikt ko- og kontravarians.

Inden C# 4 kom på gaden var dette noget, der blev omtalt en hel del, men i realiteten er de introducerede forbedringer af den type, hvor vi egentlig ikke behøver at kende detaljerne. Vi kan blot glæde os over, at tidligere problemfyldte konstruktioner nu fungerer som forventet.

Lad os først få de relevante definitioner på plads for varians:

Kovarians vil sige, at vi kan konvertere en ”mindre” type til en ”større” type. Eksempelvis kan vi konvertere fra Int32 til Int64.

Kontravarians vil sige, at vi kan konvertere fra en ”større” type til en ”mindre” type. C# kræver eksplicit konvertering i dette tilfælde, da vi risikerer at miste data. Konverterer vi således fra Int64 til Int32, kræver C# at vi laver et cast af værdien til Int32.

Ideen bag de introducerede features er at tillade disse konverteringer på en sikker måde.

Array er kovariant

C# har understøttet kovarians for arrays siden version 1 men desværre ikke uden problemer.

Betragt nedenstående klassehierarki.

public class Creature {
}

public class Monster : Creature {
   public override string ToString() { return "Argh!!"; }
}

public class Vampire : Monster {
   public override string ToString() { return "I Vant Yor Blod"; }
}

public class Zombie : Monster {
   public override string ToString() { return "Braaaaiins!"; }
}

Monster er en specialisering af Creature, og Zombie og Vampire er yderligere specialiseringer af Monster. Som følge af konventionerne for arv, er enhver Zombie således et Monster og et Creature. Ligeledes er enhver Vampire et Monster og et Creature. Har vi en instans af Zombie, kan vi således tillade os at betragte den som enten Zombie, Monster eller Creature.

Opretter vi et array af Vampire, burde det således være i orden at betragte det som et array af Monster, eftersom Vampire er en specialisering af Monster. Nedenstående kode er derfor tilladt.

Vampire[] vampires = new Vampire[1];
Monster[] monsters = vampires;

Så langt, så godt. Men Zombie er jo også et Monster, så hvad sker der, hvis vi smider en Zombie i vores array?

monsters[0] = new Zombie();

Compileren tillader dette, da Zombie jo er en specialisering af Monster. Afviklingsmiljøet opdager dog, at vi nu har en Zombie i vores Vampire[] og smider en ArrayTypeMismatchException. Ups!

Problemet og løsningen

Hvorfor er ovenstående et problem? Når vi kan indsætte en instans af Zombie i et array af Vampire, kan vi komme til at referere denne via en Vampire-reference, og det er som bekendt ikke tilladt i C# og derfor smider afviklingsmiljøet en exception. Heldigvis er denne form for varians kun understøttet for array. Forsøger vi således ovenstående med List, kommer vi ikke langt.

List<Vampire> vampires = new List<Vampire>();
List<Monster> monsters = vampires; // compilefejl

Ovenstående oversætter således ikke. Men hvorfor egentlig ikke? Hvorfor kan vi ikke anskue en samling af Vampire som en samling af Monster, når vi kan betragte en enkelt instans af Vampire som en instans af Monster?

Problemet er at array tillader, at vi ændrer indholdet, så hvis vi ønsker at betragte en samling af Vampire som en samling af Monster, er vi nødt til at gøre det på en måde, så vi ikke kan ændre elementerne som ovenfor.

IEnumerable<T> tillader os at gennemløbe en samling af objekter, uden at vi kan ændre denne. Så hvis vi ændrede eksemplet fra at bruge array til at bruge IEnumerable<T>, kunne det således ud:

List<Vampire> vampires = new List<Vampire>();
IEnumerable<Monster> monsters = vampires;

Desværre virker ovenstående ikke i tidligere version af C#, for skønt vi ved, at vi ikke kan ændre indholdet via en IEnumerable<T>-reference, er compileren desværre uvidende på dette punkt. Vi mangler altså en måde at synliggøre dette over for compileren.

I .NET 4 er IEnumerable<T> og flere andre typer således blevet opdateret med denne information via en ny syntaks til understøttelse af ko- og kontravarians. Den opdaterede definition af IEnumerable<T> er som følger:

IEnumerable<out T>

out indikerer, at vores instans af T kun bliver brugt som output. Vi kan altså få lov at løbe en samling af T igennem og betragte hvert element som instanser af Ts baseklasse, fordi elementerne kun optræder som output.

På samme vis er f.eks. Action<T> blevet opdateret som følger:

Action<in T>

Her angiver in, at instanser af T kun bliver brugt som input til metoden, og dermed kan vi tillade følgende:

Action<Monster> write_monster = m => { Console.WriteLine(m); };
Action<Vampire> write_vampire = write_monster;

Eftersom Vampire er en specialisering af Monster, kan vi betragte en metode, der tager en instans af Vampire som input, som en gyldig variant af en metode, der tager en instans af Monster som input, eftersom alle instanser af Vampire også er en instans af Monster, og dermed har vi fået en kontravariant version af Action<T>.

Afrundning og næste punkt på programmet

Ko- og kontravarians er kun understøttet for interfaces og delegate-typer, og der skal naturligvis findes en gyldig referencekonvertering mellem de involverede typer, for at det kan lade sig gøre. Ydermere er ko- og kontravarians ikke understøttet for ref og out.

Der er flere detaljer omkring varians, men jeg runder min gennemgang af her. Det er ikke nødvendigt, at forstå samtlige finurligheder for at glæde sig over, at visse oplagte konstruktioner nu er understøttet.

Dermed er vi nået til enden af min gennemgang af nyheder i C# 4.

Jeg er ved at forberede en TechTalk om PFX (tag jer ikke af overskriften, der er et eller andet galt med Microsofts event-system), så de næste mange indlæg kommer til at omhandle parallel programmering og de nye muligheder i .NET 4 og VS2010.

Resultatet af kodegolfkonkurrencen

Friday, July 30th, 2010

Konkurrence er slut, og vinderen er fundet, men inden vi kommer så langt, skal jeg først og fremmest sige tak til alle jer, der har indsendt bidrag. Sommervarme og ferie til trods har jeg modtaget ikke mindre end 30 løsninger på udfordringen denne gang og flere af dem har tilmed været igennem adskillige revisioner. Jeg er imponeret! Tusind tak fordi I ville være med.

Gennemsnittet for løsningerne ligger på 185 anslag, og der er ikke mindre end syv løsninger på under 140 anslag! 29 ud af 30 løsninger klarer alle mine test cases, så vinderen er altså fundet blandt de 29.

Der er rigtig mange gode og kreative ideer. Flere har været forbi den nye Zip-metode, og LINQ er i det hele taget pænt repræsenteret i løsningerne. Der er brugt mange forskellige datastrukturer så som arrays, List, Stack og sågar string. Vinderen benytter LINQ og string.

Nok snak, lad os se vinderen. Med blot 132 anslag:

public static int[] MadsOgPeterSandbergBrun_Add(int[] a,int[] b){
   var c="";
   for(int o=a.Length,p=b.Length,s=0;-o-p<(s=s/10+(0<o?a[--o]:0)+(0<p?b[--p]:0));)
      c=s%10+c;
   return c.Select(i=>i-48).ToArray();
}

Tillykke til Mads og Peter Sandberg Brun! Der er et stk. Visual Studio 2010 Ultimate + MSDN på vej til jer.

Resten af feltet ser ud som følger:

Mads og Peter Sandberg Brun            132
Jacob Korsgaard                        135
Jørgen Ulrik B. Krag (Jubk)            135
Asger Hallas  og Lars Udengaard        135
Fredrik Olsson                         138
Mogens Heller Grabe (Mookid8000)       138
Ole Tolshave                           139
Jan Jensen (JanJ)                      141
Niels Rasmussen (NTR)                  151
Steffen Holmslykke (SNH)               153
Asger Hallas                           157
Mads Mau Pedersen (MMP)                158
Jesper Harder (JHD)                    158
Daniel Brixen                          169
Anders Uhl Pedersen (Aup)              170
Jonathan Jørgensen (Jonathan)          170
Jesper Alf Dam (Jalf)                  176
Christian Rysgaard (CER)               183
Morten Gejl (MGE)                      193
Mads Hedegaard                         195
Carsten Hess                           196
Brian Vestergaard Andersen             208
Anders Reimer                          218
Dennis Riis                            226
Martin Faartoft                        227
Allan Tech                             248
Martin Larsen                          287
Simon Kristensen (Imonsei)             289
Claus Jensen                           296
Daniel Mellgaard Frost (Danielovich)   229 (fejler)

Jeg har lavet en fil med alle bidragene, så I kan få ideer til fremtidige kodegolfturneringer.

Endnu en gang tak til jer alle!

En overraskelse og 1000+ doubles

Friday, July 23rd, 2010

Dette indlæg rummer egentlig ingen ny viden, men på trods af at jeg efterhånden har brugt lang tid på finurlige detaljer omkring .NETs afviklingsmiljø, er jeg først nu stødt på denne detalje, så set i det lys vil jeg tillade mig at betegne emnet for dette indlæg som esoterisk og gentage noget, der muligvis ikke er nyt for alle.

Som det forhåbentlig er kendt for læserne af denne blog, foregår dynamisk allokering af hukommelse i .NET enten i generation 0 af heapen eller på Large Object Heap (LOH) i fald den allokerede instans er på 85.000 bytes eller mere.

Derfor vil small pege på et array allokeret i generation 0 og large pege på et array allokeret på LOH i nedenstående eksempel.

var small = new byte[1000];
var large = new byte[85000];

Det kan vi verificere via WinDbg eller ved at kalde GC.GetGeneration(), der som forventet returnerer henholdsvis 0 og 2 for de to referencer (bemærk, at metoden ikke skelner mellem generation 2 og LOH, da de i forhold til garbage collection behandles i samme ombæring). Bruger vi debuggeren, får vi lidt mere nøjagtig information, og her kan vi se, at large faktisk peger på en instans på LOH.

Gentager vi øvelsen for double[], burde vi igen kunne forudsige placeringen af de enkelte instanser. En double fylder 8 bytes, så for at komme over den magiske grænse, skal vi have et array med lidt over 10.000 elementer, lad os bare sige 11.000. Med det in mente burde vi altså kunne konkludere at small og large endnu en gang peger på instanser i henholdsvis generation 0 og på LOH.

var small = new double[1000];
var large = new double[11000];

Det er bare ikke sådan det forholder sig. Begge arrays bliver allokeret på LOH!

CLRen benytter nemlig en forholdsvis esoterisk optimering i dette tilfælde. Arrays af double med 1000 eller flere elementer bliver mod forventning altid allokeret på LOH. Det er nyt for mig, men hvis man nærlæser kommentarerne til dette gamle blogindlæg, kan man se, at det er ”by design”. Det er altså ikke en fejl, det er en feature.

Argumentationen er, at objekter på LOH altid ligger på 8 bytes skel, og derfor giver bedre performance ved opslag af elementerne. Jeg har ikke kunne finde nogen forklaring på, hvorfor grænsen på 1000 elementer er valgt, men sådan forholder det sig nu engang.

Det er fristende at prøve, om vi kan eftervise effekten af denne optimering, men det er ikke let i praksis, da vi har meget begrænset kontrol over og indsigt i allokering og adresselæsninger i managed code, så derfor må vi tage Microsofts ord for pålydende her.

Konsekvenserne af denne optimering kan være mange og ikke alle nødvendigvis til vores fordel. Hvis vi antager, at optimeringen har sin berettigelse, så må vi gå ud fra, at læsning af elementerne i et double[], nyder gavn af den gunstige placering i hukommelsen.

Til gengæld er disse arrays pludselig dyrere at allokere, eftersom allokering på LOH benytter en free list, hvilket kan være mange gange langsommere end den simple pointeroperation, der skal til ved allokering i generation 0. Ligeledes vil levetiden af disse arrays stige markant. Er der tale om midlertidige arrays, går de fra hurtig oprydning i generation 0 til en lang levetid på LOH. Det kan have indflydelse på hvor stor belastning garbage collection lægger på applikationen, ligesom det kan give yderligere problemer i form af fragmentering af LOH.

Konsekvenserne afhænger af den konkrete applikations forbrug af hukommelse, men givet er det, at det kan være en særdeles vigtig detalje i nogle scenarier. Så med mange års forsinkelse gør jeg hermed mit til at udbrede kendskabet til en godt bevaret hemmelighed.

Nyheder i C# 4 – sjette del: Bedre integration med COM

Thursday, July 22nd, 2010

Mens vi venter på afgørelsen af kodegolfkonkurrencen (du kan stadig nå at indsende bidrag, så go go go!), fortsætter jeg min serie om nyheder i C# 4.

Som vi så på i forrige indslag i serien, har det tidligere været lidt af et helvede at kalde Microsoft Office COM objekter via C#. Dynamiske typer samt valgfri og navngivne parametre hjælper i den sammenhæng, men heldigvis er Microsoft gået endnu længere for at gøre kode, der arbejder mod disse grænseflader lettere at arbejde med. Udover de nævnte features tilbyder C# 4 følgende til bedre COM-integration:

  • No-ref
  • Index properties
  • No PIA

Lad os se på et eksempel. Skal vi f.eks. starte Word og åbne et nyt dokument, så koden før C# 4 ud nogenlunde som følger:

private void OpenWord() {
   // instance needed due to ref
   object missing = Type.Missing;
   object index = 1;
   var word = new Word.Application();

   word.Documents.Add(ref missing, ref missing, ref missing, ref missing);

   // pseudo index property
   Document = word.Documents.get_Item(ref index);

   word.Visible = true;
}

Læg mærke til at COM-grænsefladen kræver, at alle parametre overføres via ref, og derfor er vi nødt til at oprette lokale referencer – også selvom vi egentlig ikke er interesseret i at overføre en værdi i flere af tilfældene. Det betyder også, at når vi skal overføre værdien 1 til get_Item(), er vi nødt til at oprette en reference til en boxed int med værdien 1, så vi kan overføre denne som en reference.

Heldigvis er der hjælp at hente i C# 4. No-ref fjerner behovet for at skrive alle de omfattende ref-overførelser af parametre. Selve kaldet er naturligvis ikke ændret, da grænsefladen nu engang ser ud, som den gør, men compileren sørge for at generere den nødvendige kode på baggrund af en velkendt syntaks. Ovenstående metode ser således ud som følger i C# 4.

private void OpenWord() {
   var word = new Word.Application();

   word.Documents.Add();
   Document = word.Documents[1];

   word.Visible = true;
}

Læg mærke til, at alle de kedelige ref-kald er væk. De overflødige parametre er ligeledes fjernet, og compileren tillader nu, at vi kan nu kalde get_Item(), som om det var en almindelig index property. Alt i alt får vi en syntaks for COM-interaktion, der ligner det vi er vant til fra interaktion med almindelige .NET typer.

Det bringer os til sidste nye feature i denne sammenhæng: No-PIA, men inden vi ser på den, er det nok på sin plads at forklare, hvad PIA er. PIA står for Primary Interop Assemblies, og er en betegnelse for specielle assemblies, der erklærer .NET-typedefinitioner for eksisterende COM-typer. Det er med andre ord den grænseflade, vores .NET applikation bruger til at kommunikere med COM-objekter i f.eks. Microsoft Office.

For at kunne tilgå de ønskede COM-objekter, er vi altså nødt til at have det eller de relevante PIAs til rådighed. Hvert PIA indeholder typedefinitioner for alle de relevante COM-typer for den givne applikation, så de kan blive ganske omfattende. Det vil sige, at selv hvis vores applikation kun har brug for en lille del af f.eks. Word eller Excel, er vi nødt til at inkludere de komplette PIAs for disse. Det kan blive et problem i visse sammenhænge.

Med No-PIA sørger compileren for at inkludere de nødvendige typedefinitioner i vores assembly, hvilket har to konsekvenser. For det første behøver vi ikke længere at bekymre os om distribution af PIAs og for det andet, slæber vi ikke rundt på definitioner for samtlige typer. Det gør distribution af vores applikation mere smidig.

I næste og sidste indlæg i denne serie skal vi se på varians.

Debugging adplus.exe

Tuesday, July 20th, 2010

Nej, der mangler ikke et “med” i overskriften. Dette indlæg handler om en fejl, jeg fandt i ADPlus.

I den seneste version af Debugging Tools for Windows er det gamle ADPlus vb-script blevet udskiftet med en exe-fil. ADPlus er blevet genimplementeret som en managed applikation (det gamle script er stadig tilgængelig under navnet adplus_old.vbs).

Ifølge det medfølgende Word-dokument er dette gjort for bedre at kunne udvide ADPlus med ny funktionalitet, og der er allerede kommet et par yderligere muligheder som f.eks. –pmn, der overvåger alle processer med et bestemt navn.

Det er dog ikke det, vi skal se på i dette indlæg. Ligesom sin forgænger understøtter adplus.exe, at vi kan få debuggeren til at starte vores applikation via –sc. Desværre er denne feature ikke implementeret særlig elegant i den nye version.

I forbindelse med en debugging-opgave havde jeg brug for at starte min applikation via –sc, men uanset hvad jeg gjorde, fik jeg nedenstående fejlmeddelelse:

Spawning c:\dev2010\testapp\testapp\bin\release\testapp.exe
!!! ERROR !!!
The system cannot find the file specified
   at System.Diagnostics.Process.StartWithShellExecuteEx(ProcessStartInfo startInfo)
   at System.Diagnostics.Process.Start(ProcessStartInfo startInfo)
   at ADPlus.AdplusEngine.SpawnProcess(String SpawnCommand)
   at ADPlus.AdplusApl.TryAttachSelectedProcesses()
   at ADPlus.AdplusApl.TryRun()
!!!ERROR - ADPlus failed to run

Det fik mig til at tro, at ADPlus af en eller anden grund ikke kunne finde min applikation, men det er faktisk ikke det, fejlmeddelelsen angiver.

I mangel på bedre fyrede jeg op under WinDbg for at finde ud af, hvorfor denne fejl opstod.

adplus.exe er som sagt en managed applikation, og den er bygget til CLR version 2 og AnyCPU, så vores runtime hedder mscorwks, og vi skal have fat i 64 bit versionen af WinDbg på trods af, at vi ønsker at debugge den version, der kommer med 32 bit versionen af Debugging Tools for Windows.

Via Open Executable-dialogen kan vi udvælge adplus.exe og angive de relevante opstartsparametre. Herefter starter WinDbg adplus.exe, men stopper udførelsen, så snart processen er i luften. På dette tidspunkt er CLRen ikke startet endnu, så vi får ikke noget ud af at forsøge at indlæse SOS. I stedet kan vi sætte et event filter så eksekveringen stopper, når CLRen indlæses og derefter indlæse SOS. Det kan gøres som følger:

sxe -c ".loadby sos mscorwks" ld mscorwks

Herefter kører vi videre, indtil vi rammer vores event filter og får indlæst SOS.

0:000> g
ModLoad: 000007fe`efd10000 000007fe`f06be000   C:\Windows\Microsoft.NET\Framework64\v2.0.50727\mscorwks.dll
ntdll!ZwMapViewOfSection+0xa:
00000000`77b9ffda c3              ret

Vi ved fra fejludskriften, at der bliver smidt en exception, så lad os sætte et event filter for managed exceptions.

0:000> sxe -c "!clrstack; !pe" clr

Ovenstående stopper i tilfælde af en managed exception og udskriver derefter kaldestak for den aktuelle tråd samt detaljerne om vores exception. Og så kører vi igen:

0:000> g
(14c4.13fc): CLR exception - code e0434f4d (first chance)
OS Thread Id: 0x13fc (0)
*** WARNING: Unable to verify checksum for C:\Windows\assembly\NativeImages_v2.0.50727_64\System\247913fa7ae6fcf04ea33d28d24ab611\System.ni.dll
Child-SP         RetAddr          Call Site
00000000002eec30 000007feefa406bc System.Diagnostics.Process.StartWithShellExecuteEx(System.Diagnostics.ProcessStartInfo)
00000000002eed30 000007ff00198597 System.Diagnostics.Process.Start(System.Diagnostics.ProcessStartInfo)
00000000002eed70 000007ff00197ef0 ADPlus.AdplusEngine.SpawnProcess(System.String)
00000000002eedc0 000007ff001942ce ADPlus.AdplusApl.TryAttachSelectedProcesses()
00000000002eee50 000007ff00191d98 ADPlus.AdplusApl.TryRun()
00000000002eeec0 000007ff0019026e ADPlus.AdplusApl.TryExecute(System.String[])
00000000002eef00 000007feeffdd502 ADPlus.Program.Main(System.String[])
Exception object: 000000000265fe10
Exception type: System.ComponentModel.Win32Exception
Message: The system cannot find the file specified
InnerException: <none>
StackTrace (generated):
<none>
StackTraceString: <none>
HResult: 80004005
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
KERNELBASE!RaiseException+0x39:
000007fe`fdb6aa7d 4881c4c8000000  add     rsp,0C8h

Debuggeren er stoppet som følge af en exception. Det ser ud til at passe meget godt med vores fejlsituation, så nu skal vi blot finde ud af, hvad det er for en fil, der ikke kan lokaliseres (tænk på hvor meget lettere alt ville være, hvis vi bare kunne læse det ud af vores exception).

Via kaldestakken kan vi se, at StartWithShellExecuteEx bliver kaldt med en instans af ProcessStartInfo. Hvis vi er heldige, er der en reference til denne på stakken. På 64 bit Windows overføres de fire første argumenter altid i registre, så vi kan ikke regne med at finde noget på stakken. I visse situationen opretter compileren dog lokale referencer på stakken, så det er vores første forsøg:

0:000> !dso
OS Thread Id: 0x13fc (0)
RSP/REG          Object           Name
00000000002eea30 000000000265fef8 System.Text.StringBuilder
00000000002eea68 000000000265fe10 System.ComponentModel.Win32Exception
00000000002eea70 00000000025a7b48 System.String
00000000002eea80 000000000265fe10 System.ComponentModel.Win32Exception
00000000002eeaa0 000000000265fe10 System.ComponentModel.Win32Exception
00000000002eeaf0 000000000265ff20 System.String
00000000002eeb00 0000000002660140 System.String
00000000002eeb70 000000000265fe10 System.ComponentModel.Win32Exception
00000000002eeb78 00000000025a7b48 System.String
00000000002eeb80 000000000265fe10 System.ComponentModel.Win32Exception
00000000002eebd8 00000000025a7b48 System.String
00000000002eebe0 000000000265fe10 System.ComponentModel.Win32Exception
00000000002eec10 000000000265fdf0 System.Diagnostics.ShellExecuteHelper
00000000002eec20 000000000265fe10 System.ComponentModel.Win32Exception
00000000002eec60 000000000265fbb8 System.Diagnostics.Process
00000000002eec68 00000000025a7b48 System.String
00000000002eec70 000000000265f238 System.Diagnostics.ProcessStartInfo
00000000002eece0 000000000265fcc8 Microsoft.Win32.NativeMethods+ShellExecuteInfo
00000000002eed10 000000000265f238 System.Diagnostics.ProcessStartInfo
00000000002eed18 00000000025a7a98 ADPlus.AdplusEngine
00000000002eed20 000000000265fbb8 System.Diagnostics.Process
00000000002eed30 000000000265fbb8 System.Diagnostics.Process
00000000002eed38 000000000265f238 System.Diagnostics.ProcessStartInfo
00000000002eed50 00000000025a7b48 System.String
00000000002eed58 000000000265f238 System.Diagnostics.ProcessStartInfo
00000000002eed60 000000000265f130 System.String
...

Jeg har forkortet listen en smule, men som vi kan se, er der faktisk en reference til en instans af ProcessStartInfo. Heldigt, så behøver vi ikke grave videre. Lad os se nærmere på instansen.

0:000> !do 000000000265f238
Name: System.Diagnostics.ProcessStartInfo
MethodTable: 000007feefb3ce18
EEClass: 000007feef39dcb8
Size: 128(0x80) bytes
 (C:\Windows\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll)
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007feee37ec90  4003376        8        System.String  0 instance 00000000025a7b48 fileName
000007feee37ec90  4003377       10        System.String  0 instance 000000000265f130 arguments
000007feee37ec90  4003378       18        System.String  0 instance 0000000000000000 directory
000007feee37ec90  4003379       20        System.String  0 instance 0000000000000000 verb
000007feefb0ace0  400337a       68         System.Int32  1 instance                2 windowStyle
000007feee37de60  400337b       6c       System.Boolean  1 instance                0 errorDialog
000007feee381698  400337c       60        System.IntPtr  1 instance                0 errorDialogParentHandle
000007feee37de60  400337d       6d       System.Boolean  1 instance                1 useShellExecute
000007feee37ec90  400337e       28        System.String  0 instance 0000000000000000 userName
000007feee37ec90  400337f       30        System.String  0 instance 0000000000000000 domain
000007feeeb78b50  4003380       38 ...rity.SecureString  0 instance 0000000000000000 password
000007feee37de60  4003381       6e       System.Boolean  1 instance                0 loadUserProfile
000007feee37de60  4003382       6f       System.Boolean  1 instance                0 redirectStandardInput
000007feee37de60  4003383       70       System.Boolean  1 instance                0 redirectStandardOutput
000007feee37de60  4003384       71       System.Boolean  1 instance                0 redirectStandardError
000007feee386ae0  4003385       40 System.Text.Encoding  0 instance 0000000000000000 standardOutputEncoding
000007feee386ae0  4003386       48 System.Text.Encoding  0 instance 0000000000000000 standardErrorEncoding
000007feee37de60  4003387       72       System.Boolean  1 instance                0 createNoWindow
000007feee377a90  4003388       50 System.WeakReference  0 instance 0000000000000000 weakParentProcess
000007feef5d5d68  4003389       58 ....StringDictionary  0 instance 0000000000000000 environmentVariables

Læg mærke til det første felt ved navn fileName. Det må vi hellere se nærmere på:

0:000> !do 00000000025a7b48
Name: System.String
MethodTable: 000007feee37ec90
EEClass: 000007feedf8b038
Size: 40(0x28) bytes
 (C:\Windows\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: cdb.exe
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007feee385f00  4000096        8         System.Int32  1 instance                8 m_arrayLength
000007feee385f00  4000097        c         System.Int32  1 instance                7 m_stringLength
000007feee3806d8  4000098       10          System.Char  1 instance               63 m_firstChar
000007feee37ec90  4000099       20        System.String  0   shared           static Empty
                                 >> Domain:Value  00000000003d0390:00000000025a1308 <<
000007feee380588  400009a       28        System.Char[]  0   shared           static WhitespaceChars
                                 >> Domain:Value  00000000003d0390:00000000025a1a38 <<

Hmm, det indeholder cdb.exe. Så det er altså der, skoen trykker. Det er ikke fordi adplus.exe ikke kan finde min applikation, men fordi den ikke kan finde cdb.exe.

Det er jo underligt, for de andre kommandoer har ingen problemer med at finde cdb.exe. Det betyder selvfølgelig, at vi kan løse problemet ved blot at tilføje det relevante directory til vores path, men nu er der faktisk en grund til, at jeg ikke har Debugging Tools for Windows i min path. Jeg har tre versioner installeret (nyeste version i 32 og 64 bit samt en 32 bit udgave af 6.7.5), og jeg vil selv kunne bestemme hvilken en jeg får fat i, og derfor har jeg ingen af disse i min path.

Det oplagte spørgsmål er: Hvorfor fejler –sc, når de andre kommandoer virker uden path? Kaldestakken fortæller os, at den relevante metode i ADPlus er SpawnProcess. Denne metode viser sig at være en instansmetode på typen AdplusEngineadplus.exe er jo en managed applikation, så vi kan inspicere koden ved hjælp af Reflector.

Hvis vi kan kalde SpawnProcess, må der være en instans af AdplusEngine på heapen. Går vi tilbage til ovenstående liste af stakreferencer, finder vi faktisk en reference til en sådan instans. Lad os se nærmere på den.

0:000> !do 00000000025a7a98
Name: ADPlus.AdplusEngine
MethodTable: 000007ff00054068
EEClass: 000007ff00182688
Size: 176(0xb0) bytes
 (C:\dbg32\adplus.exe)
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
0000000000000000  4000009        8                       0 instance 00000000025ab618 m_KeyWords
0000000000000000  400000a       10                       0 instance 00000000025ab500 m_KeyWordsCustom
0000000000000000  400000b       18                       0 instance 00000000025ac580 m_Exceptions
0000000000000000  400000c       20                       0 instance 00000000025ae0f0 m_Breakpoints
000007feee37ec90  400000d       28        System.String  0 instance 00000000025a8c90 m_HangConfiguration
000007feee37de60  400000e       9c       System.Boolean  1 instance                0 m_HangConfigurationChanged
0000000000000000  400000f       30                       0 instance 00000000025ae080 m_PreCommands
0000000000000000  4000010       38                       0 instance 00000000025ae0c8 m_PostCommands
0000000000000000  4000011       40                       0 instance 00000000025ae138 m_NotilyList
000007feee37ec90  4000012       48        System.String  0 instance 00000000025a32a8 m_OutputDir
000007feee37ec90  4000013       50        System.String  0 instance 00000000025b1680 m_OutputDumpDir
000007ff00053f10  4000014       98         System.Int32  1 instance                1 m_RunMode
000007feee37ec90  4000015       58        System.String  0 instance 00000000025ad9a0 m_DateTimeStamp
000007feee37ec90  4000016       60        System.String  0 instance 00000000025a1308 m_Sympath
000007feee37de60  4000017       9d       System.Boolean  1 instance                1 m_SympathSet
000007feee37ec90  4000018       68        System.String  0 instance 00000000025a1308 m_MssLocalCache
000007feee37de60  4000019       9e       System.Boolean  1 instance                0 m_AddMss
000007feee37ec90  400001a       70        System.String  0 instance 00000000025a1308 m_LastScriptCommand
000007feee37ec90  400001b       78        System.String  0 instance 00000000025a7b48 m_Debugger
000007feee37de60  400001c       9f       System.Boolean  1 instance                0 m_ExtensionInteraction
000007feee37de60  400001d       a0       System.Boolean  1 instance                0 m_ExtensionDebug
000007feee37ec90  400001e       80        System.String  0 instance 00000000025ab408 m_AdplusPath
000007feee37ec90  400001f       88        System.String  0 instance 00000000025a1308 m_DebuggersPath
000007ff00053808  4000020       90     ADPlus.AdplusApl  0 instance 00000000025a77c0 m_AdplusApl
000007feee37ec90  4000005        8        System.String  0   static 00000000025a6af0 m_Version
000007feee37ec90  4000006       10        System.String  0   static 00000000025a6b20 m_VersionDate
000007feee374748  4000007       18      System.Object[]  0   static 00000000025a6b90 m_LineSeparator
000007feee374748  4000008       20      System.Object[]  0   static 00000000025a6bb8 m_KWSeparator

Læg mærke til m_AdplusPath. Lad os se, hvad den indeholder.

0:000> !do 00000000025ab408
Name: System.String
MethodTable: 000007feee37ec90
EEClass: 000007feedf8b038
Size: 44(0x2c) bytes
 (C:\Windows\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: C:\dbg32\
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007feee385f00  4000096        8         System.Int32  1 instance               10 m_arrayLength
000007feee385f00  4000097        c         System.Int32  1 instance                9 m_stringLength
000007feee3806d8  4000098       10          System.Char  1 instance               43 m_firstChar
000007feee37ec90  4000099       20        System.String  0   shared           static Empty
                                 >> Domain:Value  00000000003d0390:00000000025a1308 <<
000007feee380588  400009a       28        System.Char[]  0   shared           static WhitespaceChars
                                 >> Domain:Value  00000000003d0390:00000000025a1a38 <<

Den indeholder c:\dbg32, som er en junction til min 32 bit installation af Debugging Tools for Windows.

ADPlus har altså de nødvendige oplysninger til rådighed, men glemmer åbenbart at bruge dem. Lad os se på implementeringen af SpawnProcess:

public void SpawnProcess(string SpawnCommand)
{
   Logger.LogAndOut("Spawning " + SpawnCommand);
   string debugger = this.m_Debugger;
   string arguments = ("-c $<\"" + this.OutputDumpDir + "\\DebuggerScript.txt\" ") + SpawnCommand;
   Process.Start(new ProcessStartInfo(debugger, arguments) { WindowStyle = ProcessWindowStyle.Minimized });
}

ProcessStartInfo oprettes blot med indholdet af m_Debugger, og får således ikke m_AdplusPath med, og der har vi altså problemet.

Nu skal jeg bare vente på, at Microsoft får rettet fejlen.

Kodegolf: Vind Visual Studio 2010 Ultimate

Wednesday, July 14th, 2010

Det er sommer, det er varmt, og det er tid til en gang golf. Her på siden foregår den slags jo ikke ude i det fri men derimod foran computeren, hvilket sikkert ikke er det værste sted at opholde sig i disse høj-UV-tider.

Denne gang går opgaven ud på at implementere nedenstående metode, der kan lægge potentielt meget store, ikke-negative heltal sammen. Koden skal skrives i C#.

public static int[] Add(int[] a, int[] b) { }

Som det fremgår, tager Add() to int[]. Hvert int i de to arrays repræsenterer et ciffer, så hvis a repræsenterer værdien 1000, indeholder den { 1, 0, 0, 0 }, og hvis b rummer værdien på Decimal.MaxValue indeholder den { 7, 9, 2, 2, 8, 1, 6, 2, 5, 1, 4, 2, 6, 4, 3, 3, 7, 5, 9, 3, 5, 4, 3, 9, 5, 0, 3, 3, 5 }. Den øvre grænse for tallenes størrelse er kun begrænset af størrelsen på int[]. De to værdier skal lægges sammen og returneres som int[].

Følgende regler gælder:

  • Alle anslag mellem metodedefinitionens start- og slut-curlies tælles med – white space, der ikke har betydning for metodens virke er dog undtaget.
  • a og b kan ikke være tomme, men de har ikke nødvendigvis samme længde.
  • Alle regneoperationer skal foregå på datatypen int.
  • Det er ikke tilladt at bruge BigInteger.
  • Alle udregninger skal foregå i Add().
  • Ingen hjemmelavede hjælpemetoder eller erklæringer uden for Add().
  • Navngiv metoden så den hedder DitNavnEllerAlias_Add, f.eks. kunne min version hedde Kodehoved_Add. Navnets længde påvirker ikke optællingen med mindre metoden benytter rekursion (så lad være med det).
  • Det er ikke tilladt at bruge using-aliaser, da alle bidrag bliver testet af samme program.
  • Resultatet må ikke indeholde foranstillede nuller.
  • Send løsningsforslag til brian@kodehoved.dk.
  • Deadline: 30. juli kl. 20.00.

Jeg har et stk. Microsoft Visual Studio 2010 Ultimate with MSDN som præmie. Vinderen er den, der leverer en korrekt løsning på ovenstående med færrest mulige anslag. I tilfælde af flere løsninger med lige få anslag trækker jeg lod blandt disse.

Skulle der være uklarheder så skriv en kommentar, så skal jeg uddybe snarest. Følg med i kommentarerne og husk at tjekke for opdateringer til dette indlæg. God fornøjelse!