c++ - ndk - Incruste recursos(por ejemplo, código de sombreado; imágenes) en el ejecutable/biblioteca con CMake
android ndk location (3)
Estoy escribiendo una aplicación en C ++ que se basa en varios recursos en mi proyecto. En este momento, tengo la ruta relativa del ejecutable producido a cada recurso codificado en mis fuentes, y eso permite que mi programa abra los archivos y lea los datos de cada recurso. Esto funciona bien, pero requiere que inicie el ejecutable desde una ruta específica en relación con los recursos. Entonces, si trato de iniciar mi ejecutable desde cualquier otro lugar, no puedo abrir los archivos y no puedo continuar.
¿Existe una forma portátil para que CMake incruste mis recursos en los archivos ejecutables (o bibliotecas) de modo que simplemente pueda acceder a ellos en la memoria en tiempo de ejecución en lugar de abrir archivos cuyas rutas sean frágiles? He encontrado una pregunta relacionada , y parece que la incorporación de recursos se puede hacer bastante bien con un poco de magia. Entonces, mi pregunta es ¿cómo puedo hacer esto de manera portátil y multiplataforma usando CMake? Realmente necesito que mi aplicación se ejecute tanto en x86 como en ARM. Estoy de acuerdo en admitir solo Linux (incrustado), pero puntos de bonificación si alguien puede sugerir cómo hacer esto para Windows (incrustado) también.
EDITAR: Olvidé mencionar una propiedad deseada de la solución. Me gustaría poder usar CMake para compilar de forma cruzada la aplicación cuando estoy compilando para ARM en lugar de compilarlo de forma nativa en mi destino ARM.
Como alternativa a la respuesta de sfstewman , aquí hay una pequeña función cmake ( 2.8 ) para convertir todos los archivos de una carpeta específica a datos C y escribirlos en el archivo de salida deseado:
# Creates C resources file from files in given directory
function(create_resources dir output)
# Create empty output file
file(WRITE ${output} "")
# Collect input files
file(GLOB bins ${dir}/*)
# Iterate through input files
foreach(bin ${bins})
# Get short filename
string(REGEX MATCH "([^/]+)$" filename ${bin})
# Replace filename spaces & extension separator for C compatibility
string(REGEX REPLACE "//.| |-" "_" filename ${filename})
# Read hex data from file
file(READ ${bin} filedata HEX)
# Convert hex data for C compatibility
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x//1," filedata ${filedata})
# Append data to output file
file(APPEND ${output} "const unsigned char ${filename}[] = {${filedata}};/nconst unsigned ${filename}_size = sizeof(${filename});/n")
endforeach()
endfunction()
Una de las maneras más fáciles de hacerlo es incluir un programa C pequeño y portátil en su compilación que lea el recurso y genere un archivo C que contenga la longitud de los datos del recurso y los datos reales del recurso como una matriz de literales de caracteres constantes. Esto será completamente independiente de la plataforma, pero solo debe usarse para recursos que sean razonablemente pequeños. Para recursos más grandes, probablemente no desee incrustar los archivos en su programa.
Para el recurso "foo", el archivo C generado "foo.c" contendría:
const char foo[] = { /* bytes of resource foo */ };
const size_t foo_len = sizeof(foo);
Para acceder al recurso desde C ++, declara los siguientes dos símbolos en un encabezado o en el archivo cpp donde se usan:
extern "C" const char foo[];
extern "C" const size_t foo_len;
Para generar foo.c
en la compilación, necesita un destino para el programa C (llámelo embedfile.c), y necesita usar el comando ADD_CUSTOM_COMMAND para llamar a este programa:
add_executable(embedfile embedfile.c)
add_custom_command(
OUTPUT foo.c
COMMAND embedfile foo foo.rsrc
DEPENDS foo.rsrc)
Luego, incluya foo.c
en la lista de origen de un objetivo que requiere el recurso "foo". Ahora tienes acceso a los bytes de "foo".
El programa embedfile.c es:
#include <stdlib.h>
#include <stdio.h>
FILE* open_or_exit(const char* fname, const char* mode)
{
FILE* f = fopen(fname, mode);
if (f == NULL) {
perror(fname);
exit(EXIT_FAILURE);
}
return f;
}
int main(int argc, char** argv)
{
if (argc < 3) {
fprintf(stderr, "USAGE: %s {sym} {rsrc}/n/n"
" Creates {sym}.c from the contents of {rsrc}/n",
argv[0]);
return EXIT_FAILURE;
}
const char* sym = argv[1];
FILE* in = open_or_exit(argv[2], "r");
char symfile[256];
snprintf(symfile, sizeof(symfile), "%s.c", sym);
FILE* out = open_or_exit(symfile,"w");
fprintf(out, "#include <stdlib.h>/n");
fprintf(out, "const char %s[] = {/n", sym);
unsigned char buf[256];
size_t nread = 0;
size_t linecount = 0;
do {
nread = fread(buf, 1, sizeof(buf), in);
size_t i;
for (i=0; i < nread; i++) {
fprintf(out, "0x%02x, ", buf[i]);
if (++linecount == 10) { fprintf(out, "/n"); linecount = 0; }
}
} while (nread > 0);
if (linecount > 0) fprintf(out, "/n");
fprintf(out, "};/n");
fprintf(out, "const size_t %s_len = sizeof(%s);/n/n",sym,sym);
fclose(in);
fclose(out);
return EXIT_SUCCESS;
}
Yo diría que la forma más elegante de tener recursos incrustados en C ++ es simplemente usar el Sistema de recursos Qt, que es portátil en diferentes plataformas, compatible con CMake, y esencialmente envuelve todo lo que se hizo en la respuesta anterior, además de proporcionar compresión, siendo completamente Probado e infalible, todo lo demás.
Cree un archivo de recursos Qt, un XML que enumere los archivos que se incrustarán:
<RCC>
<qresource prefix="/">
<file>uptriangle.png</file>
<file>downtriangle.png</file>
</qresource>
</RCC>
Llama al archivo qtres.qrc. El archivo de recursos anterior tendrá los dos archivos png (ubicados en el mismo directorio que qtres.qrc) incrustados en el archivo ejecutable final. Puede agregar / eliminar fácilmente los recursos del monitor a un archivo qrc usando QtCreator (el IDE de Qt).
Ahora en su archivo CMakeLists.txt agregue:
set(CMAKE_AUTOMOC ON)
find_package(Qt5Core)
qt5_add_resources(QT_RESOURCE qtres.qrc)
En su main.cpp, antes de que necesite acceder al recurso, agregue la siguiente línea:
Q_INIT_RESOURCE(qtres);
Ahora puede acceder a cualquiera de los recursos anteriores utilizando las clases de Qt compatibles con el Sistema de recursos de Qt, como QPixmap, QImage ... y lo más importante es que en los casos generales la clase de envoltorio QResource que envuelve un recurso Qt incorporado y permite el acceso a través de un interfaz amigable. Como ejemplo, para acceder a datos dentro de downtriangle.png en los recursos anteriores, las siguientes líneas harán el truco:
#include <QtCore>
#include <QtGui>
// ...
int main(int argc, char **argv)
{
// ...
Q_INIT_RESOURCE(qtres);
// ...
QResource res("://downtriangle.png"); // Here''s your data, anyway you like
// OR
QPixmap pm("://downtriangle.png"); // Use it with Qt classes already
// ...
}
Aquí, res se puede usar para acceder directamente a los datos usando res.data (), res.size () ... Para analizar el contenido de la imagen del archivo use pm. Utilice pm.size (), pm.width () ...
Y eres bueno para irte. Espero que haya ayudado.