在.NET 9 NativeAOT控制台程序中嵌入文件并在代码中访问的实现方法
在.NET 9 NativeAOT控制台程序中嵌入文件并在代码中访问的实现方法
嘿,刚好我前段时间在做.NET9 NativeAOT项目时踩过这个坑,给你整理两个实用的方案,完美适配你的需求:
方法一:编译时生成静态访问器(跨平台推荐)
这个是.NET 8+ NativeAOT官方支持的特性,能让编译器自动生成静态类来直接访问嵌入的资源,完全替代原来的Assembly.GetManifestResourceStream,而且跨平台可用。
步骤1:修改.csproj配置
把你原来的Content节点改成EmbeddedResource,并且开启静态访问器生成:
<ItemGroup> <EmbeddedResource Include="icon.ico"> <GenerateEmbeddedResourceAccessor>true</GenerateEmbeddedResourceAccessor> <!-- 可选:指定逻辑名,让生成的类名更规范,比如这里设置后访问类会对应`你的项目命名空间.Resources.icon_ico` --> <LogicalName>MyApp.Resources.icon.ico</LogicalName> </EmbeddedResource> </ItemGroup>
同时可以删掉原来的<CopyToOutputDirectory>,因为我们要把文件嵌入exe,不是复制到输出目录。
步骤2:代码中直接访问资源
编译器会根据你的项目命名空间,自动生成一个静态资源访问类。比如你的项目根命名空间是MyNativeAOTConsole,那么直接通过以下代码就能拿到资源的字节数组或流:
// 获取资源的字节数组 var iconBytes = MyNativeAOTConsole.Resources.icon_ico.Bytes; // 或者打开资源流(用完记得释放,最好用using包裹) using var iconStream = MyNativeAOTConsole.Resources.icon_ico.OpenStream(); // 举个测试例子:把提取的图标保存到本地 File.WriteAllBytes("extracted_from_aot.ico", iconBytes);
提示:如果不知道生成的类名,可以看项目的obj目录下的generated文件,或者直接在代码里输入你的命名空间+
.Resources,IDE会自动补全资源对应的属性名。
方法二:读取Windows PE资源(仅Windows平台)
因为你已经在.csproj里设置了<ApplicationIcon>icon.ico</ApplicationIcon>,这个图标其实已经作为Windows PE资源嵌入到exe里了。如果你只需要在Windows平台运行,可以用Windows API直接读取这个资源,不需要额外修改csproj。
代码实现
直接在项目里加这段P/Invoke代码,通过Windows API提取PE资源中的图标:
using System.Runtime.InteropServices; namespace YourProjectNamespace { internal static class ResourceHelper { // Windows API声明 [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr GetModuleHandle(string? lpModuleName); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr FindResource(IntPtr hModule, IntPtr lpName, IntPtr lpType); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr LockResource(IntPtr hResData); [DllImport("kernel32.dll", SetLastError = true)] private static extern uint SizeofResource(IntPtr hModule, IntPtr hResInfo); // 图标资源的类型标识 private static readonly IntPtr RT_ICON = (IntPtr)3; // 应用程序图标的默认ID private static readonly IntPtr IDI_APPLICATION = (IntPtr)32512; public static byte[] GetEmbeddedAppIcon() { // 获取当前进程的模块句柄(即当前exe) var hModule = GetModuleHandle(null); if (hModule == IntPtr.Zero) throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); // 查找图标资源 var hResInfo = FindResource(hModule, IDI_APPLICATION, RT_ICON); if (hResInfo == IntPtr.Zero) throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); // 加载并锁定资源 var hResData = LoadResource(hModule, hResInfo); var pResData = LockResource(hResData); var resourceSize = SizeofResource(hModule, hResInfo); // 把非托管内存的资源拷贝到托管字节数组 var iconBytes = new byte[resourceSize]; unsafe { Marshal.Copy((nint)pResData, iconBytes, 0, (int)resourceSize); } return iconBytes; } } }
然后在Main方法里调用:
var extractedIcon = ResourceHelper.GetEmbeddedAppIcon(); File.WriteAllBytes("pe_extracted_icon.ico", extractedIcon);
注意事项
- 方法一是跨平台的,如果你以后要编译到Linux/macOS,优先用这个方法;方法二仅支持Windows,依赖Windows API。
- 如果你嵌入的不是ico,而是txt、json这类通用文件,方法一完全通用,只要修改
EmbeddedResource的Include路径即可。 - 如果你之前设置了
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>,记得删掉,因为我们要的是把文件嵌入exe内部,不是复制到输出目录(NativeAOT单文件发布后,复制的文件会被打包到单文件里,但访问方式是读文件系统,不是嵌入资源)。
内容来源于stack exchange
相关产品推荐
相关产品推荐

