Tuesday, July 22, 2025

Including External File Content in a C Program and Automating with Makefile

 (Originally published in LinkedIn)

When developing C programs, you might need to embed the content of external files—such as configuration files, binary resources, or plain text—directly into your application. This approach ensures the data is self-contained within the binary, simplifying distribution and reducing dependencies.

There are multiple ways to achieve this, including manually converting files to C arrays, using tools like xxd -i, or writing custom scripts. This article explains how to automate the embedding process using a Makefile with a pre-build step.


Step 1: Converting File Content to C-Compatible Format

Manual Conversion

You can embed a file as a byte array in a C program manually. For instance, consider a file example.txt containing:

Hello, World!

You can represent this as:

unsigned char example_txt[] = {
    0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x57,
    0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0a
};
unsigned int example_txt_len = 14;

Using Tools for Automation

Instead of manual conversion, various tools can automate this process:

  • xxd -i: Converts a file to a C header file containing an array.
  • bin2c: A lightweight tool specifically for converting binary files to C arrays.
  • Custom Scripts: Scripts in Python or similar languages can read file content and output C array representations.

For example, to convert example.txt into a header using xxd:

xxd -i example.txt > example.h

This will generate:

unsigned char example_txt[] = {
    0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x57, 0x6f, 0x72,
    0x6c, 0x64, 0x21, 0x0a
};
unsigned int example_txt_len = 14;

Step 2: Using Embedded Data in C

Once the file content is converted into a C-compatible format, it can be included in your program. For example:

#include <stdio.h>
#include "example.h"  // Generated header file

int main() {
    printf("File content:\n");
    fwrite(example_txt, 1, example_txt_len, stdout);  // Print file content
    return 0;
}

Step 3: Automating the Process with a Makefile

To automate the workflow, you can use a Makefile with a pre-build step that generates the header file from the input file before compiling.

Example Makefile

# Variables
CC = gcc
CFLAGS = -Wall -Wextra -O2
TARGET = program
SRC = main.c
INPUT_FILE = example.txt
HEADER_FILE = example.h

# Build targets
all: pre-build $(TARGET)

pre-build: $(HEADER_FILE)

# Generate the header file (example uses xxd, but you can substitute your tool/script here)
$(HEADER_FILE): $(INPUT_FILE)
	@echo "Embedding $(INPUT_FILE) into $(HEADER_FILE)..."
	xxd -i $(INPUT_FILE) > $(HEADER_FILE)

$(TARGET): $(SRC) $(HEADER_FILE)
	$(CC) $(CFLAGS) $(SRC) -o $(TARGET)

clean:
	@echo "Cleaning up..."
	rm -f $(TARGET) $(HEADER_FILE)

Step 4: Running the Workflow

Create Input File Create a file named example.txt with some content:

echo "Hello, World!" > example.txt

Build the Project Run the make command:

$ make
Embedding example.txt into example.h...
gcc -Wall -Wextra -O2 main.c -o program

Run the Program Execute the compiled binary:

$ ./program
File content:
Hello, World!

Clean the Build To clean up the generated files:

$ make clean
Cleaning up...

Alternative Approaches

Custom Python Script

You can replace xxd with a Python script for more flexibility:

# bin2header.py
import sys

def generate_header(input_file, output_file):
    with open(input_file, 'rb') as f:
        data = f.read()

    with open(output_file, 'w') as f:
        f.write("unsigned char {}[] = {{\n".format(input_file.replace('.', '_')))
        for i in range(0, len(data), 12):
            f.write(", ".join(f"0x{byte:02x}" for byte in data[i:i+12]) + ",\n")
        f.write("};\n")
        f.write("unsigned int {}_len = {};\n".format(input_file.replace('.', '_'), len(data)))

if __name__ == "__main__":
    generate_header(sys.argv[1], sys.argv[2])

Update the Makefile to use the script:

$(HEADER_FILE): $(INPUT_FILE)
	python3 bin2header.py $(INPUT_FILE) $(HEADER_FILE)

Conclusion

Embedding external file content into C programs can be achieved using a variety of tools and methods. Automating the process with a Makefile ensures your workflow is efficient and reproducible. While this example uses xxd -i, the same process can be adapted for custom scripts or other tools, offering flexibility to suit your project's needs.

No comments:

Post a Comment

The Gladiator’s Leap: Did Predatory Combat Ignite the Spark of Avian Flight?

  Forget the "trees-down" vs. "ground-up" debate. The true origins of the avian power stroke may lie in the brutal arena...