C / C ++ con GCC: Agregue estáticamente archivos de recursos a ejecutable / biblioteca

¿Alguien tiene una idea de cómo comstackr estáticamente cualquier archivo de recursos directamente en el ejecutable o el archivo de biblioteca compartida usando GCC?

Por ejemplo, me gustaría agregar archivos de imagen que nunca cambian (y si lo hicieran, tendría que reemplazar el archivo de todos modos) y no querría que se quedaran en el sistema de archivos.

Si esto es posible (y creo que es porque Visual C ++ para Windows también puede hacer esto), ¿cómo puedo cargar los archivos que están almacenados en el propio binario? ¿El ejecutable se analiza automáticamente, busca el archivo y extrae los datos?

Tal vez haya una opción para GCC que aún no he visto. El uso de los motores de búsqueda realmente no escupió las cosas correctas.

Necesitaría que esto funcione para bibliotecas compartidas y ejecutables ELF normales.

Cualquier ayuda es apreciada

Con imagemgick :

convert file.png data.h 

Da algo como:

 /* 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, .... 

Para compatibilidad con otro código, puede usar ya sea fmemopen para obtener un objeto FILE * “regular” o alternativamente std::stringstream para hacer un iostream . std::stringstream no es muy bueno para esto y, por supuesto, puede usar un puntero en cualquier lugar donde pueda usar un iterador.

Si está utilizando esto con automake, no olvide configurar BUILT_SOURCES de forma adecuada.

Lo bueno de hacerlo de esta manera es:

  1. Obtienes texto, por lo que puede estar en control de versiones y parches de forma sensata
  2. Es portátil y está bien definido en cada plataforma

Actualización He llegado a preferir el control que ofrece el ensamblaje de John Ripley, la solución basada en .incbin , y ahora uso una variante sobre eso.

He usado objcopy (binutils de GNU) para vincular los datos binarios de un archivo foo-data.bin en la sección de datos del ejecutable:

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

Esto le proporciona un archivo de objeto foo-data.o que puede vincular a su ejecutable. La interfaz C se ve algo así como

 /** 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"); 

para que puedas hacer cosas como

 for (uint8_t *byte=foo_data; byte 

o

 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 su architecture de destino tiene restricciones especiales en cuanto a dónde se almacenan los datos constantes y variables, o si desea almacenar esos datos en el segmento .text para que quepa en el mismo tipo de memoria que su código de progtwig, puede jugar con los parámetros objcopy algo mas.

Puede incrustar archivos binarios en ejecutable usando ld linker. Por ejemplo, si tiene el archivo foo.bar , puede incrustarlo en ejecutable agregando los siguientes comandos a ld

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

Si está invocando ld hasta gcc , necesitará agregar -Wl

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

Aquí --format=binary le dice al vinculador que el siguiente archivo es binario y --format=default vuelve al formato de entrada predeterminado (esto es útil si especifica otros archivos de entrada después de foo.bar ).

Luego puede acceder al contenido de su archivo desde el código:

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

También hay un símbolo llamado "_binary_foo_bar_size" . Creo que es del tipo uintptr_t pero no lo uintptr_t .

Puede poner todos sus recursos en un archivo ZIP y anexarlo al final del archivo ejecutable :

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

Esto funciona, porque a) La mayoría de los formatos de imagen ejecutables no tienen en cuenta si hay datos adicionales detrás de la imagen yb) zip almacena la firma del archivo al final del archivo comprimido . Esto significa que su ejecutable es un archivo zip normal después de esto (excepto por su ejecutable inicial, que zip puede manejar), que se puede abrir y leer con libzip.

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

Recientemente tuve la necesidad de incrustar un archivo en un archivo ejecutable. Como estoy trabajando en la línea de comando con gcc, et al y no con una herramienta de RAD elegante que hace que todo suceda mágicamente, no me resultó inmediatamente obvio cómo hacer que esto sucediera. Un poco de búsqueda en la red encontró un truco para esencialmente cazarlo en el extremo del ejecutable y luego descifrar dónde estaba basado en un montón de información que no quería saber. Parecía que debería haber una mejor manera …

Y hay, es objcopy para el rescate. objcopy convierte archivos de objeto o ejecutables de un formato a otro. Uno de los formatos que entiende es “binario”, que básicamente es cualquier archivo que no está en uno de los otros formatos que comprende. Así que probablemente haya imaginado la idea: convierta el archivo que queremos incrustar en un archivo objeto, luego simplemente puede vincularse con el rest de nuestro código.

Digamos que tenemos un nombre de archivo data.txt que queremos incrustar en nuestro ejecutable:

 # cat data.txt Hello world 

Para convertir esto en un archivo objeto que podemos vincular con nuestro progtwig, simplemente usamos objcopy para producir un archivo “.o”:

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

Esto le dice a objcopy que nuestro archivo de entrada está en el formato “binario”, que nuestro archivo de salida debe estar en el formato “elf32-i386” (archivos de objeto en el x86). La opción –binary-architecture le dice a objcopy que el archivo de salida está destinado a “ejecutarse” en un x86. Esto es necesario para que ld acepte el archivo para vincularlo con otros archivos para el x86. Uno pensaría que especificar el formato de salida como “elf32-i386” implicaría esto, pero no es así.

Ahora que tenemos un archivo objeto, solo necesitamos incluirlo cuando ejecutamos el enlazador:

 # gcc main.c data.o 

Cuando ejecutamos el resultado, obtenemos el resultado deseado:

 # ./a.out Hello world 

Por supuesto, todavía no he contado toda la historia ni te he mostrado main.c. Cuando objcopy realiza la conversión anterior, agrega algunos símbolos “enlazadores” al archivo de objeto convertido:

 _binary_data_txt_start _binary_data_txt_end 

Después de vincular, estos símbolos especifican el inicio y el final del archivo incrustado. Los nombres de los símbolos se forman anteponiendo el binario y añadiendo _start o _end al nombre del archivo. Si el nombre del archivo contiene caracteres que no serían válidos en un nombre de símbolo, se convierten en guiones bajos (por ejemplo, data.txt se convierte en data_txt). Si obtiene nombres sin resolver al vincular usando estos símbolos, haga un hexdump -C en el archivo de objeto y busque los nombres que eligió objcopy al final del volcado.

El código para usar realmente el archivo incrustado ahora debería ser razonablemente obvio:

 #include  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++); } 

Una cosa importante y sutil a tener en cuenta es que los símbolos agregados al archivo objeto no son “variables”. No contienen ningún dato, sino que su dirección es su valor. Los declaro como tipo char porque es conveniente para este ejemplo: los datos incrustados son datos de caracteres. Sin embargo, puede declararlos como cualquier cosa, como int si los datos son una matriz de enteros, o como struct foo_bar_t si los datos fueron cualquier matriz de barras foo. Si los datos incrustados no son uniformes, entonces char es probablemente el más conveniente: tome su dirección y coloque el puntero al tipo correcto a medida que recorre los datos.

Si desea controlar el nombre exacto del símbolo y la ubicación de los recursos, puede usar (o crear secuencias de comandos) el ensamblador GNU (que en realidad no forma parte de gcc) para importar archivos binarios completos. Prueba esto:

Asamblea (x86 / arm):

  .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 

DO:

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

Sea lo que sea que use, probablemente sea mejor hacer una secuencia de comandos para generar todos los recursos y tener nombres de símbolos agradables / uniformes para todo.

¿Puedo agregar que el método de John Ripley es probablemente el mejor aquí por una gran razón? Alineamiento. Si hace un objcopy estándar o “ld -r -b binary -o foo.o foo.txt” y luego mira el objeto resultante con objdump -x, parece que la alineación del bloque se establece en 0. Si lo desea la alineación es correcta para datos binarios que no sean char, no puedo imaginar que esto sea algo bueno.

Al leer todas las publicaciones aquí y en Internet, he llegado a la conclusión de que no hay ninguna herramienta para recursos, que es:

1) Fácil de usar en el código.

2) Automatizado (para ser incluido fácilmente en cmake / make).

3) multiplataforma.

He decidido escribir la herramienta por mi cuenta. El código está disponible aquí. https://github.com/orex/cpp_rsc

Usarlo con cmake es muy fácil.

Debería agregar a su archivo CMakeLists.txt dicho código.

 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  VARIABLE  [TEXT]) #Adds resource files link_resource_file(pt_rsc FILE  VARIABLE  [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(  ${RSC_CPP_FILE}) 

El ejemplo real, utilizando el enfoque se puede descargar aquí, https://bitbucket.org/orex/periodic_table