![]() |
![]() |
![]() |
![]() |
![]() |
Using C/C++ in SDL
Introduction
To enable access to C or C++ declarations from an SDL specification, translation rules from C/C++ to SDL have been developed, that specify how C/C++ constructs may be represented in SDL. These translation rules have been implemented in the SDL Suite's CPP2SDL tool. CPP2SDL supports the translation of both C and C++ declarations.
When using CPP2SDL, it is possible to access C/C++ declarations and definitions in SDL. Figure 28 shows how CPP2SDL takes a set of C/C++ header files and, optionally, an import specification as input. Note that the import specification is only optional when CPP2SDL is executed from the command line. When using the utility from the Organizer, an import specification is created with a default configuration. An import specification holds CPP2SDL options, and may also specify which declarations in the header files are to be translated. CPP2SDL then translates the C/C++ declarations in the header files to SDL declarations. These resulting SDL declarations are saved in a generated SDL/PR file. See Introduction for more details.
Workflow
The typical workflow involved when using CPP2SDL will be illustrated with an example based on the AccessControl system. The example can be found in \IBM\Rational\SDL_TTCN_Suite6.3/sdt/examples/cpp_access. Please note that the example currently runs on Windows only. However, the principles that are demonstrated are the same on all platforms.
The AccessControl system controls the access to a building. The building has a user terminal consisting of a display, a card reader and a keypad. To get access to the building, a valid card has to be inserted and a correct 4-digit code has to be typed.
In this version of the AccessControl system, information about cards and valid codes is stored in an external database. The database will be accessed through ODBC1, which is a commonly used C/C++ API for accessing data from different kinds of databases.
The purpose of the example is to show how a C/C++ API can be accessed from SDL by means of the tools in the C/C++ Access. The example covers the most important issues regarding the usage of the C/C++ Access, and may serve as a basis for more advanced experiments.
The example described below is a walk-through of how to utilize CPP2SDL from within the Organizer. The different development phases illustrated are:
- A PR symbol is added to the Central process diagram.
- The PR symbol is refined to be an import specification, by double-clicking it and setting the document type to C++ Import Specification.
- A TRANSLATE section is added to the import specification, in which we list the names of all C/C++ declarations we need to access.
- The import specification is then saved in a file, and the import specification symbol is thereby automatically connected to this file.
- We use the CPP2SDL Options dialog to set various options for the import specification.
- Finally, we add a header file to be translated.
Editing
The first step in accessing a C/C++ API from SDL is to insert a PR symbol at the place in the SDL specification where the C/C++ declarations of the API are to be used. The PR symbol represents the inclusion of an SDL/PR file, in general. In C/C++ Access this mechanism is used to include the SDL/PR file that is generated by CPP2SDL.
In the AccessControl example, we insert a PR symbol named ODBC in the process Central. The ODBC API is accessed from this process exclusively, thereby maintaining the narrowest possible scope.
Normally, an import specification should be placed at the highest level where declarations imported by the import specification are used. However, if C/C++ variables are imported, the import specification must be placed in a scope where external SDL variables are allowed to be declared.
External variables cannot be declared at system or block level. They can only be declared in processes, procedures, services or in operator diagrams.
When a PR symbol has been added in the SDL Editor it will initially appear in the Organizer as an unconnected reference, see Figure 30.
By default, the Organizer assumes that an unconnected PR symbol is to be connected to an ordinary user-defined SDL/PR file. In this case this is not what we want. By double-clicking the PR symbol, either in the SDL Editor or in the Organizer, an edit Document dialog is opened, see Figure 31. If we change the document type from SDL/PR to C++ Import Specification, we specify that the SDL/PR file is generated from a set of C++ header files.
Ordinary PR symbols are connected to user-defined SDL/PR files, while import specification symbols are connected to generated SDL/PR files.
Since we want to create a new import specification, we leave the Show in editor check-box marked. If we already have an import specification to be used, there are two methods of connecting it. The first approach is to unmark all check-boxes and use the Connect command in the Organizer to connect to the existing import specification file. The second approach is to check the Copy existing file option and either browse for, or input the path to, the existing import specification. When using this method, it is necessary to view the file in the Text Editor, and then save in order to connect it.
An import specification can be edited manually by means of the Text Editor. However, an import specification can be left empty, and CPP2SDL options set from within the Organizer at a later stage. This will add a section called CPP2SDLOPTIONS where different options to CPP2SDL are stored. Often an import specification will contain a TRANSLATE section, with a list of the names of all declarations that you wish to be made accessible in SDL. For more information, refer to Import Specification.
In our case we add a TRANSLATE section with the names of all ODBC functions and types that we will need to access from SDL. See Figure 32.
When the import specification is saved to a file (called ODBC.is), the Organizer will automatically connect the import specification symbol to that file.
Figure 33 shows the connected import specification symbol.
The next step is to set appropriate options for the translation of the C/C++ declarations that are specified in the import specification. This is best done by means of the CPP2SDL Options dialog (see Figure 34). This dialog is opened by right-clicking on the import specification symbol in the Organizer. For more detailed information about the CPP2SDL options, see The CPP2SDL Tool.
The following options may be specified:
- Language
- The language option specifies the input language, i.e. if C or C++ declarations shall be translated. If C is selected as input language, CPP2SDL will assume that no C++ specific constructs are encountered in the input header files.
- Note that this option determines if the import specification is a C or C++ import specification. Refer to Figure 31 where we selected which type of import specification to use.
- Dialect
- These check-boxes make it possible to specify what C/C++ dialects that are to be supported by CPP2SDL. If no check-boxes are marked, the ANSI C/C++ dialect is supported.
- In our example we use the ODBC implementation from the Microsoft Foundation Classes, so we need support for the Microsoft dialect.
It is normally recommended to preprocess the input C/C++ headers with a compiler rather than a plain preprocessor. The reason for this is that a compiler may set several useful preprocessor defines.
- Preprocessor Options
- Pointer, Array, Template, Keyword, Incomplete, Underscore
- Generate SDL Representations for Fundamental Types
- Set this check-box if SDL representations for fundamental C/C++ types are to be included in the translation. These SDL representations are defined in SDL/PR files, which are described in detail in SDL Library for Fundamental C/C++ Types.
When appropriate CPP2SDL options have been set for an import specification, the next step is to add the C/C++ header files that are to be translated. This is done by selecting the import specification and, in the Edit menu, select Add Existing... Added header files will appear under the import specification symbol in the Organizer, see Figure 35.
An arbitrary number of header files can be added to an import specification. They will all be processed using the options that are specified for the import specification. In the AccessControl example only one header file is added (includes.h).
To see the contents of a header file double-click on its symbol in the Organizer. The Text Editor will then open and display the contents of the header file. If this is done on the includes.h header, we see that it actually includes several other header files. The reason for using a wrapper header like includes.h instead of adding the interesting headers under the import specification directly, is that we would like to avoid hard-coding the path to these files. By using #include <file> statements, and preprocessing the file with the Microsoft Visual C++ compiler, the location of these files will be known at compile-time.
Let us summarize what we have done in the example so far. We have edited the SDL system by adding a PR symbol, changed the PR symbol to an import specification, added a TRANSLATE listing the needed declarations, saved the import specification connecting it to the system, configured CPP2SDL using the options dialog, and adding the header file to the system.
This concludes the editing phase. It is now time to analyze the system.
Analyzing
The SDL declarations that are generated by CPP2SDL must be analyzed as case-sensitive SDL. Before starting the Analyzer, a case-sensitivity option must therefore be set:
- Select Tools in the Organizer and start the Preference Manager.
- In the Preference Manager, double-click on the SDT symbol and set CaseSensitive to on (it is by default set to off).
The Analyzer will perform three major steps during the analysis of an SDL system that contains C/C++ import specifications. During each step a message will be printed in the Organizer Log window to indicate the progress, see Figure 37.
- SDL/GR to SDL/PR Conversion
- The Analyzer requests the SDL Editor to perform an SDL/GR to SDL/PR conversion. This means that all graphical SDL symbols are converted to their textual representations. In particular, every PR symbol will be represented by a #include 'filename.pr' in the SDL/PR, where filename.pr is the name of the file to which the corresponding import specification is connected.
In our example we will thus get a #include 'ODBC.pr' in the SDL/PR representation of the process Central.- C/C++ to SDL Conversion
- This step is performed once for each import specification in the system. The header files associated with an import specification are parsed and analyzed by CPP2SDL. Errors that are reported during this phase may, for example, be due to differences in language support and inappropriate preprocessor settings. If so, you can set the correct language dialect and suitable preprocessor options in the CPP2SDL Options dialog. Syntax errors and some semantic errors in the header files will also be checked for during this phase. For more information about how CPP2SDL handles errors, see Example usage of some C/C++ functionality.
If no errors are found, CPP2SDL will generate an SDL/PR file with the result of the translation. Finally, some warnings may be printed, for example to notify that certain declarations for some reason could not be translated.- In our example we get a file called ODBC.pr when this step is finished.
- Syntactic and Semantic SDL Analysis
- When all SDL/PR code has been generated the SDL Analyzer will check for syntactic and semantic errors as usual. For example, it is likely that many errors will be reported if case-sensitive SDL was not set in the Preference Manager, see Figure 36. A common source for errors is that SDL representations for fundamental types were; not included at all, included at the wrong place in the SDL system, or included many times in the same SDL scope entity.
Once we have got a clean analysis of the system, it is time to proceed with code generation.
Generating
Code generation can be done either from the traditional Make dialog, or from the more powerful tool SDL Targeting Expert. To generate code for a system containing C or C++ import specifications, it is preferred to use the Targeting Expert. For example, it is much easier to link-in the object files that belong to the translated header files, using the Targeting Expert. The Make dialog will in the near future be discontinued in favor of the Targeting Expert.
A system that contains one or more C++ import specifications must be translated to C++ rather than C code. An option to the Code Generator controls whether C or C++ code is generated. This option is automatically set by the Analyzer if there are one or more C++ import specifications present in the Organizer view.
To start the Targeting Expert, select Targeting Expert... in the Generate menu in the Organizer. The Targeting Expert dialog will appear, see Figure 38. For full information about all the settings and options provided by Targeting Expert, see The Targeting Expert.
When one or more C++ import specifications are present in the SDL system, the Targeting Expert will issue a warning that a C++ compiler is needed to compile the generated code (see Figure 38). Next, right-click Component, select Simulations, and then Simulation. The compiler may be set by pressing the Compiler/Linker/Make icon, and then, under the Compiler tab, locating the compiler executable. Here we may also specify compiler options and preprocessor settings.
Make sure that the settings made in the CPP2SDL Options dialog for the preprocessor and preprocessor settings match the settings made in the Targeting Expert.
In the AccessControl example, you can use the C++ Microsoft Simulation kernel, and the generated code can be compiled with the Microsoft Visual C++ compiler.
To avoid getting loads of link errors, we also have to remember to link-in several required Microsoft libraries (e.g. the Odbc32.lib library). This is done under the Linker tab as shown in Figure 39. Simply add the file to the List of files and save.
Now everything is ready for code generation. Press the Make or Full Make buttons and the Targeting Expert will instruct the Analyzer to analyze the SDL system (see Analyzing) and then invoke the C or C++ Code Generator. Finally the generated code will be compiled and linked as specified to create a simulator executable. Figure 40 shows what the Targeting Expert may look like when this has been done.
Simulating
Naturally, it is possible to simulate and debug a system on SDL level even if it uses C or C++ declarations. The standard SDL simulator can be used for this.
A simulator will automatically start when making from Targeting Expert. At other times than making to start a simulation of a system, select SDL in the Tools menu in the Organizer or in the Targeting Expert. Then select Simulator UI and the SDL Simulator user interface will start. To load the simulator executable that was generated above, select File and Open... in the SDL Simulator UI.
For the AccessControl example, two customized buttons are available for the Simulator UI. They may be loaded by selecting Buttons and then Load...The "GUI" button starts a GUI for the AccessControl system and waits for you to single-step or go through the system by interacting with the GUI. The "GUI+MSC" button also activates the GUI, and in addition generates an MSC trace. In Figure 41 an example of an MSC trace of the AccessControl system is shown.
The Simulator will treat C++ classes as C structs, but with the additional possibility of invoking the constructors of the class. For example, when the value of a C++ class, that is instantiated in SDL, is changed from the Simulator, the following steps are performed:
- The Simulator pops up a dialog showing a list of available constructors. For example:
- If a constructor was selected, the Simulator will prompt for its actual arguments.
- Finally, the Simulator allows public member variables to be explicitly set using either the SDL or ASN.1 syntax. For example:
The steps for instantiating a C++ class from the Simulator (e.g. by sending a signal containing a parameter of class type) are similar to the ones shown above.
Summary of the AccessControl Example
The walk-through of the AccessControl example above has shown the typical workflow when using the C/C++ Access.
- The SDL specification is edited by adding PR symbols to it, and they are refined to become import specifications. C/C++ header files are added under each import specification, and appropriate translation options are set by means of the CPP2SDL Options dialog. A TRANSLATE section may also be added to the import specification listing the names of the declarations to be translated.
- The SDL specification is analyzed as case-sensitive SDL. Errors in the C/C++ headers or in the SDL specification are detected by CPP2SDL or the SDL Analyzer respectively.
- C or C++ code is generated by using the Make dialog or, preferably, the Targeting Expert. The generated code is compiled and linked together with additional object files.
- The SDL specification may be simulated using the Simulator UI.
Figure 42 below shows the Organizer view of how the AccessControl system may look like when it is completed.
As can be seen in the figure, the Central process has one PR symbol that has not been refined into an import specification. Instead this symbol is connected to an ordinary SDL/PR file, macro.pr, that contains external SDL synonyms that represent C/C++ macros that are needed in the calls to the ODBC API. The sorts of these synonyms are imported by the ODBC.is import specification. An alternative technique for accessing C/C++ macros, based on the #CODE operator, is described in Accessing C/C++ Macros from SDL.
Import Specification
The import specification is a text file written in a simple C/C++ style syntax. You can specify exactly which declarations in the input header files to access, by using an import specification. The specified subset of the declarations is translated by CPP2SDL. The import specification also enables access to e.g. class and function templates. For more information about import specifications, see Import Specifications.
The example below shows a simple import specification where the identifiers a_int, i_arr and func are made available in SDL.
Example 28 : A simple import specification
TRANSLATE {a_inti_arrfunc}If an identifier in an import specification refers to a declaration that depends on other declarations, CPP2SDL will translate all these declarations as well.
There are some more advanced constructs that can be used in an import specification:
For more information about these constructs, see Advanced Import Specifications.
Templates
By using the CPP2SDL tool, instantiations of template declarations are supported.
To be able to instantiate a C++ template, CPP2SDL needs information about its actual template arguments. This information is given in an import specification.
The C++ template declaration is not itself translated to SDL. Instead an instantiation of the template is mapped to SDL.
Accessing C/C++ Constructs not Fully Supported by CPP2SDL
Accessing C/C++ Macros from SDL
Macros are used for conditional compilation, but can also be used for other purposes:
- To define constants: #define PI 3.1415
- To define types: #define BYTE char
- To define functions: #define max(a,b) a>b?a:b
Macros are not part of the C or C++ languages and are therefore not translated to SDL. Instead, the preprocessor expands all macros before CPP2SDL perform the translation.
To be able to access macro constants from SDL, the implicit #CODE operator or SYNONYM can be used, see example below.
Example 29 : Constants defined as C++ macros
#define PI 3.1415;dcl a double;task a := #CODE('PI');SYNONYM PI double = EXTERNAL 'C++';dcl a double;task a := PI;To be able to access macro definitions for types or functions, the macro __CPP2SDL__ can be used. The __CPP2SDL__ macro is defined when CPP2SDL executes, but not otherwise, and is used in a special header file (called x.h in the examples below). This header file must then be included in the set of header files that are translated by CPP2SDL.
The following examples illustrate how the __CPP2SDL__ macro can be exploited to change C/C++ headers to make macro definitions for types and functions available in SDL.
Example 30 : Macro "types" in C++ headers
#define BYTE charIn the C++ fragment above, the macro BYTE is used as if it were a type. The preprocessor will resolve all BYTE occurrences, which result in that BYTE cannot be available in SDL. To avoid this, the definition of BYTE can be changed to the following:
x.h:#ifndef __CPP2SDL__#ifdef BYTE#undef BYTE#endif#define BYTE char#elsetypedef char BYTE;#endifThe macro BYTE is now available as a type in SDL, since __CPP2SDL__ will be defined during the C++ to SDL translation. In the generated C++ code, BYTE is a macro, since __CPP2SDL__ will be undefined.
Example 31 : Macro "functions" in C++ headers
#define max(a,b) a>b?a:bBy defining max as a macro, max can be used as if it were a function. The macro max can be used for any type for which > is defined. The following definition makes max available in SDL for char and int.
x.h:#ifndef __CPP2SDL__#ifdef max#undef max#endif#define max(a,b) a>b?a:b#elseint max(int a,int b);char max(char a, char b);#endifWith the above definition, max will be regarded as an operator by the SDL system, since __CPP2SDL__ has been defined. When the C++ code generated from SDL is compiled, the C++ preprocessor will resolve the "function calls" to max, since the macro __CPP2SDL__ then will be undefined.
Function Pointers
Function pointers are mapped to untyped pointers in SDL, ptr_void(void*). This allows function pointers to be represented in SDL. However, it is not possible to work with this SDL representation. For example to call a function that the pointer points at or to assign the function pointer with the address of an SDL operator, you have to do as shown in the following example:
Example 32 : Using function pointers
int func1(int i, int j);int con_sum(int a, int b, int (*F)(int,int));TRANSLATE {func1con_sum}NEWTYPE global_namespace_ImpSpec /*#NOTYPE*/OPERATORScon_sum : int, int, ptr_void -> int;func1 : int, int -> int;ENDNEWTYPE global_namespace_ImpSpec; EXTERNAL 'C++';SDL using #CODE alternative 1:
dclsum int,pfunc ptr_void;task {pfunc := #CODE('(void*) &func1');sum := con_sum(1,4,#CODE('(int (*)(int,int)) #(pfunc)'));};SDL using #CODE alternative 2:
dclsum int;task {sum := con_sum(1,4,#CODE('&func1'));};Unsupported Overloaded Operators
In both C++ and SDL, there is a possibility to override predefined operators. In the table below, the overloaded C++ operators that CPP2SDL supports are listed.
Both shift operators and the less/greater operators in C++ are mapped to < and > in SDL. This mapping implies that overloading is supported on either < and > or << and >> in SDL. If both these operator pairs are overloaded, CPP2SDL will issue a warning, and select the former pair to be represented in SDL.
Overloaded operators, that are not supported by CPP2SDL, can be handled using the operator #CODE.
1ODBC is a de facto standard on Windows, but it has also been implemented on other platforms.
http://www.ibm.com/rational |
![]() |
![]() |
![]() |
![]() |