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). We will continue working on the application Die (Figure 1), this time deepening the use of resources.

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. Now create a subfolder within res who calls all and, within it, creates the file strings.msg. Open it and add these lines:

Listing 2: Die message file.
 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

Then, copy in the folder all the image cards.png and the icons spain.png and usa.png. This newly created directory is a resource package, and all its content will be loaded at once by the application (Figure 2).

Windows Explorer capture showing the application resource directory.
Figure 2: Resource package in src/die/res/all.
Each subfolder within res will be considered a different resource package and may be loaded or released at runtime by the program.

Each line within the file strings.msg defines a new message and consists 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:

1
2
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:

1
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:

1
2
3
4
_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 location

We have used English as the main language of the program. Now we are going to include a translation into the Spanish language. For this we return to the folder die/res/all, where we will create the subdirectory es_es and we copy the file strings.msg (Figure 3). We press [Generate] and we see that in Visual Studio has appeared a new subfolder with a new version of strings.msg. From here we can translate the texts (Figure 4). When you are ready, again, [Generate].

Windows explorer capture showing the subdirectory of resources translated into Spanish.
Figure 3: Subdirectory of localized resources, src/die/res/all/es_es.
Visual Studio capture showing the translated message file in Spanish.
Figure 4: Translation of texts into Spanish. The identifiers must be the same as in English.

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

For each resource package, CMake has created a *.h with the same name as the folder: all.h in this case (Listing 6). This file contains the resource identifiers, as well as a function that allows us to access them all_respack () . In (Listing 7) we see the actions to be taken to use these resources in our program.

Listing 6: 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 7: 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 6), 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 5).
  • Animation of the Die application, translating the interface at run time.
    Figure 5: 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 6). 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 6: 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 8).

Listing 8: 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 9). 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 9: 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 7). 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 6). 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 7: 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. Resources 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 8). 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 9). 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 8: Distribution of a macOS application with embedded resources.
    Capture of the macOS distribution with packaged resources.
    Figure 9: 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 10). 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 10: 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 11). 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 11: 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 12), 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 12: Editing logo.ico.
❮ Back
Next ❯