Random c# stuff

IDisposable voor Excel Com

Door Wiebbe op vrijdag 20 maart 2009 14:15 - Reacties (2)
Categorie: c#, Views: 2.056

Excel en .NET... Je zou verwachten van een bedrijf als microsoft dat ze voor de belangrijke tools Excel/Word een mooie oplossing hebben gemaakt om deze aan te spreken vanuit je eigen .NET Tools. Helaas! Microsoft heeft besloten dat je Excel niet managed kan gebruiken voor het aanmaken en inlezen van Excel op een nette maar ook makkelijke manier.

Eerst wat achtergrond informatie, ik denk dat je je wel kan voorstellen dat het voor aardig wat applicaties nodig is om informatie te exporteren. Laten we als voorbeeld de performance tool nemen waar ik de Threads nodig had uit mijn vorige post. Wat wil ik hebben? Een simpele file die in Excel te openen is maar waar ik ook wat opmaak bij kan invoeren. Een mooie kop regel met dik gedrukte kop teksten en eventueel een kleurtje.

Inhoud? Simpel weg alle data die ik heb gelogd met de performance tool.

Ik ging hier uiteraard mee aan de slag met het idee dat het snel opgelost zou zijn. Oh boy, wat had ik het toch verkeerd! Het eerste wat ik ging doen is kijken of er al kant en klare oplossingen voor beschikbaar waren.

Wat ik vaak tegen kom zijn de oplossingen die het opslaan als html of csv en het dan neerzetten als een xls file. Dit werkt wel, maar je krijgt hier niet lekker alle informatie mee en het uitlezen van een excel file gaat hier niet mee.

Hiernaast komt je ook totaal oplossingen tegen die excel volledig wrappen. Op zich ook een goede oplossing, helaas zijn er veel betaald of zijn ze onder de GPL licensie gereleased wat niet gewenst is.

Maar hoe dan wel, zelf schrijven uiteraard! Nou is het uitlezen en wegschrijven van een excel file niet echt moeilijk, How to: Use COM Interop to Create an Excel Spreadsheet (C# Programming Guide) geeft aan wat je moet doen. Het punt waar ik tegen aan liep was het geheugen beheer.

Afkomend van een opleiding waar ze je "verwennen" met java en C# heb ik nog nooit echt om hoeven te gaan met unmanaged code en het geheugen beheer. De Garbage collector doet eigenlijk altijd alles voor je, en volgens mij raden ze zelfs af zelf de GC aan te roepen.

Maar helaas geld dit niet voor de Interop excel aanroepen. Het bleek na een sheet uit te lezen dat hij het excel window niet wilde sluiten met de close aanroep. Na veel zoek werk bleek het te komen omdat het unmanaged calls zijn. Als je een excel object aanmaakt weet de GC niet wanneer dit object niet meer gebruikt wordt en moet je het dus zelf netjes afhandelen.

Wat ik heb gedaan is het excel bestand op te delen in verschillende objecten. En wel zo:

* Excel Applicatie
* Excel Workbook
* Excel Worksheet

Ik had nog verder kunnen gaan door ook rows en cellen op te nemen maar dit was voldoende voor het huidige project. De Excel work sheet is als volgt gedeclareerd:


C#:
1
2
3
4
5
class ExcelWorksheet : IDisposable
    {
        private bool _alreadyDisposed = false;
        private List<object> comObjects = new List<object>();
    }



De comObjects list houd bij welke ComObjecten er al zijn geopend en welke dus ook weer moeten worden verwijderd.

In de finalizer geef je aan dat de dispose method aangeroepen moet worden:


C#:
1
2
3
4
5
//finalizer van de class
        ~ExcelWorksheet()
        {
            Dispose(false);
        }



Aangezien we zelf nu de GarbageCollector spelen zijn er twee functies die aangeroepen kunnen worden.


C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public void Dispose(bool isDisposing)
        {
            if (_alreadyDisposed)
                return;

            if (isDisposing)
            {
                //Er zijn geen managed resources om vrij te geven!
            }

            //vrijgeven van unmanaged resources
            for (int i = 0; i < comObjects.Count; i++)
            {
                if (comObjects[i] != null)
                {
                    Marshal.ReleaseComObject(comObjects[i]);
                    comObjects[i] = null;
                }
            }

            _excelWorkbook = null;
            _excelWorkSheet = null;

            //flag zetten dat hij disposed is
            _alreadyDisposed = true;
        }



Twee functies, eentje met parameters eentje zonder. De eerste wordt aangeroepen door een gebruiker. Dat betekend dat de GC zelf nog niks heeft gedaan en dat alles (dus managed en unmanaged resources) opgeruimt mag worden. Ook geven we aan dat de class niet nog een keer op finalization queue hoeft te komen zodat we niet alles nog een keer proberen te doen.

Als de finalize door het object zelf (of de GC?) wordt aangeroepen hoeft hij geen rekening te houden met de managed code en kan hij rechtstreeks alle unmanaged resources weggooien.

Als je dit voor elke object netjes afhandeld dan word alles uit het geheugen gegooit en kan je netjes de Excel application netjes afsluiten!

Delegate en Threadsafe controls

Door Wiebbe op dinsdag 17 maart 2009 15:24 - Reacties (4)
Categorie: c#, Views: 24.878

Waarschijnlijk ben ik niet de eerste die hier mee te maken krijg en ook zeker niet de laatste. Voor ik begin aan deze post, die online overal ook te vinden is, eerst maar even duidelijk maken dat ik nog geen Microsoft certificaten heb gehaald en dus veel doe met onderzoek.

Anyway, het gaat dus over het aanroepen van controls vanuit een thread. Ik ben hier opgekomen omdat er een tool moest worden geschreven waarbij met meerdere threads achter elkaar database queries konden worden verzonden om zo performance te meten.

Uiteraard kan je dit makkelijk (en gedeeltelijk) op te lossen door een simpele for loop te gebruiken als:


C#:
1
2
3
4
for (int i = 1; i <= (int)numAttempts; i++)
{
     sqlConnector.doQuery(query)
}



En dat werkt uiteraard ook gewoon. Helaas blokkeert dit je complete applicatie tot het over is en is het niet mogenlijk om meerdere verbindingen tegelijkertijd te draaien.

Gelukkig is het mogenlijk om threads te starten zodat je per thread een aantal connecties kan afschieten. Dit simuleert redelijk het gebruik van meerdere gebruikers. Wel allemaal met dezelfde query en van hetzelfde ip adres maar toch.

Het probleem waar ik tegen aanliep is dat het niet mogenlijk is om vanuit de thread een update te geven op de UI. Op zich redelijk logisch, de UI controls zijn gemaakt in de UI thread en bieden daarom geen toegang vanuit de andere threads.

Je kan het in .NET 1.0 wel proberen aan te roepen maar hij gaat toch redelijk de mist in daar. Gelukkig werk ik in Visual Studio 2005 met .NET 2.0 en kan gebruik maken van de Control.Invoke method.

Deze method maakt het mogenlijk om dus een functie aan te roepen van een control waarmee je dan de UI zelf update. Helaas is het verschrikkelijk saai om zelf Invoke te doen. Je krijgt dan voor elke method die je wilt aanroepen iets al dit:


C#:
1
2
3
4
5
public delegate void UpdateStatusDelegate(string statusBericht);

UpdateStatusDelegate del = new UpdateStatusDelegate(frmMainControl.UpdateStatus);
object[] paramList = new object[] { "Update bericht" };
frmMainControl.Invoke(del, paramList);



Het werkt, maar je moet zelf elke keer de delegate maken en mijn "performance" tool moest veel soorten protocollen aan kunnen. Voor elke method die anders was moest ik opnieuw een delegate maken.

Op google kan je hier erg veel over vinden en iedereen heeft er zijn eigen mening over. Gelukkig ben ik redelijk lui en zocht eigenlijk naar een simpele oplossing die goed werkt en weinig tijd kost. Hier kwam ik uit op de volgende site:

SafeInvoke: Making GUI Thread Programming Easier in C# van John Wood. John Wood geeft hetzelfde voorbeeld (hah! gepikt!) maar geeft meteen een fantastisch oplossing. De zogenaamde SafeThreadInvoker static class.

Ondanks dat ik hier weer niks van leer, maakt deze class het mogelijk om dynamisch een delegate te maken die je dan aanroept doormiddel van de invoke method. Hierdoor hoef je amper wat te doen als je een threadsafecall wilt uitvoeren.

Handig, Ja! Slim? Nee? Een oplossing als deze is erg makkelijk, maar toch vind ik het jammer dat ik nu zelf niet bezig ben geweest met reflection en de onderliggende technologie van het .NET framework. Via mijn werkgever is het nog steeds afwachten tot een opleiding van het microsoft ondanks dat ik mijzelf redelijk goed uit de voeten kan maken.

Ik ben me er trouwens wel van bewust dat de Invoke aanroep synchroon is. Mocht hij dus gaan wachten op de thread en de thread op de UI dan krijgen we een mooie deadlock. Ik ben hem nog niet tegen gekomen met +/- 50 threads met meer dan 1000 connecties, maar toch.

Maar wellicht kan iemand anders wat met de threadsafeinvoker! Het spaart tijd en heeft mij sneller aan de tool geholpen!