You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免C++调用C#函数触发Access Violation时崩溃

捕获Mono托管代码触发的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 05:23:28