'Why are some Xamarin.Mac exceptions not caught despite registering handlers
I've read several other threads regarding exception handling in Xamarin.Mac and it generally comes down to either missing handlers or registration of handlers in the wrong order (e.g., after calling NSApplication.Main(args);).
However I think I've set it up correctly in Main.cs, as seen in the code below, however while most of the exceptions get caught, sometimes the app just crashes with no log file.
I read somewhere that SIGABRT exceptions are difficult to catch - I'm not sure exactly what this means, it either can be caught or it can't...
Question: Is there something I'm missing from the code below that is causing exceptions to go uncaught and if not, what else can be done to catch all exceptions?
Code:
static class MainClass
{
private static AppDelegate _appDelegate;
static void Main(string[] args)
{
NSApplication.Init();
_appDelegate = new AppDelegate();
NSApplication.SharedApplication.Delegate = _appDelegate;
Runtime.MarshalManagedException += handleManagedException;
Runtime.MarshalObjectiveCException += handleObjectiveCException;
AppDomain.CurrentDomain.UnhandledException += handleUnhandledException;
NSApplication.Main(args);
}
private static void handleManagedException(object sender, MarshalManagedExceptionEventArgs e)
{
string trace = "Marshalled managed exception trace\n============================\n\n";
trace += e.Exception.StackTrace;
logExceptionTrace(trace);
}
private static void handleObjectiveCException(object sender, MarshalObjectiveCExceptionEventArgs e)
{
string trace = "Marshalled ObjC exception trace\n============================\n\n";
string[] trace_symbols = e.Exception.CallStackSymbols;
for (int i = 0; i < trace_symbols.Length; i++)
trace += trace_symbols[i] + "\n";
logExceptionTrace(trace);
}
private static void handleUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
string trace = "Unhandled exception trace\n============================\n\n";
trace += e.ExceptionObject.ToString();
logExceptionTrace(trace);
}
private static void logExceptionTrace(string trace)
{
var documents_dir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var log_dir_path = Path.Combine(documents_dir, "Documents/my-app/logs/crashes");
Directory.CreateDirectory(log_dir_path);
var now_str = DateTime.Now.ToString("yyyy_MM_dd_HH_mm_ss");
var log_filename = Path.Combine(log_dir_path, "crash_" + now_str + ".log");
File.WriteAllText(log_filename, trace);
_appDelegate.SaveRuntimeLogs();
}
}
Solution 1:[1]
As noted in the comments, the key is to not have an array of type T. You want an array of something like optional<T>, i.e. a type which can hold a T or hold nothing. But in this case, the container knows which items are populated, so the optional<T>'s internal knowledge of whether it holds a T is unnecessary.
For example, the following optional-like type should work:
template <typename T>
union storage
{
char no_value_ = {};
T value_;
constexpr void assign(T&& obj)
noexcept(std::is_nothrow_move_constructible_v<T>)
{
this->value_ = std::move(obj);
}
constexpr void assign(const T& obj)
noexcept(std::is_nothrow_copy_constructible_v<T>)
{
this->value_ = obj;
}
constexpr void reset() noexcept
{
this->value_.T::~T();
this->no_value_ = {};
}
};
But this type itself is difficult to use, because it doesn't know whether it holds a value.
So, when you use it in a container, you must be particularly careful:
template <typename T, std::size_t Capacity>
class stack
{
public:
constexpr stack() noexcept = default;
constexpr stack(const stack& other) noexcept
requires std::is_copy_constructible_v<storage<T>>
= default;
constexpr stack(const stack& other)
noexcept(std::is_nothrow_copy_constructible_v<T>)
: size_{other.size_}
{
for (std::size_t i = 0; i < size_; ++i)
data_[i].assign(other.data_[i].value_);
}
constexpr stack(stack&& other) noexcept
requires std::is_move_constructible_v<storage<T>>
= default;
constexpr stack(stack&& other)
noexcept(std::is_nothrow_move_constructible_v<T>)
: size_{std::exchange(other.size_, 0)}
{
for (std::size_t i = 0; i < size_; ++i)
{
data_[i].assign(std::move(other.data_[i].value_));
other.data_[i].reset();
}
}
constexpr ~stack()
requires std::is_destructible_v<storage<T>>
= default;
constexpr ~stack()
{
for (std::size_t i = 0; i < size_; ++i)
data_[i].reset();
}
constexpr void push(T&& obj)
noexcept(std::is_nothrow_move_constructible_v<T>)
{
assert(size_ < Capacity);
data_[size_++].assign(std::move(obj));
}
constexpr void push(const T& obj)
noexcept(std::is_nothrow_copy_constructible_v<T>)
{
assert(size_ < Capacity);
data_[size_++].assign(obj);
}
constexpr T& top() noexcept
{
assert(size_ > 0);
return data_[size_-1].value_;
}
constexpr const T& top() const noexcept
{
assert(size_ > 0);
return data_[size_-1].value_;
}
constexpr void pop()
{
assert(size_ > 0);
data_[size_--].reset();
}
private:
std::size_t size_ = 0;
storage<T> data_[Capacity];
};
See a full example.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Jeff Garrett |
