Platform Integration Aspects: Implementing Prototyper intrinsics
Embedded Wizard provides an environment to develop and test highly platform independent GUI applications. In practice, based on one and the same project you can generate code for any target system. This is achieved by Embedded Wizard own programming language Chora designed to strictly conceal all platform specific constructs while being expressive and universal enough to allow you to formulate any application specific operation.
From this high claim for platform independence results the consequence, that applications developed with Embedded Wizard have per default no access to the underlying device, be it the hardware, middleware software or the operating system. You can consider the application as running enclosed in a box. In order to interact or exchange data with the target device, the target specific dependencies have to be implemented within so-called native statements and inline code members. These contents are thereupon taken over when generating code and can be compiled by your target compiler. For more details concerning the integration with the target device please see the section Integrating with the device.
The restrictions resulting from the platform independence also affect the prototyping environment. This environment allows you to test and debug individual GUI components or the entire GUI application directly within the Embedded Wizard Studio without having to generate code for the target system, compile the code and upload the resulting binary to the target device. During prototyping Embedded Wizard interprets the Chora code only. No further C compiler nor extern build processes are used. Since native statements and inline code members contain target specific code, Embedded Wizard can't interpret, these statements and members are simply ignored when prototyping the application.
Ignoring the native statements and inline code members during the prototyping is in most cases acceptable. You can still test all GUI components. During prototyping, however, the components will use default values for all the target specific data and no target specific operation can be performed. If you consider this behavior as a limitation, this chapter explains how you extend Embedded Wizard by target specific functionality and so use real target data and perform target specific operations even during the prototyping. For example, you can extend Embedded Wizard to operate on a data base or exchange data via network.
In order to extend Embedded Wizard by target specific functionality you have to implement so-called Intrinsics Module. From technical point of view, such module is an ordinary Microsoft Windows DLL (dynamically loadable library) containing C functions where you accommodate the target specific functionality. Embedded Wizard recognizes and loads the intrinsics modules automatically. Once loaded, the functions implemented in a module are exposed in Chora and can be invoked similarly as you invoke any other built-in Chora function. These functions are called Intrinsics.
Let's assume, your GUI application has to read text files stored on a SD card. The necessary target specific code to open and read the files is consequently implemented in a native statement as mentioned above. If you want similar operations to be performed during the prototyping, you have to implement an intrinsics module containing a C function to open and read a file. The following figure demonstrates the both cases marked with the numbers 1 and 2:
When the user presses the push button Load text file contents ... the Chora code found below the button is executed. This code contains $if .. $else .. $endif preprocessor directives to distinguish between the prototyping and the non-prototyping cases:
1.When generating code for the target system, the macro $prototyper results in the value false. Consequently, the $if $prototyper condition is not fulfilled causing the code section below the $else directive to be taken in account. This section contains a native statement, which in turn includes an invocations of a C function GetTextFileContent implemented in some C module compiled and linked when building the binary. At the runtime in the target device this function takes care of loading the file contents from the SD card.
2.When testing the GUI component in the prototyping environment of Embedded Wizard Studio, the macro $prototyper results in the value true. Consequently, the $if $prototyper condition is fulfilled causing the immediately following code section to be taken in account. This section contains an invocations of a function GetTextFileContent in Chora syntax. This function is implemented in an intrinsics module. At its execution time, the function accesses Windows file system to open and read the requested file.
The above example demonstrates an ideal case, where the intrinsics module can reuse the source code (here the C function GetTextFileContent) implemented originally for the target system. If this is not possible, you can invoke from the intrinsics module any Win32 (Microsoft Windows API) function or use third-party libraries providing the necessary functionality on Microsoft Windows. In this way you are very flexible to extend Embedded Wizard functionality. The following sections provide more details concerning the creation and usage of intrinsics modules.
Create a new Visual Studio project
An Embedded Wizard intrinsics module is an ordinary Win32 DLL (dynamically loadable library). You create a new DLL by using Microsoft Visual Studio C++. The free Expression edition should be fully sufficient for this task.
IMPORTANT
Please note, this document and all included screenshots are based on the version Microsoft Visual Studio Express 2012. Be aware of eventual differences if you are using a newer version.
★If you have not done it yet, please visit Microsoft website, register and download the Visual Studio C++ Express edition.
★Start Microsoft Visual Studio Express.
★From the menu select the command to create a new project e.g. . Thereupon a New Project dialog appears.
★In the New Project dialog select Visual C++, Win32 Project, specify the name and the location for the new project. In our example we name the project IntrinsicsModule:
★Confirm the dialog by clicking on the button. Thereupon the Win32 Application Wizard dialog is shown. Press the button to switch to the next page:
★Select DLL as Application type. Also activate Empty project otherwise the new project will contain unnecessary dummy files:
★Complete the project configuration by clicking on the button.
With the above steps you have created a new Visual Studio project for a Win32 DLL. In the Solution Explorer you can see that the project is still empty. When you would start the build now, you will get a DLL file with no functionality inside. Before we start to add new functionality we have to adjust few parameters in the project settings.
★In the Solution Explorer click with the right mouse button on item representing the intrinsics module project and from the context menu select the command :
★In the thereupon shown IntrinsicsModule Property Pages select the configuration All configurations:
★Still in the same dialog ensure that the page General is selected and change there the property Target Extension to the value .ewi. This will ensure, that the resulting DLL has the file extension *.ewi as expected by Embedded Wizard in order to be recognized as a valid intrinsics module:
★In the page VC++ Directories enhance the property Include Directories by $(EMWI_DIR)\Sdk\Intrinsics. This is necessary for Visual Studio to find include files needed for the intrinsics module development. The value $(EMWI_DIR) is a Windows environment variable referring the location where Embedded Wizard Studio has been installed. Please note the semicolon separating the just entered text from the text found originally in the property. Don't delete nor modify the original contents of the property:
★In the page C/C++, Language change the property Threat WChar_t As Built In Type to No. This is necessary to suppress warnings when working with wide-char (16-bit) C strings:
★Confirm the made modifications by clicking on the button.
So far the project configuration is completed. In the next step you should add a new C file where later the functionality of the intrinsics module will be implemented. For this purpose:
★In the Solution Explorer click with the right mouse button on item representing the intrinsics module project and from the context menu select the commands and in the sub-menu :
★In the thereupon shown Add New Item dialog select Visual C++, Code, C++ File and enter the name of the new file main.c to add:
★Confirm the operation by clicking on the button.
★The new file has been added and Visual Studio shows now the file main.c empty without any contents. Add following #include directives to the file so declarations common for all intrinsics are available:
In the last step add to your project the C files ewrte.c and ewgfx.c, both found in the folder Sdk\Intrinsics just below your Embedded Wizard installation directory. The files implement functionality common for intrinsics and as such they have to belong to each intrinsics module Visual Studio project. For this purpose:
★In the Solution Explorer click with the right mouse button on item representing the intrinsics module project and from the context menu select the commands and in the sub-menu :
★In the thereupon shown Add Existing Item dialog navigate to your Embedded Wizard installation directory and then to its sub-folder Sdk\Intrinsics. Within this folder select the both files ewrte.c and ewgfx.c. Confirm by clicking on the button :
Implement the C functions to export
An intrinsics module enhances the prototyping environment of Embedded Wizard Studio by functionality not available there per default. That could be, for example, the access to a file system. By implementing an intrinsics module you allow the GUI application to perform the respective file operations even when testing the application directly within Embedded Wizard Studio.
In the first step you have to specify which operations are needed and should be implemented in the intrinsics module. Our example will demonstrate this on the already mentioned access to a file system. The intrinsics module will contain functions to enumerate files stored in a folder and to read the contents of a text file. In the real target system, the respective files would be stored on a SD card or another embedded file system. The intrinsics module, in turn, will access the files from a folder found below the directory of your Embedded Wizard project. We see following five C functions as adequate to create such interface:
void OpenFolder( XString aFolder, XString aPattern ) void CloseFolder( void ) XInt32 GetNoOfFiles( void ) XString GetFileName( XInt32 aFileNo ) XString GetTextFileContent( XString aFilePath )
The function OpenFolder() has the job to collect all files stored in the given folder aFolder and matching the file name pattern aPattern. The resulting information is stored internally in the intrinsics module. This information remains valid until the function CloseFolder() has been.
The function GetNoOfFiles() returns how many files have been collected during the preceding OpenFolder() invocation. With the function GetFileName(), the names of the collected files can be queried. The parameter aFileNo identifies in this case the respective file. The first file has the number 0, the second has the number 1, and so far. This simple interface permits the GUI application to query folder contents and enumerate files stored there.
The last function GetTextFileContent() has the job to open and read the content of a text file specified in the parameter aFilePath. The content is returned as a string the GUI application can evaluate, display, etc.
Please note the data types used in the above C functions. These types correspond to instant data types available in Chora. This is in so far important as the functions are intended to be called from Chora interpreter. The declaration of each individual function has to conform consequently the data type system of Chora. Following table gives you an overview of Chora data types and the corresponding C types available when creating intrinsics:
Chora data type |
C representation |
---|---|
void |
|
typedef signed char XInt8; typedef signed short XInt16; typedef signed int XInt32; typedef signed long long XInt64; |
|
typedef unsigned char XUInt8; typedef unsigned short XUInt16; typedef unsigned int XUInt32; typedef unsigned long long XUInt64; |
|
typedef float XFloat; |
|
typedef char XBool; |
|
typedef unsigned short XChar; |
|
typedef XChar* XString; |
|
typedef unsigned long XHandle; |
|
typedef struct { XInt32 X; XInt32 Y; } XPoint; |
|
typedef struct { XPoint Point1; XPoint Point2; } XRect; |
|
typedef struct { XUInt8 Red; XUInt8 Green; XUInt8 Blue; XUInt8 Alpha; } XColor; |
IMPORTANT
When implementing your own intrinsic functions you have to limit their declaration to the above listed data types. No other types can be used! By the way, the above listed C types are equal to the found in the generated C code and in the native code implemented for integration purpose with the target system. Accordingly, when implementing an intrinsics module you should be able to reuse the code implemented for the target system.
★Let's start implementing the both functions OpenFolder() and CloseFolder(). In Microsoft Visual Studio edit the file main.c as follows:
#include "ewrte.h" #include "ewgfx.h" /* Include VC++ header files needed in this example. */ #include <stdio.h> #include <wchar.h> #include <io.h> #include <windows.h> /* Following variables will store the collected file names. */ static int NoOfFiles = 0; static wchar_t** Files = 0; /* The function frees the information collected by the preceding OpenFolder() invocation. */ static void CloseFolder( void ) { int fileNo = 0; /* Release the memory reserved for the collected file names. */ for ( ; fileNo < NoOfFiles; fileNo++ ) if ( Files[ fileNo ]) EwFree( Files[ fileNo ]); /* Release the memory reserved for the array with file names. */ EwFree( Files ); /* No collected data anymore. */ Files = 0; NoOfFiles = 0; } /* The function evaluates the given folder and collects all files matching aPattern in the global array 'Files'. The folder is searched relative to the directory where the Embedded Wizard project is found. */ static void OpenFolder( XString aFolder, XString aPattern ) { wchar_t path[ MAX_PATH ]; long handle; struct _wfinddata_t fileInfo; int fileNo = 0; /* First ensure that names of evtl. previously collected files are freed. */ CloseFolder(); /* The folder is searched relative to the current working directory. This corresponds to the directory of the opened Embedded Wizard project. Limit to files matching the pattern. */ wcscpy_s( path, MAX_PATH, aFolder ); wcscat_s( path, MAX_PATH, L"\" ); wcscat_s( path, MAX_PATH, aPattern ); /* Phase 1: count the files matching the pattern. */ if (( handle = _wfindfirst( path, &fileInfo )) != -1 ) { do { /* Is this a file? */ if ( !( fileInfo.attrib & _A_SUBDIR )) NoOfFiles++; } while (( _wfindnext( handle, &fileInfo ) == 0 )); /* Terminate the search operation and release unused memory. */ _findclose( handle ); } /* Phase 2: Allocate memory for an array containing the file names. */ Files = EwAlloc( NoOfFiles * sizeof( XString )); memset( Files, 0, NoOfFiles * sizeof( XString )); /* Phase 3: Collect the file names matching the pattern. */ if (( handle = _wfindfirst( path, &fileInfo )) != -1 ) { do { /* Is this a file? Collect it in the array 'Files' */ if ( !( fileInfo.attrib & _A_SUBDIR ) && ( fileNo < NoOfFiles )) { int len = wcslen( fileInfo.name ); Files[ fileNo ] = EwAlloc(( len + 1 ) * sizeof( wchar_t )); wcscpy_s( Files[ fileNo++ ], len + 1, fileInfo.name ); } } while (( _wfindnext( handle, &fileInfo ) == 0 )); /* Terminate the search operation and release unused memory. */ _findclose( handle ); } }
★Now enhance the implementation in main.c by the next both functions to query the number and the names of the collected files:
/* The following function just returns how many files have been collected by the preceding OpenFolder() invocation. */ static XInt32 GetNoOfFiles( void ) { return NoOfFiles; } /* The following function returns the collected file with the given number. If the file is not available, the function returns an empty string. */ static XString GetFileName( XInt32 aFileNo ) { /* The requested file is not existing. Return an empty string. */ if (( aFileNo < 0 ) || ( aFileNo >= NoOfFiles )) return 0; /* Return an Embedded Wizard string containing a copy of the collected file name. */ return EwNewString( Files[ aFileNo ]); }
In the code above, please note the usage of the function EwNewString(). This function ensures that the returned value is a correct string, as expected by Embedded Wizard. Returning directly the pointer stored in the array Files or any other pointer to a string created by your own implementation would cause Embedded Wizard Studio to behave unpredictably or even to crash the prototyping environment. Due to the memory management strings should always be handled carefully. See also: Be careful when exchanging strings.
Similarly to the programming language C, Embedded Wizard manages strings as arrays of character codes with 0 (zero) code as terminator at the end of a string. Accordingly, when you implement intrinsic functions you can access and evaluate the string content easily. You should however know, that in Embedded Wizard created applications the characters within a string are generally 16-bit wide (16-bit C wchar_t type). Moreover, empty strings can optionally be represented by a NULL pointer. Your implementation should thus be ready to handle the situation of a string being NULL. Accordingly, a function, which returns an empty string, can return a NULL pointer.
The function EwNewString() belongs to the Runtime Environment (RTE) of the C Platform Package providing common functionality to run Embedded Wizard generated applications in the target system. Few of the RTE functions are also available when you implement an intrinsics module. The table below provides an overview of the functions you can invoke from the intrinsics implementation similarly as you do from the native code in the target device:
Runtime Environment function |
Description |
---|---|
C Platform Package function to reserve a memory block from the memory heap used by Embedded Wizard. |
|
C Platform Package function to free a memory block reserved on Embedded Wizard own memory heap. |
|
C Platform Package convenience function to create an operand of type 'XPoint' (Chora type 'point'). |
|
C Platform Package convenience function to create an operand of type 'XRect' (Chora type 'rect'). |
|
C Platform Package convenience function to create an operand of type 'XColor' (Chora type 'color'). |
|
C Platform Package function to create a duplicate of a wide-character (16-bit) string. |
|
C Platform Package function to create a wide-character (16-bit) string from an ANSI (8-bit) coded source string. |
|
C Platform Package function to create a wide-character (16-bit) string from an UTF-8 coded source string. |
|
C Platform Package function to create a string filled with a character. |
|
C Platform Package function to query the length (the number of characters) of a string. |
|
C Platform Package function to query the length of an UTF-8 string resulting from an Embedded Wizard string. |
|
C Platform Package function to compare two strings. |
|
C Platform Package function to convert the wide-character (16-bit) coded string in an ANSI (8-bit) string. |
|
C Platform Package function to convert a string in UTF-8 format. |
|
C Platform Package function to print outputs on the device console. During prototyping, all outputs are displayed in the Log window. The function is useful when debugging your intrinsics module. |
★Finally add to main.c the implementation for the remaining intrinsic function GetTextFileContent():
/* The following function opens the given text file, reads its content and returns it as a string. The parameter aFilePath specifies the location of the file relative to the directory where the Embedded Wizard project is found. */ static XString GetTextFileContent( XString aFilePath ) { FILE* file; long size; XString result; unsigned char* buf; /* Try to open the file. If the operation fails, return an empty string. The file is searched relative to the current working directory. This corresponds to the directory of the opened Embedded Wizard project. */ if ( _wfopen_s( &file, aFilePath, L"rt" ) != 0 ) return 0; /* Query the size of the file in bytes (character). */ fseek( file, 0, SEEK_END ); size = ftell( file ); fseek( file, 0, SEEK_SET ); /* Reserve temp. memory for the file to load. */ buf = EwAlloc( size ); /* Load the file content into the buffer and close the file. */ fread( buf, 1, size, file ); fclose( file ); /* Assuming, the loaded content is encoded in Utf8 format, create from it a new Embedded Wizard string. */ result = EwNewStringUtf8( buf, size ); /* Free the temp. used memory and ... */ EwFree( buf ); /* ... return the just created string. */ return result; }
With the above steps the intrinsics module contains all C functions intended to be called from Embedded Wizard. As you learned, you implement the functions using C and you can include all the libraries provided in Microsoft Visual Studio as well as invoke functions of the Win32 API. In other words, you implement the functions exact as you do with any other program module intended to run on Microsoft Windows.
The unique two differences are the strict usage of Embedded Wizard data types in the declaration of the functions and the usage of provided Runtime Environment (RTE) functions. The last aspect is especially important when the implementation of the intrinsic is intended to process or return strings. Remember: Be careful when exchanging strings.
Provide description for the Intrinsics Module
In order to be recognized as a valid intrinsics module its implementation has to include an EW_MODULE(...) section as shown in the code below:
EW_MODULE ( INTRINSICS_IFC_VERSION, L"IntrinsicsModule", L"Description of the module" )
Except the last string literal the contents of this section are always equal. In the last string literal you can specify optional description for the intrinsics module. Although this information is not evaluated in the actual version, it is a good style to describe the module properly.
★In our example, add the following code to the file main.c:
EW_MODULE ( INTRINSICS_IFC_VERSION, L"IntrinsicsModule", L"Intrinsics module to access the file system" )
Declare the interface for the Intrinsics Module
In the above section we explained how to implement the functionality of an intrinsics module. As you learned, intrinsics are ordinary C functions using Embedded Wizard conform data types in their declaration. As next the functions have to be exported so they can be seen from the Embedded Wizard prototyping environment. For this purpose it is necessary to enhance the implementation of the intrinsics module by an EW_DEFINE_INTRINSICS..EW_END_OF_INTRINSICS table. This table refers all affected C functions and provides information about their parameters and return values. Knowing this, Embedded Wizard can find and invoke the functions.
★In our example, add the following code to the file main.c. Ensure the code is arranged below the C functions:
/* Table describing all intrinsics implemented in this module */ EW_DEFINE_INTRINSICS EW_INTRINSIC ( L"IntrinsicOpenFolder", L"void", 2, L"string,string", L"aFolder,aPattern", OpenFolder ) EW_INTRINSIC ( L"IntrinsicCloseFolder", L"void", 0, L"", L"", CloseFolder ) EW_INTRINSIC ( L"IntrinsicGetNoOfFiles", L"int32", 0, L"", L"", GetNoOfFiles ) EW_INTRINSIC ( L"IntrinsicGetFileName", L"string", 1, L"int32", L"aFileNo", GetFileName ) EW_INTRINSIC ( L"IntrinsicGetTextFileContent", L"string", 1, L"string", L"aFilePath", GetTextFileContent ) EW_END_OF_INTRINSICS
The EW_DEFINE_INTRINSICS..EW_END_OF_INTRINSICS table consists of EW_INTRINSIC(...) sections, one for each function you want to expose to Embedded Wizard as an intrinsic. In our example case, we have specified an interface consisting of 5 functions. Accordingly, the table contains 5 EW_INTRINSIC(...) sections. Each section expects 6 parameters described below:
Parameter |
Description |
---|---|
1 |
16-bit wchar_t string literal specifying the name of the intrinsic how it should be exposed in Embedded Wizard prototyping environment. This name has to start with a letter A..Z or a..z followed by optional digits 0..9, underscore signs _ and further letters A..Z, a..z. |
2 |
16-bit wchar_t string literal specifying the Chora data type of the value returned by the intrinsic. This can be one of int8, int16, int32, int64, uint8, uint16, uint32, uint64, float, bool, char, string, point, rect, color according to the declaration of the respective C function. If the intrinsic does not return any value, specify void in this parameter. |
3 |
An integer specifying the number of parameters expected by the intrinsic. This value should correspond to the number of parameters declared in the respective C function. If the function does not expect any parameter, specify the value 0 here. |
4 |
16-bit wchar_t string literal containing a comma separated list of Chora data types matching the parameters of the respective C function and without any white spaces in-between. Valid data types are int8, int16, int32, int64, uint8, uint16, uint32, uint64, float, bool, char, string, point, rect, color. The number of data types found in this list has to correspond to the integer value specified in the third parameter of EW_INTRINSIC(). If the function does not expect any parameter, the string is empty. |
5 |
16-bit wchar_t string literal containing a comma separated list with parameter names according to the declaration of the respective C function and without any white spaces in-between. Each parameter name has to start with a letter A..Z or a..z followed by optional digits 0..9, underscore signs _ and further letters A..Z, a..z. The number of names found in this list has to correspond to the integer value specified in the third parameter of EW_INTRINSIC(). If the function does not expect any parameter, the string is empty. |
6 |
The C function to be associated to the intrinsic. |
Compile the Intrinsics Module
Once all C functions are implemented and the interface is declared, the intrinsics module is ready to be compiled:
★In Microsoft Visual Studio select the menu command :
In the case, your implementation contains some errors, Visual Studio will report this accordingly. If this occurs, review your code and verify whether the project configuration is correct.
Use the Intrinsics Module in an Embedded Wizard project
If you have followed all steps described in preceding sections and Visual Studio compiled the project without any error, the intrinsics module is ready to be used. Following are the steps how to add the intrinsics module to your Embedded Wizard project:
★Use Windows Explorer to navigate to the directory containing the Visual Studio project. The intrinsics module *.ewi file is stored in the sub-folder Debug or Release depending on the selected configuration in Visual Studio:
★Copy the file and paste it into the directory containing your Embedded Wizard project.
From now Embedded Wizard may load the intrinsics module automatically each time you start the prototyping environment. This is also the case when you switch to a Composer page containing a GUI component. Accordingly, even when editing the GUI component in Composer all the functionality provided in the intrinsics module can be used. For security reasons, Embedded Wizard asks for your confirmation before loading the intrinsics module for the first time. This is essential, since an intrinsics module contains executable code. Embedded Wizard presents following message box in such case:
In case of our intrinsics module example you can accept the operation by clicking on the Log window:
or . If you activate the button , the loading of the intrinsics module will be suppressed for this time. Next time when Embedded Wizard tries again to load the module the message box appears again. The loading of an intrinsics module is reported as a message in theThe usage of the functionality implemented in the intrinsics module is similar to how you invoke Chora built-functions or even how you invoke functions in C. The following code snippet uses the intrinsics module from our example to enumerate all files stored within a folder SVG_Icons and to create from the file names a string. Please note the names of the functions used in this implementation (e.g. IntrinsicOpenFolder). These correspond to the names specified in the intrinsics table:
var string names = ""; $if $prototyper var int32 fileNo = 0; // Open the folder 'SVG_Icons' and collect all text files found there. IntrinsicOpenFolder( "SVG_Icons", "*.txt" ); // Enumerate all collected files and prepare a string with their names. for ( ; fileNo < IntrinsicGetNoOfFiles(); fileNo = fileNo + 1 ) names = names + ", " + IntrinsicGetFileName( fileNo ); // Close the folder and release the collected names. IntrinsicCloseFolder(); $endif // Display all the names in the Log window trace names;
Please note in the code above the usage of the $if .. $endif preprocessor directives. These ensure that the enclosed code section is executed only in the prototyping environment. It is not taken in account when generating code for the target system as there is no intrinsics module functionality available. When implementing code intended to invoke an intrinsic you should alway enclose the code between a pair of such $if .. $endif directives, otherwise Embedded Wizard will report errors.
To demonstrate practically the creation and the usage of intrinsics modules we have prepared the following example. It contains a Visual Studio C++ 2012 project to create an intrinsics module intended to enumerate files found within a folder. This is exact the intrinsics module we analyzed and created in the preceding sections. This example also contains an Embedded Wizard project using this intrinsics module:
Please note, the example presents eventually features available as of version 12.00
The GUI component implemented in this project limits to display a list with collected file names and the content of the actually selected file. For demonstration purpose, the files contain SVG path data for simple icons. By using the Up/Down keys you can navigate in the list and scroll it. When you try out this example you will find that the intrinsics modules are not limited to the Prototyper window, but their functionality is also available in the Composer window when you edit the GUI component. Please note the file names listed in Composer:
Loading dependent DLLs
If your intrinsics module does depend on other DLLs (Windows dynamic link libraries), it is important to understand how the DLLs are found. In the case a depending DLL could not be found, the loading of the entire intrinsics module is aborted. Embedded Wizard searches for the DLLs in following locations:
★the directory containing the intrinsics module.
★all directories listed in the Windows environment variable PATH.
★Windows folder System32.
Test for the existence of an Intrinsic
By using the $if .. $endif preprocessor directives and the test operator is_intrinsic you can explicitly include or exclude code depending on whether a given intrinsic is available during the prototyping. In the simplest case, this technique is used to avoid Chora errors when trying to invoke a non existing intrinsic. The intrinsic is only invoked when it really exists. Otherwise it is ignored:
// The following code is executed only, when there is an intrinsic // 'IntrinsicOpenFolder' existing. $if is_intrinsic( IntrinsicOpenFolder ) IntrinsicOpenFolder( "SVG_Icons", "*.txt" ); $endif
It can also be used to conditionally add functionality to your application or to warn the user when some intrinsics are missing. For example, when the user forgot to copy the intrinsics module to the project directory:
$if !is_intrinsic( IntrinsicOpenFolder ) $warning "No file access intrinsic found." $endif
Understand the life cycle of an Intrinsics Module
From technical point of view intrinsics modules are ordinary Microsoft Windows DLLs (dynamically loadable libraries). In order to use the functionality implemented in such module, Embedded Wizard Studio loads it into address space of its own process. Later, when the module is not needed anymore, Embedded Wizard Studio unloads it again. This section explains this process step by step:
1.Just in the moment when the user starts the Prototyper or switches to a Composer page containing a GUI component, Embedded Wizard Studio initiates a new instance of its prototyping environment. During this step Embedded Wizard searches the project directory as well as all directories of units belonging to the project for files with the extension *.ewi. This results in a list of potential intrinsics modules.
2.Thereupon Embedded Wizard Studio tries to load the estimated *.ewi files into its address space. The order in which the files are processed is not predetermined. Files that have the right file extension *.ewi but are not valid intrinsics modules are recognized and excluded from the loading. A respective warning is shown in the Log window in such case.
3.Immediately after loading an intrinsics module, the module is initialized. If you want to handle the initialization (e.g. startup other software components existing in your module), implement the function EwInitModule(). This function returns a pointer to a data structure associated to the current instance of the intrinsics module. This so-called context can be used to store state information of the instance. If no context is needed in your intrinsics module, the function can return a NULL pointer. Following is an example of the EwInitModule() function:
EW_ENTRYPOINT void* EwInitModule( void ) { /* Perform initialization code for the module. */ ... return 0; }
4.Once all intrinsics modules have been loaded and initialized, the functions EwLinkModule() are invoked for each module implementing such function. Since these functions are invoked after all modules are loaded, they are ideal to finalize the initialization and (as its name suggest) to link the modules with each other. For example, if module A depends on the functionality implemented in module B, module A can implement the function EwLinkModule() and search for the module B. In its first parameter aList, the function receives a list with all loaded modules. The usage of this parameter as well as the application case of linking modules are explained in the separate section Access Intrinsics implemented in foreign Modules. The second parameter aContext contains the value returned from the corresponding EwInitModule() function. It can be used to identify the instance of the intrinsics module:
EW_ENTRYPOINT void EwLinkModule( XModuleContext* aList, void* aContext ) { /* Finalize the initialization and/or search for other modules to link with. */ ... }
5.After all intrinsics modules have performed their EwLinkModule() invocations, the modules are ready to be used. Concrete, the prototyping environment can invoke the intrinsics implemented in the modules.
6.When the user closes the Prototyper window or switches the Composer page, the associated prototyping environment is de-initialized again. As first the functions EwUnlinkModule() are invoked for each module implementing such function. This is ideal to resolve relations established between the modules during the preceding EwLinkModule() invocation. The function EwUnlinkModule() can also be used to perform the first de-initialization phase. The function receives in its single parameter aContext the value returned from the corresponding EwInitModule() function. This value can be used to identify the instance of the intrinsics module:
EW_ENTRYPOINT void EwUnlinkModule( void* aContext ) { /* Resolve previously established relations to other modules and/or de-initialize this module. */ ... }
7.Finally, all intrinsics modules are unloaded from the address space of Embedded Wizard Studio. The order in which the modules are unloaded is not predetermined. If a module implements the function EwDoneModule(), the function is invoked shortly before the module is unloaded giving it the last chance to release eventually used resources and finalize its de-initialization. Following is an example of the EwDoneModule() function. Please note the parameter aContext containing the value returned from the corresponding EwInitModule() function. This value can be used to identify the instance of the intrinsics module:
EW_ENTRYPOINT void EwDoneModule( void* aContext ) { /* De-initialize this module. */ ... }
The usage of a context is optional. It allows you to associate data to each instance of the intrinsics module. This data can thereupon be used in each invocation of the above explained functions. You should consider, that at the runtime Embedded Wizard Studio can manage two separate prototyping environments (1: Prototyper and 2: Composer). Accordingly two instances of an intrinsics module can exist. By using the context you are able to distinguish them properly. Within the context you can also store any state information associated to the instance. The following code demonstrates the usage of the context:
/* Definition of a structure to store context data associated to each instance of the intrinsics module. */ typedef struct { /* Some variables to store the state of the instance. */ int StateA; int StateB; ... } ModuleContext; EW_ENTRYPOINT void* EwInitModule( void ) { /* Reserve memory for the context. */ ModuleContext* context = malloc( sizeof( ModuleContext )); /* Perform initialization code for the module. */ context->StateA = ... context->StateB = ... return context; } EW_ENTRYPOINT void EwLinkModule( XModuleContext* aList, void* aContext ) { ModuleContext* context = (ModuleContext*)aContext; /* Use the context data. */ context->StateA = ... context->StateB = ... } EW_ENTRYPOINT void EwUnlinkModule( void* aContext ) { ModuleContext* context = (ModuleContext*)aContext; /* Use the context data. */ context->StateA = ... context->StateB = ... } EW_ENTRYPOINT void EwDoneModule( void* aContext ) { /* De-initialize this module and free the context data. */ free( aContext ); }
Besides the above explained functions each intrinsics module can also implement the functions EwIntrinsicsProlog() and EwIntrinsicsEpilog(). These functions are invoked automatically just before and shortly after the usage of intrinsic functions. Especially in case of multiple instances of one and the same intrinsics module (1: Prototyper, 2: Composer) the functions help to store and restore the state information individual to each instance. The functions are declared as follows. Again, the functions receive in the parameter aContext the context information associated to the particular instance:
EW_ENTRYPOINT void EwIntrinsicsProlog( void* aContext ) { ModuleContext* context = (ModuleContext*)aContext; /* Load the state of the instance from the context */ ... = context->StateA; ... = context->StateB; } EW_ENTRYPOINT void EwIntrinsicsEpilog( void* aContext ) { ModuleContext* context = (ModuleContext*)aContext; /* Save the state of the instance in the context */ context->StateA = ... context->StateB = ... }
Use Intrinsics Modules conditionally
Per default, Embedded Wizard tries to load all *.ewi files found in the project directory and all directories of units belonging to the project. As long as the *.ewi file does implement a valid intrinsics module, the file will be loaded. By explicitly specifying a condition in the name of the *.ewi file you can control this process more precisely. To specify the condition you append at the end of the *.ewi file name a pair of square brackets [...]. Between the brackets you specify identifiers for the desired condition. Following conditions are possible:
Condition |
Description |
---|---|
composer |
The intrinsics module will be loaded only for the prototyping environment used in Composer window. For example: IntrinsicsModule[composer].ewi. |
prototyper |
The intrinsics module will be loaded only for the prototyping environment used in Prototyper window. For example: IntrinsicsModule[prototyper].ewi. |
The intrinsics module will be loaded only when the specified profile is actually selected. For example: IntrinsicsModule[Win32].ewi will be loaded only when the profile Win32 is selected. |
|
composer+ (plus) Profile |
The intrinsics module will be loaded only for the prototyping environment used in Composer window and only when the specified profile is actually selected. For example: IntrinsicsModule[composer+Win32].ewi will be loaded only when switching in a Composer page with a GUI component and the profile Win32 is selected. |
prototyper+ (plus) Profile |
The intrinsics module will be loaded only for the prototyping environment used in Prototyper window and only when the specified profile is actually selected. For example: IntrinsicsModule[prototyper+Win32].ewi will be loaded only when starting the Prototyper and the profile Win32 is selected. |
If necessary, multiple conditions separated by a , (comma) sign can be specified. The affected intrinsics module will then be loaded if one of the conditions is fulfilled. For example: IntrinsicsModule[Win32,iOS].ewi will be loaded if the profile Win32 or iOS is selected. In all other cases the intrinsics module will be ignored.
Be careful with global variables
When you play with the example application provided in the section Use the Intrinsics Module in an Embedded Wizard project you will find that it has a problem. Concrete, when you switch the Composer page during the Prototyper was active and then you interact with the Prototyper, it does not update the file names anymore. The reason for this problem is the usage of global variables in the intrinsics module to store the collected files:
/* Following variables will store the collected file names. */ static int NoOfFiles = 0; static wchar_t** Files = 0;
Being global, these variables are shared by the both instances of the intrinsics module. One instance used by the Composer window and the other by the Prototyper window. Just in the moment you switch the Composer page, the instance used by it is de-initialized and the intrinsic IntrinsicCloseFolder() is invoked. This in turn discards all previously collected file names and sets the global variables to the value 0. This modification affects also the Prototyper window, which can't access the file names anymore.
In our simple case, the interference between the Composer and Prototyper is less critical. It produces incorrect screen contents. Nothing more. In other cases, however, the problem can become grave. Especially when one instance changes/frees memory still referenced by the second instance. This can lead to a crash of Embedded Wizard Studio or at least to behave unpredictably. The following sub-sections explain different approaches how to deal with the problem of global variables.
Approach 1: Use two copies of the same Intrinsics Module
This is the simplest workaround. As explained in the section Use Intrinsics Modules conditionally you can specify in the name of the *.ewi file a condition when the module should be taken in account. Accordingly, you can duplicate the file and specify the condition composer for the first file and prototyper for the second file. At the runtime, Embedded Wizard will treat the both files as separate intrinsics modules. One module will be used only when working in the Composer window and the other will be used when working in the Prototyper window. The both do not share any global variables anymore. Following figure demonstrates the resulting files in Windows Explorer:
Approach 2: Store and restore the global variables
As explained in the section Understand the life cycle of an Intrinsics Module when you implement the functions EwIntrinsicProlog() and EwIntrinsicEpilog(), Embedded Wizard will invoke them automatically just before and shortly after the usage of an intrinsic. This is ideal to restore and store the global variables within the context associated to the respective instance of the intrinsics module. The following code demonstrates the necessary modification of the main.c file:
/* Definition of a structure to store context data associated to each instance of the intrinsics module. It will contain a copy of the global variables. */ typedef struct { int NoOfFiles; wchar_t** Files; } ModuleContext; /* This function will be called when initializing an instance of the intrinsics module. It creates a context data. */ EW_ENTRYPOINT void* EwInitModule( void ) { /* Reserve memory for the context. */ ModuleContext* context = EwAlloc( sizeof( ModuleContext )); /* Perform initialization code for the module. */ context->NoOfFiles = 0; context->Files = 0; return context; } /* This function will be called when de-initializing an instance of the intrinsics module. It frees the context data. */ EW_ENTRYPOINT void EwDoneModule( void* aContext ) { /* De-initialize this module and free the context data. */ EwFree( aContext ); } /* This function will be called before the usage of an intrinsic. It restores the global variables from the context. */ EW_ENTRYPOINT void EwIntrinsicsProlog( void* aContext ) { ModuleContext* context = (ModuleContext*)aContext; /* Restore the global variables from the context. */ NoOfFiles = context->NoOfFiles; Files = context->Files; } /* This function will be called shortly after the usage of an intrinsic. It stores the global variables in the context. */ EW_ENTRYPOINT void EwIntrinsicsEpilog( void* aContext ) { ModuleContext* context = (ModuleContext*)aContext; /* Store the global variables in the context. */ context->NoOfFiles = NoOfFiles; context->Files = Files; }
If you prefer this approach, just implement the above demonstrated functions and ensure to store properly all global variables in the context. No further handling is necessary. Below you can download the file explorer example, this time using the here explained approach to avoid the global variables problem:
Please note, the example presents eventually features available as of version 12.00
Approach 3: Avoid global variables
The third approach is the most consequent: don't use global variables. Instead implement the intrinsic functions so that they exchange the state information enclosed in data structures via parameters and return values. This expects the declaration of the C functions to be adapted as shown below:
XHandle OpenFolder( XString aFolder, XString aPattern ) void CloseFolder( XHandle aHandle ) XInt32 GetNoOfFiles( XHandle aHandle ) XString GetFileName( XHandle aHandle, XInt32 aFileNo ) XString GetTextFileContent( XString aFilePath )
The first modification affects the OpenFolder() function. In its original implementation the function stores the collected file names within a global array. The new version uses a dynamically allocated memory area for this purpose and returns the pointer to this area as handle data type. All other functions intended to evaluate the collected file names have been enhanced by an additional aHandle parameter where the pointer to the memory containing the collected file names is passed - the pointer returned by the preceding OpenFolder() invocation. The following code shows the modified version of the first 4 functions. The last function GetTextFileContent() is not affected by this adaptation:
/* Following type definition determines the data structure where collected file names are stored. */ typedef struct { int NoOfFiles; wchar_t** Files; } FileNames; /* The function frees the information collected by the preceding OpenFolder() invocation and passed in the parameter aHandle. */ static void CloseFolder( XHandle aHandle ) { FileNames* fileNames = (FileNames*)aHandle; int fileNo = 0; /* No collected files. */ if ( !fileNames ) return; /* Release the memory reserved for the collected file names. */ for ( ; fileNo < fileNames->NoOfFiles; fileNo++ ) if ( fileNames->Files[ fileNo ]) EwFree( fileNames->Files[ fileNo ]); /* Release the memory reserved for the array with file names as well as for the data structure. */ EwFree( fileNames->Files ); EwFree( fileNames ); } /* The function evaluates the given folder and collects all files matching aPattern in the global array 'Files'. The folder is searched relative to the directory where the Embedded Wizard project is found. The function returns a pointer to a data structure containing the collected names. */ static XHandle OpenFolder( XString aFolder, XString aPattern ) { wchar_t path[ MAX_PATH ]; long handle; struct _wfinddata_t fileInfo; int noOfFiles = 0; int fileNo = 0; FileNames* fileNames = 0; /* The folder is searched relative to the current working directory. This corresponds to the directory of the opened Embedded Wizard project. Limit to files matching the pattern. */ wcscpy_s( path, MAX_PATH, aFolder ); wcscat_s( path, MAX_PATH, L"\" ); wcscat_s( path, MAX_PATH, aPattern ); /* Phase 1: count the files matching the pattern. */ if (( handle = _wfindfirst( path, &fileInfo )) != -1 ) { do { /* Is this a file? */ if ( !( fileInfo.attrib & _A_SUBDIR )) noOfFiles++; } while (( _wfindnext( handle, &fileInfo ) == 0 )); /* Terminate the search operation and release unused memory. */ _findclose( handle ); } /* Phase 2: Allocate memory for a data structure and an array where the file names will be stored. */ fileNames = EwAlloc( sizeof( FileNames )); fileNames->Files = EwAlloc( noOfFiles * sizeof( XString )); fileNames->NoOfFiles = noOfFiles; memset( fileNames->Files, 0, noOfFiles * sizeof( XString )); /* Phase 3: Collect the file names matching the pattern. */ if (( handle = _wfindfirst( path, &fileInfo )) != -1 ) { do { /* Is this a file? Collect it in the array 'Files' */ if ( !( fileInfo.attrib & _A_SUBDIR ) && ( fileNo < noOfFiles )) { int len = wcslen( fileInfo.name ); fileNames->Files[ fileNo ] = EwAlloc(( len + 1 ) * sizeof( wchar_t )); wcscpy_s( fileNames->Files[ fileNo++ ], len + 1, fileInfo.name ); } } while (( _wfindnext( handle, &fileInfo ) == 0 )); /* Terminate the search operation and release unused memory. */ _findclose( handle ); } /* Return a pointer to the data structure containing the collected files. */ return (XHandle)fileNames; } /* The following function just returns how many files have been collected by the preceding OpenFolder() invocation and passed in data structure aHandle. */ static XInt32 GetNoOfFiles( XHandle aHandle ) { FileNames* fileNames = (FileNames*)aHandle; /* No collected files. */ if ( !fileNames ) return 0; return fileNames->NoOfFiles; } /* The following function returns the collected file with the given number. If the file is not available, the function returns an empty string. */ static XString GetFileName( XHandle aHandle, XInt32 aFileNo ) { FileNames* fileNames = (FileNames*)aHandle; /* No collected files. */ if ( !fileNames ) return 0; /* The requested file is not existing. Return an empty string. */ if (( aFileNo < 0 ) || ( aFileNo >= fileNames->NoOfFiles )) return 0; /* Return an Embedded Wizard string containing a copy of the collected file name. */ return EwNewString( fileNames->Files[ aFileNo ]); }
Since the declaration of the functions have been changed, it is also obligatory to adapt the table describing the exported intrinsics accordingly. Concrete, the first intrinsic returns now a handle value and other intrinsics expect one aHandle parameter more. Following is the new adapted version:
/* Table describing all intrinsics implemented in this module */ EW_DEFINE_INTRINSICS EW_INTRINSIC ( L"IntrinsicOpenFolder", L"handle", 2, L"string,string", L"aFolder,aPattern", OpenFolder ) EW_INTRINSIC ( L"IntrinsicCloseFolder", L"void", 1, L"handle", L"aHandle", CloseFolder ) EW_INTRINSIC ( L"IntrinsicGetNoOfFiles", L"int32", 1, L"handle", L"aHandle", GetNoOfFiles ) EW_INTRINSIC ( L"IntrinsicGetFileName", L"string", 2, L"handle,int32", L"aHandle,aFileNo", GetFileName ) EW_INTRINSIC ( L"IntrinsicGetTextFileContent", L"string", 1, L"string", L"aFilePath", GetTextFileContent ) EW_END_OF_INTRINSICS
The modified declaration of intrinsics affects also the usage of them within the Embedded Wizard project. The simple loop used to enumerate text files stored within a folder SVG_Icons will look as follows. Please note the new variable fileNames intended to store the pointer to the list with collected file names:
var string names = ""; $if $prototyper var int32 fileNo = 0; // Open the folder 'SVG_Icons' and collect all text files found there. // Pointer to data structure containing the collected files are stored // in the variable 'fileNames'. var handle fileNames = IntrinsicOpenFolder( "SVG_Icons", "*.txt" ); // Enumerate all collected files and prepare a string with their names. // Please note the usage of the variable 'fileNames' in the invocation of the // intrinsic functions. for ( ; fileNo < IntrinsicGetNoOfFiles( fileNames ); fileNo = fileNo + 1 ) names = names + ", " + IntrinsicGetFileName( fileNames, fileNo ); // Close the folder and release the collected names. IntrinsicCloseFolder( fileNames ); $endif // Display all the names in the Log window trace names;
Below you can download the third version of the file explorer example, this time using the here explained approach to store the collected files in dynamically created data structures instead of global variables:
Please note, the example presents eventually features available as of version 12.00
Access Intrinsics implemented in foreign Modules
Usually intrinsics modules have no dependencies to any other module. Our above example of an intrinsics module to access and enumerate files found on the local files system demonstrates this application case. In more sophisticated cases, however, it can be necessary for one module to access and invoke functions implemented in foreign modules. Typically, this is the case if your intrinsics module uses Graphics Engine functionality.
Graphics Engine implements functions needed to create and display GUI contents. In the target system it is available as a static library or as source code depending on your license. Accordingly, your implementation can access and use this functionality without difficulties. In order to have the same functionality available in the prototyping environment, Embedded Wizard Platform Packages include intrinsics modules containing the appropriate version of the Graphics Engine.
When you start the Prototyper or you switch to a Composer page containing a GUI component, the Graphics Engine intrinsics module found in the actually used Platform Package is loaded automatically. Your own intrinsic modules can thereupon link to the Graphics Engine intrinsics module and invoke its functionality. For example, when you implement an intrinsics module to create bitmaps and fill them with a dynamically generated QR-code image, your implementation will use the Graphics Engine functions EwCreateBitmap(), EwLockBitmap() and EwUnlockBitmap().
To link with the Graphics Engine module you implement in your own intrinsics module the function EwLinkModule() (See the section Understand the life cycle of an Intrinsics Module for more details). In the simplest case your adaptation may limit to an invocation of the provided function EwLinkWithGraphicsEngine as shown below:
EW_ENTRYPOINT void EwLinkModule( XModuleContext* aList ) { /* This intrinsic module requires access to the Graphics Engine API. Link this module with the Graphics Engine API. */ EwLinkWithGraphicsEngine( aList ); }
The invocation EwLinkWithGraphicsEngine() automatically imports all officially documented Graphics Engine functions to your intrinsics module. The following table provides a list of all Graphics Engine functions, which are thereupon available in your intrinsics module implementation:
Function |
Description |
---|---|
C Platform Package function to create a new bitmap. |
|
C Platform Package function to free all resources occupied by a bitmap. |
|
C Platform Package function to change color values within the palette of an Index8 bitmap. |
|
C Platform Package function to lock and direct access the pixel memory of a bitmap. |
|
C Platform Package function to unlock a bitmap. |
|
C Platform Package function to query color values from the global CLUT. |
|
C Platform Package function to determine an entry within the global CLUT corresponding to a color value. |
|
C Platform Package function to store user defined colors within empty area in the global CLUT. |
|
C Platform Package function to remove a user defined color entry from the global CLUT. |
|
C Platform Package function to change color values within the global CLUT. |
|
C Platform Package function to search the global CLUT for a color value. |
If your implementation does require an access to other intrinsics modules or to Graphics Engine functions other than the above listed, the approach will become more complicated. For this purpose, the function EwLinkModule has to search for the desired module (e.g. GraphicsEngine) and once found it, search within the module for the desired function (e.g. EwFillRectangle()). If successful, this operation returns a pointer to the function (an entry point), your implementation can thereupon invoke as it does with any other function. Following code demonstrates these operations:
/* In order to fill a rectangular area of a bitmap, the intrinsics module requires the Graphics Engine function EwFillRectangle. Prepare a global variable to store a pointer to this function. */ static void* EwFillRectangleProc = 0; /* The function is called after all intrinsics modules has finalized their initialization. It determines the entry point for the intrinsic found in the foreign module 'GraphicsEngine' and needed to fill rectangular areas. */ EW_ENTRYPOINT void EwLinkModule( XModuleContext* aList, void* aContext ) { /* First search all loaded intrinsics modules for the 'GraphicsEngine'. */ XModuleContext* module = EwFindModule( aList, "GraphicsEngine" ); /* Then query the desired entry point and store it within a global variable. */ if ( module ) EwFillRectangleProc = EwFindProc( module, "EwFillRectangle" ); }
To find the desired module you use the function EwFindModule() as shown above. In its second parameter the function expects the name of the searched module. If the module is available (it has been loaded into the prototyping environment), the function returns a pointer identifying the module. If the module was not found, NULL is returned consequently. Knowing the pointer to the module it is possible as next to search for its exported functions (entry points). For this purpose the function EwFindProc() is used. If existing, the function EwFindProc() returns a pointer to the corresponding entry point. Otherwise it returns NULL.
Now, knowing the entry point, it is possible to invoke the corresponding function. For this purpose you have to implement a wrapper function. The declaration of the wrapper function has to be equal to the original declaration of the respective function found in the foreign module. This ensures that all parameters and return values are passed in the function invocation as expected. Following is the implementation of such wrapper function for the Graphics Engine function EwFillRectangle:
/* Wrapper to the Graphics Engine API function EwFillRectangle(). This function is implemented in the separate 'GraphicsEngine' intrinsics module. Its job is to fill a rectangular area of a bitmap. */ void EwFillRectangle( XBitmap* aDst, XInt32 aDstFrameNo, XRect aClipRect, XRect aDstRect, XColor aColorTL, XColor aColorTR, XColor aColorBR, XColor aColorBL, XBool aBlend ) { typedef void (*tmp)( XBitmap* aDst, XInt32 aDstFrameNo, XRect aClipRect, XRect aDstRect, XColor aColorTL, XColor aColorTR, XColor aColorBR, XColor aColorBL, XBool aBlend ); /* Only, if the entry point to the function is known */ if ( EwFillRectangleProc ) ((tmp)EwFillRectangleProc)( aDst, aDstFrameNo, aClipRect, aDstRect, aColorTL, aColorTR, aColorBR, aColorBL, aBlend ); }
Please note, the above example uses a global variable to store the pointer to function implemented in a foreign intrinsics module. Also the approach using the convenience function EwLinkWithGraphicsEngine stores the pointers to all relevant Graphics Engine functions in internal global variables. You should consider, that this may lead to problems if there are different configurations in your project, for example when the project contains two different profiles configured to use different Platform Packages. Or there are different versions of intrinsics modules loaded conditionally.
Your intrinsics module may then be linked with two different versions of the foreign intrinsics module. It can, for example, be linked with two different Graphics Engines when you start the Prototyper using the first configuration and then you switch the configuration while there is a GUI component opened in Composer. The global variable is thereupon re-initialized with a new pointer referring the new foreign intrinsics module. In worse case, invoking the foreign intrinsic will result in undesired behavior or it will crash the Embedded Wizard Studio.
To handle with this situation, please recall the section Be careful with global variables. Summarized, you should store and restore the global variables in the context associated to the instance of your intrinsic. In case of the global variables managed by the convenience function EwLinkWithGraphicsEngine, the operation is not complicated. The following code demonstrates all you have to do to save and restore the global variables containing the pointers to Graphics Engine functions:
/* Declaration of a data structure intended to store a copy of all globally existing data members permitting multiple instances of the module to coexist simultaneously. Since this example uses Graphics Engine, it is important to store the pointers to the Graphics Engine module in the context. */ typedef struct { XGraphicsEngineAPI GraphicsEngineAPI; } YourContextStruct; /* At the initialization time of the module, create a data structure to store there a copy of global data members. */ EW_ENTRYPOINT void* EwInitModule( void ) { YourContextStruct* context; context = malloc( sizeof( YourContextStruct )); memset( context, 0, sizeof( YourContextStruct )); return context; } /* At the de-initialization time of the module, free the data structure used to store a copy of global data members. */ EW_ENTRYPOINT void EwDoneModule( void* aContext ) { free( aContext ); } /* Just before Chora interpreter invokes an intrinsic, restore the state of all relevant (global) variables from the context data structure. */ EW_ENTRYPOINT void EwIntrinsicsProlog( void* aContext ) { YourContextStruct* context = (YourContextStruct*)aContext; /* Since this example uses Graphics Engine, it is important to store the pointers to the Graphics Engine module in the context. */ EwGraphicsEngineAPI = context->GraphicsEngineAPI; } /* Just after Chora interpreter invoked an intrinsic, store the state of all relevant (global) variables in the context data structure. */ EW_ENTRYPOINT void EwIntrinsicsEpilog( void* aContext ) { YourContextStruct* context = (YourContextStruct*)aContext; /* Since this example uses Graphics Engine, it is important to store the pointers to the Graphics Engine module in the context. */ context->GraphicsEngineAPI = EwGraphicsEngineAPI; }
To demonstrate practically how to access functionality found in a foreign intrinsics module we have prepared the following example. It contains a Visual Studio C++ 2012 project to create an intrinsics module intended to render from a given text the corresponding QR-code image and return it via Extern Bitmap interface. Since this intrinsics module depends on the functionality available in the Graphics Engine, it uses the above explained approach to link with the Graphics Engine and access the functions EwCreateBitmap(), EwLockBitmap() and EwUnlockBitmap():
Please note, the example presents eventually features available as of version 12.00
This example contains also an Embedded Wizard project with a GUI component where you can enter some text. When you start the GUI component in the Prototyper and edit the text, you will see how the displayed QR-code image changes dynamically:
Take in account security aspects
Intrinsics modules contain code to enhance the prototyping environment by user specific functionality available in the target system only. This code is executed in context and with privileges of the Embedded Wizard Studio process. Be careful if you don't trust the author of the intrinsics module. Before loading a new intrinsics module, Embedded Wizard displays a message box asking your explicit confirmation to load the module. The module is loaded only if you activate the button or :
Activating the button *.ewi file or rename its file extension to e.g. *.bak.
suppresses the loading of the affected intrinsics module for this time only. The next time when Embedded Wizard attempts to load the module, the message box is displayed again asking your confirmation. In order to permanently prevent an intrinsics module from being loaded please delete the correspondingTIP
In any case, when the loading is suppressed be aware of eventual error messages resulting from the missing functionality provided originally in the intrinsics module.