Como lidar com AccessViolationException

185

Estou usando um objeto COM (MODI) de dentro do meu aplicativo .net. O método que estou chamando lança um System.AccessViolationException, que é interceptado pelo Visual Studio. O estranho é que envolvi minha chamada em uma captura de tentativa, que possui manipuladores para AccessViolationException, COMException e tudo mais, mas quando o Visual Studio (2010) intercepta a AccessViolationException, o depurador interrompe a chamada de método (doc.OCR), e se eu avançar, ela continua na próxima linha, em vez de entrar no bloco de captura. Além disso, se eu executar isso fora do visual studio, meu aplicativo trava. Como posso lidar com essa exceção lançada no objeto COM?

MODI.Document doc = new MODI.Document();
try
{
    doc.Create(sFileName);
    try
    {
        doc.OCR(MODI.MiLANGUAGES.miLANG_ENGLISH, false, false);
        sText = doc.Images[0].Layout.Text;
    }
    catch (System.AccessViolationException ex)
    {
        //MODI seems to get access violations for some reason, but is still able to return the OCR text.
        sText = doc.Images[0].Layout.Text;
    }
    catch (System.Runtime.InteropServices.COMException ex)
    {
        //if no text exists, the engine throws an exception.
        sText = "";
    }
    catch
    {
        sText = "";
    }

    if (sText != null)
    {
        sText = sText.Trim();
    }
}
finally
{
    doc.Close(false);

    //Cleanup routine, this is how we are able to delete files used by MODI.
    System.Runtime.InteropServices.Marshal.FinalReleaseComObject(doc);
    doc = null;
    GC.WaitForPendingFinalizers();
    GC.Collect();
    GC.WaitForPendingFinalizers();

}
Jeremy
fonte
Você já tentou colocar um Exceptionmanipulador (temporariamente!) Para interceptar todas as exceções e ver qual é realmente a exceção ?
ChrisF
3
@ ChrisF - sim, veja o último manipulador de captura? Isso deve pegar tudo, incluindo Exception e qualquer subclasse de Exception. Além disso, o Visual studio relata que a exceção é System.AccessViolationException
Jeremy

Respostas:

299

No .NET 4.0, o tempo de execução lida com certas exceções geradas como erros SEH (Windows Structured Error Handling) como indicadores de estado corrompido. Essas exceções de estado corrompidas (CSE) não podem ser capturadas pelo seu código gerenciado padrão. Não vou entrar no porquê ou como está aqui. Leia este artigo sobre CSE's no .NET 4.0 Framework:

http://msdn.microsoft.com/en-us/magazine/dd419661.aspx#id0070035

Mas há esperança. Existem algumas maneiras de contornar isso:

  1. Recompile como um assembly .NET 3.5 e execute-o no .NET 4.0.

  2. Adicione uma linha ao arquivo de configuração do seu aplicativo no elemento configuration / runtime: <legacyCorruptedStateExceptionsPolicy enabled="true|false"/>

  3. Decore os métodos nos quais você deseja capturar essas exceções com o HandleProcessCorruptedStateExceptionsatributo Consulte http://msdn.microsoft.com/en-us/magazine/dd419661.aspx#id0070035 para obter detalhes.


EDITAR

Anteriormente, referenciei uma postagem no fórum para obter detalhes adicionais. Porém, como o Microsoft Connect foi desativado, eis os detalhes adicionais, caso você esteja interessado:

De Gaurav Khanna, desenvolvedor da equipe Microsoft CLR

Esse comportamento ocorre por design devido a um recurso do CLR 4.0 chamado Exceções de Estado Corruptas. Simplificando, o código gerenciado não deve tentar capturar exceções que indiquem estado de processo corrompido e o AV é um deles.

Em seguida, ele passa a referenciar a documentação no HandleProcessCorruptedStateExceptionsAttribute e no artigo acima. Basta dizer que definitivamente vale a pena ler se você está pensando em capturar esses tipos de exceções.

villecoder
fonte
12
HandleProcessCorruptedStateExceptionsfunciona para mim no .Net 4.5.
Deerchao
2
Graças villecoder, você é uma jóia! Estou lidando com esse problema há semanas, tentando resolver o problema raiz e, finalmente, me resignei a tratar o sintoma. Sua solução é perfeita.
gadildafissh
19
! Esteja ciente: é altamente recomendável encerrar o processo após AccessViolationException que é uma Exceção de Estado Corrupta (CSE). Caso contrário, pode levar a erros mais críticos.
Chris W
6
Obrigado, isso é realmente útil, embora, no início, tenha a impressão de que preciso executar todas as três etapas para poder capturar essas exceções, enquanto na verdade é uma "lógica OR" das maneiras de fazer isso. :)
Lou
@deerchao Espero que você tenha lido o primeiro link fornecido em resposta. Lidar com exceções de CSE é uma má ideia.
pixel
17

Adicione o seguinte no arquivo de configuração, e ele será capturado no bloco try catch. Palavra de cautela ... tente evitar essa situação, pois isso significa que algum tipo de violação está acontecendo.

<configuration>
   <runtime>
      <legacyCorruptedStateExceptionsPolicy enabled="true" />
   </runtime>
</configuration>
Partha
fonte
2
Para aqueles que usam c ++ / cli como uma dll, o código deve ser adicionado ao projeto .exe superior.
Felix
9

Compilado a partir das respostas acima, funcionou para mim, seguiu as etapas para obtê-lo.

Etapa 1 - Adicione o seguinte trecho ao arquivo de configuração

<configuration>
   <runtime>
      <legacyCorruptedStateExceptionsPolicy enabled="true" />
   </runtime>
</configuration>

Passo 2

Adicionar -

[HandleProcessCorruptedStateExceptions]

[SecurityCritical]

no topo da função que você está amarrando, pegue a exceção

fonte: http://www.gisremotesensing.com/2017/03/catch-exception-attempted-to-read-or.html

EvilInside
fonte
De acordo com msdn.microsoft.com/en-us/library/… , o SecurityCriticalAttribute é equivalente a uma demanda de link para confiança total. Eu não acho que o problema descrito exija exigir total confiança.
Jeremy
0

Microsoft: "Exceções de estado do processo corrompidas são exceções que indicam que o estado de um processo foi corrompido. Não recomendamos a execução do aplicativo nesse estado ..... Se você tiver certeza absoluta de que deseja manter o tratamento desses exceções, você deve aplicar o HandleProcessCorruptedStateExceptionsAttributeatributo "

Microsoft: "Use domínios de aplicativo para isolar tarefas que podem derrubar um processo."

O programa abaixo protegerá seu aplicativo / thread principal de falhas irrecuperáveis ​​sem riscos associados ao uso HandleProcessCorruptedStateExceptionse<legacyCorruptedStateExceptionsPolicy>

public class BoundaryLessExecHelper : MarshalByRefObject
{
    public void DoSomething(MethodParams parms, Action action)
    {
        if (action != null)
            action();
        parms.BeenThere = true; // example of return value
    }
}

public struct MethodParams
{
    public bool BeenThere { get; set; }
}

class Program
{
    static void InvokeCse()
    {
        IntPtr ptr = new IntPtr(123);
        System.Runtime.InteropServices.Marshal.StructureToPtr(123, ptr, true);
    }

    private static void ExecInThisDomain()
    {
        try
        {
            var o = new BoundaryLessExecHelper();
            var p = new MethodParams() { BeenThere = false };
            Console.WriteLine("Before call");

            o.DoSomething(p, CausesAccessViolation);
            Console.WriteLine("After call. param been there? : " + p.BeenThere.ToString()); //never stops here
        }
        catch (Exception exc)
        {
            Console.WriteLine($"CSE: {exc.ToString()}");
        }
        Console.ReadLine();
    }


    private static void ExecInAnotherDomain()
    {
        AppDomain dom = null;

        try
        {
            dom = AppDomain.CreateDomain("newDomain");
            var p = new MethodParams() { BeenThere = false };
            var o = (BoundaryLessExecHelper)dom.CreateInstanceAndUnwrap(typeof(BoundaryLessExecHelper).Assembly.FullName, typeof(BoundaryLessExecHelper).FullName);         
            Console.WriteLine("Before call");

            o.DoSomething(p, CausesAccessViolation);
            Console.WriteLine("After call. param been there? : " + p.BeenThere.ToString()); // never gets to here
        }
        catch (Exception exc)
        {
            Console.WriteLine($"CSE: {exc.ToString()}");
        }
        finally
        {
            AppDomain.Unload(dom);
        }

        Console.ReadLine();
    }


    static void Main(string[] args)
    {
        ExecInAnotherDomain(); // this will not break app
        ExecInThisDomain();  // this will
    }
}
TS
fonte
-1

Você pode tentar usar AppDomain.UnhandledException e ver se isso permite capturá-lo.

**EDITAR*

Aqui estão mais algumas informações que podem ser úteis (é uma leitura longa).

Tony Abrams
fonte
2
Esta resposta não está mais completa com precisão devido a alterações na estrutura do .NET. Antes da 4.0, está correto. Por seção de AccessViolationException e blocos try / catch em msdn.microsoft.com/en-us/library/...
Tedford