Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

C# NativeAOT: How to Embed Files in .exe?

Learn how to embed and access files in a C# NativeAOT .exe without ResourceManager using byte arrays, .rc files, and P/Invoke.
Illustration of a C# NativeAOT .exe file being embedded with binary data using byte arrays and .rc files, with a no ResourceManager symbol behind it Illustration of a C# NativeAOT .exe file being embedded with binary data using byte arrays and .rc files, with a no ResourceManager symbol behind it
  • 🚀 NativeAOT compiles C# right to machine code. This means quick startup times and self-contained programs.
  • 🔒 NativeAOT turns off features that use a lot of reflection. So, common ways to load resources, like ResourceManager, do not work.
  • ⛔ Standard ways to embed files do not work in NativeAOT. This is because it removes metadata and has fewer runtime features.
  • ⚙️ Using Win32 .rc files to embed resources, and then getting them with P/Invoke, is the most reliable way for bigger files.
  • 🧰 Programs that run on different systems need other ways to embed files. This is because Win32 resources only work on Windows.

C# developers looking at NativeAOT (Ahead-of-Time Compilation) often find a problem. They need to know how to embed files in the program. This is because many common .NET APIs, like ResourceManager or reflection, do not work in NativeAOT. This guide talks about modern, proven ways to embed and get resources from compiled .exe files. This helps you release strong, self-contained programs for things like DevOps, air-gapped systems, and apps sensitive to security.


What Is NativeAOT and Why It Matters

NativeAOT (short for Native Ahead-of-Time Compilation) makes your C# app a true native program. It does this by compiling code right into machine code when you build it. This is a big change from the usual .NET way. That way uses a Just-In-Time (JIT) compiler and needs a big runtime. A NativeAOT-compiled .exe is self-contained. It does not need the .NET runtime on the system where it runs.

NativeAOT is very important for:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

  • CLI utilities that need quick startup
  • Tools for limited or very important places where no runtime can be installed
  • Apps sent out as single files (SLSBs) with no dependency problems
  • Situations that need less room for attacks or hiding source code

Microsoft has a full guide in its NativeAOT documentation.


Why Embedding Files Is a Challenge with NativeAOT

By default, .NET allows you to embed resources using tools like ResourceManager. You can also read files with Assembly.GetManifestResourceStream. But these use a lot of reflection and runtime metadata. And these are greatly cut back in NativeAOT.

NativeAOT has these limits:

  • ❌ No ResourceManager support
  • ❌ Limited or disabled Assembly metadata
  • ❌ No runtime dynamic method generation or late binding
  • ❌ Very limited reflection support (if any)

This means common .NET resource methods will not work. Instead, you will have to use lower-level ways. These are more like how native C/C++ apps embed and get binary data.

This has effects for developers used to modern .NET's easy features. But it also gives better performance and better security.


Scenarios Where Embedding Makes Sense

Knowing when to embed files is important for getting the most from NativeAOT. Here are actual situations where embedding files inside your .exe makes sense:

  • 💡 Offline command-line tools used for automation. Here, external config files are hard to send or change.
  • 🔐 Security apps, like license checkers or digital-signature tools. Here, embedded certificates or data stop changes.
  • 🛰️ Air-gapped systems (like in finance, defense, or aviation) that need fully self-contained programs.
  • 🚀 Quick cold-start tools, needed for quick tasks like DevOps pipelines or container startups.
  • 📦 Single-file programs for easy sharing through scripts or platforms like GitHub Releases.

These situations gain from fixed behavior, fast startup, and good portability. These are key features of NativeAOT.


Ways to Embed Files in NativeAOT

NativeAOT's limits mean you must think another way. Here is a comparison of working ways to embed and get files:

Method Platform File Size Limit Complexity Use Case
Byte Arrays in C# Cross-platform Small (<10KB) Low Tiny assets like version info or flags
Win32 .rc + .res Windows-only Large (<10MB or more) Medium Configs, models, certs
Custom Tools (e.g. codegen) Cross-platform Varies High When all else fails or for Linux/mac support

Let us look at these ways.


Method 1: Embedding Small Files as Byte Arrays (Simplest Option)

Putting small pieces of data right into your C# source code as byte arrays is the easiest way. It works on all platforms that NativeAOT supports. Here is how:

Example:

static readonly byte[] LicenseData = new byte[]
{
    0x50, 0x4B, 0x03, 0x04, // Example binary data
    0x0A, 0x00
};

Pros:

  • ✅ Simple and effective for very small files
  • ✅ Portable across all supported platforms (Windows, Linux, macOS)
  • ✅ Needs no special build tools or outside things

Cons:

  • ❌ Difficult to manage manually for files over a few KB
  • ❌ Makes source files too big and hard to read
  • ❌ Needs you to rebuild the project for every change

Best for embedding:

  • Build information
  • Simple license text
  • Small default config payloads

Method 2: Embedding Files via Win32 Resource Scripts (.rc Files)

For medium to large items, like certificates, JSON settings, or binary models, putting them in as native resources is a good idea. But this only works on Windows.

Step-by-Step Guide

Step 1: Create a Resource Script

Create a new file named resources.rc and add:

MYDATA RCDATA "myconfig.json"
  • MYDATA is the resource name you’ll use later.
  • RCDATA shows this is raw binary data.
  • "myconfig.json" is the file to embed.

Step 2: Compile .rc into .res

Using the Windows SDK Developer Command Prompt:

rc resources.rc

This creates a compiled resources.res file that packs myconfig.json.

Step 3: Reference the Resource in .csproj

Edit your project’s .csproj file:

<ItemGroup>
  <Win32Resource Include="resources.res" />
</ItemGroup>

This tells MSBuild to link the .res file into your final .exe binary.

Further reading: Working with native resources in Win32


Method 3: Accessing Embedded Resources with P/Invoke

Resource data packed with .rc files cannot be used right away like normal files. Instead, you use the Windows API to get it. Luckily, C# can do this with P/Invoke.

Required Win32 APIs

[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr FindResource(IntPtr hModule, string lpName, string lpType);

[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo);

[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr LockResource(IntPtr hResData);

[DllImport("kernel32.dll", SetLastError = true)]
static extern uint SizeofResource(IntPtr hModule, IntPtr hResInfo);

Practical Usage

public static byte[] LoadEmbeddedResource(string resourceName)
{
    IntPtr hModule = Process.GetCurrentProcess().Handle;
    IntPtr hResInfo = FindResource(hModule, resourceName, "RCDATA");

    if (hResInfo == IntPtr.Zero)
        throw new Exception("Resource not found.");

    IntPtr hRes = LoadResource(hModule, hResInfo);
    IntPtr pResData = LockResource(hRes);
    uint size = SizeofResource(hModule, hResInfo);

    byte[] data = new byte[size];
    Marshal.Copy(pResData, data, 0, (int)size);

    return data;
}

This function lets you get any data put in with a .rc file. Change the resource name string as needed.

Resource access examples: See Stack Overflow thread


Full JSON Config Example with NativeAOT

Here is a full example of embedding and reading a JSON config file:

1. Create settings.json

{
  "ApiKey": "my-secret-key",
  "Timeout": 30
}

2. Add a Resource Script: app.rc

SETTINGS RCDATA "settings.json"

3. Compile Resource File

Run:

rc app.rc

Output: app.res

4. Modify Project File

<ItemGroup>
  <Win32Resource Include="app.res" />
</ItemGroup>

5. Load and Deserialize:

var json = Encoding.UTF8.GetString(LoadEmbeddedResource("SETTINGS"));
var config = JsonSerializer.Deserialize<AppConfig>(json);

6. Define Config Class

public class AppConfig
{
    public string ApiKey { get; set; }
    public int Timeout { get; set; }
}

Good job. You now have one .exe that holds your config inside it.


Cross-Platform Limits: Windows vs Linux/macOS

Compiling native resources (with .rc and Win32Resource) is only for Windows. If you build for Linux or macOS, you must use other ways.

Alternatives on Non-Windows:

  • 🧬 Make .cs byte arrays automatically using build tools like xxd, xxd -i, or dotnet gen
  • 📦 Put files with the app in .tar/.zip, and use unpacking scripts.
  • 🔐 Encrypt outside binary data and load it only with permission when the app runs.

NativeAOT does not have standard cross-platform support for embedded binary resources right now. But community tools may fill this need soon.


Debugging and Validation Tips

To make sure your resources are put in correctly:

  • 🛠️ Use Resource Hacker to check the contents of the binary.
  • 🔍 Run dumpbin /resources myapp.exe in Developer Command Prompt (comes with Visual Studio)
  • ✅ Check FindResource and SizeofResource results. This helps stop it from failing without a message.
  • 🧪 Log the size of embedded data at startup to find missing files.

Not loading the resource, or having names that do not match, are common problems. Be careful.


Performance and Security Considerations

Putting files right into the .exe affects your app in several key ways:

Factor Benefit Trade-Off
Startup Performance Eliminates file I/O delays N/A
Portability Ships as a single file Higher .exe size
Security Reduces risk of tampering No runtime updates
Maintainability Predictable behavior Requires rebuild for changes

Do not forget. You cannot change embedded config when the app runs. But this can be good for programs sent out that must make sure certain settings are used.


When Not to Use Embedding

Even with the benefits, embedding is not always right:

❌ Avoid embedding if:

  • Files are very large (>10MB for models or media that are often updated)
  • You need to edit files as the app runs.
  • Your system is like Linux/Unix and has changing file needs.

Think about embedding only defaults inside. Then let users change them with external files.


Real-World Use Cases

You are not alone. Here is how others use C# NativeAOT with embedded files:

  • 🛠️ DevOps-style CLI tools built for no-runtime, no-dependency automation
  • 🛡️ Secure check systems packaging X.509 or licensing info inside.
  • 🤖 AI inference engines putting ONNX or gemmlowp models into small .exe files for robotics or control systems.
  • 🔐 Certificate services that avoid disk or I/O leaks in high-security places.

These examples show how companies and hobbyists test the limits of .NET with NativeAOT.


Looking Ahead: NativeAOT and Evolving Tooling

As the .NET system grows, expect these improvements for embedding resources:

  • ✅ Direct MSBuild support for cross-platform embedding (possibly replacing .rc/.res)
  • 🧰 Community tools to make .cs byte blobs automatically.
  • 🤖 Roslyn-based compilers and Source Generators that support better static resource embedding.
  • 📦 Better third-party bundlers for Linux/macOS options.

For now, simple tools like rc, Resource Hacker, and making code by hand help for now.


Quick Recap

  • C# NativeAOT makes very efficient native programs with no .NET runtime needs.
  • NativeAOT turns off common .NET embedding ways. So developers must change.
  • Use direct byte arrays for small files. Use .rc and .res for Windows apps. Or use code generation methods for cross-platform.
  • Access resources via Win32 APIs (FindResource, LoadResource, etc.) using P/Invoke.
  • Always check, debug, and think about speed and security trade-offs.

With these ways you know, you are ready to build very fast, self-contained, and secure programs in C# using NativeAOT.


References

Want more like this? Go to Devsolus and keep testing the limits of what you can do with modern .NET.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading