4

Hi this is an exmaple of how C# language handles Com interop resource management. orginal source :

Excel.Application app = null;
Excel.Workbooks books = null;
Excel.Workbook book = null;
Excel.Sheets sheets = null;
Excel.Worksheet sheet = null;
Excel.Range range = null;

try
{
    app = new Excel.Application();
    books = app.Workbooks;
    book = books.Add();
    sheets = book.Sheets;
    sheet = sheets.Add();
    range = sheet.Range["A1"];
    range.Value = "Lorem Ipsum";
    book.SaveAs(@"C:\Temp\ExcelBook" + DateTime.Now.Millisecond + ".xlsx");
    book.Close();
    app.Quit();
}
finally
{
    if (range != null) Marshal.ReleaseComObject(range);
    if (sheet != null) Marshal.ReleaseComObject(sheet);
    if (sheets != null) Marshal.ReleaseComObject(sheets);
    if (book != null) Marshal.ReleaseComObject(book);
    if (books != null) Marshal.ReleaseComObject(books);
    if (app != null) Marshal.ReleaseComObject(app);
} 

Personally i think the code above is reasonable and necessary. But it is not functional or F# way. I ended up defining all these com variables at different levels of nested try... finally and try...with and because the variable has to be defined before try blocks the clean up codes exist in both the finally and with block. It is very messy.

how can i implement the same thing in F# properly? Its a little ironic there are lots of examples on the internet explaining how to use F# with interop as way of demonstrating the F# power. However none of them convers how to manage the com resource cleanup.

any advice on good pattern is appreicated.

casbby
  • 896
  • 7
  • 20
  • 3
    Very briefly since I'm on my phone: use IDisposable objects (or wrappers if Com doesn't implement IDisposable), and use the F# `use` expression. It's like `let` except that it calls Dispose() when the object goes out of scope. – rmunn Aug 18 '16 at 01:40

1 Answers1

6

You could make a computation expression that calls each step in a try/catch and releases in a finally when done. We can create a builder with plugin functions for the create/finalize so we can see what's going on.

type FinalizationBuilder(oncreate, onfinal) =
  member __.Bind(m, f) = 
    oncreate(box m)
    try
      try
        f m
      with ex ->
        Choice2Of2 ex.Message
    finally
      onfinal(box m)
  member __.Return(m) = Choice1Of2 m
  member __.Zero() = Choice1Of2()

Then you want a COM workflow that just releases the COM component when it's finished.

let com = new FinalizationBuilder(ignore, System.Runtime.InteropServices.Marshal.ReleaseComObject >> ignore)

You use it like this:

[<EntryPoint>]
let main _ = 
  com { 
    let! app = new Excel.Application()
    let! books = app.Workbooks
    let! book = books.Add()
    // ...
    app.Quit()
  } |> ignore
  0

I don't have excel installed, but I can simulate it with exceptions and printfns.

let demo = new FinalizationBuilder(printfn "Created %A", printfn "Released %A")

[<EntryPoint>]
let main _ = 
  demo { 
    let! x = 1
    let! y = 2
    let! z = 3
    return x + y + z
  } |> printfn "Result: %A"
  0

// Created 1
// Created 2
// Created 3
// Released 3
// Released 2
// Released 1
// Result: Choice1Of2 6

Or with exception:

[<EntryPoint>]
let main _ = 
  demo { 
    let! x = 1
    let! y = 2
    let! z = failwith "boom"
    return x + y + z
  } |> printfn "Result: %A"
  0

// Created 1
// Created 2
// Released 2
// Released 1
// Result: Choice2Of2 "boom"

All that said, it looks like none of this is necessary anyway. A simple GC.Collect(); GC.WaitForPendingFinalizers() can solve the issue without requiring any of this: https://stackoverflow.com/a/25135685/171121

Community
  • 1
  • 1
Dax Fohl
  • 10,654
  • 6
  • 46
  • 90
  • Hi Dax, i cant test the code right now but can I ask the let! z = failwith "boom" code failed during the creation of z not execution of x + y + z. That means if the program will use the interop excel object to do something, every single line of the logic will have to be written using let binding right? if there is a exception happens in x + y + z and it is not wrapped in let binding the error handle and release logic wont apply ? – casbby Aug 18 '16 at 04:52
  • 1
    @casbby nope, the computation expression handles any exception that occurs anywhere within it and applies the release logic. So if you change the first example to `return x + y + z + (failwith "boom")`, it'll release everything it's created so far (x, y, z) and return `Choice2Of2 "boom"`. – Dax Fohl Aug 18 '16 at 05:03
  • Indeed! It effectively creates a nested try ..with ...finally for each level of let! saving me from defining them manually. thank you very much for such a quick response. one more question. i dont fully understand the zero(). what is the purpose of it in general and in this instance? your code will work regardless if zero() is specified. – casbby Aug 18 '16 at 05:36
  • 2
    @casbby It's necessary if there's no `return` in the expression. Such as in the first example where the expression ends with `app.Quit()` and no `return`. – Dax Fohl Aug 18 '16 at 05:38