如何避免C++调用C#函数触发Access Violation时崩溃
你的核心问题是Mono托管代码触发的Access Violation(AV)直接导致C++宿主崩溃,试过SEH和信号处理都没效果。下面优先给Windows平台的可行方案,再补充Linux的思路。
Windows 平台解决方案
1. 托管侧强制捕获 + 原生层检查异常
之所以原生SEH抓不到,很多时候是因为AV是托管代码触发的,Mono会把这类致命异常包装成.NET的AccessViolationException,但默认.NET不让捕获这类异常,得先开权限,再在C#侧兜底,最后C++侧通过Mono API检查异常。
具体操作:
C#侧配置捕获权限:
要么在项目的app.config里加配置:<configuration> <runtime> <legacyCorruptedStateExceptionsPolicy enabled="true" /> </runtime> </configuration>要么给风险函数加特性:
using System.Security; using System.Runtime.ExceptionServices; [HandleProcessCorruptedStateExceptions] [SecurityCritical] public static void RiskyFunction() { try { // 可能触发AV的代码 } catch (AccessViolationException ex) { // 打日志或者标记异常状态 Console.WriteLine($"AV触发: {ex.Message}"); // 抛个自定义异常让C++侧好识别 throw new InvalidOperationException("托管代码出AV了", ex); } catch (Exception ex) { // 处理其他常规异常 } }C++侧调用时检查托管异常:
用mono_runtime_invoke调用方法时,传入异常指针参数,调用完直接检查是否有异常抛出:MonoObject* exception = nullptr; // 第三个参数是托管方法的参数数组,这里传null表示无参 mono_runtime_invoke(your_method_ptr, nullptr, nullptr, &exception); if (exception != nullptr) { // 获取异常类型和消息 MonoClass* ex_class = mono_object_get_class(exception); const char* ex_name = mono_class_get_name(ex_class); printf("抓到托管异常: %s\n", ex_name); MonoObject* msg_obj = mono_runtime_invoke( mono_class_get_method_from_name(ex_class, "get_Message", 0), exception, nullptr, nullptr ); const char* msg = mono_string_to_utf8((MonoString*)msg_obj); printf("异常消息: %s\n", msg); // 释放异常对象(可选,Mono GC会回收,但显式处理更稳妥) mono_gchandle_free(mono_gchandle_new(exception, MONO_GCHANDLE_WEAK)); // 这里写你的恢复逻辑:比如重置托管对象、跳过当前任务,别直接继续用原来的上下文 }
2. 原生SEH的正确打开方式
如果AV是Mono运行时本身触发的(不是托管代码),可以试试用__try/__except包裹mono_runtime_invoke,但要注意几个坑:
- 必须确保Mono已经初始化完成,异常发生在托管代码执行阶段。
- 异常过滤器要精准匹配
EXCEPTION_ACCESS_VIOLATION(代码0xC0000005)。 - Release模式下编译器优化可能干掉SEH,试试把优化改成
/O1或者直接禁用,同时项目属性里把Enable C++ Exceptions设为Yes (/EHsc),关掉Whole Program Optimization。
示例代码:
#include <windows.h> void CallManagedRiskyFunc(MonoMethod* method) { __try { mono_runtime_invoke(method, nullptr, nullptr, nullptr); } __except(GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { printf("原生SEH抓到AV\n"); // 恢复逻辑:别复用当前线程的托管上下文,最好重启Mono环境或者开新线程执行后续任务 } }
Linux 平台补充方案
Linux下用SIGSEGV信号处理,但要和Mono自带的信号处理协作——Mono默认会注册自己的SIGSEGV handler,所以得先保存原handler,自定义handler里先调用原逻辑,再判断是否是托管代码触发的异常:
#include <signal.h> #include <stdlib.h> #include <stdio.h> void (*original_sigsegv_handler)(int); void sigsegv_handler(int sig) { // 先让Mono处理一遍 original_sigsegv_handler(sig); // 检查当前线程是否在执行托管代码 if (mono_thread_is_in_managed()) { printf("托管代码触发SIGSEGV\n"); // 恢复逻辑:终止当前托管任务,重新初始化线程,别硬扛着继续跑 } else { // 非托管代码的问题,直接退出 exit(1); } } // 初始化信号处理 void SetupSigHandler() { original_sigsegv_handler = signal(SIGSEGV, sigsegv_handler); }
注意:mono_thread_is_in_managed()需要链接Mono的库,不同版本的Mono可能有差异,建议用稳定版。
重要提醒
AV是致命异常,就算抓到了,Mono运行时可能已经处于不稳定状态,别想着直接继续执行原来的逻辑,最好的恢复策略是:终止当前托管任务,重新初始化相关的托管对象或者开新线程处理后续工作。另外,不同版本的Mono异常处理逻辑可能不一样,尽量用最新的稳定版。
内容的提问来源于stack exchange,提问作者ChrisHusky

