André Alves de Lima

Talking about Software Development and more…

Resolvendo o erro do Crystal Reports FileNotFoundException crdb_adoplus.dll

Um erro clássico que acontece ao tentarmos exibir um relatório do Crystal Reports em aplicações que utilizam o .NET Framework 4 ou superior é o “FileNotFoundException” relacionado ao arquivo “crdb_adoplus.dll“. Como é que esse erro pode ser resolvido? A maneira mais simples e mais utilizada é fazermos um pequeno ajuste no arquivo app.config da nossa aplicação. Porém, essa não é a única maneira. Existe uma outra alternativa que faz muito sentido em alguns casos. No artigo de hoje você verá essas duas maneiras de resolver o erro do Crystal Reports FileNotFoundException crdb_adoplus.dll.

Entendendo o problema

Para entendermos esse problema, vamos criar um novo projeto do tipo “Windows Forms Application“. Dentro desse projeto, vamos adicionar um relatório do Crystal Reports em branco (vamos dar o nome de “Relatorio” para esse novo relatório que está sendo criado). Em seguida, no formulário, temos que arrastar um controle do Crystal Reports e selecionar o relatório que foi criado no passo anterior.

Ao executarmos essa aplicação, não recebemos erro algum:

Porém, vamos combinar que uma aplicação com um relatório em branco sem fonte de dados não tem nenhuma utilidade. Dessa forma, vamos criar uma classe muito simples que servirá de fonte de dados para o relatório. Essa classe se chamará “QualquerClasse” e terá somente uma propriedade (chamada “Propriedade“):

    // C#
    public class QualquerClasse
    {
        public int Propriedade { get; set; }
    }
' VB.NET
Public Class QualquerClasse
    Public Property Propriedade As Integer
End Class

Feito isso, no relatório, vamos até o Database Expert para arrastarmos essa classe para dentro do relatório:

A partir desse momento, ao executarmos novamente a nossa aplicação, o Crystal Reports mostrará a típica caixa de login que aparece quando não passamos a fonte de dados para o nosso relatório:

Isso faz todo o sentido, uma vez que temos uma tabela definida no nosso relatório e nós não estamos alimentando essa tabela ao exibirmos o relatório. OK, então vamos alimentar essa tabela com uma coleção de “QualquerClasse“. Essa coleção conterá somente uma instância dessa classe:

        // C#
        public FormRelatorio()
        {
            InitializeComponent();
            Relatorio1.SetDataSource(new List<QualquerClasse>(new QualquerClasse[] { new QualquerClasse() { Propriedade = 1 } }));
        }
    ' VB.NET
    Public Sub New()
        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        Relatorio1.SetDataSource(New List(Of QualquerClasse)(New QualquerClasse() {New QualquerClasse() With {.Propriedade = 1}}))
    End Sub

E é aí que surge o problema. Nesse ponto, ao executarmos novamente a nossa aplicação, receberemos esse belo erro:

An unhandled exception of type ‘System.IO.FileNotFoundException’ occurred in mscorlib.dll
Additional information: Could not load file or assembly ‘file:///C:\Program Files (x86)\SAP BusinessObjects\Crystal Reports for .NET Framework 4.0\Common\SAP BusinessObjects Enterprise XI 4.0\win32_x86\dotnet1\crdb_adoplus.dll’ or one of its dependencies. The system cannot find the file specified.

Como mencionei no início do artigo, esse erro pode ser consertado de duas maneiras. Vamos conferir as duas opções?

Opção 1: useLegacyV2RuntimeActivationPolicy no app.config

A primeira opção é bem simples. Nós só temos que fazer um pequeno ajuste no arquivo app.config da nossa aplicação. Na tag “startup“, temos que definir o elemento “useLegacyV2RuntimeActivationPolicy” como “true“:

Se você quiser copiar e colar, segue o código da tag “startup” já com o ajuste desse elemento:

    <startup useLegacyV2RuntimeActivationPolicy="true"> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
    </startup>

Execute novamente a aplicação e veja que o erro foi resolvido.

Opção 2: RuntimePolicyHelper

Em algumas situações, adicionarmos essa tag no arquivo app.config acaba sendo muito complicado. Isso acontece principalmente quando desmembramos a exibição dos relatórios em uma biblioteca (dll) separada. Nesse caso, seria o app.config da aplicação consumidora que deveria ser alterado, e isso é definitivamente algo que deve ser evitado ao disponibilizarmos uma biblioteca que pode ser consumida por várias aplicações consumidoras.

Na empresa onde eu trabalho nós tivemos exatamente esse problema. A lógica “genérica” de exibição de relatórios do Crystal Reports fica em uma dll separada, que é consumida por “N” aplicações. Nós queríamos evitar que o app.config de cada aplicação consumidora tivesse que ser alterado ao utilizar o Crystal Reports. Ao invés disso, nós queríamos fazer essa alteração do “legacy runtime” automaticamente quando a aplicação fosse executada.

Depois de pesquisar um pouquinho, encontramos uma classe “mágica que faz com que esse elemento seja adicionado em tempo de execução, evitando que o app.config tenha que ser alterado nas aplicações consumidoras. Vamos ver como essa classe funciona?

Primeiramente, vamos adicionar uma nova classe ao nosso projeto, dando o nome de “RuntimePolicyHelper“. Aqui vai o código dessa classe:

    // C#
    public static class RuntimePolicyHelper
    {
        public static bool LegacyV2RuntimeEnabledSuccessfully { get; private set; }

        static RuntimePolicyHelper()
        {
            ICLRRuntimeInfo clrRuntimeInfo =
                (ICLRRuntimeInfo)System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeInterfaceAsObject(
                    Guid.Empty,
                    typeof(ICLRRuntimeInfo).GUID);
            try
            {
                clrRuntimeInfo.BindAsLegacyV2Runtime();
                LegacyV2RuntimeEnabledSuccessfully = true;
            }
            catch (System.Runtime.InteropServices.COMException)
            {
                // This occurs with an HRESULT meaning 
                // "A different runtime was already bound to the legacy CLR version 2 activation policy."
                LegacyV2RuntimeEnabledSuccessfully = false;
            }
        }

        [System.Runtime.InteropServices.ComImport]
        [System.Runtime.InteropServices.InterfaceType(System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIUnknown)]
        [System.Runtime.InteropServices.Guid("BD39D1D2-BA2F-486A-89B0-B4B0CB466891")]
        private interface ICLRRuntimeInfo
        {
            void xGetVersionString();
            void xGetRuntimeDirectory();
            void xIsLoaded();
            void xIsLoadable();
            void xLoadErrorString();
            void xLoadLibrary();
            void xGetProcAddress();
            void xGetInterface();
            void xSetDefaultStartupFlags();
            void xGetDefaultStartupFlags();

            [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.InternalCall, MethodCodeType = System.Runtime.CompilerServices.MethodCodeType.Runtime)]
            void BindAsLegacyV2Runtime();
        }
    }
' VB.NET
Public NotInheritable Class RuntimePolicyHelper
    Public Shared Property LegacyV2RuntimeEnabledSuccessfully() As Boolean
        Get
            Return m_LegacyV2RuntimeEnabledSuccessfully
        End Get
        Private Set
            m_LegacyV2RuntimeEnabledSuccessfully = Value
        End Set
    End Property
    Private Shared m_LegacyV2RuntimeEnabledSuccessfully As Boolean

    Shared Sub New()
        Dim clrRuntimeInfo As ICLRRuntimeInfo = DirectCast(System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeInterfaceAsObject(Guid.Empty, GetType(ICLRRuntimeInfo).GUID), ICLRRuntimeInfo)
        Try
            clrRuntimeInfo.BindAsLegacyV2Runtime()
            LegacyV2RuntimeEnabledSuccessfully = True
        Catch generatedExceptionName As System.Runtime.InteropServices.COMException
            ' This occurs with an HRESULT meaning 
            ' "A different runtime was already bound to the legacy CLR version 2 activation policy."
            LegacyV2RuntimeEnabledSuccessfully = False
        End Try
    End Sub

    <System.Runtime.InteropServices.ComImport>
    <System.Runtime.InteropServices.InterfaceType(System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIUnknown)>
    <System.Runtime.InteropServices.Guid("BD39D1D2-BA2F-486A-89B0-B4B0CB466891")>
    Private Interface ICLRRuntimeInfo
        Sub xGetVersionString()
        Sub xGetRuntimeDirectory()
        Sub xIsLoaded()
        Sub xIsLoadable()
        Sub xLoadErrorString()
        Sub xLoadLibrary()
        Sub xGetProcAddress()
        Sub xGetInterface()
        Sub xSetDefaultStartupFlags()
        Sub xGetDefaultStartupFlags()

        <System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.InternalCall, MethodCodeType:=System.Runtime.CompilerServices.MethodCodeType.Runtime)>
        Sub BindAsLegacyV2Runtime()
    End Interface
End Class

A utilização dessa classe é muito simples. Nós só temos que acessar a propriedade “LegacyV2RuntimeEnabledSuccessfully“, que retornará “true” caso o elemento tenha sido adicionado no startup com sucesso e “false” caso contrário. No nosso exemplo, como a exibição dos relatórios está sendo feita diretamente no projeto da aplicação, o lugar ideal para adicionarmos essa chamada seria no método “Main“:

        // C#
        [STAThread]
        static void Main()
        {
            if (RuntimePolicyHelper.LegacyV2RuntimeEnabledSuccessfully)
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new FormRelatorio());
            }
            else
            {
                MessageBox.Show("Não foi possível ativar o LegacyV2Runtime para a aplicação.", "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

No VB.NET, por padrão, você não encontrará um método “Main” em projetos do tipo “Windows Forms Application“. Nesse caso, ou você altera as propriedades do projeto de forma que ele tenha um método “Main” (como descrito nesta thread do StackOverflow) ou você coloca a chamada dessa propriedade no construtor do formulário onde o relatório está sendo exibido:

    ' VB.NET
    Public Sub New()
        If (Not RuntimePolicyHelper.LegacyV2RuntimeEnabledSuccessfully) Then
            MessageBox.Show("Não foi possível ativar o LegacyV2Runtime para a aplicação.", "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End If

        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        Relatorio1.SetDataSource(New List(Of QualquerClasse)(New QualquerClasse() {New QualquerClasse() With {.Propriedade = 1}}))
    End Sub

Pronto! Com essas alterações, você não precisa mais definir o elemento no arquivo app.config. Ele será automaticamente adicionado em tempo de execução.

Concluindo

O disparo de uma “FileNotFoundException” é algo comum ao utilizarmos o controle do Crystal Reports em aplicações desenvolvidas com o .NET Framework 4 ou superior. Esse erro pode ser facilmente corrigido de duas maneiras: fazendo um ajuste no arquivo app.config ou adicionando dinamicamente um elemento nas políticas de runtime em tempo de execução. Neste artigo você conferiu as duas maneiras de resolver esse problema.

E você, já passou por esse problema com o Crystal Reports? Conhecia a segunda metodologia de resolução? Qual das duas opções você achou melhor? Conte-nos mais detalhes na caixa de comentários!

Por fim, convido você a inscrever-se na minha newsletter. Ao fazer isso, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário logo abaixo.

Até a próxima!

André Lima

Newsletter do André Lima

* indicates required



Powered by MailChimp

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *