Incorporando DLLs em um executável compilado


619

É possível incorporar uma DLL pré-existente em um executável C # compilado (para que você tenha apenas um arquivo para distribuir)? Se for possível, como alguém faria isso?

Normalmente, sou legal em deixar as DLLs do lado de fora e fazer com que o programa de instalação lide com tudo, mas algumas pessoas no trabalho me perguntaram isso e eu sinceramente não sei.


Eu recomendaria que você verifique o utilitário .NETZ, que também comprime o conjunto com um esquema de sua escolha: http://madebits.com/netz/help.php#single
Nathan Baulch

2
É possível, mas você acabará com um executável grande (o Base64 será usado para codificar sua dll).
Paweł Dyda 28/10/10

Além do ILMerge , se você não quer se preocupar com opções de linha de comando, eu realmente recomendo o ILMerge-Gui . É um projeto de código aberto, muito bom!
perfil completo de Tyron

2
@ PawełDyda: Você pode incorporar dados binários brutos em uma imagem do PE (consulte RCDATA ). Nenhuma transformação é necessária (ou recomendada).
11nspectable

Respostas:


761

Eu recomendo usar o Costura.Fody - de longe a melhor e mais fácil maneira de incorporar recursos em sua montagem. Está disponível como pacote NuGet.

Install-Package Costura.Fody

Depois de adicioná-lo ao projeto, ele incorporará automaticamente todas as referências copiadas para o diretório de saída no seu assembly principal . Você pode limpar os arquivos incorporados adicionando um destino ao seu projeto:

Install-CleanReferencesTarget

Você também poderá especificar se deve incluir os pdb's, excluir determinados assemblies ou extrair os assemblies em tempo real. Tanto quanto sei, também são suportados conjuntos não gerenciados.

Atualizar

Atualmente, algumas pessoas estão tentando adicionar suporte ao DNX .

Atualização 2

Para a versão mais recente do Fody, você precisará do MSBuild 16 (portanto, Visual Studio 2019). O Fody versão 4.2.1 fará o MSBuild 15. (referência: Fody é suportado apenas no MSBuild 16 e acima. Versão atual: 15 )


79
Obrigado por esta sugestão impressionante. Instale o pacote e pronto. Até comprime montagens por padrão.
Daniel

9
Odeio ser um 'eu também', mas eu também - isso me salvou de muita dor de cabeça! Obrigado pela recomendação! Isso me permitiu empacotar tudo o que preciso para redistribuir em um único exe e agora é menor do que o exe e as dlls originais foram combinados ... Eu só uso isso há alguns dias, então não posso dizer que estou ' Eu coloquei o seu ritmo, mas, com exceção de qualquer coisa ruim, posso ver isso se tornando uma ferramenta regular na minha caixa de ferramentas. Isso simplesmente funciona!
mattezell

20
É legal. Mas há uma desvantagem: a montagem gerada no Windows não é mais binária compatível com o Linux mono. Isso significa que você não pode implantar o assembly no Linux mono diretamente.
Tyler longo

7
Isso é adorável! Se você estiver usando o vs2018, não esqueça o arquivo FodyWeavers.xml que está localizado na raiz do seu projeto.
27518 Alan Deep

4
Como complemento ao último comentário: adicione o FodyWeavers.xml com o seguinte conteúdo ao seu projeto: <? Xml version = "1.0" encoding = "utf-8"?> <Weavers VerifyAssembly = "true"> <Costura /> </Weavers>
HHenn

89

Apenas clique com o botão direito do mouse no seu projeto no Visual Studio, escolha Propriedades do projeto -> Recursos -> Adicionar recurso -> Adicionar arquivo existente ... E inclua o código abaixo no seu App.xaml.cs ou equivalente.

public App()
{
    AppDomain.CurrentDomain.AssemblyResolve +=new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}

System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
    string dllName = args.Name.Contains(',') ? args.Name.Substring(0, args.Name.IndexOf(',')) : args.Name.Replace(".dll","");

    dllName = dllName.Replace(".", "_");

    if (dllName.EndsWith("_resources")) return null;

    System.Resources.ResourceManager rm = new System.Resources.ResourceManager(GetType().Namespace + ".Properties.Resources", System.Reflection.Assembly.GetExecutingAssembly());

    byte[] bytes = (byte[])rm.GetObject(dllName);

    return System.Reflection.Assembly.Load(bytes);
}

Aqui está o meu post original: http://codeblog.larsholm.net/2011/06/embed-dlls-easily-in-a-net-assembly/


6
Você pode ter esse comportamento pronto para uso. Confira minha resposta stackoverflow.com/a/20306095/568266
Matthias

4
Também é importante observar um comentário incrivelmente útil no seu blog da AshRowe: se você tiver um tema personalizado instalado, ele tentará resolver o assembly PresentationFramework.Theme que trava e queima! Conforme a sugestão do AshRowe, você pode simplesmente verificar se o dllName contém PresentationFramework assim: if (dllName.ToLower (). Contém ("presentationframework")) return null;
YasharBahman

4
Dois comentários sobre isso. Um: você deve verificar se bytesé nulo e, em caso afirmativo, retornar nulo lá. É possível que a dll não esteja nos recursos, afinal. Dois: Isso só funciona se a classe em si não tiver um "uso" para nada desse assembly. Para ferramentas de linha de comando, tive que mover meu código de programa real para um novo arquivo e criar um pequeno programa principal novo que apenas faça isso e depois chame o principal original na classe antiga.
Nyerguds

2
A vantagem dessa abordagem é que ela não depende da instalação de bibliotecas externas para alcançar a funcionalidade desejada. A desvantagem dessa abordagem é que ela é útil apenas quando se trata de dlls gerenciadas - dlls de interoperabilidade (pelo menos no que diz respeito a meus testes) não acionam o evento assemblyresolve e mesmo que tenham feito o Assembly.Load (<bytes de alguma interoperabilidade .dll>) não alcança o efeito desejável no futuro. stackoverflow.com/questions/13113131/... Just my 2c sobre o assunto
XDS

3
Apenas no caso de alguém encontrar o meu problema: se o .dllnome contiver hífens (ie twenty-two.dll), eles também serão substituídos por um sublinhado (ie twenty_two.dll). Você pode alterar esta linha de código para isso:dllName = dllName.Replace(".", "_").Replace("-", "_");
Micah Vertal

87

Se eles são realmente assemblies gerenciados, você pode usar o ILMerge . Para DLLs nativas, você terá um pouco mais de trabalho a fazer.

Consulte também: Como uma DLL do Windows C ++ pode ser mesclada em um aplicativo C # exe?


Estou interessado na mesclagem DLL nativa, há algum material?
Baiyan Huang 05/03/09


@BaiyanHuang, veja github.com/boxedapp/bxilmerge , a idéia é criar "ILMerge" para DLLs nativas.
Artem Razin

Desenvolvedores de VB NET como eu não se assustem com isso C++no link. O ILMerge também funciona com muita facilidade para o VB NET. Veja aqui https://github.com/dotnet/ILMerge . Obrigado @ Shog9
Ivan Ferrer Villa

26

Sim, é possível mesclar executáveis ​​.NET com bibliotecas. Existem várias ferramentas disponíveis para realizar o trabalho:

  • O ILMerge é um utilitário que pode ser usado para mesclar vários assemblies .NET em um único assembly.
  • Mono mkbundle , empacota um exe e todos os assemblies com libmono em um único pacote binário.
  • O IL-Repack é um FLOSS que altera o ILMerge, com alguns recursos adicionais.

Além disso, isso pode ser combinado com o Mono Linker , que remove o código não utilizado e, portanto, diminui o conjunto resultante.

Outra possibilidade é usar o .NETZ , que não apenas permite a compactação de um assembly, mas também pode empacotar as dlls diretamente no exe. A diferença para as soluções mencionadas acima é que o .NETZ não as mescla, elas permanecem montagens separadas, mas são empacotadas em um pacote.

O .NETZ é uma ferramenta de código aberto que compacta e compacta os arquivos executáveis ​​do Microsoft .NET Framework (EXE, DLL) para diminuí-los.


NETZ parece ter desaparecido
Rbjz

Uau - eu pensei que finalmente encontrei, então li este comentário. Parece ter desaparecido totalmente. Existem garfos?
Mafii

Bem, ele acabou de se mudar para o GitHub e não está mais vinculado ao site ... então "sumiu totalmente" é um exagero. Provavelmente, ele não é mais suportado, mas ainda está lá. Eu atualizei o link.
Bobby

20

O ILMerge pode combinar montagens em uma única montagem, desde que a montagem tenha apenas código gerenciado. Você pode usar o aplicativo de linha de comando ou adicionar referência ao exe e mesclar programaticamente. Para uma versão da GUI, existe o Eazfuscator e também o .Netz, ambos gratuitos. Os aplicativos pagos incluem BoxedApp e SmartAssembly .

Se você precisar mesclar assemblies com código não gerenciado, sugiro o SmartAssembly . Eu nunca tive soluços com o SmartAssembly, mas com todos os outros. Aqui, ele pode incorporar as dependências necessárias como recursos ao seu exe principal.

Você pode fazer tudo isso manualmente, sem precisar se preocupar se o assembly é gerenciado ou no modo misto, incorporando a dll aos seus recursos e depois confiando no Assembly do AppDomain ResolveHandler. Essa é uma solução única, adotando o pior caso, ou seja, montagens com código não gerenciado.

static void Main()
{
    AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
    {
        string assemblyName = new AssemblyName(args.Name).Name;
        if (assemblyName.EndsWith(".resources"))
            return null;

        string dllName = assemblyName + ".dll";
        string dllFullPath = Path.Combine(GetMyApplicationSpecificPath(), dllName);

        using (Stream s = Assembly.GetEntryAssembly().GetManifestResourceStream(typeof(Program).Namespace + ".Resources." + dllName))
        {
            byte[] data = new byte[stream.Length];
            s.Read(data, 0, data.Length);

            //or just byte[] data = new BinaryReader(s).ReadBytes((int)s.Length);

            File.WriteAllBytes(dllFullPath, data);
        }

        return Assembly.LoadFrom(dllFullPath);
    };
}

A chave aqui é gravar os bytes em um arquivo e carregar a partir de sua localização. Para evitar problemas com galinhas e ovos, você deve garantir que declara o manipulador antes de acessar a montagem e que não acessa os membros da montagem (ou instancia algo que tenha que lidar com a montagem) dentro da peça de carregamento (resolução da montagem). Além disso, tome cuidado para garantir que GetMyApplicationSpecificPath()não haja nenhum diretório temporário, pois os arquivos temporários podem ser apagados por outros programas ou por você mesmo (não que sejam excluídos enquanto o programa estiver acessando a dll, mas pelo menos isso é um incômodo. localização). Observe também que você precisa escrever os bytes de cada vez, não é possível carregar a partir do local apenas porque a dll já reside lá.

Para dlls gerenciadas, você não precisa escrever bytes, mas carregar diretamente do local da dll ou apenas ler os bytes e carregar o assembly da memória. Assim ou assim:

    using (Stream s = Assembly.GetEntryAssembly().GetManifestResourceStream(typeof(Program).Namespace + ".Resources." + dllName))
    {
        byte[] data = new byte[stream.Length];
        s.Read(data, 0, data.Length);
        return Assembly.Load(data);
    }

    //or just

    return Assembly.LoadFrom(dllFullPath); //if location is known.

Se o conjunto é totalmente gerenciado, você pode ver esta ligação ou este a respeito de como carregar essas DLLs.


Observe que a "Ação de compilação" do recurso precisa ser definida como "Recurso incorporado".
Mavamaarten

@Mavamaarten Não necessariamente. Se ele for adicionado ao Resources.resx do projeto com antecedência, você não precisará fazer isso.
Nyerguds

2
O EAZfuscator agora é comercial.
Telemat

16

O trecho de Jeffrey Richter é muito bom. Em resumo, adicione as bibliotecas como recursos incorporados e adicione um retorno de chamada antes de qualquer outra coisa. Aqui está uma versão do código (encontrada nos comentários de sua página) que eu coloquei no início do método Main para um aplicativo de console (apenas verifique se todas as chamadas que usam a biblioteca estão em um método diferente do Main).

AppDomain.CurrentDomain.AssemblyResolve += (sender, bargs) =>
        {
            String dllName = new AssemblyName(bargs.Name).Name + ".dll";
            var assem = Assembly.GetExecutingAssembly();
            String resourceName = assem.GetManifestResourceNames().FirstOrDefault(rn => rn.EndsWith(dllName));
            if (resourceName == null) return null; // Not found, maybe another handler will find it
            using (var stream = assem.GetManifestResourceStream(resourceName))
            {
                Byte[] assemblyData = new Byte[stream.Length];
                stream.Read(assemblyData, 0, assemblyData.Length);
                return Assembly.Load(assemblyData);
            }
        };

1
Mudou um pouco, fez o trabalho, tnx amigo!
Sean Ed-Man

O projeto libz.codeplex.com usa esse processo, mas também fará algumas outras coisas, como gerenciar o manipulador de eventos para você e algum código especial para não quebrar os " Managed Extensibility Framework Catalogs " (que por si só esse processo quebraria)
Scott Chamberlain

Isso é ótimo!! Obrigado @Steve
Ahmer Afzal

14

Para expandir no @ Bobby's asnwer acima. Você pode editar seu .csproj para usar o IL-Repack para empacotar automaticamente todos os arquivos em um único assembly quando criar.

  1. Instale o nuget ILRepack.MSBuild.Task pacote com Install-Package ILRepack.MSBuild.Task
  2. Edite a seção AfterBuild do seu .csproj

Aqui está um exemplo simples que mescla ExampleAssemblyToMerge.dll à saída do projeto.

<!-- ILRepack -->
<Target Name="AfterBuild" Condition="'$(Configuration)' == 'Release'">

   <ItemGroup>
    <InputAssemblies Include="$(OutputPath)\$(AssemblyName).exe" />
    <InputAssemblies Include="$(OutputPath)\ExampleAssemblyToMerge.dll" />
   </ItemGroup>

   <ILRepack 
    Parallel="true"
    Internalize="true"
    InputAssemblies="@(InputAssemblies)"
    TargetKind="Exe"
    OutputFile="$(OutputPath)\$(AssemblyName).exe"
   />
</Target>

1
A sintaxe do IL-Repack foi alterada. Verifique o README.md que está no repositório do github vinculado ( github.com/peters/ILRepack.MSBuild.Task ). Dessa maneira, foi o único que funcionou para mim e eu pude usar um curinga para corresponder a todas as dlls que desejava incluir.
Seabass77

8

Você pode adicionar as DLLs como recursos incorporados e fazer com que seu programa descompacte-as no diretório do aplicativo na inicialização (após verificar se elas já existem).

Os arquivos de instalação são tão fáceis de criar, que não acho que valeria a pena.

EDIT: Essa técnica seria fácil com assemblies .NET. Com as DLLs que não são do .NET, seria muito mais trabalhoso (você teria que descobrir onde descompactar os arquivos, registrá-los e assim por diante).


Aqui você tem um ótimo artigo que explica como fazer isso: codeproject.com/Articles/528178/Load-DLL-From-Embedded-Resource
azulada

8

Outro produto que pode lidar com isso com elegância é o SmartAssembly, no SmartAssembly.com . Esse produto, além de mesclar todas as dependências em uma única DLL, (opcionalmente) ofusca seu código, remove metadados extras para reduzir o tamanho do arquivo resultante e também pode otimizar a IL para aumentar o desempenho do tempo de execução.

Há também algum tipo de recurso de manipulação / relatório de exceção global que ele adiciona ao seu software (se desejado) que pode ser útil. Acredito que ele também tenha uma API de linha de comando para que você possa fazer parte do seu processo de compilação.


7

Nem a abordagem ILMerge nem Lars Holm Jensen manipulando o evento AssemblyResolve funcionarão para um host de plug-in. Digamos que o executável H carrega o conjunto P dinamicamente e o acessa via IP da interface definido em um conjunto separado. Para incorporar o IP no H, é necessário uma pequena modificação no código de Lars:

Dictionary<string, Assembly> loaded = new Dictionary<string,Assembly>();
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{   Assembly resAssembly;
    string dllName = args.Name.Contains(",") ? args.Name.Substring(0, args.Name.IndexOf(',')) : args.Name.Replace(".dll","");
    dllName = dllName.Replace(".", "_");
    if ( !loaded.ContainsKey( dllName ) )
    {   if (dllName.EndsWith("_resources")) return null;
        System.Resources.ResourceManager rm = new System.Resources.ResourceManager(GetType().Namespace + ".Properties.Resources", System.Reflection.Assembly.GetExecutingAssembly());
        byte[] bytes = (byte[])rm.GetObject(dllName);
        resAssembly = System.Reflection.Assembly.Load(bytes);
        loaded.Add(dllName, resAssembly);
    }
    else
    {   resAssembly = loaded[dllName];  }
    return resAssembly;
};  

O truque para lidar com tentativas repetidas de resolver o mesmo assembly e retornar o existente em vez de criar uma nova instância.

EDIT: para que não estrague a serialização do .NET, retorne nulo para todos os assemblies não incorporados no seu, padronizando assim o comportamento padrão. Você pode obter uma lista dessas bibliotecas:

static HashSet<string> IncludedAssemblies = new HashSet<string>();
string[] resources = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceNames();
for(int i = 0; i < resources.Length; i++)
{   IncludedAssemblies.Add(resources[i]);  }

e retorne null se o assembly passado não pertencer IncludedAssemblies.


Desculpe por publicá-lo como resposta e não como comentário. Não tenho o direito de comentar as respostas de outras pessoas.
Ant_222

5

O .NET Core 3.0 suporta nativamente a compilação em um único .exe

O recurso é ativado pelo uso da seguinte propriedade no seu arquivo de projeto (.csproj):

    <PropertyGroup>
        <PublishSingleFile>true</PublishSingleFile>
    </PropertyGroup>

Isso é feito sem nenhuma ferramenta externa.

Veja minha resposta para esta pergunta para mais detalhes.


3

Pode parecer simplista, mas o WinRar oferece a opção de compactar vários arquivos em um executável de extração automática.
Possui muitas opções configuráveis: ícone final, extrair arquivos para o caminho especificado, arquivo para executar após a extração, logotipo / textos personalizados para o pop-up exibido durante a extração, nenhuma janela pop-up, texto do contrato de licença etc.
Pode ser útil em alguns casos .


O próprio Windows possui uma ferramenta semelhante chamada iexpress. Aqui está um tutorial
Ivan Ferrer Villa

2

Eu uso o compilador csc.exe chamado de um script .vbs.

No seu script xyz.cs, adicione as seguintes linhas após as diretivas (meu exemplo é para o Renci SSH):

using System;
using Renci;//FOR THE SSH
using System.Net;//FOR THE ADDRESS TRANSLATION
using System.Reflection;//FOR THE Assembly

//+ref>"C:\Program Files (x86)\Microsoft\ILMerge\Renci.SshNet.dll"
//+res>"C:\Program Files (x86)\Microsoft\ILMerge\Renci.SshNet.dll"
//+ico>"C:\Program Files (x86)\Microsoft CAPICOM 2.1.0.2 SDK\Samples\c_sharp\xmldsig\resources\Traffic.ico"

As tags ref, res e ico serão selecionadas pelo script .vbs abaixo para formar o comando csc.

Em seguida, adicione o chamador do resolvedor de montagem no Principal:

public static void Main(string[] args)
{
    AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
    .

... e adicione o resolvedor em algum lugar da classe:

    static Assembly CurrentDomain_AssemblyResolve (remetente do objeto, args ResolveEventArgs)
    {
        String resourceName = new AssemblyName (args.Name) .Name + ".dll";

        usando (var stream = Assembly.GetExecutingAssembly (). GetManifestResourceStream (resourceName))
        {
            Byte [] assemblyData = novo Byte [stream.Length];
            stream.Read (assemblyData, 0, assemblyData.Length);
            retornar Assembly.Load (assemblyData);
        }

    }

Eu nomeio o script vbs para corresponder ao nome do arquivo .cs (por exemplo, ssh.vbs procura por ssh.cs); isso facilita muito a execução do script, mas se você não for um idiota como eu, um script genérico poderá capturar o arquivo .cs de destino em um arrastar e soltar:

    Dim name_, oShell, fso
    Defina oShell = CreateObject ("Shell.Application")
    Defina fso = CreateObject ("Scripting.fileSystemObject")

    TOME O NOME DO SCRIPT DO VBS COMO NOME DO ARQUIVO-ALVO
    '########################################################
    nome_ = Dividir (wscript.ScriptName, ".") (0)

    'OBTENHA OS DLLs EXTERNOS E OS NOMES DE ÍCONE DO ARQUIVO .CS
    '######################################################### ######
    Const OPEN_FILE_FOR_READING = 1
    Defina objInputFile = fso.OpenTextFile (nome_ & ".cs", 1)

    LEIA TUDO EM UMA DISPOSIÇÃO
    '#############################
    inputData = Split (objInputFile.ReadAll, vbNewline)

    Para cada strData Em inputData

        se esquerda (strData, 7) = "// + ref>" então 
            csc_references = csc_references & "/ reference:" & trim (substitua (strData, "// + ref>", "")) & ""
        fim se

        se esquerda (strData, 7) = "// + res>" então 
            csc_resources = csc_resources & "/ resource:" & trim (substituir (strData, "// + res>", "")) e ""
        fim se

        se esquerda (strData, 7) = "// + ico>" então 
            csc_icon = "/ win32icon:" & aparar (substituir (strData, "// + ico>", "")) & ""
        fim se
    Próximo

    objInputFile.Close


    COMPILAR O ARQUIVO
    '################
    oShell.ShellExecute "c: \ windows \ microsoft.net \ framework \ v3.5 \ csc.exe", "/ warn: 1 / target: exe" & csc_references & csc_resources & csc_icon & "" & name_ & ".cs" , "", "runas", 2


    WScript.Quit (0)

0

É possível, mas não tão fácil, criar um assembly nativo / gerenciado híbrido em C #. Você estava usando C ++ em vez disso, seria muito mais fácil, pois o compilador Visual C ++ pode criar assemblies híbridos tão facilmente quanto qualquer outra coisa.

A menos que você tenha um requisito estrito para produzir uma montagem híbrida, eu concordo com o MusiGenesis que isso não vale a pena o trabalho de C #. Se você precisar fazer isso, talvez mude para C ++ / CLI.


0

Geralmente, você precisaria de algum tipo de ferramenta de pós-compilação para executar uma mesclagem de montagem como você está descrevendo. Existe uma ferramenta gratuita chamada Eazfuscator (eazfuscator.blogspot.com/), projetada para manipulação de bytecode que também lida com a fusão de montagem. Você pode adicionar isso a uma linha de comando pós-compilação com o Visual Studio para mesclar seus assemblies, mas sua milhagem variará devido a problemas que surgirão em qualquer cenário de fusão de assemblies que não seja equivalente.

Você também pode verificar se a compilação faz até que o NANT tenha a capacidade de mesclar assemblies após a compilação, mas eu não estou familiarizado o suficiente com o NANT para dizer se a funcionalidade está embutida ou não.

Há também muitos plugins do Visual Studio que executam a mesclagem de assemblies como parte da criação do aplicativo.

Como alternativa, se você não precisar fazer isso automaticamente, existem várias ferramentas, como o ILMerge, que mesclam os assemblies .net em um único arquivo.

O maior problema que tive com a mesclagem de assemblies é se eles usam espaços para nome semelhantes. Ou pior, faça referência a versões diferentes da mesma dll (meus problemas geralmente eram com os arquivos dll do NUnit).


1
O Eazfuscator ligará para IlMerge, AFAIK.
Bobby

+1 Bobby. Eu deveria ter lembrado disso. Sobre tudo o que o Eazfucator faz por você é abstrato as chamadas reais para o ILMerge com um arquivo de configuração mais geral.
Wllmsaccnt
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.