Cross-platform C SDK logo

Cross-platform C SDK

Resources

❮ Back
Next ❯
This page has been automatically translated using the Google Translate API services. We are working on improving texts. Thank you for your understanding and patience.

Resources are data necessary for the application but do not reside in the executable area. In other words, they are not directly accessible through the program variables, but must be preloaded to be able to use them. The most common are the texts and the images used in the user interface, although any type of file can become a resource (sounds, fonts, 3d models, html pages, etc). To illustrate its use with a real example, we are going to use the application Die (Figure 1), included in /src/demo/die.

Capture the sample application Die.
Figure 1: Die application.

1. Resource types

  • Texts: Although it is very easy to include texts in the code as variables of C, in practice this is not advisable for two reasons: The first is that, normally, it is not the programmers who write the messages that the program shows. By separating them into a separate file, other team members can review and edit them without having to access the code directly. The second reason is internationalization. It is an almost indispensable requirement today to be able to change the language of the program and this may involve several members of the team, as well as the fact that several text strings will refer to the same message. Therefore, extracting them from the source code will be almost indispensable.
  • Images: It is not usual for the icons of the program to change according to the language, although this may be the case. The tricky thing here is to transform a .jpg or .png file into a C variable (Listing 1). You have to serialize the file and paste it into the code, something very tedious and difficult for the programmer to maintain. It is preferable to have the images in a separate folder and access them at runtime.
  • Listing 1: .png image embedded in the source code.
    1
    2
    3
    4
    5
    6
    
    const uint32_t IMG_SIZE = 1262;
    
    const byte_t IMG[] = { 
                0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
                0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 
                ... };
    
  • Files: Apart from text and images, any file can become a resource. In this case, the application will receive a block of bytes with the contents thereof, which must know how to interpret.

2. Create resources

If we go to the source directory of the application (C:\nappgui\die), we see that there is a folder called res added by CMake when creating the project. Inside there are several icons logo.* and a license.txt in which you must write the license with which you are going to distribute the program. A copy of this file will be included in the final package Installers.

You can also see a folder named /res/all that was not created by CMake, but added later when writing the program. This subfolder is considered a resource bundle and will contain a set of texts, images or files that will be loaded "in block" at some point during execution. We can create as many packages as necessary depending on the size and logic of our program.

In big applications, organize your resources in such a way that it is not necessary to load them all when the application starts. Certain resources may only be required when the user performs an action.

You will see that within /res/all there is a strings.msg whose content is shown below:

Listing 2: Fichero de mensajes de Die.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/* Die strings */
TEXT_FACE       Face
TEXT_PADDING    Padding
TEXT_CORNER     Corner
TEXT_RADIUS     Radius
TEXT_ONE        One
TEXT_TWO        Two
TEXT_THREE      Three
TEXT_FOUR       Four
TEXT_FIVE       Five
TEXT_SIX        Six
TEXT_TITLE      Die Simulator
TEXT_INFO       Move the sliders to change the parametric representation of the die face.
TEXT_LANG       Language
TEXT_ENGLISH    English
TEXT_SPANISH    Spanish

It also contains the cards.png image and the spain.png and usa.png icons (Figure 2).

Windows Explorer capture showing the application resource directory.
Figure 2: Resource package in src/die/res/all.

Each line within the strings.msg file defines a new message consisting of an identifier (eg TEXT_FACE) followed by the text that will be displayed in the program (Face in this case). Text is considered from the first non-blank character after the identifier to the end of the line. It is not necessary to put it in quotation marks ("Face") as in C:

 
BILLY   Billy "the Kid" was an American Old West outlaw.
OTHER   Other text.

Neither do you have to use escape sequences, with the sole exception of '\n' for multiline messages:

 
TWO_LINES   This is the first line\nAnd this is the second.

The identifier of the message follows the rules of the C identifiers, except that the letters must be uppercase:

 
_ID1    Ok
0ID2    Wrong!!
id3     Wrong!!
ID3     Ok

The messages accept any Unicode character. We can divide the texts into so many files *.msg as necessary and should be stored in UTF8 format.

Visual Studio does not save the files in UTF8 by default. Make sure you do it at each *.msg contains non-US-ASCII characters. File->Save As->Save with encoding-> Unicode (UTF8 Without Signature) - Codepage 65001.

3. Resources i18n location

We have used English as the main language of the program. Now we are going to include a translation into the Spanish language. To do this, we return to the /res/all folder, where we see the /es_es subdirectory that contains another strings.msg file. The identifiers in this file are the same as in /all/strings.msg but the texts are in another language. Depending on the selected language, the program will use one version or another.

Listing 3: Die's message file, translated into Spanish.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/* Die strings */
TEXT_FACE       Cara
TEXT_PADDING    Margen
TEXT_CORNER     Borde
TEXT_RADIUS     Radio
TEXT_ONE        Uno
TEXT_TWO        Dos
TEXT_THREE      Tres
TEXT_FOUR       Cuatro
TEXT_FIVE       Cinco
TEXT_SIX        Seis
TEXT_TITLE      Simulador de dado
TEXT_INFO       Mueve los sliders para cambiar la representación paramétrica de la cara del dado.
TEXT_LANG       Idioma
TEXT_ENGLISH    Inglés
TEXT_SPANISH    Español

We must take into account some simple rules when locating resources:

  • If the local version of a resource does not exist, the global version of it will be used. CMake will warn if there are untranslated texts Resource warnings.
  • Those resources only present in localized folders will be ignored. It is imperative that the global version exists.
  • No "subpackets" of resources are allowed. Only two levels will be processed: /res/pack for the global and /res/pack/local for those localized.
  • The existing resources in the root folder (/res) will be ignored. All resources must be linked to a package.
  • The localized texts must have the same identifier as their global corresponding. Otherwise, different messages are considered.
  • To create the localized version of an image or another file, include it in its corresponding localized folder (eg /es/all/es_es/cards.png) using the same file name as the global version.
  • To name the localized folders, use the two-letter language code ISO 639-1 (en, es, fr, de, zh, ...) and, optionally, the two-letter country code ISO-3166 (en_us, en_gb, ...).

4. Loading and translation

After pressing [Generate], for each resource bundle, CMake creates a *.h with the same name as the folder: all.h in this case (Listing 4). This file contains the resource identifiers, as well as a function that allows us to access them all_respack(). In (Listing 5) we see the actions to take to use these resources in our program.

Listing 4: Header file all.h.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* Automatic generated by NAppGUI Resource Compiler (nrc-r1490) */

#include "core.hxx"

__EXTERN_C

/* Messages */
extern ResId TEXT_FACE;
extern ResId TEXT_PADDING;
extern ResId TEXT_CORNER;
extern ResId TEXT_RADIUS;
extern ResId TEXT_ONE;
extern ResId TEXT_TWO;
extern ResId TEXT_THREE;
extern ResId TEXT_FOUR;
extern ResId TEXT_FIVE;
extern ResId TEXT_SIX;
extern ResId TEXT_TITLE;
extern ResId TEXT_INFO;
extern ResId TEXT_LANG;
extern ResId TEXT_ENGLISH;
extern ResId TEXT_SPANISH;

/* Files */
extern ResId CARDS_PNG;
extern ResId SPAIN_PNG;
extern ResId USA_PNG;

ResPack *all_respack(const char_t *local);

__END_C
Listing 5: Load and use of resources.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include "all.h"

gui_respack(all_respack);
gui_language("");
...
label_text(label1, TEXT_FACE);
imageview_image(vimg, CARDS_PNG);
...
static void i_OnLang(App *app, Event *e)
{
    const EvButton *params = event_params(e, EvButton);
    const char_t *lang = params->index == 0 ? "en_us" : "es_es";
    gui_language(lang);
    unref(app);
}
  • Line 1 includes the resource package header (Listing 4), which has been generated automatically by CMake.
  • Line 3 registers the package in Gui, the library in charge of the graphic interface. If the application had more resource packages we would add them in the same way.
  • Line 4 sets the default language (English).
  • Lines 6 and 7 assign a text and an image to two controls respectively. The identifiers are defined in "all.h", as we have just seen.
  • Line 13 translates the entire interface in response to a change in control PopUp (Figure 3).
  • Animation of the Die application, translating the interface at run time.
    Figure 3: Translation of the Die application, without destroying the window or restarting.

Basically, a call to gui_language, involves coordinating three actions:

  • Load localized resources and replace them with current ones.
  • Assign new texts and images to all program controls and menus.
  • Resize windows and menus, since changing text and images will influence the size of controls.

5. Editing resources

To add new resource files or delete any of the existing ones, we just have to go to the folder res/all through the file browser and do it there directly. The message files *.msg can be edited from Visual Studio, since CMake includes them within the IDE (Figure 4). After making any changes to the resource folder or editing a file *.msg, we must press [Generate] in CMake so that these modifications can be integrated into the project. After each update, the identifiers of the new resources will be created and those whose associated resource will have disappeared will be eliminated, which will produce compilation errors that will facilitate the correction of the code.

Capture Visual Studio by editing a message file.
Figure 4: Edit resources within Visual Studio.

6. Manual management

Although the usual will be to delegate the management of resources in the library gui, it is possible to access the content of the packages directly, as we see in (Listing 6).

Listing 6: Direct access to resources.
1
2
3
4
5
6
7
8
#include "all.h"

ResPack *pack = all_respack("es_es");
...
label_text(label1, respack_text(pack, TEXT_FACE));
imageview_image(vimg, respack_image(pack, CARDS_PNG));
...
respack_destroy(&pack);
  • Line 1 includes the resource package header.
  • Line 3 creates an object with the content of the package in Spanish language. Each resource package will provide its own constructor, whose name will begin with that of its folder xxxx_respack().
  • Lines 5 and 6 get a text and an image respectively to assign them to the interface controls.
  • Line 8 destroys the resource package, at the end of its use.

There is a big difference between allocating resources through ResId or by functions respack_ (Listing 7). In the first case, the label control will be "sensitive" to the language changes made by gui_language. However, in cases 2 and 3 a constant text has been assigned to the control, which will not be affected by this function. We will be responsible for changing the text, if necessary.

Listing 7: Different ways of accessing resources.
1
2
3
label_text(label1, TEXT_FACE);
label_text(label1, respack_text(pack, TEXT_FACE));
label_text(label1, "Face");

The choice of one or the other access mode will depend on the requirements of the program. We remind you that in order to carry out the automatic translations, resources must be registered with gui_respack.


7. Resource processing

Let's see in a little more detail how NAppGUI generates the resource modules. When establishing NRC_EMBEDDED in the command desktopApp(), we tell CMake to process the resources of the Die project. We can also choose the option NRC_PACKED of which we will speak next. When we press [Generate] CMake traverses the subfolders within the directory res of each project, calling the nrc utility (NAppGUI Resource Compiler) (Figure 5). This program is in the folder prj/scripts of the distribution of the SDK. For each resource package, nrc creates two source files (one .c and a .h) and links them to the project. The .h contains the identifiers and the constructor that we have seen in (Listing 4). For its part, the .c performs the package implementation based on the content of each folder and the nrcMode modality.

Schema showing how the nrc compiler processes the resource folders.
Figure 5: Processing resources using CMake and nrc.
Files created by nrc are considered generated code and are not saved in the folder src but in the folder build. They will be updated every time you press [Generate] in CMake, regardless of the platform on which we are working. On the contrary, the original resource files (located in the folder res) are considered part of the source code.

8. Resource distribution

In the previous chapter, when creating the Visual Studio solution, we indicated that we had to use the constant NRC_EMBEDDED in the sentence desktopApp() inside the file CMakeLists.txt. There are two other modalities more related to the management of resources and that can be configured separately within each command desktopApp():

  • NRC_NONE: CMake will ignore the contents of the folder res, except for the icon of the application. No resource packages will be generated even if there is content within this folder.
  • NRC_EMBEDDED: The resources, with all their translations, are integrated as part of the executable (Figure 6). It is a very interesting option for applications of small or medium size, since in a single file *.exe we will supply the entire program. You will not need an installer and we will have the certainty that the software will not fail due to the lack of an external file. The downside is that, obviously, the size of the executable will grow considerably so it is not advisable in programs with many resources, very heavy, or with a multitude of translations.
  • NRC_PACKED: For each resource package, a *.res file will be created external to the executable that will be loaded and released at runtime as needed (Figure 7). The advantages of this method are the disadvantages of the previous one and vice versa: Smaller executables, but with external dependencies (the own ones .res) that must be distributed together. The use of memory will also be optimized, being able to load the *.res on demand.
  • Capture of the macOS distribution with embedded resources.
    Figure 6: Distribution of a macOS application with embedded resources.
    Capture of the macOS distribution with packaged resources.
    Figure 7: Distribution of the same macOS application with packaged resources.

CMake manages the location of the resource packages for us. In Windows and Linux applications, it will copy all *.res in the executable directory. In macOS it will place them in the folder resources of the bundle. A very important fact is that we do not have to modify the source code when going from one modality to another. nrc is already responsible for managing the load according to the type of package. Something logical can be to start with NRC_EMBEDDED, and if the project grows, change to NRC_PACKED. We just have to press again [Generate] (as we always do) and recompile the project so that the change becomes effective.

In Windows and Linux files *.res they must always be installed in the same directory as the executable. In the case of macOS, CMake generates a bundle ready for distribution and installs the resource packages in the directory /resources of said bundle.

9. Resource warnings

nrc is a silent script whose work is integrated into CMake's build process, going unnoticed in most cases. But there are times that it detects anomalies in the resource directories and it must inform us in some way. In these cases, a red line will appear in the CMake console indicating the project and affected package(s) (Figure 8). The details dumps in the file NRCLog.txt located in the generated resources folder (CMake shows the complete path).

Capture of CMake showing anomalies detected in the processing of resources by nrc.
Figure 8: nrc has encountered anomalies when processing resources.

If the failures are critical, nrc will not be able to generate the *.h and *.c associated to the package, preventing the application from being compiled (in essence it does not stop being a compilation error). Other times they are mere warnings that should be fixed, but they allow you to continue compiling. Specifically, the critical errors that affect nrc are the following: (we show them in English as they are written in NRCLog.txt).

  • MsgError (%s:%d): Comment not closed (%s).
  • MsgError (%s:%d): Invalid TEXT_ID (%s).
  • MsgError (%s:%d): Unexpected end of file after string ID (%s).
  • Duplicate resource id in '%s' (%s).
  • Can't load resource file '%s'.
  • Error reading '%s' resource directory.
  • Error reading '%s' subdirectories.
  • Error creating '%s' header file.
  • Error creating '%s' source file.
  • Error creating '%s' packed file.

Por otro lado, los avisos no-críticos:

  • Empty message file '%s'.
  • Ignored localized text '%s' in '%s'. Global resource doesn't exists.
  • Ignored localized file '%s' in '%s'. Global resource doesn't exists.
  • There is no localized version of the text '%s' in '%s'.
  • Localized directory '%s' is empty or has invalid resources.

10. Application icon

When we create a new project, CMake establishes a default icon for the application, which it places in the directory /res, with the name logo*. This image will be "embedded" in the executable and the operating system will use it to represent the application on the desktop (Figure 9). Windows and Linux also use it in the title bar of the window. We have three versions:

  • logo256.ico: Version for Windows Vista and later. They should include the resolutions: 256x256, 48x48, 32x32 and 16x16.
  • logo48.ico: Version for Linux and Visual Studio 2008 and 2005, which do not support resolutions of 256x256. This version only includes: 48x48, 32x32 and 16x16.
  • logo.icns: Version for macOS. Resolutions 512x512, 256x256, 128x128, 32x32 and 16x16 both in normal resolution (@1x) and Retina Display (@2x).
  • Capture the Windows taskbar with various application icons.
    Figure 9: Application icons in the Windows taskbar.

CMake is already responsible for using the appropriate version of the icon according to the platform we are compiling. To change the default icon, open the files logo* with some graphic editor (Figure 10), make the changes and press again [Generate] in CMake. Very important: do not change the names of the files, they should always be logo256.ico, logo48.ico and logo.icns.

Capture of an image editor, modifying the program icon.
Figure 10: Editing logo.ico.
❮ Back
Next ❯