web-dev-qa-db-fra.com

C / C ++ avec GCC: ajouter statiquement des fichiers de ressources à l'exécutable / bibliothèque

Quelqu'un at-il une idée de la façon de compiler statiquement un fichier de ressources directement dans l'exécutable ou le fichier de bibliothèque partagée à l'aide de GCC?

Par exemple, j'aimerais ajouter des fichiers image qui ne changent jamais (et s'ils le font, je devrais quand même remplacer le fichier) et je ne voudrais pas qu'ils traînent dans le système de fichiers.

Si c'est possible (et je pense que c'est parce que Visual C++ pour Windows peut le faire aussi), comment puis-je charger les fichiers qui sont stockés dans le propre binaire? L'exécutable s'analyse-t-il, trouve-t-il le fichier et en extrait-il les données?

Il y a peut-être une option pour GCC que je n'ai pas encore vue. L'utilisation des moteurs de recherche n'a pas vraiment craché les bonnes choses.

J'en aurais besoin pour travailler avec des bibliothèques partagées et des exécutables ELF normaux.

Toute aide est appréciée

87
Atmocreations

Avec imagemagick :

convert file.png data.h

Donne quelque chose comme:

/*
  data.h (PNM).
*/
static unsigned char
  MagickImage[] =
  {
    0x50, 0x36, 0x0A, 0x23, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 
    0x77, 0x69, 0x74, 0x68, 0x20, 0x47, 0x49, 0x4D, 0x50, 0x0A, 0x32, 0x37, 
    0x37, 0x20, 0x31, 0x36, 0x32, 0x0A, 0x32, 0x35, 0x35, 0x0A, 0xFF, 0xFF, 
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 

....

Pour la compatibilité avec un autre code, vous pouvez alors utiliser soit fmemopen pour obtenir un objet FILE * "Normal", soit std::stringstream Pour créer un iostream. std::stringstream N'est cependant pas idéal pour cela et vous pouvez bien sûr simplement utiliser un pointeur partout où vous pouvez utiliser un itérateur.

Si vous utilisez ceci avec automake, n'oubliez pas de définir BUILT_SOURCES de manière appropriée.

La bonne chose à faire de cette façon est:

  1. Vous obtenez du texte, il peut donc être dans le contrôle de version et les correctifs sensiblement
  2. Il est portable et bien défini sur toutes les plateformes
46
Flexo

Mise à jour J'ai grandi pour préférer le contrôle John Ripley's Assembly .incbin solution basée offre et utilise maintenant une variante à ce sujet.

J'ai utilisé objcopy (GNU binutils) pour lier les données binaires d'un fichier foo-data.bin dans la section des données de l'exécutable:

objcopy -B i386 -I binary -O elf32-i386 foo-data.bin foo-data.o

Cela vous donne un foo-data.o fichier objet que vous pouvez lier à votre exécutable. L'interface C ressemble à quelque chose comme

/** created from binary via objcopy */
extern uint8_t foo_data[]      asm("_binary_foo_data_bin_start");
extern uint8_t foo_data_size[] asm("_binary_foo_data_bin_size");
extern uint8_t foo_data_end[]  asm("_binary_foo_data_bin_end");

pour pouvoir faire des trucs comme

for (uint8_t *byte=foo_data; byte<foo_data_end; ++byte) {
    transmit_single_byte(*byte);
}

ou

size_t foo_size = (size_t)((void *)foo_data_size);
void  *foo_copy = malloc(foo_size);
assert(foo_copy);
memcpy(foo_copy, foo_data, foo_size);

Si votre architecture cible a des contraintes particulières quant à l'emplacement de stockage des données constantes et variables, ou si vous souhaitez stocker ces données dans le .text segment pour l'adapter au même type de mémoire que votre code de programme, vous pouvez jouer avec les paramètres objcopy un peu plus.

84
ndim

Vous pouvez incorporer des fichiers binaires dans un exécutable à l'aide de l'éditeur de liens ld. Par exemple, si vous avez le fichier foo.bar alors vous pouvez l'intégrer dans l'exécutable en ajoutant les commandes suivantes à ld

--format=binary foo.bar --format=default

Si vous appelez ld à gcc, vous devrez ajouter -Wl

-Wl,--format=binary -Wl,foo.bar -Wl,--format=default

Ici --format=binary indique à l'éditeur de liens que le fichier suivant est binaire et --format=default revient au format d'entrée par défaut (utile si vous spécifiez d'autres fichiers d'entrée après foo.bar).

Ensuite, vous pouvez accéder au contenu de votre fichier à partir du code:

extern uint8_t data[]     asm("_binary_foo_bar_start");
extern uint8_t data_end[] asm("_binary_foo_bar_end");

Il y a aussi un symbole nommé "_binary_foo_bar_size". Je pense que c'est de type uintptr_t mais je ne l'ai pas vérifié.

47
Simon

Vous pouvez mettre toutes vos ressources dans un fichier Zip et ajouter cela à la fin du fichier exécutable:

g++ foo.c -o foo0
Zip -r resources.Zip resources/
cat foo0 resources.Zip >foo

Cela fonctionne, car a) La plupart des formats d'image exécutables ne se soucient pas s'il y a des données supplémentaires derrière l'image et b) Zip stocke la signature du fichier à la fin du fichier Zip. Cela signifie que votre exécutable est un fichier Zip normal après cela (à l'exception de votre exécutable initial, que Zip peut gérer), qui peut être ouvert et lu avec libzip.

39
Nordic Mainframe

Si vous voulez contrôler le nom de symbole exact et l'emplacement des ressources, vous pouvez utiliser (ou script) l'assembleur GNU (qui ne fait pas vraiment partie de gcc) pour importer des fichiers binaires entiers. Essayez ceci:

Assemblage (x86/bras):

    .section .rodata
    .global thing
    .type   thing, @object
    .align  4
thing:
    .incbin "meh.bin"
thing_end:
    .global thing_size
    .type   thing_size, @object
    .align  4
thing_size:
    .int    thing_end - thing

C:

#include <stdio.h>
extern char thing[];
extern unsigned thing_size;
int main() {
  printf("%p %u\n", thing, thing_size);
  return 0;
}

Quoi que vous utilisiez, il est probablement préférable de créer un script pour générer toutes les ressources et d'avoir des noms de symboles Nice/uniformes pour tout.

35
John Ripley

De http://www.linuxjournal.com/content/embedding-file-executable-aka-hello-world-version-5967 :

J'ai récemment eu besoin d'intégrer un fichier dans un exécutable. Puisque je travaille sur la ligne de commande avec gcc, et al et non avec un outil sophistiqué RAD qui fait que tout se passe comme par magie, il n'était pas immédiatement évident pour moi comment y arriver. Un peu de recherche sur le net a trouvé un hack pour le placer essentiellement à la fin de l'exécutable, puis déchiffrer où il était basé sur un tas d'informations que je ne voulais pas savoir. On dirait qu'il devrait y avoir une meilleure façon ...

Et il y a, c'est objcopy à la rescousse. objcopy convertit les fichiers objets ou exécutables d'un format à un autre. L'un des formats qu'il comprend est "binaire", c'est-à-dire tout fichier qui n'est pas dans l'un des autres formats qu'il comprend. Vous avez donc probablement imaginé l'idée: convertir le fichier que nous voulons incorporer dans un fichier objet, puis il peut simplement être lié au reste de notre code.

Disons que nous avons un nom de fichier data.txt que nous voulons intégrer dans notre exécutable:

# cat data.txt
Hello world

Pour le convertir en un fichier objet que nous pouvons lier à notre programme, nous utilisons simplement objcopy pour produire un fichier ".o":

# objcopy --input binary \
--output elf32-i386 \
--binary-architecture i386 data.txt data.o

Ceci indique à objcopy que notre fichier d'entrée est au format "binaire", que notre fichier de sortie doit être au format "elf32-i386" (fichiers objets sur le x86). L'option --binary-architecture indique à objcopy que le fichier de sortie est censé "s'exécuter" sur un x86. Ceci est nécessaire pour que ld accepte le fichier pour la liaison avec d'autres fichiers pour le x86. On pourrait penser que spécifier le format de sortie comme "elf32-i386" impliquerait cela, mais ce n'est pas le cas.

Maintenant que nous avons un fichier objet, nous devons l'inclure uniquement lorsque nous exécutons l'éditeur de liens:

# gcc main.c data.o

Lorsque nous exécutons le résultat, nous obtenons la sortie priée:

# ./a.out
Hello world

Bien sûr, je n'ai pas encore raconté toute l'histoire, ni vous montré main.c. Lorsque objcopy effectue la conversion ci-dessus, il ajoute des symboles "linker" au fichier objet converti:

_binary_data_txt_start
_binary_data_txt_end

Après la liaison, ces symboles spécifient le début et la fin du fichier incorporé. Les noms de symboles sont formés en ajoutant binaire et en ajoutant _start ou _end au nom du fichier. Si le nom de fichier contient des caractères qui ne seraient pas valides dans un nom de symbole, ils sont convertis en traits de soulignement (par exemple, data.txt devient data_txt). Si vous obtenez des noms non résolus lors de la liaison à l'aide de ces symboles, effectuez un hexdump -C sur le fichier objet et recherchez à la fin du vidage les noms choisis par objcopy.

Le code pour utiliser réellement le fichier incorporé devrait maintenant être raisonnablement évident:

#include <stdio.h>

extern char _binary_data_txt_start;
extern char _binary_data_txt_end;

main()
{
    char*  p = &_binary_data_txt_start;

    while ( p != &_binary_data_txt_end ) putchar(*p++);
}

Une chose importante et subtile à noter est que les symboles ajoutés au fichier objet ne sont pas des "variables". Ils ne contiennent aucune donnée, leur adresse est plutôt leur valeur. Je les déclare comme type char parce que c'est pratique pour cet exemple: les données incorporées sont des données de caractères. Cependant, vous pouvez les déclarer comme n'importe quoi, comme int si les données sont un tableau d'entiers, ou comme struct foo_bar_t si les données étaient un tableau de barres foo. Si les données incorporées ne sont pas uniformes, alors char est probablement le plus pratique: prenez son adresse et transformez le pointeur en type approprié lorsque vous parcourez les données.

35
Hazok

En lisant tous les articles ici et sur Internet, j'ai conclu qu'il n'y a pas d'outil pour les ressources, qui est:

1) Facile à utiliser dans le code.

2) Automatisé (pour être facilement inclus dans cmake/make).

3) multiplateforme.

J'ai décidé d'écrire l'outil par moi-même. Le code est disponible ici. https://github.com/orex/cpp_rsc

L'utiliser avec cmake est très simple.

Vous devez ajouter à votre fichier CMakeLists.txt un tel code.

file(DOWNLOAD https://raw.github.com/orex/cpp_rsc/master/cmake/modules/cpp_resource.cmake ${CMAKE_BINARY_DIR}/cmake/modules/cpp_resource.cmake) 

set(CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR}/cmake/modules)

include(cpp_resource)

find_resource_compiler()
add_resource(pt_rsc) #Add target pt_rsc
link_resource_file(pt_rsc FILE <file_name1> VARIABLE <variable_name1> [TEXT]) #Adds resource files
link_resource_file(pt_rsc FILE <file_name2> VARIABLE <variable_name2> [TEXT])

...

#Get file to link and "resource.h" folder
#Unfortunately it is not possible with CMake add custom target in add_executable files list.
get_property(RSC_CPP_FILE TARGET pt_rsc PROPERTY _AR_SRC_FILE)
get_property(RSC_H_DIR TARGET pt_rsc PROPERTY _AR_H_DIR)

add_executable(<your_executable> <your_source_files> ${RSC_CPP_FILE})

Le véritable exemple, en utilisant l'approche peut être téléchargé ici, https://bitbucket.org/orex/periodic_table

4
user2794512