1

This is a new attempt to pose a version of a question asked less successfully this morning.

Consider the following program, which we'll run once inside Visual Studio 2010 and once more by double-clicking the executable directly

namespace ConsoleApplication3
{
    delegate void myFoo(int i, string s);

    class Program
    {
        static void Main(string[] args)
        {
            Foo(1, "hello");
            Delegate Food = (myFoo)Foo;
            Food.DynamicInvoke(new object[] { 2, null });
        }

        static void Foo(int i, string s)
        {
            Console.WriteLine("If the next line triggers an exception, the stack will be unwound up to the .Invoke");
            Console.WriteLine("i=" + i + ", s.Length = " + s.Length);
        }
    }
}

When the exception in Foo triggers while running VS, the debugger shows the stack correctly and shows that the problem occured on the second WriteLine in Foo.

But when the exception occurs while running the executable directly, one gets a little popup window from the CLR indicating that the program threw an unhandled exception. Click debug and select the VS debugger. In this case, the stack unwinds up to the point of the most recent .DynamicInvoke and when you attach with the debugger, the stack context that existed at the time of the exception has been partially lost.

It does exist, in a limited form, within the "inner exception" portion of the exception event. You click to expand the associated information and find the line number where the problem occured. But obviously local variables and other context will be gone.

If one tries the same thing but without the .DynamicInvoke (for example, call Foo(1, null) on line 1 of Main), still by double-clicking the .exe file, we DO get the correct line number when the debugger attaches. Similarly if the application is launched by clicking on the .exe, but then the debugger is attached before the exception gets thrown.

Does anyone know how an application using dynamic reflection/invocation could avoid this problem? In my intended use case, in a system the name of which I won't mention here, I cannot predict the type signature of the object that will be used in the .DynamicInvoke, or even the number of arguments that will be employed, hence static typing or even generics aren't a way out of this.

My question is this: does anyone know why we get such different behaviors when running directly from the debugger versus when attaching to the program after the exception is thrown?

abatishchev
  • 92,232
  • 78
  • 284
  • 421
Ken Birman
  • 1,021
  • 8
  • 22
  • 3
    In your first scenario, where you're running the program in the debugger, have you got the debugger configured to break on the "first chance" or the "second chance" exception? That is, the debugger can be configured to break when the exception is *thrown* -- first chance -- or to break when an exception *has been determined by the runtime to be handled by no catch block* -- second chance. The debugger behaviour can be different in these two scenarios, and that *might* explain the difference you're seeing. (I've not actually tried to reproduce your problem; this is just an educated guess.) – Eric Lippert Mar 15 '12 at 18:51
  • 1
    Look at the checkboxes in the "Thrown" column in the "Debug/Exceptions..." window. The checked ones will give you "first chance" behavior. – kvb Mar 15 '12 at 19:06
  • @kvp: Thanks. Tried this but it has no effect. I'm guessing that the bad behavior occurs in the .exe when run directly from the CLR and isn't controlled by debugger parameters (e.g. by the time VS gets to look at the stack, it already has been unwound). My theory is that VS is getting first-chance behavior in the first place when I run under VS, and isn't getting that chance if I run under .exe, hence in the latter case the .Invoke ends up intercepting and then rethrowing it, explaining why the exception is shown at the Invoke line and not the thing that really triggered it. A guess. – Ken Birman Mar 15 '12 at 19:23
  • @KenBirman The "bad" behavior seems correct to me: `DynamicInvoke` catches your `NullReferenceException` and rethrows it as a `TargetInvocationException`. The `TargetInvocationException` isn't thrown in your code, so the debugger can't jump to your code, and you only get the chance to automatically attach the debugger on unhandled exceptions, but your `NullReferenceException` is handled (by rethrowing it as a `TargetInvocationException`). One way around this would be to not use `.DynamicInvoke`: the result of an expression tree's `.Compile()` should behave as you want, not catching anything. –  Mar 17 '12 at 13:00
  • @hvd I've tried DynamicInvoke with the identical outcome in the style of code shown above. But I haven't tried calling .Compile first and will look at that API. If that does it, I'll be a happy camper! Thanks... – Ken Birman Mar 17 '12 at 13:07
  • 1
    @KenBirman Actually, thinking about it, what's simpler and also works is to use `.Invoke` dynamically: it behaves differently from `.DynamicInvoke`. `((dynamic)Food).Invoke(2, null);` :) –  Mar 17 '12 at 13:14
  • My mistake: ((dynamic)Food).Invoke(2, null); does the trick! Awesome -- thanks very much for helping! – Ken Birman Mar 17 '12 at 13:24
  • Glad to help, but now I just wonder, based on your extended description, whether you can actually apply this to your real function. If you can, great, but if not, could you update your question with details about how you wish to call your real function? –  Mar 17 '12 at 13:35
  • Just for completeness, the situation arises in isis2 (isis2.codeplex.com), where users register event handlers for multicasts, as in myGroup.Handlers[UPDATE] += (signature)delegate(int i, double d, string s) { ... code to execute when an UPDATE is done ... }; Later they multicast as in myGroup.Send(UPDATE, 1, 2.0, "fred"); (Or Query, or run Paxos... many options). My issue was that once I figure out which handler to upcall to, I need to invoke the delegate obtained when they first registered the handler. But if their code then throws an exception, it looked like my code was throwing it! – Ken Birman Mar 17 '12 at 14:53

2 Answers2

2

As per the comments, whether you see the NullReferenceException as unhandled depends on whether it's handled. Here are some ways to call Foo, the first three will leave the exception as unhandled, the last two will handle the NullReferenceException by wrapping it, and throwing a new exception.

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace ConsoleApplication3
{
    delegate void myFoo(int i, string s);

    internal class Program
    {
        private static void Main(string[] args)
        {
            Foo(1, "hello");

            // From a delegate
            try
            {
                Delegate Food = (myFoo)Foo;
                ((dynamic)Food).Invoke(2, null);
            }
            catch (NullReferenceException ex)
            { Console.WriteLine("Caught NullReferenceException at " + ex.StackTrace); }

            MethodInfo Foom = typeof(Program).GetMethod("Foo", BindingFlags.Static | BindingFlags.NonPublic);

            // From a MethodInfo, obtaining a delegate from it
            try
            {
                Delegate Food = Delegate.CreateDelegate(typeof(Action<,>).MakeGenericType(Foom.GetParameters().Select(p => p.ParameterType).ToArray()), Foom);
                ((dynamic)Food).Invoke(2, null);
            }
            catch (NullReferenceException ex)
            { Console.WriteLine("Caught NullReferenceException at " + ex.StackTrace); }

            // From a MethodInfo, creating a plain Action
            try
            {
                Expression.Lambda<Action>(
                    Expression.Call(
                        Foom,
                        Expression.Constant(2),
                        Expression.Constant(null, typeof(string)))).Compile()();
            }
            catch (NullReferenceException ex)
            { Console.WriteLine("Caught NullReferenceException at " + ex.StackTrace); }

            // MethodBase.Invoke, exception gets wrapped
            try
            {
                Foom.Invoke(null, new object[] { 2, null });
            }
            catch (NullReferenceException)
            { Console.WriteLine("Won't catch NullReferenceException"); }
            catch (TargetInvocationException)
            { Console.WriteLine("Bad!"); }

            // DynamicInvoke, exception gets wrapped
            try
            {
                Delegate Food = (myFoo)Foo;
                Food.DynamicInvoke(2, null);
            }
            catch (NullReferenceException)
            { Console.WriteLine("Won't catch NullReferenceException"); }
            catch (TargetInvocationException)
            { Console.WriteLine("Bad!"); }
        }

        private static void Foo(int i, string s)
        {
            Console.WriteLine("i=" + i + ", s.Length = " + s.Length);
        }
    }
}
  • 1
    you rock. Thanks again! I've learned a lot about the underlying model here; this will pay off in many ways (plus I can fix the Isis2 calling scheme now so that my users will realize that their code, not mine, was throwing exceptions!) – Ken Birman Mar 17 '12 at 14:33
1

Actually answered by @hvd:

((dynamic)Food).Invoke(2, null);

solves my problem in one line of code. Thanks!

abatishchev
  • 92,232
  • 78
  • 284
  • 421
Ken Birman
  • 1,021
  • 8
  • 22
  • So just from curiosity: what's the take-away here? What is the cast to (dynamic) really doing? – Ken Birman Mar 17 '12 at 13:34
  • Also: would it work with a methodInfo? E.g. if I have a methodInfo mi and want to code mi.Invoke(obj, args) is there a way to use dynamic that would again avoid the rethrown TargetInvocationException if the called method throws an exception? – Ken Birman Mar 17 '12 at 13:55
  • 1
    The `dynamic` causes the `.Invoke` method to be late-bound. A delegate has a `.Invoke` method, so that, given for example a `Func x`, `x.Invoke` is a method accepting and returning an `int`, which calls the function. What `dynamic` does here is force looking for that available `.Invoke` method, and call that. It does it slightly differently from `.DynamicInvoke` in that once the method to call is found, it directly calls that method. It doesn't wrap it in any `try`/`catch` blocks, it just calls it and whatever happens happens. –  Mar 17 '12 at 13:58
  • 1
    It wouldn't work if you try it with `MethodInfo.Invoke`; if that catches and rethrows exceptions, it doesn't matter how you call it, it'll catch and rethrow the exception. For that, I'll again suggest building an expression tree or something similar; I'll try to come up with some working alternatives based on your original question. –  Mar 17 '12 at 14:01
  • @hvd I think I can work from this. Added an explanation of why I need this in Isis2 up above, where you asked what I'm really trying to do and why this arises. ken@cs.cornell.edu to chat, if you want to follow up. But with this help from you I'm probably in good shape now -- I'll figure out how to handle the two or three cases in which this arises in Isis2. My goal, fundamentally, was to make sure that if an Isis2 application is running and crashes, one can just attach a debugger and see why it died. With the CLR rethrowing the exception, it always looked as if the thing died in .Invoke. – Ken Birman Mar 17 '12 at 14:55