Carregando DLLs em tempo de execução em C #

91

Estou tentando descobrir como você poderia importar e usar um .dll em tempo de execução dentro de um aplicativo C #. Usando Assembly.LoadFile () consegui fazer com que meu programa carregasse a dll (esta parte está definitivamente funcionando porque consigo obter o nome da classe com ToString ()), mas não consigo usar o 'Output' método de dentro do meu aplicativo de console. Estou compilando o .dll e movendo-o para o projeto do meu console. Existe uma etapa extra entre CreateInstance e, em seguida, ser capaz de usar os métodos?

Esta é a classe em minha DLL:

namespace DLL
{
    using System;

    public class Class1
    {
        public void Output(string s)
        {
            Console.WriteLine(s);
        }
    }
}

e aqui está o aplicativo que desejo carregar a DLL

namespace ConsoleApplication1
{
    using System;
    using System.Reflection;

    class Program
    {
        static void Main(string[] args)
        {
            var DLL = Assembly.LoadFile(@"C:\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\DLL.dll");

            foreach(Type type in DLL.GetExportedTypes())
            {
                var c = Activator.CreateInstance(type);
                c.Output(@"Hello");
            }

            Console.ReadLine();
        }
    }
}
danbroooks
fonte

Respostas:

128

Os membros devem ser resolvidos em tempo de compilação para serem chamados diretamente do C #. Caso contrário, você deve usar reflexão ou objetos dinâmicos.

Reflexão

namespace ConsoleApplication1
{
    using System;
    using System.Reflection;

    class Program
    {
        static void Main(string[] args)
        {
            var DLL = Assembly.LoadFile(@"C:\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\DLL.dll");

            foreach(Type type in DLL.GetExportedTypes())
            {
                var c = Activator.CreateInstance(type);
                type.InvokeMember("Output", BindingFlags.InvokeMethod, null, c, new object[] {@"Hello"});
            }

            Console.ReadLine();
        }
    }
}

Dinâmico (.NET 4.0)

namespace ConsoleApplication1
{
    using System;
    using System.Reflection;

    class Program
    {
        static void Main(string[] args)
        {
            var DLL = Assembly.LoadFile(@"C:\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\DLL.dll");

            foreach(Type type in DLL.GetExportedTypes())
            {
                dynamic c = Activator.CreateInstance(type);
                c.Output(@"Hello");
            }

            Console.ReadLine();
        }
    }
}
Dark Falcon
fonte
12
Observe que isso tenta chamar Outputtodos os tipos na montagem, o que provavelmente ocorrerá antes que a classe "certa" seja encontrada ...
Reed Copsey
1
@ReedCopsey, Concordo, mas por seu exemplo simples, seu tipo é o único visível. "Os únicos tipos visíveis fora de um assembly são os tipos públicos e os tipos públicos aninhados em outros tipos públicos." Para um exemplo não trivial, obviamente isso será um problema ...
Dark Falcon
1
Legal com os dois exemplos! :)
Niels Abildgaard
22
É por isso que as interfaces são freqüentemente usadas e você pode fazer a detecção de recursos como IDog dog = someInstance as IDog;e testar se não for nulo. Coloque suas interfaces em uma DLL comum compartilhada pelos clientes, e qualquer plugin que será carregado dinamicamente deve implementar essa interface. Isso permitirá que você codifique seu cliente na interface do IDog e tenha a verificação de tipo intellisense + forte em tempo de compilação ao invés de usar dinâmico.
AaronLS
1
@ Tarek.Mh: Isso exigiria uma dependência em tempo de compilação do Class1. Nesse ponto, você pode apenas usar new Class1(). O solicitante especificou explicitamente uma dependência de tempo de execução. dynamicpermite que o programa não exija nenhuma dependência de tempo de compilação Class1.
Dark Falcon
39

Agora, você está criando uma instância de cada tipo definido na montagem . Você só precisa criar uma única instância de Class1para chamar o método:

class Program
{
    static void Main(string[] args)
    {
        var DLL = Assembly.LoadFile(@"C:\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\DLL.dll");

        var theType = DLL.GetType("DLL.Class1");
        var c = Activator.CreateInstance(theType);
        var method = theType.GetMethod("Output");
        method.Invoke(c, new object[]{@"Hello"});

        Console.ReadLine();
    }
}
Reed Copsey
fonte
19

Você precisa criar uma instância do tipo que expõe o Outputmétodo:

static void Main(string[] args)
    {
        var DLL = Assembly.LoadFile(@"C:\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\DLL.dll");

        var class1Type = DLL.GetType("DLL.Class1");

        //Now you can use reflection or dynamic to call the method. I will show you the dynamic way

        dynamic c = Activator.CreateInstance(class1Type);
        c.Output(@"Hello");

        Console.ReadLine();
     }
Alberto
fonte
Muito obrigado - isso é exatamente o que estou procurando. Não posso acreditar que essa não seja uma classificação mais alta do que as outras respostas, pois mostra o uso da palavra-chave dinâmica.
skiphoppy
Ah, agora eu vejo que estava na resposta de DarkFalcon também. O seu era mais curto e tornou mais fácil de ver, no entanto. :)
skiphoppy
0

Activator.CreateInstance() retorna um objeto, que não tem um método de saída.

Parece que você vem de linguagens de programação dinâmicas? Definitivamente, C # não é isso, e o que você está tentando fazer será difícil.

Já que você está carregando uma dll específica de um local específico, talvez queira apenas adicioná-la como uma referência ao seu aplicativo de console?

Se você realmente deseja carregar a montagem via Assembly.Load, terá que ir via reflexão para chamar qualquer membroc

Algo como type.GetMethod("Output").Invoke(c, null);deveria servir.

Fredrik
fonte
0
foreach (var f in Directory.GetFiles(".", "*.dll"))
            Assembly.LoadFrom(f);

Isso carrega todas as DLLs presentes na pasta do seu executável.

No meu caso estava tentando usar Reflectionpara encontrar todas as subclasses de uma classe, até mesmo em outras DLLs. Isso funcionou, mas não tenho certeza se é a melhor maneira de fazer isso.

EDIT: Eu cronometrei, e só parece carregá-los na primeira vez.

Stopwatch stopwatch = new Stopwatch();
for (int i = 0; i < 4; i++)
{
    stopwatch.Restart();
    foreach (var f in Directory.GetFiles(".", "*.dll"))
        Assembly.LoadFrom(f);
    stopwatch.Stop();
    Console.WriteLine(stopwatch.ElapsedMilliseconds);
}

Saída: 34 0 0 0

Portanto, é possível executar esse código antes de qualquer pesquisa do Reflection, apenas para garantir.

Samuel Cabrera
fonte
-1

Não é tão difícil.

Você pode inspecionar as funções disponíveis do objeto carregado e, se encontrar o que está procurando pelo nome, espiar seus parâmetros esperados, se houver. Se for a chamada que você está tentando encontrar, chame-a usando o método Invoke do objeto MethodInfo.

Outra opção é simplesmente construir seus objetos externos para uma interface e lançar o objeto carregado para essa interface. Se for bem-sucedido, chame a função nativamente.

Isso é uma coisa muito simples.

ChrisH
fonte
Uau, não sei por que os votos negativos. Eu tenho um aplicativo de produção fazendo exatamente isso nos últimos 12 anos. * encolher os ombros * Se alguém precisar de algum código para fazer isso, me mande uma mensagem. Vou empacotar partes do meu código de produção e enviá-lo junto.
ChrisH
10
Eu suspeito que os votos negativos teriam a ver com a falta de exemplos e tom de condensação ... Parece que você tem a base para uma resposta completa, então não tenha medo de editar em mais detalhes :)
Shadow
1
É meio rude dizer "isso é uma coisa bem simples", e é por isso que você ganhou votos negativos.
ABPerson
1
Eu não estava sendo rude ou condescendente ... 6 anos atrás. O tom não aparece no texto, claramente. Foi realmente feito para ser bem alegre ... Eu também realmente sinto que tinha um link para um exemplo de código lá todos aqueles anos, e não tenho ideia de para onde foi (presumindo que realmente estava lá como estou lembrando ) : \
ChrisH
Não sei como o MethodInfo funciona, mas parece valioso. Eu diria que sua resposta tem potencial para ser melhor do que a atual aceita, mas precisa ser concluída. Se algum dia você fizer isso, será apreciado. Em caso afirmativo, não crie um link para uma amostra de código. Eles podem quebrar no futuro. É melhor fornecer você mesmo a amostra, possivelmente com um link para uma fonte ou informações extras para leitura contínua.
SpaghettiCook