your programing

컴파일 된 실행 파일에 DLL 포함

lovepro 2020. 10. 3. 11:27
반응형

컴파일 된 실행 파일에 DLL 포함


기존 DLL을 컴파일 된 C # 실행 파일에 포함 할 수 있습니까 (배포 할 파일이 하나만 있도록)? 가능하다면 어떻게할까요?

일반적으로 나는 DLL을 외부에두고 설치 프로그램이 모든 것을 처리하도록하는 것이 멋지지만, 직장에서이 문제를 묻는 사람이 몇 명 있었지만 솔직히 모르겠습니다.


Costura.Fody 를 사용하는 것이 좋습니다 . 어셈블리에 리소스를 포함하는 가장 쉽고 쉬운 방법입니다. NuGet 패키지로 제공됩니다.

Install-Package Costura.Fody

프로젝트에 추가하면 출력 디렉터리에 복사 된 모든 참조가 어셈블리에 자동으로 포함됩니다 . 프로젝트에 대상을 추가하여 포함 된 파일을 정리할 수 있습니다.

Install-CleanReferencesTarget

또한 pdb를 포함할지, 특정 어셈블리를 제외할지 또는 즉시 어셈블리를 추출할지 여부를 지정할 수 있습니다. 내가 아는 한 관리되지 않는 어셈블리도 지원됩니다.

최신 정보

현재 일부 사람들은 DNX에 대한 지원 을 추가하려고합니다 .


실제로 관리되는 어셈블리 인 경우 ILMerge 를 사용할 수 있습니다 . 네이티브 DLL의 경우 수행 할 작업이 조금 더 많습니다.

참고 항목 : C ++ Windows dll을 C # 응용 프로그램 exe에 병합하는 방법은 무엇입니까?


Visual Studio에서 프로젝트를 마우스 오른쪽 단추로 클릭하고 프로젝트 속성-> 리소스-> 리소스 추가-> 기존 파일 추가…를 선택하고 아래 코드를 App.xaml.cs 또는 이와 동등한 항목에 포함하면됩니다.

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);
}

내 원래 블로그 게시물은 다음과 같습니다. http://codeblog.larsholm.net/2011/06/embed-dlls-easily-in-a-net-assembly/


예, .NET 실행 파일을 라이브러리와 병합 할 수 있습니다. 작업을 완료하는 데 사용할 수있는 여러 도구가 있습니다.

  • ILMerge 는 여러 .NET 어셈블리를 단일 어셈블리로 병합하는 데 사용할 수있는 유틸리티입니다.
  • Mono mkbundle , libmono가 있는 exe 및 모든 어셈블리를 단일 바이너리 패키지로 패키징합니다.
  • IL-Repack 은 몇 가지 추가 기능이있는 ILMerge의 FLOSS 대안입니다.

또한 사용하지 않는 코드를 제거하고 결과 어셈블리를 더 작게 만드는 Mono Linker 와 결합 할 수 있습니다 .

또 다른 가능성은 어셈블리 압축을 허용 할뿐만 아니라 dll을 exe에 바로 압축 할 수있는 .NETZ 를 사용 하는 것입니다. 위에서 언급 한 솔루션과의 차이점은 .NETZ가 이러한 솔루션을 병합하지 않고 별도의 어셈블리로 유지하지만 하나의 패키지로 압축된다는 것입니다.

.NETZ는 Microsoft .NET Framework 실행 파일 (EXE, DLL) 파일을 더 작게 만들기 위해 압축하고 압축하는 오픈 소스 도구입니다.


ILMerge 는 어셈블리에 관리 코드 만있는 경우 어셈블리를 하나의 단일 어셈블리로 결합 할 수 있습니다. 명령 줄 앱을 사용하거나 exe에 대한 참조를 추가하고 프로그래밍 방식으로 병합 할 수 있습니다. GUI 버전의 경우 Eazfuscator.Netz 모두 무료입니다. 유료 앱에는 BoxedAppSmartAssembly가 포함됩니다 .

관리되지 않는 코드로 어셈블리를 병합해야하는 경우 SmartAssembly를 제안 합니다. 나는 SmartAssembly가 아니라 다른 모든 것들과 함께 딸꾹질을 한 적이 없습니다 . 여기에서 필요한 종속성을 기본 exe에 대한 리소스로 포함 할 수 있습니다.

리소스에 dll을 포함하고 AppDomain의 Assembly에 의존하여 어셈블리가 관리되거나 혼합 모드에 있는지 걱정할 필요없이이 모든 작업을 수동으로 수행 할 수 있습니다 ResolveHandler. 이것은 관리되지 않는 코드가있는 어셈블리와 같은 최악의 경우를 채택하여 원 스톱 솔루션입니다.

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);
    };
}

여기서 핵심은 파일에 바이트를 쓰고 해당 위치에서로드하는 것입니다. 닭고기와 달걀 문제를 방지하려면 어셈블리에 액세스하기 전에 핸들러를 선언해야하며 로딩 (어셈블리 해결) 부품 내부의 어셈블리 멤버에 액세스 (또는 어셈블리를 처리해야하는 모든 것을 인스턴스화)하지 않도록해야합니다. 또한 GetMyApplicationSpecificPath()임시 파일은 다른 프로그램이나 사용자가 지우려고 시도 할 수 있으므로 임시 디렉토리가 아닌지 확인 하십시오 (프로그램이 dll에 액세스하는 동안 삭제되지는 않지만 적어도 귀찮은 일입니다. AppData는 좋습니다. 위치). 또한 매번 바이트를 작성해야하며 dll이 이미 거기에 있기 때문에 위치에서로드 할 수 없습니다.

관리되는 dll의 경우 바이트를 쓸 필요가 없지만 dll 위치에서 직접로드하거나 바이트를 읽고 메모리에서 어셈블리를로드합니다. 이 정도 :

    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.

어셈블리가 완벽하게 관리되지 않는 경우, 당신이 볼 수있는 링크 또는 같은 DLL을로드하는 방법에있다.


Jeffrey Richter발췌문 은 매우 좋습니다. 간단히 말해, 라이브러리를 임베디드 리소스로 추가하고 다른 것보다 먼저 콜백을 추가하십시오. 다음은 콘솔 앱에 대한 Main 메서드의 시작 부분에 넣은 코드 (그의 페이지의 주석에서 찾을 수 있음)입니다 (라이브러리를 사용하는 모든 호출이 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);
            }
        };

의 @Bobby의 asnwer 를 확장하려면 . 빌드 할 때 IL-Repack 을 사용 하여 모든 파일을 단일 어셈블리로 자동 패키징 하도록 .csproj를 편집 할 수 있습니다 .

  1. 너겟 ILRepack.MSBuild.Task 패키지를 다음과 같이 설치하십시오. Install-Package ILRepack.MSBuild.Task
  2. .csproj의 AfterBuild 섹션 편집

다음은 ExampleAssemblyToMerge.dll을 프로젝트 출력에 병합하는 간단한 샘플입니다.

<!-- 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>

DLL을 포함 된 리소스로 추가 한 다음 프로그램이 시작시 응용 프로그램 디렉터리에 압축을 풀도록 할 수 있습니다 (이미 있는지 확인한 후).

하지만 설정 파일은 만들기가 너무 쉬워서 그만한 가치가 없다고 생각합니다.

편집 :이 기술은 .NET 어셈블리를 사용하면 쉽습니다. .NET이 아닌 DLL을 사용하면 훨씬 더 많은 작업이 수행됩니다 (파일의 압축을 풀고 등록하는 등의 작업을 수행해야 함).


Another product that can handle this elegantly is SmartAssembly, at SmartAssembly.com . This product will, in addition to merging all dependencies into a single DLL, (optionally) obfuscate your code, remove extra meta-data to reduce the resulting file size, and can also actually optimize the IL to increase runtime performance. There is also some kind of global exception handling / reporting feature it adds to your software (if desired) that I didn't take the time to understand, but could be useful. I believe it also has a command line API so you can make it part of your build process.


Neither the ILMerge approach nor Lars Holm Jensen's handling the AssemblyResolve event will work for a plugin host. Say executable H loads assembly P dynamically and accesses it via interface IP defined in an separate assembly. To embed IP into H one shall need a little modification to Lars's code:

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;
};  

The trick to handle repeated attempts to resolve the same assembly and return the existing one instead of creating a new instance.

EDIT: Lest it spoil .NET's serialization, make sure to return null for all assemblies not embedded in yours, thereby defaulting to the standard behaviour. You can get a list of these libraries by:

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]);  }

and just return null if the passed assembly does not belong to IncludedAssemblies .


It may sound simplistic, but WinRar gives the option to compress a bunch of files to a self-extracting executable.
It has lots of configurable options: final icon, extract files to given path, file to execute after extraction, custom logo/texts for popup shown during extraction, no popup window at all, license agreement text, etc.
May be useful in some cases.


I use the csc.exe compiler called from a .vbs script.

In your xyz.cs script, add the following lines after the directives (my example is for the 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"

The ref, res and ico tags will be picked up by the .vbs script below to form the csc command.

Then add the assembly resolver caller in the Main:

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

...and add the resolver itself somewhere in the class:

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

        using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
        {
            Byte[] assemblyData = new Byte[stream.Length];
            stream.Read(assemblyData, 0, assemblyData.Length);
            return Assembly.Load(assemblyData);
        }

    }

I name the vbs script to match the .cs filename (e.g. ssh.vbs looks for ssh.cs); this makes running the script numerous times a lot easier, but if you aren't an idiot like me then a generic script could pick up the target .cs file from a drag-and-drop:

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

    'TAKE THE VBS SCRIPT NAME AS THE TARGET FILE NAME
    '################################################
    name_ = Split(wscript.ScriptName, ".")(0)

    'GET THE EXTERNAL DLL's AND ICON NAMES FROM THE .CS FILE
    '#######################################################
    Const OPEN_FILE_FOR_READING = 1
    Set objInputFile = fso.OpenTextFile(name_ & ".cs", 1)

    'READ EVERYTHING INTO AN ARRAY
    '#############################
    inputData = Split(objInputFile.ReadAll, vbNewline)

    For each strData In inputData

        if left(strData,7)="//+ref>" then 
            csc_references = csc_references & " /reference:" &         trim(replace(strData,"//+ref>","")) & " "
        end if

        if left(strData,7)="//+res>" then 
            csc_resources = csc_resources & " /resource:" & trim(replace(strData,"//+res>","")) & " "
        end if

        if left(strData,7)="//+ico>" then 
            csc_icon = " /win32icon:" & trim(replace(strData,"//+ico>","")) & " "
        end if
    Next

    objInputFile.Close


    'COMPILE THE FILE
    '################
    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)

.NET Core 3.0 natively supports compiling to a single .exe

The feature is enabled by the usage of the following property in your project file (.csproj):

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

This is done without any external tool.

See my answer for this question for further details.


It's possible but not all that easy, to create a hybrid native/managed assembly in C#. Were you using C++ instead it'd be a lot easier, as the Visual C++ compiler can create hybrid assemblies as easily as anything else.

Unless you have a strict requirement to produce a hybrid assembly, I'd agree with MusiGenesis that this isn't really worth the trouble to do with C#. If you need to do it, perhaps look at moving to C++/CLI instead.


Generally you would need some form of post build tool to perform an assembly merge like you are describing. There is a free tool called Eazfuscator (eazfuscator.blogspot.com/) which is designed for bytecode mangling that also handles assembly merging. You can add this into a post build command line with Visual Studio to merge your assemblies, but your mileage will vary due to issues that will arise in any non trival assembly merging scenarios.

You could also check to see if the build make untility NANT has the ability to merge assemblies after building, but I am not familiar enough with NANT myself to say whether the functionality is built in or not.

There are also many many Visual Studio plugins that will perform assembly merging as part of building the application.

Alternatively if you don't need this to be done automatically, there are a number of tools like ILMerge that will merge .net assemblies into a single file.

The biggest issue I've had with merging assemblies is if they use any similar namespaces. Or worse, reference different versions of the same dll (my problems were generally with the NUnit dll files).

참고URL : https://stackoverflow.com/questions/189549/embedding-dlls-in-a-compiled-executable

반응형