You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Framework 4.8手动合并DLL至单EXE的实操疑问(含PDFium)

.NET Framework 手动把DLL嵌入EXE的分步实操指南

一、先从单个托管DLL入手(三步法落地)

1. 修改.csproj文件

直接打开项目的.csproj文件,添加一段MSBuild任务,指定要嵌入的托管DLL:

<Target Name="EmbedSingleAssembly" BeforeTargets="ResolveReferences">
  <ItemGroup>
    <!-- 替换成你要嵌入的DLL路径,比如NuGet包路径:$(SolutionDir)packages\YourPackage.x.x.x\lib\net4xx\YourDll.dll -->
    <EmbeddedResource Include="path\to\YourManagedDll.dll">
      <LogicalName>YourManagedDll.dll</LogicalName>
    </EmbeddedResource>
  </ItemGroup>
</Target>

你之前那段代码崩溃,大概率是遍历+CultureInfo判断的逻辑存在潜在空引用,先简化成单个DLL的处理,避免不必要的遍历。

2. 编写DLL加载类

在项目里新建EmbeddedAssemblyLoader.cs,代码如下,用来在程序启动时从资源中加载嵌入的DLL:

using System;
using System.Reflection;

public static class EmbeddedAssemblyLoader
{
    public static void Init()
    {
        AppDomain.CurrentDomain.AssemblyResolve += ResolveEmbeddedAssembly;
    }

    private static Assembly ResolveEmbeddedAssembly(object sender, ResolveEventArgs args)
    {
        var assemblyName = new AssemblyName(args.Name);
        // 资源名要和csproj里的LogicalName完全一致
        var resourceName = $"{typeof(EmbeddedAssemblyLoader).Namespace}.{assemblyName.Name}.dll";
        
        using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
        {
            if (stream == null) return null;
            
            var assemblyBytes = new byte[stream.Length];
            stream.Read(assemblyBytes, 0, assemblyBytes.Length);
            return Assembly.Load(assemblyBytes);
        }
    }
}

如果你的DLL不在当前命名空间下,需要调整resourceName的格式,确保和嵌入时的LogicalName匹配。

3. 启动时初始化加载器

  • WPF项目:打开App.xaml.cs,在OnStartup方法中添加初始化代码:
protected override void OnStartup(StartupEventArgs e)
{
    EmbeddedAssemblyLoader.Init();
    base.OnStartup(e);
}
  • WinForms项目:打开Program.cs,在Application.Run之前添加:
static void Main()
{
    EmbeddedAssemblyLoader.Init();
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new MainForm());
}

二、原生DLL(如PDFium)的处理方法

原生DLL无法通过程序集解析事件加载,需要先从资源中提取到临时目录,再进行加载:

1. 嵌入原生DLL

修改csproj文件,添加原生DLL作为嵌入资源:

<EmbeddedResource Include="path\to\pdfium.dll">
  <LogicalName>pdfium.dll</LogicalName>
</EmbeddedResource>

2. 提取并加载原生DLL

在EmbeddedAssemblyLoader中新增提取加载方法:

using System.IO;
using System.Runtime.InteropServices;

public static string ExtractAndLoadNativeDll(string dllName)
{
    var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
    Directory.CreateDirectory(tempDir);
    var dllPath = Path.Combine(tempDir, dllName);
    
    using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(dllName))
    {
        if (stream == null) throw new FileNotFoundException("找不到嵌入的原生DLL资源", dllName);
        
        using (var fileStream = new FileStream(dllPath, FileMode.Create))
        {
            stream.CopyTo(fileStream);
        }
    }
    
    // 调用Windows API加载原生DLL
    LoadLibrary(dllPath);
    return dllPath;
}

[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr LoadLibrary(string lpFileName);

然后在程序启动时调用:

// 放在Init方法之后执行
EmbeddedAssemblyLoader.ExtractAndLoadNativeDll("pdfium.dll");

三、解决你之前遇到的问题

  1. csproj代码崩溃:原代码的CultureInfo判断容易触发空引用,先从单个DLL开始,逻辑简单不易出错,等单个DLL嵌入成功后,再扩展遍历多个DLL的逻辑。
  2. 未处理包文件夹中的DLL:要嵌入NuGet包中的DLL,直接在csproj里用$(SolutionDir)packages路径定位即可,比如ImageMagick的配置:
<EmbeddedResource Include="$(SolutionDir)packages\ImageMagick.NET-Q16-AnyCPU.x.x.x\lib\net472\Magick.NET-Q16-AnyCPU.dll">
  <LogicalName>Magick.NET-Q16-AnyCPU.dll</LogicalName>
</EmbeddedResource>
  1. Fody/Costura失效:旧版Costura对原生DLL支持较差,手动处理反而更可控;如果非要用工具,建议尝试Costura的最新版本。

内容的提问来源于stack exchange,提问作者Gopher Rabbit

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 08:13:22