Cách gộp các Dll vào ứng dụng WPF

Làm sao để gộp các dll và exe làm một? Như bài trước tôi đã chia sẻ, chúng ta hoàn toàn có cách, một cách khá đơn giản để có thể gom các dll và exe làm một. Nhưng chúng ta có một vấn đề với ứng dụng WPF. Tôi sẽ chia sẻ sau đây.

Merge dll to wpf application

 

Như bài viết trước về ILMerge tool. Chúng ta có thể merge tất cả các tập tin IL lại với nhau (DLL, EXE), nhưng nếu chúng ra merge các DLL với một ứng ứng dụng WPF thì dù merge thành công nhưng chúng ta không thể chạy ứng dụng này lên được.

Cụ thể là khi bạn viết một ứng dụng WPF, trong ứng dụng này bạn có sử dụng một số thành phần liên quan đến UI hay XAML từ một DLL khác (là của bạn dev hay 3rd đều như nhau); sau đó bạn dùng tool ILMerge để gom các DLL đó vào ứng dụng của bạn (tập tin EXE); Bạn sẽ không nhận được lỗi khi merge. Nhưng sau đó ứng dụng của bạn không thể chạy được nữa. Bạn sẽ nhận được một số Exception liên quan đến XAML như XamlParseException.

Vấn đề này xảy ra chỉ khi framework cố tìm các thành phần XAML mà không tìm thấy. Đơn giản vì XAML có một cơ chế resolve DLL khác – chúng đi tìm và cố load các DLL thực thay vì tìm trong CURRENT EXECUTABLE FILE.

Chúng ta sẽ giải quyết vấn đề này như sau:

  • Không dùng ILMerge mà thay vào đó chúng ta sẽ NHÚNG các dll vào WPF Application như một Embedded resources. Nghĩa là các dll sau khi build sẽ được gom hết vào tập tin EXE và chúng được xem như một tập tin bình thường tương tự như tập tin hình ảnh hay tập tin Office hay tập tin video…
  • Đăng ký AssemblyResolve event để can thiệp vào quá trình tìm các tập tin dll. Tức khi ứng dụng tìm không thấy các dll cần thiết, ứng dụng sẽ trigger một event là AssemblyResolve, khi nhận được event này chúng ta sẽ tìm đúng các tập tin nhúng trước đó để trả về cho ứng dụng.

Merge dll to WPF application solution

 

Cách thực hiện như sau:

  • Đăng ký build event cho project để tìm các reference dll sau đó add tất cả các dll đó vào project như một Embedded resources. Thay vì các bạn phải add các reference dll thủ công trên Visual Studio thì bước này giúp các bạn quên đi việc add các dll đó vào project trước khi build. Tức sau khi bạn hoàn thành bước này, bạn cứ code như bình thường – quên đi Embedded resources.
  • Xử lý cho sự kiện AssemblyResolve. Trong sự kiện này bạn sẽ nhận được input là ResolveEventArgs. Bằng cách nào đó chúng ta sẽ biết ứng dụng muốn tìm dll nào. Sau đó chúng ta sẽ dựa vào thông tin dll ta sẽ đọc Embedded resources và trả về cho ứng dụng.

Sau đây là các bước thực hiện. Tôi có một solution, solution này có một project là Library project (DataUtilities.csproj) và một là WPF application (DataCollector.csproj).

Mở file DataCollector.csproj tìm đến dòng sau:

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

Chúng ta sẽ thêm một Target mới vào ngay sau dòng trên. Nếu ngay sau dòng trên đã có một Target khác, chúng ta sẽ thêm vào ngay sau Target đó. Project trong ví dụ đã có EnsureNuGetPackageBuildImports, vì vậy tôi tiếp tục thêm hai Target mới với tên EmbedReferencedAssemblies _CopyFilesMarkedCopyLocal ngay sau target kia.

  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
    <PropertyGroup>
      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
    </PropertyGroup>
  </Target>

  <Target Name="EmbedReferencedAssemblies" AfterTargets="ResolveAssemblyReferences">
    <ItemGroup>
      <AssembliesToEmbed Include="@(ReferenceCopyLocalPaths)" Condition="'%(Extension)' == '.dll'" />
      <EmbeddedResource Include="@(AssembliesToEmbed)">
        <LogicalName>%(AssembliesToEmbed.DestinationSubDirectory)%(AssembliesToEmbed.Filename)%(AssembliesToEmbed.Extension)</LogicalName>
      </EmbeddedResource>
    </ItemGroup>
    <Message Importance="high" Text="Embedding: @(AssembliesToEmbed->'%(DestinationSubDirectory)%(Filename)%(Extension)', ', ')" />
  </Target>

  <Target Name="_CopyFilesMarkedCopyLocal" />

Sau đó lưu file trên lại, ta đã xong bước 1.
Bước 2 chúng ta sẽ đăng ký handle AssemblyResolve event. Mở file App.xaml.cs trong WPF application project. Sửa thành như sau:

    public partial class App : Application
    {
        private void OnStartup(object sender, StartupEventArgs e)
        {
            AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
        }
        
        static Assembly ResolveAssembly(object sender, ResolveEventArgs args)
        {
            Assembly parentAssembly = Assembly.GetExecutingAssembly();

            var name = args.Name.Substring(0, args.Name.IndexOf(',')) + ".dll";
            var resourceName = parentAssembly.GetManifestResourceNames()
                .First(s => s.EndsWith(name));

            using (Stream stream = parentAssembly.GetManifestResourceStream(resourceName))
            {
                byte[] block = new byte[stream.Length];
                stream.Read(block, 0, block.Length);
                return Assembly.Load(block);
            }
        }
    }

Thế là xong, rebuild solution để xem kết quả.

Để rõ hơn, các bạn có thể tải về project ví dụ ở link sau đây. Sau khi tải về các bạn sẽ có một solution gồm 3 project trong đó có một project minh họa cho bài viết này một project minh họa việc sử dụng ILMerge. Nhớ rebuild solution để tải về các Nuget package cần thiết.

Download

Phạm Tuân

IL và MSIL là gì?
Preprocessor Directives trong C# là gì?
Powershell là gì và các lệnh hữu ích cho bạn
Hãy bình luận trực tiếp ở đây để được trả lời nhanh hơn. 3 bình luận.

Trả lời