2

So I followed the recommended answer shown at How to rethrow the inner exception of a TargetInvocationException without losing the stack trace and ended up with code that looks like this:

// Inside DpmEntrypoint static class.
public static object Invoke(Delegate d, object[] args)
{
    try
    {
        // Invoke directly if not networked.
        if (LocalNode.Singleton == null)
            return d.DynamicInvoke(args);

        // Get the network name of the object and the name of the method.
        string objectName = (d.Target as ITransparent).NetworkName;
        string methodName = d.Method.Name;

        // Get our local node and invoke the method.
        return LocalNode.Singleton.Invoke(objectName, methodName, args);
   }
   catch (Exception ex)
   {
        ex.Rethrow();
        return null;
    }
}

// Inside ExceptionExtensions static class.
public static void Rethrow(this Exception ex)
{
    if (ex is TargetInvocationException)
        ex.InnerException.Rethrow();
    else
    {
        typeof(Exception).GetMethod("PrepForRemoting",
            BindingFlags.NonPublic | BindingFlags.Instance)
            .Invoke(ex, new object[0]);
        throw ex;
    }
}

In this case, a post-processor is run over assemblies after compile time and specified methods are wrapped in a delegate and invoked using the method above. In order to then call the delegate, I have to use Invoke somewhere along the line (even if it's in the reflection used inside Singleton.Invoke).

The Rethrow method above correctly preserves the stack trace like so:

Server stack trace: at Example.ExampleController.NullTest() in C:\Server Storage\Projects\Redpoint\Pivot\Example\ExampleController.cs:line 19 at Example.ExampleWorld.t_Spawned(Object sender, EventArgs e) in C:\Server Storage\Projects\Redpoint\Pivot\Example\ExampleWorld.cs:line 29 at Pivot.Core.Actor.OnSpawned__Distributed0() in C:\Server Storage\Projects\Redpoint\Pivot\Pivot.Core\Actor.cs:line 62

Exception rethrown at [0]: at Process4.Providers.ExceptionExtensions.Rethrow(Exception ex) at Process4.Providers.ExceptionExtensions.Rethrow(Exception ex) at Process4.Providers.DpmEntrypoint.Invoke(Delegate d, Object[] args) at Pivot.Core.Actor.OnSpawned() at Pivot.Core.GameInfo.set_World__Distributed0(WorldInfo value) in C:\Server Storage\Projects\Redpoint\Pivot\Pivot.Core\GameInfo.cs:line 35

Exception rethrown at 1: at Process4.Providers.ExceptionExtensions.Rethrow(Exception ex) at Process4.Providers.ExceptionExtensions.Rethrow(Exception ex) at Process4.Providers.DpmEntrypoint.SetProperty(Delegate d, Object[] args) at Pivot.Core.GameInfo.set_World(WorldInfo value) at Example.ExampleGame.t_Spawned(Object sender, EventArgs e) in C:\Server Storage\Projects\Redpoint\Pivot\Example\ExampleGame.cs:line 25 at Pivot.Core.Actor.OnSpawned__Distributed0() in C:\Server Storage\Projects\Redpoint\Pivot\Pivot.Core\Actor.cs:line 62

Exception rethrown at [2]: at Process4.Providers.ExceptionExtensions.Rethrow(Exception ex) at Process4.Providers.ExceptionExtensions.Rethrow(Exception ex) at Process4.Providers.DpmEntrypoint.Invoke(Delegate d, Object[] args) at Pivot.Core.Actor.OnSpawned() at Pivot.Engine.set_Game(GameInfo value) in C:\Server Storage\Projects\Redpoint\Pivot\Pivot.Core\Engine.cs:line 47 at Example.Program.Main(String[] args) in C:\Server Storage\Projects\Redpoint\Pivot\Example\Program.cs:line 14 at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args) at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args) at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() at System.Threading.ThreadHelper.ThreadStart_Context(Object state) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart()

but the problem I have is that even when the library is compiled in Release mode, Visual Studio still shows the exception source at Rethrow instead of NullTest (where the NullReferenceException is thrown).

Since all methods in an application will be hooked, throwing the TargetInvocationException as it is is useless to the developer; they're more interested in where the original exception occurred in their code.

Without the ability to rethrow the inner exception as it is, it basically makes the entire exception system in .NET when used with the Distributed Processing Library without the developer being aware of what's going on behind the scenes (which the exact opposite goal of the library).

Does anyone know a way of causing Visual Studio to show it at the location it was originally thrown?

EDIT:

I'm thinking of solving the issue using C++/CLI since that gives me access some special functionality as described in the MSDN article Transporting Exceptions Between Threads.

Problem is that those functions don't let me "transport managed exceptions" and hence rethrow_exception throws an SEHException when I try to use it on a managed exception. If anyone knows a way around this issue then it can be solved using C++/CLI (if only there was a way to get the exact pointer of the current exception then the rethrow IL instruction could be used!).

namespace Rethrow {
    [System::Runtime::CompilerServices::Extension]
    public ref class ExceptionExtensions abstract sealed
    {
    public: 
        [System::Runtime::CompilerServices::Extension]
        static void Rethrow(System::Exception^ s)
        {
            std::rethrow_exception(std::copy_exception<System::Exception^>(s));
            throw;
        }
    };
}
Community
  • 1
  • 1
June Rhodes
  • 3,047
  • 4
  • 23
  • 29
  • I'm thinking the only option to solve this problem is to automatically replace the catch handlers in rewritten methods to really execute the code if it's a TargetInvocationException that has an InnerException that matches. EDIT: Hmm, although that /still/ won't get Visual Studio to show the correct source of unhandled exceptions. – June Rhodes Dec 09 '11 at 09:56
  • I recently read CLR via C# by Jeff Richter ( Don't have it at work though - so I can't give you specifics ) but there was a part of where he accumulated exception data into the "Data" field and kept rethrowing this first exception. – Alex Dec 09 '11 at 10:07
  • There are a few places in the .NET framework where what you are trying to do would have been appropriate. Control.Invoke() for example re-throws the inner-most exception. But same problem, you see the plumbing on the stack trace. Hiding frames is completely counter to what exception handling was designed to do. You can't make it work. Also note that rethrowing the inner-most exception is in itself a problem, the invoked method may itself catch and rethrow, passing the original exception as the inner. You'll rethrow the wrong exception. No fix for that either. – Hans Passant Dec 09 '11 at 10:37

3 Answers3

0

Hmmm... Try replace "throw ex" with this construction:

Exception HighelLevelException = new Exception("An inner error occured!", ex);
throw HighelLevelException;
Dmitriy Konovalov
  • 1,777
  • 14
  • 14
  • That doesn't solve the issue that users have to catch a different type of exception than the one that was actually thrown. In fact, that's the same as what's happening now with TargetInvocationException. – June Rhodes Dec 09 '11 at 09:16
0

throw ex; will unwind the stack.

catch (Exception ex)
{
    throw;
    return null;
}

is enough. But remember this is C# syntax.

Community
  • 1
  • 1
ali_bahoo
  • 4,732
  • 6
  • 41
  • 63
0

I managed to solve this issue by modifying the post-processor to invoke the method directly via a matching delegate (since the post-processor knows at runtime method signatures and can generate appropriate matching delegates). When using Invoke() on a delegate it won't wrap any exceptions with TargetInvocationException; that only occurs when dynamically invoking a method.

Unfortunately this solution doesn't actually solve dynamically invoking methods and passing inner exceptions; it just takes advantage of the post-processor that already exists in the build process for this particular library (i.e. this solution doesn't solve the problem for anyone building a library that doesn't do post-processing anyway).

June Rhodes
  • 3,047
  • 4
  • 23
  • 29