I am learning how to create libraries with just a terminal. Is it possible (and I suspect it might not be) to place resources, such as images, text, API-keys, etc. into a statically linked archive file, like in an NSBundle? How do I reference these resources?
-
This is not a standard linker function and there is no standard method to do it. There are, however, quite some linkers that support embedment of static resources, even into libraries. As you have not specified OS and development environment, it is not possible to answer your question.tofro– tofro2016-03-19 08:45:47 +00:00Commented Mar 19, 2016 at 8:45
-
@tofro Any OS i'll be using will be POSIX, but I was hoping for something portable if possible. Are there any linkers you could recommend?William Rosenbloom– William Rosenbloom2016-03-19 08:47:21 +00:00Commented Mar 19, 2016 at 8:47
-
If you are looking for something really portable: Write your own converter that creates C code from your binary data like const unsigned char blob[] = {0x0,022,.....} . Then compile and link. shouldn't be too hard to do. Everything else is non-portable.tofro– tofro2016-03-19 09:30:41 +00:00Commented Mar 19, 2016 at 9:30
-
@tofro I was thinking about that but I wanted to make sure there wasn't a better way. I'd feel silly if I started doing that and it turns out I'm supposed to just be including the file, you know? Thanks for your input.William Rosenbloom– William Rosenbloom2016-03-19 09:31:59 +00:00Commented Mar 19, 2016 at 9:31
2 Answers
On POSIX systems, you have a pretty much standard shell sh, four very useful shell utilities called rm, printf, od, and sed.
Let's say you wish to create file foo.c with a const unsigned char blob[] array containing the contents of binary file foo.bar:
export LANG=C LC_ALL=C
rm -f foo.c
printf 'const unsigned char blob[] = {\n' > foo.c
od -A n -t x1 foo.bar | sed -e 's| *\([0-9A-Fa-f][0-9A-Fa-f]\)| 0x\1U,|g; s|^| |' >> foo.c
printf '};\n' >> foo.c
The first line sets the locale to C. This ensures the utilities use the format we expect, instead of some localized variant.
The second line removes the possibly existing foo.c. (Some shells complain if you direct output to an existing file without appending to it.) The -f quiets any complaints, if the file does not even exist yet.
The third and fifth lines append the array definition and closing to the foo.c file.
The fourth line uses the od utility to output the contents of foo.bar as hexadecimal bytes. We use sed to prepend 0x and append U, to each hexadecimal byte to make it nice and pretty C, and then we prepend an additional three spaces to the line (making it indented by four spaces).
If you don't like the approach, you can always write your own utility to do exactly this. For example: #include #include #include #include
#ifndef DEFAULT_VARNAME
#define DEFAULT_VARNAME "blob"
#endif
#ifndef DEFAULT_STORAGE
#define DEFAULT_STORAGE "const"
#endif
#ifndef DEFAULT_ATTRIBUTES
#define DEFAULT_ATTRIBUTES ""
#endif
#ifndef DEFAULT_INDENT
#define DEFAULT_INDENT " "
#endif
#ifndef DEFAULT_COLUMNS
#define DEFAULT_COLUMNS 16
#endif
int usage(const char *argv0)
{
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv0);
fprintf(stderr, " %s [ OPTIONS ] file [ [ OPTIONS ] file .. ]\n", argv0);
fprintf(stderr, "\n");
fprintf(stderr, "Options:\n");
fprintf(stderr, " -n %-10s Variable name\n", "'" DEFAULT_VARNAME "'");
fprintf(stderr, " -s %-10s Storage type\n", "'" DEFAULT_STORAGE "'");
fprintf(stderr, " -a %-10s Attributes\n", "'" DEFAULT_ATTRIBUTES "'");
fprintf(stderr, " -i %-10s Indentation\n", "'" DEFAULT_INDENT "'");
fprintf(stderr, " -c %-10d Bytes per line\n", (int)DEFAULT_COLUMNS);
fprintf(stderr, "\n");
fprintf(stderr, "This program will output the contents of the specified file(s)\n");
fprintf(stderr, "as C source code.\n");
fprintf(stderr, "\n");
return EXIT_SUCCESS;
}
int main(int argc, char *argv[])
{
const char *storage = DEFAULT_STORAGE;
const char *varname = DEFAULT_VARNAME;
const char *attributes = DEFAULT_ATTRIBUTES;
const char *indent = DEFAULT_INDENT;
size_t columns = DEFAULT_COLUMNS;
FILE *in;
size_t bytes;
int ch, arg;
char dummy;
if (argc < 2)
return usage(argv[0]);
arg = 1;
while (arg < argc) {
if (argv[arg][0] == '-') {
if (argv[arg][1] == 'n') {
if (argv[arg][2] != '\0') {
varname = argv[arg] + 2;
arg++;
continue;
} else
if (arg + 1 < argc) {
varname = argv[arg + 1];
arg += 2;
continue;
}
} else
if (argv[arg][1] == 's') {
if (argv[arg][2] != '\0') {
storage = argv[arg] + 2;
arg++;
continue;
} else
if (arg + 1 < argc) {
storage = argv[arg + 1];
arg += 2;
continue;
}
} else
if (argv[arg][1] == 'a') {
if (argv[arg][2] != '\0') {
attributes = argv[arg] + 2;
arg++;
continue;
} else
if (arg + 1 < argc) {
attributes = argv[arg + 1];
arg += 2;
continue;
}
} else
if (argv[arg][1] == 'i') {
if (argv[arg][2] != '\0') {
indent = argv[arg] + 2;
arg++;
continue;
} else
if (arg + 1 < argc) {
indent = argv[arg + 1];
arg += 2;
continue;
}
} else
if (argv[arg][1] == 'c') {
if (argv[arg][2] != '\0') {
if (sscanf(argv[arg] + 2, " %zu %c", &columns, &dummy) != 1 || columns < 1) {
fprintf(stderr, "%s: Invalid number of bytes per line.\n", argv[arg] + 2);
return EXIT_FAILURE;
}
arg++;
continue;
} else
if (arg + 1 < argc) {
if (sscanf(argv[arg+1], " %zu %c", &columns, &dummy) != 1 || columns < 1) {
fprintf(stderr, "%s: Invalid number of bytes per line.\n", argv[arg + 1]);
return EXIT_FAILURE;
}
arg += 2;
continue;
}
} else
if (!strcmp(argv[arg], "-h") || !strcmp(argv[arg], "--help"))
return usage(argv[0]);
}
in = fopen(argv[arg], "r");
if (!in) {
fprintf(stderr, "%s: %s.\n", argv[arg], strerror(errno));
return EXIT_FAILURE;
}
printf("%s unsigned char %s[] %s= {", storage, varname, attributes);
bytes = 0;
while ((ch = getc(in)) != EOF)
if (bytes++ % columns)
printf(", %3u", (unsigned int)ch);
else
printf("\n%s%3u", indent, (unsigned int)ch);
printf("\n}; /* %zu bytes */\n\n", bytes);
if (ferror(in)) {
fclose(in);
fprintf(stderr, "%s: Read error.\n", argv[arg]);
return EXIT_FAILURE;
}
if (fclose(in)) {
fprintf(stderr, "%s: Delayed read error.\n", argv[arg]);
return EXIT_FAILURE;
}
if (fflush(stdout) || ferror(stdout)) {
fprintf(stderr, "Error writing to standard output.\n");
return EXIT_FAILURE;
}
arg++;
}
return EXIT_SUCCESS;
}
The only POSIX-ism in the above code is the use of %zu to scan and print a size_t.
Comments
What you want to do is creating an object file from a binary blob in order to be able to link to it. It needs to be an object file that contains a symbol table with at least one symbol (probably the start of your blob), because you want to have a handle to your binary data in the rest of the program.
Because object files are entirely system- (sometimes even compiler-) dependent, there is no portable way to actually do this directly. Linux, for example, used to have at least two different object file formats for quite some time.
The most portable (and, maybe, even simplest) way to do that: Use a tool that knows how to build object files for your dev environment: The C compiler.
Write your own converter that creates C code from your binary data like
const unsigned char blob[] = {0x0,0x22,.....};
Then compile and link. shouldn't be too hard to do, rather a 10-mins job. Everything else is entirely non-portable.
On platforms where you have GNU objcopy available, this might be another way of doing things. (Specify 'binary' as input file format and your object file format as output target. You also need to use "--add-symbol" to add that start symbol of your blob.) But I have never done this myself.