3
votes

Morning all,

Bit of a language theory question here... I've found some references online suggesting that exception handling and delegates in C# have some different behaviour in some cases but I cannot find any concrete documentation on the matter.

We recently had some big problems with exceptions inside delegates for a Microsoft Excel addin causing a hard-crash in the MSVC runtime. Removing delegates solved this but I'm now curious to find out the gory details.

As a terse example of the core code:

Delegate del; // initialized elsewhere
try
{
    del.DynamicInvoke();
}
catch(Exception e)
{
    /* Parsing of exception to generate user-friendly message here */
}

The above construct allowed a centralized form of error handling and from a pure code point of view was clean and concise. Each publicly exposed function was declared as a delegate and executed via the above fragment.

In a simple console app, throwing an exception from the delegate or just a plain unexpected error (e.g. "accidentally" calling ToString() on a null pointer) works as expected and the error is handled as desired.

Throw in MS Excel and you get hard-crashes. Stepping through the code will show where the error occurs but no stack unwinding appears to take place before everything comes down in a big fireball of destruction.

My hypothesis is that COM, hosting the .NET runtime (and hence our code) is doing something different to normal .NET code execution. This kills the end-point and Excel doesn't know this, which in turn tries to access the end-point via COM only to find it has somehow disappeared and Excel craps out in return.

This only happens with the combination of Excel+COM+delegates, but I don't honestly know which is the more influential in this behaviour... any thoughts?

4

4 Answers

1
votes

I think what you might find here is that you are not releasing the MS Excel COM objects you are implicitly creating when an exception is thrown. In my experience MS Office apps are very sensitive to their resources not being released (though most of my experience is with Outlook).

I would tend NOT to try to handle COM-based exceptions in this way if at all possible. If you want centalized logging, look at Application.ThreadException (for WinForms app) and AppDomain.CurrentDomain.UnhandledException instead.

Inside of your functions, you may want to call Marshal.ReleaseComObject() in your methods' finally {} blocks in order to make sure Excel doesn't get hosed when your exceptions are thrown.

0
votes

Side note: The whole idea of exception catching is not to "swallow" them and display the error message, but to recover, if possible from the situation, as well as to cleanup resources.

Better use try/catch wherever you need (i.e. around the code which may throw), and then call a common function to display/log the problem.

Also, if you use different threads to access Excel COM objects a lot of strange things can happen.

For proper answer to your question more information is needed:

  1. Does it happen always?
  2. If not, when you see the problem is it a managed exception, or one thrown from an Excel object?
  3. Can you create a simple method, which just throws an exception( throw new ApplicationException();), and see if you still have the problem?

Cheers

0
votes

One reason i feel is due to the fact that delegate is invoking the actual code which is throwing exception in another thread. so the exception in another thread which was called async would not be catch in the current thread as the current thread function would mostly exit before the async invoke executes.

0
votes

Apologies for the late reply - busy week!

The whole idea of exception catching is not to "swallow" them and display the error message, but to recover, if possible from the situation, as well as to cleanup resources.

Yup, that's the idea but in practice it doesn't always work out that cleanly. The code in question here is essentially a middleman and often can't know if an error will occur and if one happens it can't know what the user was actually trying to do and thus automagically recover.

Does it happen always?

Yes, if it is an unexpected exception and it always appears as a .NET one originating within the C#/.NET code

create a simple method, which just throws an exception( throw new ApplicationException();), and see if you still have the problem?

No, throwing our own exceptions works fine every time. It's only if the runtime itself generates the exception that we get hard crashes.

you are not releasing the MS Excel COM objects you are implicitly creating when an exception is thrown

This sounds plausible. A VBA layer passes data from Excel over COM and into .NET, so it could well be doing something funky with resource ownership.

If you want centalized logging, look at Application.ThreadException (for WinForms app) and AppDomain.CurrentDomain.UnhandledException instead.

I didn't look into the former, but the latter would never get called. One of my first attempts was to catch it at the AppDomain level assuming that, for whatever reason, a delegate-based exception was slipping through the net.

you may want to call Marshal.ReleaseComObject()

Interesting, I wasn't aware of that function. We've mostly ignored any explicit COM code and gone with the bare minimum and hope (!) that the .NET CLR does its magic...

I've worked around it now by removing the delegates and everything seems happy. Just have to remember to be careful (or outright avoid) delegates and exceptions in future Office+VBA+COM+CLR code :-)