4

I am writing a small application in Matlab using GUIDE. This application calls on a .Net library. The library connects to a serial device. Using a BackgroundWorker, the library polls the port for new data, and raises an IncomingData event whenever a new packet is received. (I know this because I used a decompiler to view the guts of the library.)

The problem is that the SDK I'm using didn't implement the RunWorkerCompleted method properly. It does not check to see if an exception occurred via the e.Errors property before accessing the e.Result property. This causes a TargetInvocationException to be thrown. This exception is going unhandled and causes Matlab to crash with the following event in the Windows event log. The inner exception is not serialized to the event log, so I don't know what's actually causing the failure.

Application: MATLAB.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.Reflection.TargetInvocationException
Stack:
   at System.ComponentModel.AsyncCompletedEventArgs.RaiseExceptionIfNecessary()
   at System.ComponentModel.RunWorkerCompletedEventArgs.get_Result()
   at TargetInvocationIssueMVCE.BlackBox._backgroundWorker_RunWorkerCompleted(System.Object, System.ComponentModel.RunWorkerCompletedEventArgs)
   at System.ComponentModel.BackgroundWorker.OnRunWorkerCompleted(System.ComponentModel.RunWorkerCompletedEventArgs)
   at System.ComponentModel.BackgroundWorker.AsyncOperationCompleted(System.Object)
   at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(System.Object)
   at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext,  System.Threading.ContextCallback, System.Object, Boolean)
   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

I was able to replicate the behavior of the library that I'm using with the following C# class library. You can think of this BlackBox example below as the library that I cannot change.

using System;
using System.ComponentModel;

namespace TargetInvocationIssueMVCE
{
    public class BlackBox
    {
        private BackgroundWorker _backgroundWorker;

        public event EventHandler<EventArgs> IncomingData;

        public void Connect()
        {
            _backgroundWorker = new BackgroundWorker()
            {
                WorkerSupportsCancellation = true
            };

            _backgroundWorker.DoWork += _backgroundWorker_DoWork;
            _backgroundWorker.RunWorkerCompleted += _backgroundWorker_RunWorkerCompleted;
            _backgroundWorker.RunWorkerAsync();
        }

        private void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // BlackBox should check e.Errors first, but doesn't, so throws a TargetInvocationException that I can't seem to catch, so it crashes Matlab.
            Console.Write(e.Result == null ? "Failure" : "Success");
        }

        private void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            throw new InvalidOperationException("I could be any exception.");

            // the real worker is supposed to raise IncomingData here.
        }
    }
}

I'm calling this library in a Matlab GUIDE GUI like this.

% --- Executes just before Figure1 is made visible.
function Figure1_OpeningFcn(hObject, eventdata, handles, varargin)
% This function has no output args, see OutputFcn.
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
% varargin   command line arguments to Figure1 (see VARARGIN)

% load the library

NET.addAssembly('C:\path\to\TargetInvocationIssueMVCE.dll');
blackbox = TargetInvocationIssueMVCE.BlackBox();
handles.blackbox = blackbox;

addlistener(blackbox, 'IncomingData', @OnIncomingData);

% Choose default command line output for Figure1
handles.output = hObject;

% Update handles structure
guidata(hObject, handles);

% ... some other irrelevant callbacks

% --- button click callback starts .Net BackgroundWorker process
function btnConnect_Callback(hObject, eventdata, handles)
try
    handles.blackbox.Connect();
    set(hObject, 'String', 'Disconnect');

catch ex
    warning(ex.message);
    set(hObject, 'Value', 1);
end

% --- Callback to process incoming data packets
function OnIncomingData(source, arg)
    % It doesn't matter what I put here, the exception is raised before
    % I ever get a packet event and Matlab crashes.

    msgbox('Received Packet');

In .Net, if I really had to, I could set up a catch in my static void Main() method so that I could inspect the inner exception.

try
{
    Application.Run(new Form1());
}
catch (TargetInvocationException exception)
{
    System.Windows.Forms.MessageBox.Show(exception.InnerException.ToString());
}

I tried the same in Matlab by creating a Script to run the Figure, but it still did not catch the issue. It seems to somehow bypass the catch below. Matlab still crashes with the same event log that I get if I just double click Figure1 to run it.

try
    Figure1
catch ex
    warning(ex.message)
end

So, I really don't know where to go from here. I know this is an XY problem. I don't really need to be able to catch this exception, but I do need to be able to inspect the inner exception and I don't what else I can do to get a look at it.


I just found this interesting tidbit.

Callbacks of all kinds are executed in the context of the base workspace, not in the context of the routine that set the Callback function. You can only "catch" the exceptions of statements you call directly, not of functions automatically executed on your behalf through a callback.

If this is true, it would explain why I can't catch the exception when creating the figure from a script. Am I screwed? Does this mean I can't catch this exception?

Community
  • 1
  • 1
RubberDuck
  • 11,933
  • 4
  • 50
  • 95

0 Answers0