카테고리 보관물: C#

C#

컴파일 된 실행 파일에 DLL 포함 파일에 포함시켜 배포 할

기존 DLL을 컴파일 된 C # 실행 파일에 포함시켜 배포 할 수있는 파일이 하나만있을 수 있습니까? 가능하다면 어떻게 할 것인가?

일반적으로 DLL을 외부에두고 설치 프로그램이 모든 것을 처리하도록하는 것이 멋지지만 직장에서 나에게 이것을 요청한 사람이 몇 명있어서 정직하게 알지 못합니다.



답변

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

Install-Package Costura.Fody

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

Install-CleanReferencesTarget

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

최신 정보

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

업데이트 2

최신 Fody 버전의 경우 MSBuild 16 (Visual Studio 2019)이 있어야합니다. Fody 버전 4.2.1은 MSBuild 15를 수행합니다 (참조 : Fody는 MSBuild 16 이상에서만 지원됩니다. 현재 버전 : 15 )


답변

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/


답변

실제로 관리되는 어셈블리 인 경우 ILMerge 를 사용할 수 있습니다 . 네이티브 DLL의 경우 좀 더해야 할 일이 있습니다.

또한 참조 : 어떻게 C ++ 윈도우는 C # 응용 프로그램 exe 인에 병합 DLL 수 있습니까?


답변

예. .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에 액세스하는 동안 삭제되지는 않지만 최소한 성가신 것입니다) 위치). 또한 매번 바이트를 작성해야한다는 것을 주목하십시오 .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>