Přidat otázku mezi oblíbenéZasílat nové odpovědi e-mailem C#, Excel, uvolňování/zavírání/ukončování souboru

Ahoj, potřeboval bych poradit (vysvětlit) s uzavíráním a ukončováním Excelovského souboru programově vygenerovaného přes C#.
Jde mi o následující: uživateli program vygeneruje/zapíše do dokumentu určité statistiky a nyní dá uživateli na výběr, jestli chce soubor buď uložit na zadané místo nebo zobrazit.
Když zvolí uložení, vyskočí dialog, zadá cestu a jméno a soubor a OK a např. na ploše vznikne soubor statistiky.xls.
Když zvolí zobrazení, rovnou se spustí Excel a uživatel statistiky přímo vidí a už je na něm, co s otevřeným souborem bude dělat.
Teď ale nevím, jak po sobě "správně" uklidit, aby nezůstalo něco otevřené.
Četl jsem v několika článcích, že by se mělo uklidit následovně: Marshal.ReleaseComObject(object);
V parametru je vždy list v souboru, do kterého se zapisovalo nebo workbook, do kterého se zapisovaly listy nebo aplikace, ve které se otevřel workbook.
Co toto uvolnění vůbec znamená?
A jak udělám Marshal.ReleaseComObject(application); v případě zobrazování statistik na obrazovku? Když je Excel stále otevřený, asi to nemůžu zavolat, ne?

Když se statistiky neukládají, ale zobrazují, ukončuji následovně:

workbook = excel.CreateStatsFile();
application.Visible = true;
Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(application);

Když se soubor ukládá na zadané místo:

application.Visible = false;
workbook = excel.CreateStatsFile();
workbook.SaveAs(fileName, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Excel.XlSaveAsAccessMode.xlNoChange, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
workbook.Close();
application.Quit();
Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(application);

Je toto v obou případech správně řešení? Nejspíš není, protože když vyzkouším obě možnosti, zůstane mi ve správci úloh, že Excel je 2x (někdy? 3x) otevřený. Na liště nic nemám, takže je někde něco špatně ukončené.

Díky za pomoc.

Předmět Autor Datum
Vždy, keď dostaneš referenciu na COM objekt, tak sa zvýši počet referencií na ten COM objekt. Po uko…
los 10.04.2013 19:42
los
Ok a nejdříve se zeptám: je nějaká obecná definice, co je to vlastně ten COM objekt? To je applicati…
freez2 10.04.2013 20:20
freez2
COM objekt je objekt, ktorý bol vytvorený pomocou COM. Keď získaš ďalší objekt cez nejaký COM objekt…
los 10.04.2013 21:07
los
Tak jsem se v tom nějak vrtal a četl a závěrem je tedy, že musím pro naprosto každou získanou refere…
freez2 12.04.2013 22:52
freez2
Volanie sheet.Cells[row, column] vráti COM objekt typu Range, ktorý treba tiež uvoľniť. Na prácu s…
los 12.04.2013 23:13
los
Tys mi tedy dal s těmi názvy...Tak teď už snad vím, co je early a late binding, co je wrapper třída… poslední
freez2 12.04.2013 23:53
freez2

Vždy, keď dostaneš referenciu na COM objekt, tak sa zvýši počet referencií na ten COM objekt. Po ukončení práce s COM objektom by si mal tento počet referencií znížiť volaním metódy Marshal.ReleaseComObject (silno odporúčam prečítať časť Remarks). Keď počet referencií dosiahne nulu, COM objekt sa uvoľní z pamäte. Takémuto spôsobu sa vo všeobecnosti hovorí reference counting.

A teraz tá zábavnejšia časť. Ak chceš napríklad otvoriť zošit, tak by sa ti mohlo zdať ako dobrý nápad robiť to takto:

var workbook = application.Workbooks.Open(...)

A to je zle. Je to mimochodom dosť častá chyba ľudí, ktorí s tým nemajú skúsenosti. Pretože keď si získal referenciu na COM objekt Workbooks, tak sa zväčšil aj počet referencií na ten COM objekt. Ale ten objekt si si nikde neuložil, takže mu nemôžeš znížiť počet referencií volaním metódy Marshal.ReleaseComObject. Objekt sa uvoľní až po zbehnutí garbage collectora (GC) pri uvoľňovaní pamäte. A keďže samotná referencia (RCW) je z pohľadu GC len pár bajtov (na rozdiel od samotného COM objektu), tak to môže byť dosť nepríjemné.

Správne sa to robí tak, že nikdy nepoužívaš dve bodky v jednom príkaze. Každý jeden prístup k nejakej vlastnosti, ktorá nie je primitívneho typu, musí byť priradený do nejakej premennej, ktorú neskôr uvoľníš pomocou metódy Marshal.ReleaseComObject. Ideálne je napísať si na to nejaký wrapper, ktorý implementuje rozhranie IDisposable, a používať ho v bloku using. Plus si pozri ešte, čo je to late binding, a vyhneš sa aj problémom s rôznymi verziami Excelu.

Ak aplikáciu nechceš zatvoriť, tak nevolaj metódu Quit, ale určite použi Marshal.ReleaseComObject.

Ok a nejdříve se zeptám: je nějaká obecná definice, co je to vlastně ten COM objekt? To je application, workbook a každý sheet co do workbooku přidám a nadále používám? Co dalšího?
Soubor odnikud neotevírám, generuji ho od začátku přímo z programu.
Ááno, to s těmi tečkami jsem četl, ale až teď jsem pochopil, jak to bylo myšleno.

Odkazy zkusím nastudovat.

Tak jsem se v tom nějak vrtal a četl a závěrem je tedy, že musím pro naprosto každou získanou referenci na nějaký COM objekt Marshalem po následné práci snížit počet referencí na ten objekt. Jelikož pracuji s Excelem, tak "jediné" COM objekty, které mne zajímají, jsou Application, Workbooks, Workbook, Worksheet, Sheets a nyní jsem zjistil, že i Range (překvapivě).
Je tento postup tedy správný?:

Worksheet sheet;
Sheets SHEETS;
SHEETS = this.workbook.Worksheets; //rozdělit na SHEETS, protože potom nemůžu udělat: sheet = this.workbook.Worksheets.Add() - kvůli tečkám - ztratím referenci
sheet = SHEETS.Add();
sheet.Name = "September";
Marshal.ReleaseComObject(sheet);
sheet = SHEETS.Add();
sheet.Name = "August";
Marshal.ReleaseComObject(sheet);
sheet = SHEETS.Add();
sheet.Name = "July";
Marshal.ReleaseComObject(sheet);
Marshal.ReleaseComObject(SHEETS);//workbook uvolním až v pozdějším kódu, ten si nechávám stále otevřený

Nebo následovně - ŠPATNĚ:

sheet.Range[sheet.Cells[topIndent + 1, leftIndent + 3], sheet.Cells[topIndent + 1, leftIndent + 4]].Merge();

a SPRÁVNĚ:

Range TEST;
TEST = sheet.Range[sheet.Cells[topIndent + 1, leftIndent + 3], sheet.Cells[topIndent + 1, leftIndent + 4]];
TEST.Merge();
Marshal.ReleaseComObject(TEST);

Dá se říci, že když takto opravím celý kód, bude vše v pořádku?

//EDIT
Ještě mě napadlo, co tedy na každý objekt, s kterým jsem pracoval, ke konci práce zavolat Marshal.FinalReleaseComObject Method?

Volanie sheet.Cells[row, column] vráti COM objekt typu Range, ktorý treba tiež uvoľniť.

Na prácu s COM objektami mám napísaný takú pomocnú wrapper triedu, s ktorou sa pracuje takýmto spôsobom:

using (var application = new ComObject("Excel.Application"))
using (var workbooks = application.GetComProperty("Workbooks"))
using (var workbook = workbooks.InvokeComMethod("Open", ...))
using (var sheets = workbook.GetComProperty("Sheets"))
using (var sheet = sheets.GetComProperty("Item", sheetName))
{
  ...
}

Na tvojom mieste by som si na to napísal tiež takú helper triedu. Ja som používal late binding. Ak chceš používať early binding, tak tvoja helper trieda bude vyzerať trochu inak, ale princíp zostane rovnaký.

FinalReleaseComObject spraví v podstate to, že volá dokola ReleaseComObject, až pokým sa neuvoľnia všetky referencie. Osobne by som používal iba ten ReleaseComObject, pretože ak pracuješ s tým objektom ešte niekde inde, tak by si ho takto mohol omylom predčasne uvoľniť.

Tys mi tedy dal s těmi názvy...Tak teď už snad vím, co je early a late binding, co je wrapper třída a jak se používá konstrukce using, ale pořád nevidím význam té wrapper třídy... nebo možná vidím, ale netuším, jak implementovat.
Ohledně toho FinalReleaseComObject... Pokud získám referenci na nějaký sheet např. pomocí sheet = SHEETS.Add(); nebo sheet = SHEETS.get_Item(12); a poté na sheet zavolám FinalReleaseComObject, objekt se uvolní. Pokud ale poté znovu zavolam sheet = SHEETS.get_Item(12);, objekt už nezískám?

Zpět do poradny Odpovědět na původní otázku Nahoru