programing

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

mailnote 2023. 5. 26. 22:08
반응형

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

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

보통 DLL을 밖에 두고 설치 프로그램에서 모든 것을 처리하는 것은 괜찮지만, 직장에서 이런 질문을 하는 사람이 몇 명 있었는데 솔직히 잘 모르겠습니다.

코스투라를 사용하는 것을 강력히 추천합니다.FODY - 어셈블리에 리소스를 내장하는 가장 쉽고 최고의 방법입니다.NuGet 패키지로 제공됩니다.

Install-Package Costura.Fody

프로젝트에 추가한 후 출력 디렉터리에 복사된 모든 참조를 기본 어셈블리에 자동으로 포함합니다.프로젝트에 대상을 추가하여 내장된 파일을 치료할 수 있습니다.

Install-CleanReferencesTarget

또한 PDB를 포함할지, 특정 어셈블리를 제외할지, 또는 어셈블리를 즉시 추출할지 여부를 지정할 수 있습니다.관리되지 않는 어셈블리도 지원되는 것으로 알고 있습니다.

갱신하다

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

업데이트 2

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

Visual Studio에서 프로젝트를 마우스 오른쪽 버튼으로 클릭하고 Project Properties -> Resources -> Add Resources -> Add Existing File...을 선택한 다음 아래 코드를 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++ 윈도우 dll을 C# 응용 프로그램 exe로 병합하려면 어떻게 해야 합니까?

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

  • ILMerge는 여러 .NET 어셈블리를 하나의 어셈블리로 병합하는 데 사용할 수 있는 유틸리티입니다.
  • Monomkbundle은 libmono가 포함된 exe와 모든 어셈블리를 단일 이진 패키지로 패키지화합니다.
  • IL-Repack은 ILMerge의 FLOSS 대안으로, 몇 가지 추가 기능이 있습니다.

또한 는 모노 링커와 결합할 수 있으며, 이는 사용되지 않는 코드를 제거하므로 결과 어셈블리를 더 작게 만들 수 있습니다.

.NETZ를 사용하면 어셈블리를 압축할 수 있을 뿐만 아니라 dll을 exe로 직접 패킹할 수도 있습니다.위에서 언급한 솔루션과의 차이점은 .NETZ가 솔루션을 병합하지 않고 별도의 어셈블리로 유지되지만 하나의 패키지로 포장된다는 것입니다.

.NETZ는 마이크로소프트 .NET Framework 실행 파일(EXE, DLL)을 압축하고 압축하여 압축하는 오픈 소스 도구입니다.

어셈블리에 관리 코드만 있는 경우 ILMerge는 어셈블리를 하나의 어셈블리로 결합할 수 있습니다.명령줄 앱을 사용하거나 exe에 참조를 추가하여 프로그래밍 방식으로 병합할 수 있습니다.GUI 버전의 경우 Eazfulcator가 있으며, 또한넷츠 둘 다 무료입니다.유료 앱에는 박스형 앱과 스마트 어셈블리가 포함됩니다.

관리되지 않는 코드로 어셈블리를 병합해야 한다면 SmartAssembly를 제안합니다.저는 스마트 어셈블리를 제외하고는 딸꾹질을 한 적이 없습니다.여기서 필요한 종속성을 기본 exe의 리소스로 포함할 수 있습니다.

으로 이 작업을 할 수 .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을 로드하는 방법에 대해 이 링크 또는 이 링크를 볼 수 있습니다.

.NET Core 3.0은 기본적으로 단일 .exe로의 컴파일을 지원합니다.

이 기능은 프로젝트 파일(.csproj)에서 다음 속성을 사용하여 실행할 수 있습니다.

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

이 작업은 외부 도구 없이 수행됩니다.

자세한 내용은 이 질문에 대한대답을 참조하십시오.

제프리 리히터의 발췌본은 매우 훌륭합니다.즉, 라이브러리를 내장된 리소스로 추가하고 콜백을 먼저 추가합니다.다음은 콘솔 앱의 메인 메서드를 시작할 때 입력한 코드 버전입니다(그 페이지의 주석에 있음). 라이브러리를 사용하는 모든 호출이 메인과는 다른 메서드인지 확인하십시오.

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의 답변을 확장합니다..csproj를 편집하여 IL-Repack을 사용하여 빌드할 때 모든 파일을 하나의 어셈블리로 자동으로 패키징할 수 있습니다.

  1. 너겟 IL 리팩입니다.MS 드빌포. 패지태(함) 태스크 패키지Install-Package ILRepack.MSBuild.Task
  2. .csproj의 After Build 섹션 편집

다음은 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을 자동으로 포함합니다(수동 작업 필요 없음, 컴파일 시 모든 작업 수행).

는 여기서 ILMerge, ILRepack 또는 Jeffrey Richcher 방법을 사용하라는 답변을 많이 읽었지만 WPF 애플리케이션에서 작동하지도 않고 사용하기도 쉽지도 않았습니다.

DLL이 많은 경우 필요한 DLL을 exe에 수동으로 포함하기가 어려울 수 있습니다.내가 찾은 가장 좋은 방법은 여기 StackOverflow에서 Wegged에 의해 설명되었습니다.

Copy는 명확성을 위해 답변을 여기에 붙였습니다(Wegged에 대한 모든 공).


을 당신의 니추합다에 추가하세요..csproj파일 이름:

<Target Name="AfterResolveReferences">
  <ItemGroup>
    <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
      <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
    </EmbeddedResource>
  </ItemGroup>
</Target>

으로 만들기Program.cs다음과 같이 표시:

[STAThreadAttribute]
public static void Main()
{
    AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
    App.Main();
}

추가합니다.OnResolveAssembly방법:

private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
{
    Assembly executingAssembly = Assembly.GetExecutingAssembly();
    AssemblyName assemblyName = new AssemblyName(args.Name);

    var path = assemblyName.Name + ".dll";
    if (assemblyName.CultureInfo.Equals(CultureInfo.InvariantCulture) == false) path = String.Format(@"{0}\{1}", assemblyName.CultureInfo, path);

    using (Stream stream = executingAssembly.GetManifestResourceStream(path))
    {
        if (stream == null) return null;

        var assemblyRawBytes = new byte[stream.Length];
        stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
        return Assembly.Load(assemblyRawBytes);
    }
}

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

하지만 설정 파일은 만들기가 너무 쉬워서 그럴 가치가 없을 것 같습니다.

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

이를 우아하게 처리할 수 있는 또 다른 제품은 SmartAssembly.com 의 SmartAssembly입니다.이 제품은 모든 종속성을 단일 DLL로 병합하는 것 외에도 (선택적으로) 코드를 난독화하고, 추가 메타데이터를 제거하여 결과 파일 크기를 줄일 수 있으며, 실제로 IL을 최적화하여 런타임 성능을 향상시킬 수 있습니다.

또한 소프트웨어에 추가되는 글로벌 예외 처리/보고 기능(원하는 경우)도 유용할 수 있습니다.명령줄 API도 포함되어 있어 빌드 프로세스의 일부로 만들 수 있다고 생각합니다.

ILMerge 접근 방식이나 Lars Holm Jensen의 Assembly Resolve 이벤트 처리는 플러그인 호스트에서 작동하지 않습니다.H 실행 파일어셈블리 P를 동적으로 로드하고 별도 어셈블리에 정의된 인터페이스 IP를 통해 액세스한다고 가정합니다.IP를 Hone에 포함시키려면 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;
};  

동일한 어셈블리를 확인하고 새 인스턴스를 만드는 대신 기존 어셈블리를 반환하려는 반복적인 시도를 처리하는 방법입니다.

편집: 상하지 않도록 합니다.NET의 직렬화는 사용자의 어셈블리에 포함되지 않은 모든 어셈블리에 대해 null을 반환하여 기본 동작으로 설정해야 합니다.다음을 통해 이러한 라이브러리 목록을 얻을 수 있습니다.

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

된 어셈블리가 null에 을 반환합니다.IncludedAssemblies.

단순하게 들릴 수도 있지만 WinRar는 파일 묶음을 자동 압축 해제 실행 파일로 압축하는 옵션을 제공합니다.
최종 아이콘, 지정된 경로로 파일 추출, 추출 후 실행할 파일, 추출 중 팝업에 대한 사용자 지정 로고/텍스트, 팝업 창이 전혀 표시되지 않음, 라이센스 계약 텍스트 등 다양한 옵션이 있습니다.
경우에 따라 유용할 수 있습니다.

.NET Core 3.0을 사용하는 경우

PublishSingleFile 속성이 있는 dotnet publish 명령을 사용하여 이 작업을 수행할 수 있습니다.

도트넷 게시 -r win-x64-c 릴리스 /p:단일 파일=true 게시

단 한 가지 단점은 큰 크기의 EXE 파일이 하나 있다는 것입니다.

.vbs 스크립트에서 호출된 csc.exe 컴파일러를 사용합니다.

xyz.cs 스크립트에서 지침 뒤에 다음 행을 추가합니다(예: 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"

ref, res 및 ico 태그는 아래의 .vbs 스크립트를 통해 선택되어 csc 명령을 형성합니다.

그런 다음 기본:에서 어셈블리 확인 프로그램 호출자를 추가합니다.

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

...그리고 클래스의 어딘가에 해결 프로그램 자체를 추가합니다.

정적 어셈블리 CurrentDomain_AssemblyResolve(개체 보낸 사람, ResolveEventArgs 인수){문자열 resourceName = 새 AssemblyName(인수)입니다.이름). 이름 + ".dll";
(var stream = 어셈블리)를 사용합니다.실행 어셈블리를 가져옵니다.GetManifestResourceStream(resourceName){바이트[] 어셈블리데이터 = 새 바이트[스트림].길이];
개울.(어셈블리 데이터, 0, 어셈블리 데이터)를 읽습니다.길이);
리턴 어셈블리.하중(조립 데이터);
}
}

.cs 파일 이름과 일치하도록 vbs 스크립트의 이름을 지정합니다(예: ssh.vbs가 ssh.cs 을 찾습니다). 이렇게 하면 스크립트 실행이 훨씬 쉬워지지만, 저처럼 바보가 아니라면 일반 스크립트가 드래그 앤 드롭에서 대상 .cs 파일을 선택할 수 있습니다.

Dim name_,oShell,fsoSetoShell = CreateObject("Shell").응용프로그램")set fso = CreateObject("스크립팅").fileSystemObject")
'VBS 스크립트 이름을 대상 파일 이름으로 사용합니다.''################################################name_ = 분할(wscript).스크립트 이름, "."(0)
'.CS 파일에서 외부 DLL 및 아이콘 이름 가져오기''#######################################################상수 OPEN_FILE_FOR_READING = 1objInputFile = fso를 설정합니다.OpenTextFile(이름_ & .cs", 1)
'모든 것을 배열로 읽는 것''#############################inputData = 분할(objInputFile).모두 읽기, vbNewline)
각 strData 입력 데이터에 대해
왼쪽에 있는 경우(strData,7)="//+ref>" 그러면csc_references = csc_references & "/references:" & trim(strData,//+ref>",") & "끝이 나다
왼쪽에 있는 경우(strData,7)="//+res>" 그러면csc_cisco = csc_cisco & " /resource:" & trim(strData,//+res>",") & ""끝이 나다
남겨진 경우(strData,7)="//+ico>",csc_icon = " /win32icon:" & trim(strData,//+ico>",") & ""끝이 나다다음 분.
objInputFile.가까운.

'파일 컴파일하기''################oShell.ShellExecute "c:\windows\microsoft.net \filename\v3".5\csc.exe", "/csc:1 /target:exe" &csc_dferences &csc_icon &" &name_&.cs", "", "runas", 2

W스크립트.종료(0)

C#에서 하이브리드 네이티브/관리 어셈블리를 만드는 것은 가능하지만 그렇게 쉽지는 않습니다.C++를 대신 사용했다면 Visual C++ 컴파일러가 다른 것처럼 쉽게 하이브리드 어셈블리를 만들 수 있기 때문에 훨씬 더 쉬울 것입니다.

하이브리드 어셈블리를 생산해야 하는 엄격한 요구 사항이 없는 한, 저는 이것이 C#에 대한 수고를 들일 가치가 없다는 MusiGenesis의 의견에 동의합니다.필요한 경우 C++/CLI로 이동하는 것을 검토하십시오.

일반적으로 설명하는 것처럼 어셈블리 병합을 수행하려면 빌드 후 도구가 필요합니다.Eazfulcator(Eazfulcator)라는 무료 도구가 있습니다.어셈블리 병합도 처리하는 바이트 코드 망글링을 위해 설계된 blogspot.com/) .Visual Studio를 사용하여 빌드 후 명령행에 이 명령을 추가하여 어셈블리를 병합할 수 있지만, 중요하지 않은 어셈블리 병합 시나리오에서 발생하는 문제로 인해 마일리지가 달라집니다.

빌드 make untilt가 빌드 후 어셈블리를 병합할 수 있는지 확인할 수도 있지만, 이 기능이 내장되어 있는지 여부를 말할 수 있을 정도로 NANT에 익숙하지 않습니다.

또한 응용프로그램 빌드의 일부로 어셈블리 병합을 수행하는 많은 Visual Studio 플러그인이 있습니다.

이 작업을 자동으로 수행할 필요가 없는 경우 ILMerge와 같은 여러 도구가 .net 어셈블리를 하나의 파일로 병합합니다.

어셈블리를 병합할 때 가장 큰 문제는 어셈블리가 비슷한 네임스페이스를 사용하는지 여부입니다.또는 동일한 dll의 다른 버전을 참조하십시오(일반적으로 NUnit dll 파일에 문제가 있었습니다).

사용해 보십시오.

https://github.com/ytk2128/dll-merger

여기서 32비트 dll/exe를 모두 병합할 수 있습니다. 심지어 ".net"이 아닌 dll도 병합할 수 있습니다. 그래서 예를 들어, 저는 병합하는 것보다 낫습니다.

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

반응형