Introduction
Bringing data into your program is always a source of problems. Problems like filesystems, paths and unreliable system calls sound familiar? These problems are not the ones you want to battle with in addition to your unit tests. There is light at the end of the tunnel though. Static data in the executable. We've all done it, quick hardcoded tables inline with the code. Taking it to the next step is simply integrating this into the buildstep and creating these tables from ordinary data, bypassing the filesystem at runtime completely.
bin2c.py
bin2c is short for binary to c array. The input to this little script is any file and the output is a plain textfile that is suitable for inclusion in a standard C array. Listing 1 shows the script. If you are stuck on a windows box, there are a couple of caveats to using this script. The first one is that the pipes on win32 are horribly broken so you can't redirect input any way you want and have it interact with python scripts. Secondly, there is a wonderful idea of binary v/s text files in windows, which this script handles.
Interesting python point is the fact that you can import modules anywhere in the script, not that I'm importing a windows specific module after I've checked that I'm in fact on a windows box.
#!/usr/bin/env python import sys OCTETS_PER_LINE = 16 def convert( outstream, instream ): writtenBytes = 0 while True: byte = instream.read(1) if len(byte) != 1: break outstream.write( "0x%.2x, " % ord(byte) ) writtenBytes += 1 if writtenBytes % OCTETS_PER_LINE == 0: outstream.write( "\n" ) message = "\n\n/* Size = %i bytes */\n" % writtenBytes outstream.write( message ) if __name__ == '__main__': if len(sys.argv) == 1: instream = sys.stdin outstream = sys.stdout if sys.platform == "win32": import os,msvcrt msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) elif len(sys.argv) == 3: instream = file(sys.argv[1],'rb') outstream = file(sys.argv[2], 'wt') else: print "Usage: bin2c [infile outfile]" print "\tIf no filenames are given, read from stdin and out" print "\tOn windows, invoke through python.exe if redirecting" print "\tinput and output since pipes are broken on win32." sys.exit(0) convert(outstream, instream) sys.exit(0)
0x98, 0xab, 0xad, 0x16, 0xd5, 0x5c, 0xcd, 0xd6, 0xd6, 0xd6, 0xcc, 0xd4, 0xd8, 0xfd, 0xa2, 0x2b, 0x10, 0x8e, 0x19, 0xfc, 0xb9, 0x6d, 0xa0, 0x15, 0xfe, 0x78, 0xe9, 0xff, 0x58, 0xfb, 0x43, 0x7d, 0x62, 0x62, 0x1a, 0xaf, 0x5f, 0xab, 0x4d, 0x4b, 0x19, 0xd8, 0x1f, 0x72, 0xa5, 0xbc, 0x08, 0xe9, 0x02, 0x5b, 0xcf, 0x3e, 0x39, 0xb4, 0x5b, 0xae, 0x77, 0x11, 0x0e, 0xa1, 0x3e, 0xa0, 0xbf, 0xb1, 0x00, 0x49, 0x45, 0x4e, 0x44, /* Size = 5049 bytes */
Sample
The following shows how to glue everything together. This small program will print the first four bytes from an image in the executable, blue.png, as well as list its' original size in bytes. The makefiles is very simple and glosses over the how to find the compiler and script. I hate to do this to you, but how to encode that is left as an exercise for the reader (hint: look at GNU make standard library).
#include <cstdio> static const unsigned char image[] = { #include "blue.inl" }; int main( int, char** ) { std::printf( "Image length is %d bytes\n", sizeof(image) ); std::printf( "First four octets: %02x%02x%02x%02x", image[0], image[1], image[2], image[3] ); return 0; }
# Windows specific make file PYTHON = python RM = rm -f CXX = cl CXXFLAGS = -nologo SRCDATA = blue.png INLDATA = $(subst .png,.inl, $(SRCDATA)) SOURCE = test.cpp EXE = test.exe .PHONY: clean all all: $(INLDATA) $(EXE) clean: @$(RM) $(INLDATA) $(EXE) $(subst .cpp,.obj, $(SOURCE)) %.inl: %.png @echo $< @$(PYTHON) bin2c.py > $@ < $< %.exe: %.cpp @$(CXX) $(CXXFLAGS) $<
In closing
The makefile that I've shown is just for demonstration purposes and very limited. In real life you might want to have something more advanced, e.g. dependency checking and automatic generation. There is nothing that prevents inclusion of this technique to visual studio projects, through the custom build step in visual studio one can easily provide automatic generation of the .inl file.