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;
}
};
}