0

I'm not super handy at managing COM Objects. I've done a fair bit of reading, but I can't quite wrap my head around what happens when you have two references pointed at a single COM Object, and you release one of them. Consider the following [semi-]hypothetical:

I've created a basic wrapper class for Excel Workbooks in C#:

using ExcelInterop = Microsoft.Office.Interop.Excel;
...

public class WorkbookWrapper : IDisposable
{
    protected internal ExcelInterop.Workbook _wb;

    public WorkbookWrapper(ExcelInterop.Workbook workbook)
    {
        _wb = workbook;
    }

    public WorkbookWrapper(WorkbookWrapper workbookWrapper)
    {
        _wb = workbookWrapper._wb;
    }

    #region IDisposable

    // basic Dispose function which releases _wb COM Object (omitted to shorten post length)

    #endregion

    ...
}

For one particular project I'm working on, I've found it necessary to attach some additional information to WorkbookWrapper objects, so I've extended it:

class LinkingWorkbookWrapper : WorkbookWrapper
{
    internal string workbookGUID = null;

    internal LinkingWorkbookWrapper(WorkbookWrapper workbookWrapper, string GUID) : base(workbook)
    {
        workbookGUID = GUID;
    }

    ...
}

This particular class uses the WorkbookWrapper(WorkbookWrapper) constructor to store a new reference to the Interop Workbook, this is suddenly problematic when using functions like the following:

public static void ProcessAllCharts(WorkbookWrapper wbw)
{
    LinkingWorkbookWrapper lwbw = null;
    if(wbw is LinkingWorkbookWrapper)
        lwbw = (LinkingWorkbookWrapper)wbw;
    else
        lwbw = new LinkingWorkbookWrapper(wbw, GenerateGUID());

    //... do stuff ...
}

For now let's say that the function must accept normal WorkbookWrappers. When is the appropriate time to dispose of lwbw? If I do so at the end of ProcessAllCharts I may separate the COM Object referenced in the original wbw from its RCW; on the other hand, if I do not dispose of lwbw, I run the risk of leaving a reference to the COM Object floating in the ether.

Sandy Gifford
  • 7,219
  • 3
  • 35
  • 65
  • You could use a destructor. – IS4 Oct 14 '14 at 15:48
  • @IllidanS4 Since the destructor (if I'm understanding you correctly) would be on the wrapper, wouldn't that have roughly the same effect? The wrapper goes out of scope at the end of `ProcessAllCharts`, at which point it becomes eligible for garbage collection even if members of it are still referenced from other places. Again, I'm not very confident about this sort of thing, so I could be totally wrong, but that (so far) is my understanding. – Sandy Gifford Oct 14 '14 at 15:55
  • 1
    Sigh, talking programmers out of this is a herculean task that never ends. Read [this post](http://stackoverflow.com/a/17131389/17034) to find out why you *think* you need to do this. Don't do it, you'll never get it right. – Hans Passant Oct 14 '14 at 16:00
  • 1
    Alright, so I think it would be the best to have only one sealed wrapper class that keeps track of one COM object. Then don't call Dispose at the and of ProcessAllCharts at all, to ensure the object could still be used outside the method scope. – IS4 Oct 14 '14 at 16:01
  • @IllidanS4 I was a little worried the answer to my example would be "you can't really do it that way". =( – Sandy Gifford Oct 14 '14 at 16:04
  • @HansPassant my head just exploded a little bit. I've read plenty of "don't manually release" posts, but that one just sort of blew away everything I thought I knew. It doesn't quite jive with the behavior of the larger program I took this snip-it from, but it certainly does answer the core question I've asked here. Do you think you could provide that link (and a short summary of the post) in an answer so I can select it? – Sandy Gifford Oct 14 '14 at 16:09
  • @HansPassant I've answered the question linking to your original post. If you ever find yourself by here again and want to grab the 15 rep for yourself you're more than welcome to it. – Sandy Gifford Oct 31 '14 at 22:13

1 Answers1

0

This one really belongs to Hans Passant who casually dropped by the comments of the original question but never posted an actual answer.

In a lengthy answer to another question, he details why manually releasing is a fool's errand at best. He also goes into a fair bit of detail as to why we actually get these ghost instances hanging around, and how to rid ourselves of them. I really encourage anyone having this problem to go read the entire post (it's very informative) but if you're in a pinch and need to now, let me summarize it:

enter image description here

Yup, that's it. That's all there is to it. The debugger is a bit of a nag and seems to like convincing the garbage collector that some things are still important when they're really not. I know, you don't think this is your problem, you think this is craziness. I felt the same way, but give it a try; fixed my problem in a jiffy.

Thanks again, Hans Passant, you stoic C-Sharpian you.

Community
  • 1
  • 1
Sandy Gifford
  • 7,219
  • 3
  • 35
  • 65
  • You got half of the answer. The other half is Tools + Options, Debugging, General, untick the "Suppress JIT optimization" option. Now it runs the way it does on your user's machine. Pretty unusual to have it off by accident btw. – Hans Passant Oct 31 '14 at 22:21
  • @HansPassant Yeah, I saw that but didn't even need to go that far. Somehow just this much worked pretty amazingly well for me. I'll add it to the answer though. Thanks, again! – Sandy Gifford Nov 01 '14 at 13:57