SCA Framework User's Guide 2010
SCA Framework User's Guide 2010
SCA Framework User's Guide 2010
Corporate
MSC.Software Corporation 2 MacArthur Place Santa Ana, CA 92707 USA Telephone: (800) 345-2078 Fax: (714) 784-4056
Europe
MSC.Software GmbH Am Moosfeld 13 81829 Munich, Germany Telephone: (49) (89) 43 19 87 0 Fax: (49) (89) 43 61 71 6
Asia Pacific
MSC.Software Japan Ltd. Shinjuku First West 8F 23-7 Nishi Shinjuku 1-Chome, Shinjyku-Ku Tokyo 160-0023, JAPAN Telephone: (03)-6911-1200 Fax: (03)-6911-1201
Worldwide Web
www.mscsoftware.com
Disclaimer
MSC.Software Corporation reserves the right to make changes in specifications and other information contained in this document without prior notice. The concepts, methods, and examples presented in this text are for illustrative and educational purposes only, and are not intended to be exhaustive or to apply to any particular engineering problem or design. MSC.Software Corporation assumes no liability or responsibility to any person or company for direct or indirect damages resulting from the use of any information contained herein. User Documentation: Copyright 2008 MSC.Software Corporation. Printed in U.S.A. All Rights Reserved. This notice shall be marked on any reproduction of this documentation, in whole or in part. Any reproduction or distribution of this document, in whole or in part, without the prior written consent of MSC.Software Corporation is prohibited. This software may contain certain third-party software that is protected by copyright and licensed from MSC.Software suppliers. MSC, MD, Dytran, Marc, MSC Nastran, MD Nastran, Patran, MD Patran, the MSC.Software corporate logo, and Simulating Reality are trademarks or registered trademarks of the MSC.Software Corporation in the United States and/or other countries. NASTRAN is a registered trademark of NASA. PAMCRASH is a trademark or registered trademark of ESI Group. SAMCEF is a trademark or registered trademark of Samtech SA. LS-DYNA is a trademark or registered trademark of Livermore Software Technology Corporation. All other brand names, product names or trademarks belong to their respective owners. PCGLSS 6.0, Copyright 1992-2005, Computational Applications and System Integration Inc. All rights reserved. PCGLSS 6.0 is licensed from Computational Applications and System Integration Inc.
Contents
SCA Framework Users Guide (DEV)
Introduction
Introduction 2 3 Organization of this manual Source Code Examples 4
SCA Overview
Introduction 6 7 11 Interface Based Programming
SCA Components 13 What is the SCA Kernel The SCA Framework Language support Platform support Summary 17
24 25 25
Defining the Component 26 Include guard 26 Include declarations 26 Component declaration 27 Generating the Code Skeletons 28 The genskeleton command 28 Adding the required functionality to the skeletons Implementing the service in C++ 29 Implementing the service in java 30 Implementing the service in C# 31 Implementing the service in visual basic 32 Building the Component 34
28
Creating a Client for the Service 35 Implementing the Application in C++ 35 Implementing the Application in Java 38 Implementing the Application in C# 39 Implementing the Application in Visual Basic Implementing the Application in Python 43 Intra-language Support Summary 46 45
41
52
Contents 3
55
IDL Specification for Interfaces and Types Grammar notation 57 IDL specification 57 Basic IDL Types 58 User-defined Types 62 Template Types 66 Constants 70 74
57
Modules, Names and Scoping Name lookup rules 74 Qualified names 75 Scoping rules 76 Interfaces and Operations 78 Interface header 78 Interface inheritance specification Interface body 79 Operation declaration 79 Forward declarations 81 Interface inheritance 82 SCAIService interface 83 Exceptions 85 86
79
SDL Specifications
Using the SCA-IDL Compiler 96 The IDL compiler command 96 Compiler processing modes 97 Location for compiler generated files The genskeleton command 99 Summary 100
98
Mapping for Enumerated Types Mapping for Structures Mapping for Arrays 110 Fixed size arrays 110 Dynamic arrays 111 Mapping for Sequences Mapping for Type Aliases Mapping for SCATypeCode 116 119 120 109
Mapping for SCAAny 121 The SCAAny class definition 121 Creating new SCAAny values 123 Inserting values into exiting SCAAny values 123 Extracting the value contained in a SCAAny 124 Assigning SCAAny values 124 Interrogating the value contained in a SCAAny 125 Miscellaneous SCAAny methods 125 Special SCAAny methods for Dynamic Arrays 126 Limitations in the SCAAny 126 Mapping for SCAResult 127 SCAResult class definition 127 Creating new SCAResult values 128 Resetting the error code in a SCAResult value 128 Adding parameters to a SCAResult value 128 Interrogating the contents of a SCAResult 129 Miscellaneous SCAResult methods 129 Mapping for Constants 130 132
Mapping for Interfaces 131 Mapping for Interface Smart Pointer Definition
Contents 5
Mapping for Interface Abstract Base Class Definition Mapping for Interface Operations 133 Mapping Interface Operation Parameters 134 Using Smart Pointers 135 Mapping for Exceptions 137 SCAException class 137 SCAUserException class 137 SCASystemException class 138 Mapping for IDL defined user exceptions 138 Special Rules for using SCA Exceptions in C++ Mapping for SCA Services 141 The inheritance form of implementation The ServiceAccess interface 145 The delegation form of implementation Singleton Services 148 Aggregation 149 Mapping for SCA Components Embedded Components 150 150 142 147
133
139
Mapping for Unsigned Data Types Mapping for String Types 158
Mapping for Enumerated Types Mapping for Structures Mapping for Arrays 161 Fixed size arrays 161 Dynamic Arrays 163 Mapping for Sequences Mapping for Type Aliases 164 166 160
159
167
Mapping for SCAAny 168 The SCAAny class definition 168 Creating new SCAAny values 169 Inserting values into exiting SCAAny values Extracting the value contained in a SCAAny Miscellaneous SCAAny methods 172
170 171
Mapping for SCAResult 173 SCAResult class definition 173 Creating new SCAResult values 174 Resetting the error code in a SCAResult value 174 Adding parameters to a SCAResult value 175 Interrogating the contents of a SCAResult 175 Miscellaneous SCAResult methods 175 Mapping for Constants 177 178 178
Mapping for Interfaces 178 Mapping for Interface Operations Mapping for Interface Parameters
Mapping for Exceptions 180 SCAException interface 180 SCAUserException class 180 SCASystemException class 181 Mapping for IDL defined user exceptions The IDL raises clause 182 Mapping for SCA Services 184 The inheritance form of implementation The ServiceAccess interface 188 The delegation form of implementation Singleton Services 188 Aggregation 188 Mapping for SCA Components Embedded Components 189 SCA Framework / JVM Interaction Java Virtual Machine Initialization The IDLTypes.jar archive 190 189 190 190
181
185 188
Contents 7
Mapping for Enumerated Types Mapping for Structures Mapping for Arrays 201 Fixed size arrays 201 Dynamic Arrays 202 Mapping for Sequences Mapping for Type Aliases Mapping for SCATypeCode 203 204 205 200
Mapping for SCAAny 206 The SCAAny class definition 206 Creating new SCAAny values 207 Inserting values into existing SCAAny value Extracting the value contained in a SCAAny Miscellaneous SCAAny methods 210
208 209
Mapping for SCAResult 211 SCAResult class definition 211 Creating new SCAResult values 212 Resetting the error code in a SCAResult value 212 Adding parameters to a SCAResult value 213 Interrogating the contents of a SCAResult 213 Miscellaneous SCAResult methods 214 Mapping for Constants 215 216 216
Mapping for Interfaces 216 Mapping for Interface Operations Mapping for Interface Parameters Mapping for Exceptions SCAException interface 217 217
SCAUserException class 217 SCASystemException class 217 Mapping for IDL defined user exceptions The IDL raises clause 218
218
Mapping for SCA Services 220 The inheritance form of implementation 221 Implementation for subservice classes 222 Creating instances of subservice classes 223 The ServiceAccess interface 224 The delegation form of implementation 224 Singleton Services 226 Aggregation 226 Mapping for SCA Components Embedded Components 227 227
Mapping for Enumerated Types Mapping for Structures Mapping for Arrays 240 241 242 243 238
Mapping for Sequences Mapping for Type Aliases Mapping for TypeCode
Mapping for SCAAny 244 The SCAAny class definition 244 Creating new SCAAny values 245 Inserting values into existing SCAAny values Extracting the value contained in a SCAAny Miscellaneous SCAAny methods 247 Mapping for SCAResult 248 SCAResult class definition 248
246 247
Contents 9
Creating new SCAResult values 249 Resetting the error code in a SCAResult value 249 Adding parameters to a SCAResult value 250 Interrogating the contents of a SCAResult 250 Mapping for Constants 252
Mapping for Interfaces 253 Mapping for Interface Operations 253 Special Interface Attributes 253 Mapping for Interface Parameters 254 Use of getInterface in Python 255 Implementing SCA Interfaces 256 Mapping for Exceptions 258 SCAException exception 258 Mapping for other exception types The IDL raises clause 260 Mapping for SCA Services 261 262
258
Accessing IDL Type Definitions from Python Running Python Scripts 267 Running Scripts with the ScriptBroker Running Scripts from the command line 267 267
264
271
10
Error Processing
Introduction 292 Using SCA Exceptions for Error Handling 294 SCA Exception Hierarchy 295 SCA Framework provided Exceptions 295 Exception API 298 Exception Propagation Rules 298 Recommended usage of SCA Exceptions 300 Complete Exception Error Handling Example for a SCA Service Using SCAResult for Error Handling 305 Introduction 305 The SCAResult data contents 306 Overview of using the SCAResult for Error Handling 307 Registering Message Tables 307 Formatting a SCAResult value 308 Complete SCAResult Error Handling Example for a SCA Service Use of SCAResult values in the client 312
301
310
MessageDispatcher Service 316 SCA::Framework::SCAIMsgTableManager Interface 316 SCA::Framework::SCAIMsgListenerManager Interface 318 SCA::Framework::SCAIMessageDispatcher Interface 319 SCA::Framework::SCAIMessageListener Interface 322 Message Listener example using SCAServiceObjectImpl template 324 Message Listener example using a Logger Service Example 325
11
Multi-Threaded Applications
Introduction 332 333 337 Thread Safety 333 SCA Framework Synchronization Primitives Kernel Thread Safety Configuration Options Threading Infrastructure 338 339
Contents 11
341
12
Versioning
Introduction 350 351 Component Metadata
13
356
Changing the Prefix for Environement Variable Names Building Applications using the SCA Kernel Running Applications using the SCA Kernel 366 367
The SCA Services Catalog 370 SCASCons Build options for catalog processing 370 Catalog Configuration Options 370 Service Catalog Precedence Rule for Duplicate Entries Sample Service Catalog File 371 Other Configuration Files IDL Type Definitions Files Message Files 372 Language Support Files 372 372 372
370
Service Manager 373 SCA::Framework::SCAIServiceProvider Interface Reference SCA::Framework::SCAIKernelInfo Interface 374 SCA::Framework::SCAIServiceCatalog Interface 375
373
Shared Library Manager 377 SCA::Framework::SCAISharedLibraryManager Interface Reference Library loading logic 379 Library Release Queue 380 SharedLibraryManager example 381
377
14
Utility Services
Introduction XML Parser 384 385
15
391
Testing of SCA components 393 SCA::Framework::SCAIBatchTest Interface Reference 393 Running Tests Manual 393 Running Tests with the Build System 394 Setting up Test Aliases 395 Specifying when Tests run by Default 396 Running Test Using a SCA Test Component 396 Testing Using a Program 396 Testing Using Python Script 397 Using a fixup Routine 398 Using the Preprocessor on the Baseline Text 399 Performing Setup and Clean Operations 399 Special Construction Variables used by the TestRun Command
401
16
Contents 13
Construction Variables 405 Directory Trees Processed by the Build System 405 Configuration files 409 Setting up the Build System in a New Source Tree 412 Setting up your Runtime Environment for the Build System 413 The SCons Construction Environment 413 Modifying Construction Variables in the SConscript File 414 Commonly Used Environment Routines 416 Microsoft Visual Studio Projects 417 Running the Build 418 Build Tasks 418 SCons Command 418 Running SCons from a Subdirectory Phases of the Build Process 419 Removing Files Created by the Build SCons Debugging Options 420 Selecting Debug and Optimize builds
Introduction
Introduction Organization of this manual Source Code Examples
Introduction
The Simulation Component Architecture, or SCA, is designed to enable the delivery of MSCs simulation technology as reusable software components. With this framework, engineers can develop integrated high-performance computing (HPC) applications more quickly and efficiently, while also making the technology more accessible to MSC clients applications. It also provides a framework that allows clients to build extensions or customizations that can easily be plugged into MSC applications or reuse components that are delivered my MSC.
Chapter 1: Introduction 3
Organization of this manual
The rest of the chapters in this manual provide details on more specialized topics that can be reviewed when needed to use the features they cover.
SCA Overview
Introduction Interface Based Programming SCA Interfaces and the IDL language SCA Services SCA Components The SCA Framework Language support Platform support Summary
Introduction
A central principle of the SCA architecture is the concept of interface-based programming which is sometimes called component-based programming. Interfaces provide a separation of the API, the clients see, from their actual implementation. When a developer separates an interface from its implementation, the client code is developed around an abstraction of the implementation or the interface. When developing SCA based components, you will be working with three levels of abstractions.
SCA Interfaces define the API that your clients will be exposed to. SCA Services provide the actual implementation of the interfaces. SCA Components provide the packages that are used to deliver the services to your clients.
This type of inter dependency is unacceptable in a large software development environment. In such environments it is desired to hide internal changes to the private portion of a class from the users of the class. For example, why should the client have to be recompiled because the implementation adds a new private data member or method? Only changes to public methods should have an effect on them. What we really need is a way of separating the API that the client uses from the code that actually implements it. To do this we introduce the concept of an interface. The following is a definition of an interface class, MyInterface, for our sample class.
MyInterface.h class MyInterface { public: virtual void doSomething() = 0; virtual void doSomethingElse() = 0; }; The interface definition is a normal C++ class where every method is a pure virtual method. In C++, a pure virtual function declaration provides only the prototype of the method and no implementation. The actual implementation for the methods remains in the same C++ class, MyImplementation, we had before. The only difference is now our implementation class must inherit from the interface class. MyImplementation.h class MyImplementation : public MyInterface { public: void doSomething(); void doSomethingElse(); }; MyImplementation.cpp void MyImplementation::doSomething() { }; void MyImplementation::doSomethingElse() { }; When using interfaces, there is one other issue that needs to be resolved. The desire is for the client to only reference the interface class and have no knowledge of the actual implementation. But, how does the client get an instance of the class. They cannot do a C++ new operation on the interface class because it is abstract meaning it does not contain all of the implementation for the methods it contains. C++ does not allow you to instantiate an abstract class. To fix this problem we introduce the concept of a factory. Each class that implements an interface that you want to expose to your clients must provide a factory function. The factory function is responsible for getting new instances of the class. Since the factory function is part of the class's implementation, it has access to the header files for the implementation which are required for the C++ new operation. The following is the definition of the factory for our example class. MyFactory.h MyInterface* MyFactory(); MyFactory.cpp MyInterface* MyFactory() { return new MyImplementation(); };
Notice that this function returns a pointer to the interface class, MyInterface, and not the implementation class. We now have all of the pieces to recode our client application to use the interface version of our example class. Client.cpp #include MyInterface.h #include MyFactory.h main() { MyInterface* inf; inf = MyFactory(); inf->doSomething(); inf->doSomethingElse(); } The client gets an instance of the MyImplementation class using its factory function, MyFactory. But it knows nothing about the implementation class because it is returned a pointer to the interface class. Once the interface pointer is obtained, any of the methods it contains may be called. When you package this class for delivery to your clients, you would only need to deliver the header file for the interface class and the object for the implementation class and its factory. There is no need to delivery any of the header or implementation files from the MyImplementation class. Because of this, the client has no knowledge of the implementation other then the API exposed to them through its interface. Another advantage of interface-based programming is that one version of the implementation can replace a different version without requiring the client code to change. This new implementation could correct errors in the previous version or it could provide different internal algorithms. In the example, you could write a different implementation class called MyImplementation2, and as long as it inherits from the same MyInterface interface class and implements all of the methods, it can be used interchangeably with the first version. The clients code would be able to use this updated code without any changes except to link with the object file for MyImplementation2. It is also possible to deliver the implementation in a shared library instead of an object library. By using this method, the clients code would not have to be modified in any way, including linking, to use the new implementation. This illustrates another important feature of an interface: once an interface is delivered to a client, it should not change. This insures that the client code will continue to function correctly even if the underlying implementation is changed. If the new implementation needs to change the interface, then it should provide a different interface for the new functionality and continue to support the old interface, if possible. The client code can then determine at run time if the implementation they are using provides the new or old interface and follow the appropriate procedure. This allows old clients to work with new implementations and new clients to work with old implementations. In summary, the use of interfaces provides the following advantages.
Client code is separated from class implementation code. As long as the interface does not change, then class implementation changes do not affect the
client.
One version of implementation can be replaced with another without requiring the client to be
The use of an IDL promotes good software engineering practice by reinforcing the idea of good interface design by forcing the developer to consider the interfaces to the system before the implementation details are coded.
Because the IDL language is different from the various implementation languages, it enables
cross language applications development. You can use any of the supported implementation languages to implement the API defined in an interface.
The use of an IDL compiler can greatly enhance productivity by automating the generation of
much of the code required to implement the many low-level details. This frees the developer from performing these mundane tasks.
Similar to multi-language support, the use of IDL also allows for the transparent access between
SCA Services
SCA services provide the implementation for the interfaces. One or more classes may be used to implement each service. These classes inherit from one or more interface classes and provide the implementation for their methods. The service is the level of functionality that a client requests. They do not request an interface because several different implementations for the same interface may reside in difference services. By requesting the service, the client can easily select from the available implementations that they require. Instances of the classes that make up a SCA service are called SCA service objects. The lifecycle of SCA service objects is automatically handled by the SCA Kernel. The technique used to manage the lifecycle depends on the language being used.
In C++, smart pointers are used to automate the destruction of service objects when all
SCA Components
A SCA component is the container that is used to deliver one or more services. Depending on the language, physically it may be a dynamically linked shared library or a Java JAR file. Client code is not aware of components. When a client requests an instance of a SCA service, the SCA Kernel will look in the SCA Service Catalog to determine which component contains the requested service. It will then load the appropriate shared library or JAR file and request an instance of the service from its factory. The lifecycle of components is also automatically handled. When all instances of services in a component are released, the shared library will be unloaded.
contain.
Gets instances of the class for a service by calling its factory method. Unloads the shared library when it is no longer required.
Some of the infrastructure related utilities provided by the SCA Framework include the following:
Build system based on the SCons software IDL compiler
Language support
The SCA Framework language support differs from language to language. The level of support for a given language can be described by the following capabilities.
Can you call SCA interfaces implemented in the same language or in other languages? Can you implement a SCA interface in the language? Can you implement a SCA service in the language?
The following is the supports matrix for the languages currently supported. Language C++ Java C# Visual Basic Python Call Interface Implement Interfaces Implement Services Yes Yes Yes Yes Yes Yes Yes Yes Yes No Yes Yes Yes Yes Yes No No
Platform support
The SCA Framework is currently support on the following Windows, Linux and UNIX platforms. Identifier aix aixi8 aix32 hpux hpuxi8 hpux32 hpuxipf hpuxipfi8 irix linux64 linux64i8 linux32 linuxipf linuxipfi8 solaris solarisi8 solaris32 win64 win64i8 win32 Description IBM RS/6000 AIX, 64 bit IBM RS/6000 AIX, ILP64 bit IBM RS/6000 AIX, 32 bit Hewlett-Packard HP-UX PA-RISC, 64 bit Hewlett-Packard HP-UX PA-RISC, ILP64 bit Hewlett-Packard HP-UX PA-RISC, 32 bit Hewlett-Packard HP-UX IPF (IA-64) Hewlett-Packard HP-UX IPF (IA- 64) (ILP64) SGI Irix, 64-bit Linux on Intel x86_64 or AMD Opteron hardware, 64 bit Linux on Intel x86_64 or AMD Opteron hardware, ILP64 bit Linux on Intel x86 or similar AMD hardware, 32 bit Linux on Intel IPF (IA-64) hardware Linux on Intel IPF (IA- 64) hardware (ILP64) Solaris on Sparc hardware, 64 bit Solaris on Sparc hardware, ILP64 bit Solaris on Sparc hardware, 32 bit Windows on Intel x86_64 or AMD Opteron hardware, 64 bit Windows on Intel x86_64 or AMD Opteron hardware,ILP64 bit Windows on x86 or similar AMD hardware, 32 bit
Note that not all of the advanced features of the platform are supported on all platforms.
Summary
An important thing to remember about the SCA Architecture is that it is not anything entirely new or radical.
SCA interfaces are just C++ pure virtual classes or Java or .NET interface classes Services are made up of one or more normal language classes. Components are just shared libraries or JAR files
The development of SCA component is not that different from the development of a normal C++ application. What SCA provides is a formalization of the processes that govern normal object based programming practices. In addition to this, the SCA Framework provides a set of tools that automates much of the mundane aspects of building and deploying SCA components. These include benefits at both build time and run time. For example the build time benefits include the automatic generation of much of the infrastructural code required by SCA service like the service factories. At runtime the SCA Kernel provides the automatic lifecycle management of SCA components. The benefits provided by SCA do not come completely free. In order to provide them it is necessary to impose some restrictions on what you are allowed to do. An example of this is the restriction on the type of interface arguments and return values that are allowed. Because of the requirement that the API defined by the IDL language must be implemented in a number of different languages and you must be able to marshal the data from one language to the other at run time, you are only allowed to use types that are fully defined. This means that indescript types like pointers are not allowed. Full details on all of these restrictions are presented in the appropriate chapters of this manual. In summary, the use of interface-based programming and the SCA Framework provide a number of benefits to the developers:
The success of any large scale system may be strongly influenced by the design of its interfaces.
The use of an IDL promotes good software engineering practice by reinforcing the idea of good interface design by forcing the developer to consider the interfaces to the system before the implementation details are coded.
Because the IDL language is different from the various implementation languages, it enables
cross language applications development. You can use any of the supported implementation languages to implement the API defined in an interface.
The use of an IDL compiler can greatly enhance productivity by automating the generation of
much of the code required to implement the many low-level details. This frees the developer from performing these mundane tasks.
Similar to multi-language support, the use of IDL also allows for the transparent access between
A services implementation can change without affecting the client, as long as the interface does
not change. Developers can replace an existing version of a service with a new version without requiring any changes to the client.
Applications can be built on a component-by-component basis.
A service can be used by all applications that use the SCA Framework.
Introduction
20 21 24 26 28
35
Introduction
The Simulation Component Architecture (SCA) Framework is designed to enable the development of reusable software components quickly and efficiently. In this chapter we will show the actual steps involved in implementing a SCA component by creating a simple component that contains one service named HelloWorld. This service will implement the SCAIHello interface. We will then show an example client application that uses this service. Each of these examples will be done in the following languages which are currently supported by the SCA Framework.
C++ C# Java Visual Basic Python
Currently you cannot implement SCA components in Python so for this language we will only show a sample client application. The following are the basic steps that you must follow to create a SCA component. The steps are the same regardless of which implementation language you choose. 1. Define the interfaces 2. Define the services 3. Define the component 4. Generating code stubs for the language of your choice 5. Add the required functionality to the generated stubs All of the code for the examples created in this chapter is delivered with the SCA Software Development Kit. The actual building of the components and client applications will not be discussed in this chapter. If you wish to actual build and run the samples please consult the instructions delivered with the examples.
Include guard
The first two lines of the IDL definition contain the standard include guard definitions that keep the file from being expanded more than once during each compilation. This may happen if the IDL file is included by more than one file that is part of the current compilation. To make sure the guard name is unique, the convention is use the full relative path to the file being guarded followed by _INCLUDED. In this case we have used SCA_HELLOWORLD_EXAMPLE_HELLOWORLD_IDL_INCLUDED for the guard name.
Include declarations
The next section of the IDL file is for the include statements for any other IDL files that are required by the interface definitions. If the SCA Framework or some other service has previously defined an interface that is being implemented, the IDL file where it is defined should be included, instead of redefining the interface in your file. It is important to remember that the file names used in these include statements must include the correct relative directory location in the delivery tree for the IDL files. You should not use the directory names in the source tree. This is because the IDL you create must be delivered to the users of your service. When they use the IDL files, they will be using them from the delivery tree. They should have no knowledge of the structure of your source tree. To enforce this, The IDL compiler will not look in the source tree for the files that are included. The build system will automatically copy the IDL files from the source tree into the correct location in your delivery tree before it runs the IDL compiler. However, if the IDL compiler is manually run, you may need to copy the files and add appropriate include paths on the command. In our IDL file we have included the IDL file SCA/Service.idl because the SCAIHello interface inherits from the SCAIService interface which is defined in this file.
Module declarations
The IDL file's module statements are used to define the namespace and the locations in the delivery tree where the IDL and header files are stored. A namespace should always be used for interface definitions to minimize naming collisions with other definitions. The fully qualified name of an interface is determined by combining the module names and the interface name. The fully qualified interface name in this example is SCA.HelloWorld.Example.SCAIHello. The implementation language's namespace for the interface is also taken directly from the module statements as well. For example in C++ the namespace in this example would be SCA::HelloWorld::Example and in Python it would be SCA.HelloWorld.Example. The installation subdirectories for the IDL in the delivery tree are also taken from the module statements. In this example, the IDL files will be stored in the idl/SCA/HelloWorld/Example directory in the delivery tree. This is the relative location that should be used by all users of the SCAIHello interface. In a similar manner, any language specific support files that are generated by the IDL compiler and are required for the clients to use your interface will also be stored in the delivery tree. The installation subdirectories for these files are also taken from the module statements. In this example, the C++ include file for this interface will be stored in the include/SCA/HelloWorld/Example directory in the delivery tree.
Interface declarations
Each interface is declared with an interface statement that contains the definitions of the methods it implements. The syntax for interface definition is similar to C++ class declarations. The main difference is that each parameter in the method definitions must contain a direction parameter. This parameter can be in, out, or inout and is used to define whether the parameter is input only, output only, or both input and output. It is used to determine how the parameters are declared and passed. It also determines the data transfer direction for marshalling the parameters between different languages or different address spaces. A SCA Framework requires that each interface inherits from the SCAIService interface. This inheritance may be direct, as in this example, or indirectly through another interface from which it inherits. The SCAIService interface defines the methods used to support interface navigation, reference counting, and runtime introspection. You need to include the file that defines this interface, SCA/Service.idl, in the IDL file unless it is already included in another file that you have included. Since every interface must inherit from the SCAIService interface, the IDL compiler will generate all the code that is required to implement the methods it contains.
Include guard
The first two lines of the SDL definition contain the standard include guard that keep the file from being expanded more than once during each compilation. To make sure the guard name is unique, the convention is use the full relative path to the file being guarded followed by _INCLUDED. In this case we have used HELLOWORLDCPP_HELLOWORLD_SDL_INCLUDED for the guard name.
Include declarations
The next section of the SDL file is for the include statements. You should include all the IDL files that define the interfaces implemented by the service. Once again remember that when including IDL files you need to use the correct relative directory location in the deliver tree and not the directory names from the source tree.
Module declarations
The SDL file's module statements are used to define the implementation code's namespace. For example, in C++, the namespace for the service's implementation class would be Samples::HelloWorld::Example. The general policy for SDL files is that each service is put in its own separate implementation code namespace to minimize the change of symbol collisions. The SDL namespace is not related to the IDL namespace and will normally be different. Remember that the interface namespace is exposed to clients and has different requirements than the implementation code's namespace. The IDL namespace needs to coexist with all of the other interfaces defined by all of the components in your application. It therefore should follow some application determined convention. The SDL namespace is unique to your implementation and you can choose any appropriate value you like. One common convention is to use the directory structure of the source tree for you namespace. There is no installation information needed for services because they are delivered in components and not installed by themselves. As a result, the SDL namespace has no affect on any installation decisions.
Service declaration
The definition of the service starts with the service statement in the SDL file. The fully qualified service name is the name clients will use to request instances of this service and is defined by the name following the service statement. It must be a fully qualified name to make sure the service name is unique from all other service names in the system. The service name in the example is SCA.Example.CPP.HelloWorld. The fully qualified service name is not based on the namespace defined by the SDL file's module statements. The service name is also not related to the IDL namespace, even though it may be similar to it. The last part of the service name, HelloWorld, is used as the name of the top level class that will be generated to implement the service.
#ifndef HELLOWORLDCPP_HELLOWORLD_CDL_INCLUDED #define HELLOWORLDCPP_HELLOWORLD_CDL_INCLUDED #include "HelloWorld.sdl" component SCA.HelloWorld.CPPGreeting { service HelloWorld; }; #endif Once again, note that this is the CDL file for the C++ implementation of the HelloWorld service. The only difference for the implementation in the other languages is the include guard and the actual name of the component. The component definition does not require a namespace or module declarations. If you include one it will have no affect on the build.
Include guard
The first two lines of the CDL definition contain the standard include guard that keep the file from being expanded more than once during each compilation. To make sure the guard name is unique, the convention is use the full relative path to the file being guarded followed by _INCLUDED. In this case we have used HELLOWORLDCPP_HELLOWORLD_CDL_INCLUDED for the guard name.
Include declarations
The next section of the CDL file is for the include statements. You should include all the SDL files that define the services that the component will contain. Because the SDL files are never delivered to the clients of your services, they are never installed in the delivery tree. As a result, the directory structure for including SDL files should be relative to the source tree and not the delivery tree.
Component declaration
The fully qualified component name is determined directly from the name following the component statement, which is SCA.HelloWorld.CPPGreeting in the example. It is not related to the IDL namespace, SDL namespace, or service name, though it may look similar to any of them. The short name is CPPGreeting, and it is used for the name of the generated library file. The fully qualified component defines the relative path where the build system stores the shared library. The delivery locations for the example components in each of the supported languages are show below. Language C++ Java C# Visual Basic Platform Windows Linux All Windows Windows File WINNT/bin/SCA/HelloWorld/CPPGreeting.dll LX86/lib/SCA /HelloWorld/libGreeting.so lib/java/SCA/HelloWorld/JavaGreeting.jar WINNT/bin/SCA/HelloWorld/CSGreeting.dll WINNT/bin/SCA/HelloWorld/VBGreeting.dll
inheritance structure but you may not remove the generated one.
You cannot change the signature of the constructor for the implementation class. The IDL
compiler will also generate factory code that is used to instantiate instances of the service and it requires the signature of the constructor as generated.
In the HelloWorld example, the only implementation that is required is a simple write statement in the printHello method. In the following sections we show the formats for the generated skeleton file for the implementation in each of the supported languages. In the example code, the light gray background shows the code generated by the genskeleton command. The lines of code that have the darker gray background represent the lines of code that were changed to add the required functionality to the generated skeletons.
The following is the content of the C++ header file HelloWorld.h that is generated. #ifndef SAMPLES_HELLOWORLD_EXAMPLE_HELLOWORLD_H_INCLUDED #define SAMPLES_HELLOWORLD_EXAMPLE_HELLOWORLD_H_INCLUDED #include "HelloWorldBase.h" namespace Samples { namespace HelloWorld { namespace Example { class HelloWorld : public HelloWorldBase { public: // Constructor and Destructor HelloWorld(SCAIHelloWorldFactoryAccess* factoryAccess); virtual ~HelloWorld(); // Methods for interface SCA.HelloWorld.Example.SCAIHello virtual SCA::SCAResult printHello(const SCA::SCAString sName);
};
} } } #endif The following is the content of the C++ implementation file HelloWorld.cpp that is generated.
#include "HelloWorld.h" #include <iostream> namespace Samples { namespace HelloWorld { namespace Example { // Constructor HelloWorld::HelloWorld(SCAIHelloWorldFactoryAccess* factoryAccess) : HelloWorldBase(factoryAccess) { } // Destructor HelloWorld::~HelloWorld() { } SCA::SCAResult HelloWorld::printHello(const SCA::SCAString sName) { std::cout << sName << " client calling HelloWorld " << "implemented in C++" << std::endl; return SCA::SCASuccess; } } } } Next we just need to add the actual functionality to the stubs. In our simple case, there were no changes required to the header file. The only change required to the implementation file was to add an include statement and an output statement in the printHello method. We now have a complete SCA service implemented in C++ that is ready to be built.
For Java, the generated implementation files will not be in the current directory. This is because the Java compiler uses the relative directory structure to determine the name of the Java package that is created. The SDL namespace is used to determine this package name. In our example, the generated Java implementation files will actually be in the directory Samples/HelloWorld/Example relative to the current directory. The following is the content of the Java implementation file Samples/HelloWorld/Example/HelloWorld.java that is generated.
package Samples.HelloWorld.Example; public class HelloWorld extends HelloWorld_base { // Constructor public HelloWorld (SCA.Framework.SCAIServiceProvider provider) { super(); setServiceProvider(provider); } // Methods for interface SCA.HelloWorld.Example.SCAIHello public final SCA.SCAResult printHello (String sName) { System.out.println(sName+" client calling HelloWorld "+ "implemented in Java"); return SCA.SCAResult.SCASuccess; }
Next we just need to add the actual functionality to the stubs. In our simple case, the only changed required to the implementation file was to add a print statement in the printHello method and to return the appropriate value. We now have a complete SCA service implemented in Java that is ready to be built.
The following is the content of the C# implementation file HelloWorld.cs that is generated.
using System.Collections.Generic; using SCA; namespace Samples { namespace HelloWorld { namespace Example { public class HelloWorld: HelloWorld_base , SCA.HelloWorld.Example.SCAIHello { // Constructor public HelloWorld (SCA.Framework.SCAIServiceProvider provider) { setServiceProvider(provider); } // Methods for interface SCA.HelloWorld.Example.SCAIHello SCA.SCAResult SCA.HelloWorld.Example.SCAIHello.printHello (string sName) { System.Console.WriteLine(sName+" client calling HelloWorld "+ "implemented in C#"); return SCA.SCAResult.SCASuccess; } } } } } Next we just need to add the actual functionality to the stubs. In our simple case, the only changed required to the implementation file was to add a write statement in the printHello method and to return the appropriate value. We now have a complete SCA service implemented in C# that is ready to be built.
The following is the content of the Visual Basic implementation file HelloWorld.vb that is generated.
Imports System.Collections.Generic Namespace Samples Namespace HelloWorld Namespace Example Public Class HelloWorld Inherits HelloWorld_base Implements SCA.HelloWorld.Example.SCAIHello ' Constructor Public Sub New(ByVal provider As SCA.Framework.SCAIServiceProvider) setServiceProvider(provider) End Sub ' Methods for interface SCA.HelloWorld.Example.SCAIHello Public Function printHello (ByVal sName As String) As SCA.SCAResult Implements SCA.HelloWorld.Example.SCAIHello.printHello System.Console.WriteLine(sName & " client calling HelloWorld " & "implemented in VB") Return SCA.SCAResult.SCASuccess End Function
_ _
Next we just need to add the actual functionality to the stubs. In our simple case, the only change required to the implementation file was to add a write statement in the printHello method and to return the appropriate value. We now have a complete SCA service implemented in Visual Basic that is ready to be built.
C++ these include header files that are stored in the delivery tree. For the other language they include implementation files that will be stored in the object directory which will be compiled and linked as required by the language.
Run the IDL compiler on the SDL file to generate the required base class implementation files
for the service. These will be stored in the object directory and be compiled and linked in with your component.
Run the IDL compiler on the CDL file to generate the required initialization function for the
component. These will be stored in the object directory and be compiled and linked in with your component. Not all languages require this step.
Compile and link your code for the implementation of the service.
#include <iostream> #include <SCA/HelloWorld/Example/SCAIHello.h> #include <SCA/SCAKernel.h> using namespace std; using namespace SCA; using namespace SCA::HelloWorld::Example; int main() { try { initializeSCAKernel(1); } catch (SCAException& e) { cout << "Initialization of SCAKernel failed" << endl << e.what() << endl; return 0; } SCAIService spService; SCAIHello hwInf; try { spService = getSCAService("SCA.Example.CPP.HelloWorld"); hwInf = static_cast<SCAIHello>(spService); spService = NULLSP; hwInf->printHello("C++"); hwInf = NULLSP; } catch (SCAException& e) { cout << "Load of HelloWorld service failed" << endl << e.what() << endl; } try { terminateSCAKernel(); } catch (SCAException& e) { cout << "Termination of the SCAKernel failed" << << e.what() << endl; } return 0;
endl
The basic SCA Kernel operations are provided in the SCA/SCAKernel.h header file. These include the following functions that are used in this example.
void initializeSCAKernel( SCA::SCAInt32 verbose=0, const SCA::SCAString& configPath="" ); SCA::SCAIService getSCAService( const SCA::SCAString name ); SCA::SCAInt32 terminateSCAKernel(); All of these will throw an exception if they encounter an error so they have been included in try/catch blocks in the sample application. The first thing our application must do is initialize the SCA kernel. initializeSCAKernel(1); Since this call may throw an exception, in the actual application we have enclosed it in a try/catch block. This is the case for all of the functions defined in the SCA/SCAKernel.h header file that we will be using. Once we have initialized the SCA Kernel, we need to get an instance of the HelloWorld service. In this example we are getting an instance of the version of the service that is implemented in C++ but it could have been one of the versions implemented in any of the other languages by just changing the service name. To do this we use the following lines of code. SCAIService spService; spService = getSCAService("SCA.Example.CPP.HelloWorld"); The getSCAService function will always return a SCAIService interface pointer on the service. This is possible because every SCA interface must inherit from the SCAIService interface. But what we really want is a SCAIHello interface pointer. In the C++ language mapping, SCA interface pointers are special C++ classes know as smart pointers. These smart pointers can automate many of the details of using the interfaces. This includes the automatic handling of reference counting and providing a simplified method of interface navigation using normal C++ casting syntax. The following lines of code will get a SCAIHello interface pointer from the SCAIService pointer that was returned when we got an instance of the service. SCAIHello hwInf; hwInf = static_cast<SCAIHello>(spService); Once we have the SCAIHello interface pointer we can then call the printHello method that it contains. hwInf->printHello("C++"); The final thing our application should do is terminate the SCA kernel.
terminateSCAKernel();
In Java, the SCA.SystemProvider interface provided by the SCA Kernel is used to access the basic SCA Kernel operations. All of the method of this interface will throw exceptions if they encounter errors, so be sure to enclose these calls in a try/catch block. The first thing our application must do is initialize the SCA kernel utilizing this interface.
import SCA.SystemProvider.*; SCA.SystemProvider.loadSCA(); Once we have initialized the SCA Kernel, we need to get an instance of the HelloWorld service. In this example we are getting an instance of the version of the service that is implemented in Java but it could have been one of the versions implemented in any of the other languages by just changing the service name. To do this we use the following lines of code. SCAIService spService; spService = SCA.SystemProvider.getService( "SCA.Example.Java.HelloWorld"); The getService function will always return a SCAIService interface pointer on the service. This is possible because every SCA interface must inherit from the SCAIService interface. But what we really want is a SCAIHello interface pointer. In the Java language mapping it is only possible to navigate from one interface to anther using the normal JAVA casting syntax if the interface is pointing to a service implemented in Java. If the service is implemented in any other language you must make an explicit getInterface call to do the navigation. Since you never know for sure what language the service you are using is written in, it is a good practice to always use this syntax. The following lines of code will get a SCAIHello interface pointer from the SCAIService pointer that was returned when we got an instance of the service. SCAIHello hwInf; hwInf = (SCAIHello)spService.getInterface( "SCA.HelloWorld.Example.SCAIHello"); Once we have the SCAIHello interface pointer we can then call the printHello method that it contains.
.
hwInf.printHello("Java"); The final thing our application should do is terminate the SCA kernel. SCA.SystemProvider.unloadSCA();
using System; using SCA; using SCA.HelloWorld.Example; class CSDriver { static void Main(string[] args) { try { SCA.SystemProvider.loadSCA(); } catch (SCAException e) { Console.WriteLine("Initialization of SCAKernel failed\n" + e.what()); } SCAIService spService; SCAIHello hwInf; try { spService = SCA.SystemProvider.getService( "SCA.Example.CS.HelloWorld"); hwInf = (SCAIHello)spService.getInterface( "SCA.HelloWorld.Example.SCAIHello"); spService = null; hwInf.printHello("C#"); } catch (SCAException e) { Console.WriteLine("Load of HelloWorld service failed\n" + e.what()); } finally { hwInf = null; } try { SCA.SystemProvider.unloadSCA(); } catch (SCAException e) { Console.WriteLine("Termination of the kernel failed\n" + e.what()); } } }
In C#, the SCA.SystemProvider interface provided by the SCA Kernel is used to access the basic SCA Kernel operations. All of the method of this interface will throw exceptions if they encounter errors, so be sure to enclose these calls in a try/catch block. The first thing our application must do is initialize the SCA kernel utilizing this interface.
using SCA; SCA.SystemProvider.loadSCA(); Once we have initialized the SCA Kernel, we need to get an instance of the HelloWorld service. In this example we are getting an instance of the version of the service that is implemented in C# but it could have been one of the versions implemented in any of the other languages by just changing the service name. To do this we use the following lines of code. SCAIService spService; spService = SCA.SystemProvider.getService( "SCA.Example.CS.HelloWorld"); The getService function will always return a SCAIService interface pointer on the service. This is possible because every SCA interface must inherit from the SCAIService interface. But what we really want is a SCAIHello interface pointer. In the C# language mapping it is only possible to navigate from one interface to anther using the normal C# casting syntax if the interface is pointing to a service implemented in one of the .Net languages which include C# and VB. If the service is implemented in any other language you must make an explicit getInterface call to do the navigation. Since you never know for sure what language the service you are using is written in, it is a good practice to always use this syntax. The following lines of code will get a SCAIHello interface pointer from the SCAIService pointer that was returned when we got an instance of the service. SCAIHello hwInf; hwInf = (SCAIHello)spService.getInterface( "SCA.HelloWorld.Example.SCAIHello"); Once we have the SCAIHello interface pointer we can then call the printHello method that it contains.
.
hwInf.printHello("C#"); The final thing our application should do is terminate the SCA kernel. SCA.SystemProvider.unloadSCA();
Imports SCA Imports System Imports SCA.HelloWorld.Example Module ModuleMain Sub Main(ByVal args As String()) Try SystemProvider.loadSCA() Catch e As SCASystemException System.Console.WriteLine("Initialization of SCAKernel failed\n" + End Try e.ToString())
Try Dim spService As SCA.SCAIService = SystemProvider.getService( _ "SCA.Example.VB.HelloWorld") Dim hwInf As SCAIHello = CType(spService.getInterface( _ "SCA.HelloWorld.Example.SCAIHello"), SCAIHello) hwInf.printHello("Visual Basic") Catch e As SCASystemException System.Console.WriteLine("Load of HelloWorld service failed\n" + End Try e.ToString())
Try SystemProvider.unloadSCA() Catch e As SCASystemException System.Console.WriteLine("Termination of SCAKernel failed\n" + _ e.ToString()) End Try End Sub End Module
In Visual Basic, the SCA.SystemProvider interface provided by the SCA Kernel is used to access the basic SCA Kernel operations. All of the method of this interface will throw exceptions if they encounter errors, so be sure to enclose these calls in a try/catch block. The first thing our application must do is initialize the SCA kernel utilizing this interface. Imports SCA SystemProvider.loadSCA() Once we have initialized the SCA Kernel, we need to get an instance of the HelloWorld service. In this example we are getting an instance of the version of the service that is implemented in Visual Basic but
it could have been one of the versions implemented in any of the other languages by just changing the service name. To do this we use the following line of code. Dim spService As SCA.SCAIService = SystemProvider.getService( _ "SCA.Example.VB.HelloWorld") The getService function will always return a SCAIService interface pointer on the service. This is possible because every SCA interface must inherit from the SCAIService interface. But what we really want is a SCAIHello interface pointer. In the Visual Basic language mapping it is only possible to navigate from one interface to anther using the Visual Basic DirectCast keyword if the interface is pointing to a service implemented in one of the .Net languages which include C# and VB. If the service is implemented in any other language you must make an explicit getInterface call to do the navigation. Since you never know for sure what language the service you are using is written in, it is a good practice to always use this syntax. The following lines of code will get a SCAIHello interface pointer from the SCAIService pointer that was returned when we got an instance of the service. Dim hwInf As SCAIHello = CType(spService.getInterface( _ "SCA.HelloWorld.Example.SCAIHello"), SCAIHello) Once we have the SCAIHello interface pointer we can then call the printHello method that it contains. hwInf.printHello("Visual Basic") The final thing our application should do is terminate the SCA kernel. SystemProvider.unloadSCA()
try: import SCA svc = SCA.getService("SCA.Example.CPP.HelloWorld") (ret,hwinf) = svc.getInterface("SCA.HelloWorld.Example.SCAIHello") hwinf.printHello("Python") except SCA.SCAException, exc: print "SCAException:",exc.what() In Python, the SCA module provided by the SCA Kernel is used to access the basic SCA Kernel operations. All of the method of this interface will throw exceptions if they encounter errors, so be sure to enclose these calls in a try/except block. The first thing our application must do is initialize the SCA kernel utilizing this interface. This is done by importing the SCA module. In Python there is no explicit call required to initialize the SCA Kernel. It is done automatically when it is required. import SCA Once we have initialized the SCA Kernel, we need to get an instance of the HelloWorld service. In this example we are getting an instance of the version of the service that is implemented in C++ but it could have been one of the versions implemented in any of the other languages by just changing the service name. To do this we use the following line of code. svc = SCA.getService("SCA.Example.CPP.HelloWorld") The getService function will always return a SCAIService interface pointer on the service. This is possible because every SCA interface must inherit from the SCAIService interface. But what we really want is a SCAIHello interface pointer. Since Python is a type less language, it is only possible to navigate from one interface to anther using an explicit getInterface call to do the navigation. The following line of code will get a SCAIHello interface pointer from the SCAIService pointer that was returned when we got an instance of the service. (ret,hwinf) = svc.getInterface("SCA.HelloWorld.Example.SCAIHello") Once we have the SCAIHello interface pointer we can then call the printHello method that it contains. hwinf.printHello("Python") In Python there is no requirement to terminate the SCA kernel.
Intra-language Support
Summary
In this chapter we have shown how to develop a simple SCA HelloWorld service in each of the supported languages. Let's review the basic steps that are required.
Design the interfaces the service will support and code them in IDL Create the SDL file for the service. Create the CDL file for the component. Run the IDL compiler to generated skeleton implementation code for the service. Add code to skeletons to implement the desired behavior for each interface method. Build the component using the SCA build system.
We then showed how to implement a simple client application in each of the supported languages that exercised our HelloWorld service.
48 49 51 56 57
94 96
Introduction
The Object Management Group (OMG) Interface Definition Language, or IDL, is the language used to describe SCA interfaces. The OMG is an open membership, not-for-profit consortium that produces and maintains computer industry specifications for interoperable enterprise applications. Some of these specifications include the Unified Modeling Language, or UML and the CORBA middleware platform. An interface definition written in the IDL language completely defines the interface. This includes its methods, each of their parameters and any user constructed data types required. The IDL definitions of the interfaces provide the information needed to develop clients that use the interface's operations. Extensions to the OMG IDL language have been added to allow for the description of SCA services and components. The IDL language is a purely descriptive language. This means that services are not written in IDL, but in languages for which mappings from the IDL concepts have been defined. Currently the SCA Framework supports mapping for the C++, Java, C#, Visual Basic and Python languages.
Source Files
The SCA IDL compiler processes three types of source files. All of these files use the same IDL language but each is restricted to different types of definitions.
IDL
These source files contain definitions of interfaces and user constructed data types. These files must have an extension of ".idl". These definitions declare the exposed API for a service that is available to its users. No implementation details are contained in the IDL files.
SDL
These source files contain service definitions and must have an extension of ".sdl". Service definitions specify the implementation details that the IDL compiler needs to generate the appropriate code to link a service to the SCA Framework. This information includes which interfaces the service will implement and how these interfaces are mapped to language specific class definitions which will be used in the implementation.
CDL
These source files contain component definitions and must have an extension of ".cdl". Component definitions specify which services a component will contain. The separation of the different types of definitions into different files has been done for several reasons.
Because IDL files contain the definitions of the published interfaces and their supporting types,
these are generally delivered with their components. Since service and component definitions contain implementation specific information you do not want to deliver these. By separating these definitions from the interface and type definitions, they do not need to be delivered.
Separating the definitions into separate files allow them to be placed in the appropriate locations
in the source tree. For example a component may include several services and each is implemented in a different directory in the source tree. This way the SDL files can be places with their service definition and the CDL file place where the component is built.
The separate files allow developers and the SCA Build system to easily determine the types of
objects that are being built in the various source tree directories without having to examine the contents of the files. For example, if a directory contains a CDL file, then it is immediately known that an appropriate package file must for a SCA component needs be built in that directory. The SCA IDL compiler processes the IDL definitions and generates the appropriate code in the chosen implementation language. In general, the following types of source code are generated.
Interface definitions: For each interface, appropriate source files are generated that defines the
interface and the operations it contains. Additional files may also be generated for any user constructed IDL types defined.
Service definitions: Two types of code are generated for SCA service definitions. During the
build process, various source files are generated which are used to link the developer's implementation code to the SCA Framework. This is done to reduce as much as possible the amount of code that must be written by the developer. This support code also provides a level of isolation between the service's implementation code and the SCA Framework. This allows framework changes to be made with less impact on the existing service implementation code. The developer can also run the IDL compiler to generate skeleton implementation code for their service. This is usually done once at the beginning of the development cycle. The generated skeletons can then be expanded with the code to implement the desired behavior for each interface method. You can also run the IDL compiler at a later time if any interface changes are made and you want to see how these affect the skeletons.
Component definitions: During the build process, the IDL compiler may be run to generate
support code used to initialize the services it implements. This is not required for all support languages. The developer never required to generate any code for the implementation of a SCA component. See the sections on the IDL mappings for the language you will be using for additional information on the generated code.
Lexical Rules
The IDL language obeys the same lexical rules as C++ with a few exceptions.
Comments
Both C and C++ style comments are supported. C style comments start with the characters "/*" and terminate with the characters "*/". These comments do not nest. C++ style comments start with the characters "//" and terminate at the end of the line on which they occur. The comment characters "//", "/*", and "*/" have no special meaning within a C++ style comment and are treated just like other characters. Similarly, the comment characters "//" and "/*" have no special meaning within a C style comment. Comments may contain alphabetic, digit, graphic, space, horizontal tab, vertical tab, form feed, and new line characters.
Identifiers
An identifier is an arbitrarily long sequence of ASCII alphabetic, digits, and underscores characters. The first character of an identifier must be an ASCII alphabetic character. All characters in the sequence are significant. When comparing two identifiers, upper and lower case letters are treated as the same letter. Identifiers that differ only in case of the letters are treated as the same identifier and can cause compilation errors if used improperly. This rule is used to allow mapping to implementation languages that are not case sensitive.
Keywords
The identifiers listed in the following table are reserved by the OMG IDL specification for use as keywords and may not be used otherwise unless they are properly escaped. Keywords must be written exactly as shown in the table. Identifier names that collide with keywords are illegal. For example, since boolean is a valid keyword, Boolean and BOOLEAN would be illegal identifiers.
The SCA Framework does not support all of the capabilities of the OMG IDL language, but since it uses a modified OMG IDL compiler, all of the OMG keywords are still restricted.
Escaped identifiers
As described in the previous section, the IDL language contains a set of reserved keywords that may not be used as identifiers. As the language evolves, new keywords that are added may inadvertently collide with identifiers used in existing IDL definitions and implementation code. Fixing these collisions could require not only the modification to the IDL definitions, but also to the implementation coded that uses them. To minimize this effect, the language allows you to lexically escape identifiers by prefixing an underscore "_" to an identifier. This is purely a lexical convention that turns off keyword checking. The resulting identifier follows all of the other rules for identifier processing except the underscore is not considered part of its name. For example, the identifier _attribute is treated the same as if it were attribute but it will not clash with the IDL reserved attribute keyword. The implementation code should still use the identifier name without the prefix. This way only the IDL definitions and not the implementation code need to be changed to fix any conflicts.
Literals
The IDL language supports the same literals as C++. Integer literals An integer literal consists of an optional "+" or "-" sign character followed by a sequence of digits that is treated as a decimal (base ten) value. If the sequence of digits starts with the digit zero, then they are treated as an octal (base eight) value. If the characters "0x" or "0X" precedes the sequence of digits, then they are treated as a hexadecimal (base sixteen) value. The hexadecimal digits also include the letters "A" through "F" which represent the decimal values of ten through fifteen, respectively. Hexadecimal digits can be either upper or lower case. Here are some examples for integer literals.
const SCAInt32 i1 = -123; const SCAInt32 i2 = 0123; const SCAInt32 i3 = 0X1A2B3C; Floating-point Literals
A floating-point literal consists of an optional sign character, an integer part, a decimal point, a fraction part, and optionally a signed integer exponent proceeded by the character "e" or "E". The integer and fraction parts both consist of a sequence of decimal (base ten) digits. Either the integer part or the fraction part, but not both, may be missing; either the decimal point or the exponent part, but not both, may be missing. The following are some examples for floating point literals. const const const const const const SCAReal64 SCAReal64 SCAReal64 SCAReal64 SCAReal64 SCAReal64 D1 D2 D3 D4 D5 D6 = = = = = = 1.23e-10; -1.23; .23; 1.; 1e10; .23e-1 // // // // // // integer, fraction and exponent integer and fraction fraction only integer only integer and exponent fraction and exponent
Character literals A character literal is one or more character enclosed in single quotes. A character is an 8-bit quantity defined by the ISO Latin-1 character set which supports a superset of the ASCII character set. The following escape sequences are supported. Newline Horizontal tab Vertical tab Backspace Carriage return Form feed Alert Backslash Question mark Single quote Double quote Octal byte value Hexadecimal byte value \n \t \v \b \r \f \a \\ \? \ \ \ooo \xhh
Each escape sequence specifies a single character. The escape "\ooo" consists of the backslash followed by one, two, or three octal digits that are taken to specify the value of the desired character. The escape "\xhh" consists of the backslash followed by the character "x" followed by one or two hexadecimal digits
that are taken to specify the value of the desired character. A sequence of octal or hexadecimal digits is terminated by the first character that is not an octal or a hexadecimal digit, respectively. The following are some examples for character literals. const const const const SCAChar SCAChar SCAChar SCAChar C1 C2 C3 C4 = = = = 'c'; '\123'; '\t'; '\XAB'; // // // // character c character with value of octal 123 horizontal tab character character with a value of hex AB
Wide character literals have an L prefix, for example. const SCAWChar WC1 = L'c' // wide character c
Attempts to assign a wide character literal to a non-wide character constant or to assign a non-wide character literal to a wide character constant will result in a compile time diagnostic. String literals A string literal is a sequence of characters surrounded by double quotes. Adjacent string literals are concatenated. Characters in concatenated strings are kept distinct. For example, the following string contains the two characters "\xA" and "B" after concatenation and not the single hexadecimal character "xAB" \xA B The size of a string literal is the number of character literals enclosed by the quotes after concatenation. Within a string, the double quote character " must be preceded (escaped) by a "\". A string literal may not contain the null character "\0". The following are some examples of string literals. const SCAString S1 = "this is a string"; const SCAString S2 = "\"quoation\""; const SCAString S3 = "one" "string"; // simple string // embeded double quotes // concatenated string // = "onestring" const SCAString S4 = "this is a " "string"; // concatenated string // = "this is a string" const SCAString S3 = "ap" "pear"; // concatenated string // = "appear"
Wide string literals have an L prefix, for example: const SCAWString WS1 = L"this is a string"; // simple wide string A wide string literal may not contain a wide character with a value of zero. Attempts to assign a wide string literal to a non-wide string constant or to assign a non-wide string literal to a wide string constant will result in a compile time diagnostic.
Constant expressions
The IDL language offers the arithmetic and bitwise binary operators shown in the following table. Operator + * / % | & ^ << >> ~ Meaning Arithmetic addition Arithmatic subtraction Arithmatic multiplication Arithmatic division Arithmatic modulo Bitwise OR Bitwise AND Bitwise exclusive OR Bitwise left shift Bitwise right shift Bitwise complement
The arithmetic operators apply to both floating-point and integer expressions with the exception of "%" which must have integer operands. Bitwise operators only apply to integer expressions. The semantics of these operators are the same as their C++ counterparts with the following exceptions.
The arithmetic operators do not support mixed-mode expressions. You may not mix integer and
See section Constant Declaration the detailed syntax and semantics of constant expressions.
Preprocessing
IDL source files are preprocessed to perform file inclusion and macro substitution before being compiled. Preprocessing is controlled by directives introduced by lines having "#" as the first non white space character. The preprocessing rules for IDL are the same as defined for the C and C++ languages. When coding IDL files, make sure you include the appropriate preprocessor guard definitions just as you would in a C or C++ header file. These are required to make sure the file will not get expanded more than once in the same compilation, which can cause compilation errors due to duplicate definitions of the symbols. #ifndef TEST_KERNEL_BASEIMPL_IDL_INCLUDED #define TEST_KERNEL_BASEIMPL_IDL_INCLUDED #include "SCA/Service.idl" module Test { module Kernel { interface SCAIBaseImpl : SCAIService { }; }; }; #endif
The preprocessor used by the IDL compiler also supports the normal conditional compilation directives. But you are strongly discouraged from using them because they may cause some confusion to the build system.
IDL specification
An OMG IDL specification consists of one or more type, constant, exception, interface, module, service and component definitions. The grammar is: <specification> ::= <definition>+ <definition> ::= <type_dcl> ";" | <const_dcl> ";" | <except_dcl> ";" | <interface> ";" | <module> ";" | <service> ";" | <component> ";" See section Type Declaration for the specification of <type_dcl>. See section Constant Declaration for the specification of <const_dcl>. See section SCA Exception Declaration the specification of <except_decl>. See section SCA Interface Declaration for the specification of <interface>. See section Module Declaration for the specification of <module>. See section SCA Services Declaration for the specification of <service>. See section SCA Component Declaration for the specification of <component>.
<boolean_type> ::= "SCABool" <any_type> ::= "SCAAny" <struct_type> ::= "struct" <identifier> "{" <member_list> "}" <member_list> ::= <member>+ <member> ::= <type_spec> <declarators> ";" <enum_type> ::= "enum" <identifier> "{" <enumerator> { "," <enumerator> }??"}" <enumerator> ::= <identifier> <sequence_type> ::= "SCASequence" "<" <simple_type_spec> ">" <string_type> ::= "SCAString" <wide_string_type> ::= "SCAWString" <array_declarator> ::= <identifier> <array_size> | <identifier> <array_size> <array_size> <array_size> ::= "[" <positive_int_const> "]" <dynarray_declarator> ::= <identifier> "[" "]" | <identifier> "[" "]" "[" "]" <scoped_name> ::= <identifier> | "::" <identifier> | <scoped_name> "::" <identifier> <positive_int_const> ::= <const_exp> The <scoped_name> in <simple_type_spec> must be a previously defined type introduced by an interface declaration <interface_dcl>, see section SCA Interface Declaration, or a type declaration <type_dcl>, see section Type Declaration. An IDL <type_spec>, which can consist of a basic or constructed type, can be used in operation declarations to assign data types to parameters and in the construction of user defined data types. The next sections describe the basic and constructed types. Basic types The IDL language supports a number of different basic data types. When specifying the size of the basic data types, the OMG IDL requirements only specify a lower bound. Since not all CPU architectures or languages provide the ability to implement exact size definitions, the range requirements for the IDL types have been left loose. When passing IDL data types between machines or languages, you are only guaranteed of maintaining the documented data ranges. For example, you may have code running on a CPU architecture or language that allows values larger than 16 bits in a SCAInt16 value, but when the data is marshaled to another CPU type or a different language, which restricts the size to 16 bits, then the data will be truncated. All basic data types are subject to changes in representation if they are transmitted between different CPU architectures. For example, a SCAInt32 value undergoes byte swapping when sent from a big-endian to a little-endian machine. The grammar for the basic type declarations is as follows: <base_type_spec> ::= <floating_pt_type> | <integer_type> | <char_type> | <wide_char_type>
| <boolean_type> | <any_type> <floating_pt_type> ::= "SCAReal32" | "SCAReal64" <integer_type> ::= <signed_int> | <unsigned_int> <signed_int> ::= <signed_byte_int> | <signed_short_int> | <signed_long_int> | <signed_llong_int> <signed_byte_int> ::= "SCAInt8" <signed_short_int> ::= "SCAInt16" <signed_long_int> ::= "SCAInt32" <signed_llong_int> ::= "SCAInt64" <unsigned_int> ::= <unsigned_byte_int> | <unsigned_short_int> | <unsigned_long_int> | <unsigned_llong_int> <unsigned_byte_int> ::= "SCAUInt8" <unsigned_short_int> ::= "SCAUInt16" <unsigned_long_int> ::= "SCAUInt32" <unsigned_llong_int> ::= "SCAUInt64" <char_type> ::= "SCAChar" <wide_char_type> ::= "SCAWChar" <boolean_type> ::= "SCABool" <any_type> ::= "SCAAny" Integer The following basic types represent integer values in the ranges indicated. SCAInt8 SCAInt16 SCAInt32 SCAInt64 SCAUInt8 SCAUInt16 SCAUInt32 SCAUInt64 Floating-point A number of different floating-point types are supported. See the IEEE Standard for Binary FloatingPoint Arithmetic, ANSI/IEEE Standard 754-1985, for complete details.
The number of bits includes the sign, the mantissa and the exponent. The actual number of bits in each field is defined in the IEEE standard.
Character
The SCAChar data type is defined as an 8-bit quantity. The ISO Latin-1 character set, which supports a superset of the ASCII character set, is used. The bottom 128-character positions are identical to ASCII. The upper 128 character positions are extensions to ASCII that allow most European languages to be used with an 8-bit character set. SCAChar Wide character The SCAWChar data type that encodes wide characters from any character set. As with character data, an implementation is free to use any code set internally for encoding wide characters. The size of a SCAWChar is implementation dependent. SCAWChar Booleans The SCABool data type is used to denote a data item that can only take a value of TRUE or FALSE. The IDL specification has no requirements on how these values are represented or about their size. SCABool SCAAny The SCAAny data type is a universal container type that can hold a value of any arbitrary IDL type with the exception of an exception type. This includes all basic types, user constructed types and interface types. This allows for interface operations to pass a value when the actual type is not known before run time. A SCAAny contains a pair of values that includes a type code value, which describes what type of data is contained, and the actual value of the data. The language mappings for each IDL data type provide operations that allow you to insert and extract the type code and data value from a SCAAny. SCAAny Any IDL type TRUE or FALSE
The SCAAny type can be compared to a void* in C. Like a pointer to a void, a SCAAny value can denote a value of any type. However, there is an important difference. The void* denotes a completely type less value that can be interpreted only with advance knowledge of its contents. In contrast, values of type
SCAAny maintain type safety. For example, if the caller places a string value in a SCAAny, the receiver cannot extract the string as a value of the wrong type. Attempts to treat the contents of the SCAAny as the wrong type will cause a run-time error. This is possible because the SCAAny contains a type code value, defining the type of data stored, which can be used by the language mappings to enforce type safety. Type codes not only serve to enforce type safety but also provide an introspection capability. The receiver of the SCAAny value can access the type code to find out what type of value it contains. This capability is useful because it makes SCAAny values stand-alone data items. The receiver of the SCAAny can always interpret the value inside it without requiring additional contextual information.
User-defined Types
In addition to the basic data types, The IDL language allows you to construct more complex types like enumerations, structures and arrays. Enumeration Enumerated types consist of ordered lists of identifiers. The grammar for an enum is: <enum_type> ::= "enum" <identifier> "{" <enumerator> { "," <enumerator> }* "}" <enumerator> ::= <identifier> The <identifier> in the <enum_type> specification introduces the name of the new legal data type. The IDL enumeration is similar to the C++ version except IDL does not allow you to control the ordinal values of the enumerator values. enum Grade {A, B, C, D, F,}; A maximum of 232 identifiers may be specified in an enumeration. Therefore, the enumerated names are mapped to a data type capable of representing a 32-bit value. Enum definitions do not introduce a new namespace. Enumeration value names are introduced into the enclosing scope and then are treated like any other declaration in that scope. See section on Scoping Rules for further details. Enumerated types may also be defined using a typedef declaration, which will introduce an additional alias for the type. typedef enum Grade {A, B, C, D, F,} MyGrade; In this example, Grade and MyGrade are now valid IDL type names that can be used to reference the newly defined enum.
Structure The IDL language supports structures containing one or more named members of arbitrary type, including user constructed complex types. The grammar for the struct type is <struct_type> ::= "struct" <identifier> "{" <member_list> "}" <member_list> ::= <member>+ <member> ::= <type_spec> <declarators> ";" <declarators> ::= <declarator> { "," <declarator> }? <declarator> ::= <simple_declarator> | <complex_declarator> <simple_declarator> ::= <identifier> <complex_declarator> ::= <array_declarator> | <dynarray_declarator> The <identifier> in the <struct_type> specification introduces the name of the new legal data type. The following is an example of a simple structure definition.. struct TimeOfDay { SCAInt8 hour; SCAInt8 minute; SCAInt8 second; }; These definitions can be more complicated by using other constructed types as members.. typedef SCAInt16 MeetingDate[3]; struct MeetingTime { SCAInt8 Hour,Minute; MeetingDate Date; }; enum MeetingType { REQUIRED, OPTIONAL }; typedef SCASequence<SCAString> MeetingComments; struct Meeting { SCAString Subject; MeetingTime StartTime,EndTime; MeetingType Type; MeetingComments Comments; }; Structure definitions form a new namespace, so the names of the structure members need to be unique only within their enclosing structure. The following demonstrates this.. struct FirstStructure { SCAInt32 first; SCAInt32 second; }; struct SecondStructure { SCAInt32 first; SCAInt32 second; };
While this type of definition is legal, it should be considered a bad practice to reuse the same identifiers for two different purposes. Structure types may also be defined using a typedef declaration, which will introduce an additional alias for the type.. Typedef struct TimeOfDay { SCAInt8 hour; SCAInt8 minute; SCAInt8 second; } CurrentTime; In this example, TimeOfDay and CurrentTime are now valid IDL type names that can be used to reference the newly defined struct. The IDL grammar allows for the generation of recursive structures for members that have a sequence type. For example, the following is a valid IDL definition:. struct Node { SCAInt32 value; sequence<Node> children; }; This example defines a structure for a Node, which contains a SCAInt32 value and list of children Nodes. Fixed-size array The IDL language supports multidimensional arrays with fixed-sized dimensions. A fixed-size array definition must include explicit sizes for each dimension. Only arrays of rank 1 or 2 are allowed. Arrays of arbitrary element types are supported in IDL. The grammar for arrays is: <array_declarator> ::= <identifier> <array_size> | <identifier> <array_size> <array_size> <array_size> ::= "[" <positive_int_const> "]" <positive_int_const> ::= <const_exp> The array size, <const_exp>, for each dimension is fixed at compile time and must be a positive constant integer expression. When an array is passed as a parameter in an operation invocation, all elements of the array are transmitted. The implementation of array indices is language mapping specific. Some language mappings may define the first element in the array as having an index value of zero and others may use
a value of one. As a result, passing of array indices as parameters may yield incorrect results unless they are correctly handled.. typedef SCAInt32 ArrayOfLongs[100]; typedef TimeOfDay ArrayOfTimes[100]; typedef SCAInt32 MultiDimensionalArray[10][100]; The use a typedef construct to declare an array is required. The following array definition is invalid.. SCAInt32 ArrayOfLongs[100]; // Error: typedef missing
All array dimensions must be specified in the IDL. Open-ended arrays are not supported because IDL does not support pointers. The complete size for each data type must be known at compilation time. Because of this rule, the following definition is invalid.. typedef SCAString StringTable[][10]; Dynamic array The IDL language also supports multidimensional arrays with dynamic dimensions which are not set until run time. Only arrays of rank 1 or 2 are allowed. Dynamic arrays differ from fixed-size arrays in that the actual array dimensions are not determined until run time. Arrays of arbitrary element types are supported in IDL. The grammar for arrays is: <dynarray_declarator> ::= <identifier> "[" "]" | <identifier> "[" "]" "[" "]" The array size for each dimension must be left blank and is specified when the array is allocated at run time. When a dynamic array is passed as a parameter in an operation invocation, all elements of the array are transmitted. The implementation of dynamic array indices is language mapping specific. Some language mappings may define the first element in the array as having an index value of zero and others may use a value of one. As a result, passing of array indices as parameters may yield incorrect results unless they are correctly handled. typedef SCAInt32 ArrayOfLongs[]; typedef TimeOfDay ArrayOfTimes[]; typedef SCAInt32 MultiDimensionalArray[][]; The use a typedef construct to declare an array as is required. The following array definition is invalid. // Error: missing dimension
SCAInt32 ArrayOfLongs[];
For convenience, the SCA Framework provides predefined dynamic arrays types for each basic SCA type. These are described in the section Special SCA Framework Provided Types.
Template Types
SCASequence The IDL language provides the sequence type that is a one-dimensional array with a length that is determined at run time. The grammar for a sequence is: <sequence_type> ::= "SCASequence" "<" <simple_type_spec> ">" <simple_type_spec> ::= <base_type_spec> | <template_type_spec> | <scoped_name> Variable length arrays of arbitrary element types are supported in IDL with the sequences type. You must use a typedef construct to declare a sequence. typedef SCASequence<SCAInt32> SCAInt32Sequence; typedef SCASequence<Grade> Grades; typedef SCASequence<TimeOfDay> Times; The SCASequence only supports one-dimensional arrays, but defining a sequence of sequences can approximate a multi-dimensional array. typedef SCASequence<SCAInt32> SCAInt32Sequence; typedef SCASequence<SCAInt32Sequence> SequenceOfSequence; typedef SCASequence< SCASequence<SCAInt32> > SequenceOfSequence; Notice that in the nested sequence declaration a white space must be used to separate the two tokens of ">" at the end of the declaration. This is required to keep the two characters from being parsed as a single token of ">>". The SCA Framework does not support bounded sequences as defined by the OMG IDL specification. For convenience, the SCA Framework provides predefined sequence types for each basic SCA type. These are described in the section Special SCA Framework Provided Types. SCAString The IDL language provides a string type of SCAString that is a sequence of character values. ASCII null values of '\0' are not allowed inside IDL strings. Strings are singled out as a separate data type because
many languages have special built-in or standard library functions for string manipulation. A separate string type may permit substantial optimization in the handling of strings compared to what can be done with sequences of characters. The grammar for a string declaration is: <string_type> ::= "SCAString" The SCA Framework does not support bounded string types as defined by the OMG IDL specification. SCAWString The IDL language provides a wide string type of SCAWString that is a sequence of wide character values. Wide character null values of L'\0' are not allowed inside IDL wide strings. Wide strings are singled out as a separate data type because many languages have special built-in or standard library functions for wide string manipulation. A wide separate string type may permit substantial optimization in the handling of strings compared to what can be done with sequences of characters. The grammar for a string declaration is: <wide_string_type> ::= "SCAWString" The SCA Framework does not support bounded string types as defined by the OMG IDL specification. Aliases to other types The typedef construct can be used to create a new type name or alias for any other valid IDL type. The grammar for a typedef declaration is as follows: ::= ::= ::= ::= | <simple_declarator> ::= <complex_declarator> ::= <type_dcl> <type_declarator> <declarators> <declarator> "typedef" <type_declarator> <type_spec> <declarators> <declarator> { "," <declarator> }? <simple_declarator> <complex_declarator> <identifier> <array_declarator>
Some simple examples of typedef declarations follow: typedef SCAInt16 YearType; typedef SCAInt16 MonthType; These types of specifications can make the IDL file more readable and self-documenting. This example would indicate to the reader that the values represent a year or month, rather than the more generic SCAInt16 type. For more complex data types, the typedef construct can be used to define the data type and create an alias for it in the same statement.:
typedef sequence <SCAInt32> SCAInt32Sequence; typedef struct Time { SCAInt8 hour; SCAInt8 minute; SCAInt8 second; } CurrentType; typedef enum Color { RED, BLUE, GREEN } CurrentColor; typedef SCAInt32 ArrayOfLongs[100];
Sequences versus arrays Sequences and the two arrays types are similar because they all provide a vector of elements of the same type. The three types of vectors are provided to allow more efficient mappings in the various languages. In some languages, the mappings for some of the vector types may be the same and in others they may all be different. The language dependent mappings have been chosen to optimize performance and to provide various memory management models where appropriate. You should consult the IDL mapping section for your language for complete details. Here are some general guidelines to help you decide whether a sequence or an array is the more appropriate type.
If the length of the list is not known at compilation time, either a dynamic array or a sequence
must be used.
If you have a fixed length list and all of the elements exist all of the time, use either the fixed-
may be more efficient to use a sequence rather than an array. Consider the following IDL example for the definition of a matrix.: typedef SCAReal32 Matrix[100][100]; This definition defines a square matrix of dimension 100. It also will cause the allocation of 10,000 storage locations, which could be very inefficient if the matrix is sparse. If this matrix is passed out-ofproc, it is even more inefficient because all 10,000 values will be transmitted, even if only a few are used. In contrast, consider the following IDL definition which could be used to define a sparse matrix storage scheme.:
struct MatrixElement { SCAUInt32 row; SCAUInt32 col; SCAReal32 value; }; typedef sequence<MatrixElement> Matrix; This definition is more efficient in both storage and transmission time because only the meaningful matrix elements will be stored and processed.
Constants
The grammar for a constant declaration in the IDL language is:
<const_dcl> ::= "const" <const_type> <identifier> "=" <const_exp> <const_type> ::= <integer_type> | <char_type> | <wide_char_type> | <boolean_type> | <floating_pt_type> | <string_type> | <wide_string_type> | <scoped_name> <const_exp> ::= <or_expr> <or_expr> ::= <xor_expr> | <or_expr> "|" <xor_expr> <xor_expr> ::= <and_expr> | <xor_expr> "^" <and_expr> <and_expr> ::= <shift_expr> | <and_expr> "&" <shift_expr> <shift_expr> ::= <add_expr> | <shift_expr> ">>" <add_expr> | <shift_expr> "<<" <add_expr> <add_expr> ::= <mult_expr> | <add_expr> "+" <mult_expr> | <add_expr> "-" <mult_expr> <mult_expr> ::= <unary_expr> | <mult_expr> "*" <unary_expr> | <mult_expr> "/" <unary_expr> | <mult_expr> "%" <unary_expr> <unary_expr> ::= <unary_operator> <primary_expr> | <primary_expr> <unary_operator> ::= "-" | "+" | "~" <primary_expr> ::= <scoped_name> | <literal> | "(" <const_exp> ")" <literal> ::= <integer_literal> | <string_literal> | <character_literal> | <floating_pt_literal> | <boolean_literal> <boolean_literal> ::= "TRUE" | "FALSE" <positive_int_const> ::= <const_exp> <scoped_name> ::= <identifier> | "::" <identifier> | <scoped_name> "::" <identifier>
The <scoped_name> in the <const_type> definition must be a previously defined name of an <integer_type>, <char_type>, <wide_char_type>, <string_type>, <wide_string_type>, <enum_type> <boolean_type> or <floating_pt_type> constant. The value of a <positive_int_const> expression must evaluate to a positive integer constant. One side effect of a constant type definition is the <identifier> in the <const_dcl> specification introduces the name of the new legal data type with the same type as <const_type>. This can be confusing if you use this type for any other purposes in the IDL definitions. Consider the following example.: const SCAInt32 ConstType = 123; interface Inf { SCAVoid meth1 ( in ConstType inval ); SCAVoid meth2 ( in SCAInt32 inval ); }; In this example the signature for both methods in the interface definition will be identical. This is because the first method is just defining a parameter of type ConstType which is the same as SCAInt32. It is not defining parameter with a value of "123". To avoid this confusion you should not use the type names introduced by constant definitions in any other places in the IDL. They should only used by the implementation code where normal constant can appear. For example it is perfectly fine to use the constant value as a parameter value in a call to an interface method, but it is not appropriate to use in the declaration of a method. Only integer values can be assigned to integer (SCAInt8, SCAInt16, SCAInt32 and SCAInt64) constants. Only positive integer values can be assigned to unsigned integer (SCAUInt8, SCAUInt16, SCAUInt32 and SCAUInt64) constants. If the value of the right hand side of an integer constant declaration is too large to fit in the actual type of the constant, or the value is inappropriate for the actual type of the left hand side, it is flagged as a compile time error.
const SCAInt16 s = 655592; // Error: value to large const SCAUInt16 o = -54; // Error: negative value in unsigned type Only floating-point values can be assigned to floating point (SCAReal32 and SCAReal64) constants. If the value of the right hand side is too large to fit in the actual type of the constant to which it is being assigned it is flagged as a compile time error. A binary operator can combine two integers or two floats, but not mixtures of these. Binary operators are applicable only to integer and float-point types. If the type of an integer constant is a SCAInt8, SCAUInt8, SCAInt16, SCAUInt16, SCAInt32 or SCAUInt32, then each sub expression of the associated constant expression is treated as a signed SCAInt32 or unsigned SCAUInt32 value accordingly. It is an error if any sub expression values exceed the precision of the SCAInt32 or SCAUInt32 type, or if a final expression value exceeds the precision of the target type. A similar rule applies to SCAInt64 and SCAUInt64 constants. If the type of a floating-point constant is SCAReal64, then each sub expression of the associated constant expression is treated as a SCAReal64. It is an error if any sub expression value exceeds the precision of SCAReal64. Unary (+ -) and binary (* / + -) operators are applicable in floating-point expressions. Unary (+ - ~) and binary (* / % + - << >> & | ^) operators are only applicable in integer expressions. The "~" unary operator indicates that the bit-complement of the expression to which it is applied should be generated. For the purposes of such expressions, the values are 2's complement numbers. As such, the complement can be generated as follows: Integer Expression Type SCAInt32 SCAUInt32 SCAInt64 SCAUInt64 Generated 2s Complement Numbers SCAInt32 -(value+1) SCAUInt32 (2**32-1) - value SCAInt64 -(value+1) SCAUInt64 (2**64-1) - value
The "%" binary operator yields the remainder from the division of the first expression by the second. If the second operand is zero, the result is undefined. If both operands are nonnegative, then the remainder is nonnegative; if not, the sign of the remainder is implementation dependent. The "<<" binary operator indicates that the value of the left operand should be shifted left the number of bits specified by the right operand with zero fill for the vacated bits. The right operand must be in the range 0 <= right operand < 64. The ">>" binary operator indicates that the value of the left operand should be shifted right the number of bits specified by the right operand with zero fill for the vacated bits. The right operand must be in the range 0 <= right operand < 64.
The "&" binary operator indicates that the logical, bitwise AND of the left and right operands should be generated. The "|" binary operator indicates that the logical, bitwise OR of the left and right operands should be generated. The "^" binary operator indicates that the logical, bitwise EXCLUSIVE-OR of the left and right operands should be generated. An enum constant can only be defined using a correctly scoped name for the enumerator. The scoped name is resolved using the normal scope resolution rules described in section Scoping Rules. For example: enum Color { red, green, blue }; const Color FAVORITE_COLOR = red; module M { enum Size { small, medium, large }; }; const M::Size MYSIZE = M::medium; The constant name for the right hand size of an enumerated constant definition must denote one of the enumerators defined for the enumerated type of the constant. For example:: const Color col = red; const Color another = M::medium; // OK // Error: medium is not a Color
Constant definitions in IDL are not permitted for user constructed complex types of structures or arrays. The following are some examples of constant definitions.: const SCAString Version = "Version 10.3"; const SCAWString wstr = L"Test wide string"; const SCAReal32 Pi = 3.14159; const SCAChar Null = '\0'; const SCAWChar wchr = 'A'; const SCABool myTrue = TRUE; enum Color { RED, GREEN, BLUE }; const Color Favorite_Color = RED; const SCAReal32 Test1 = (1.0+4.0-2.0)*20.0/2.0; const SCAInt32 Test2 = (1+4-2)*20/2; const SCAUInt32 Test3 = ~2; const SCAInt32 Test4 = 100 % 12; const SCAInt32 Test5 = 2 << 4; const SCAInt32 Test6 = 32 >> 4; const SCAInt32 Test7 = 1 | (1<<4) | (1<<8); const SCAInt32 Test8 = 0x1234 & 0xF; const SCAInt32 Test9 = 0xF ^ 0x5;
module M { typedef SCAInt32 Long; // Error: Long clashes with keyword long typedef SCABool MyBool; interface I { typedef SCAInt32 MyLong; SCAResult meth1( in myLong val; // Error: inconsistent capitalization in MyBool mybool; // Error: MyBool clashes with mybool ); }; };
Qualified names
A qualified name, of the form scope::identifier, is resolved by first resolving the qualifier scope in scope S, where S is the current scope, and then locating the definition of identifier within S::scope. If the qualified name is not found in S, then each of S's enclosing scopes will be checked. The identifier must exist in the namespace scope; it is not searched for directly in the enclosing scopes of S. Consider the following example. module A { module B { typedef C::X mytype; }; }; The following fully qualified names will be tried to find X ::A::B::C::X ::A::C::X ::C::X The following fully qualified names will NOT be tried to find X ::A::B::X ::A::X ::X When a qualified name begins with "::", the resolution process will only look in the file or global scope and not look in any intermediate enclosing scopes. For example:
module A { module B { typedef ::C::X mytype; }; }; The only fully qualified name that will be tried to find X ::C::X Inheritance causes all of the identifiers defined in the base interfaces, both direct and indirect, to be visible in the derived interfaces. Such identifiers are considered to be semantically the same as the original definitions. interface CBase { typedef long X; }; interface C : CBase } }; Either of the following references are identical typedef CBase::X mytype1; typedef C::X mytype2;
Scoping rules
The scoping rules used in IDL are the same as in C++. The IDL compiler searches for the definition of an identifier from the innermost scope outward toward the outermost scope. The entire contents of an IDL file, together with the contents of any files referenced by #include statements, forms a naming scope. Definitions that do not appear inside a scope are part of the global scope. There is only a single global scope, irrespective of the number of source files that form a specification. The following IDL definitions form new naming scopes:
module struct interface interface operation
The appearance of a declaration for any of these in any scope opens a nested scope associated with that declaration. An identifier can only be defined once in a scope. However, identifiers can be redefined in nested scopes. An identifier declaring a module is defined by its first occurrence in a scope. Subsequent occurrences of a module declaration with the same identifier within the same scope reopens the module and hence its scope, allowing additional definitions to be added to it.
The name of an interface, struct, or a module may not be redefined within the immediate scope of the interface, struct, or the module. For example: module Test { typedef SCAInt16 Test; interface Inf { SCAResult inf ( ); }; };
// Error: Test is the name of the module // Error: inf clashes with interface Inf
Enum definitions do not introduce a new scope. Enumeration value names are introduced into the enclosing scope and then are treated like any other declaration in that scope. For example: module Test { enum VAL { E1, E2, E3 }; enum BAD { E3, E4, E5 }; };
Interface header
The interface header consists of these elements:
The keyword interface. The interface name consists of an <identifier> that names the interface. The SCA Framework
uses the convention that all interface names should begin with the prefix SCAI but this is not enforced by the IDL compiler.
An optional inheritance specification, which consists of, a colon followed by a comma-separated
Definition of the interface body enclosed by the characters "{" and "}".
interface A {}; interface B : A {}; interface C : A, B {}; The <identifier> that names an interface introduces a new legal type name. Such a type name may be used anywhere an <identifier> is legal in the grammar.
Interface body
The interface body can contain the following kinds of declarations:
Type declarations, which specify the type definitions that the interface exports as described in
each, including operation name, the type of data returned and the types of all parameters for the operation. Operation declarations are described in section Operation Declaration. Although it is legal to include type and constant definitions inside an interface body, it is a discouraged practice. This is because the required language mapping for these types is not always obvious and can lead to undesirable implementation code. Empty interfaces, that contain no declarations, are permitted.
Operation declaration
Operation declarations in the IDL language are similar to C++ method declarations. The grammar is: <op_dcl> ::= <op_type_spec> <identifier> <parameter_dcls> [ <raises_expr> ] <op_type_spec> ::= "SCAResult" <parameter_dcls> ::= "(" <param_dcl> { "," <param_dcl> }* ")"
| "(" ")" <param_dcl> ::= <param_attribute> <param_type_spec> <simple_declarator> <param_attribute> ::= "in" | "out" | "inout" <raises_expr> ::= "raises" "(" <scoped_name> {"," <scoped_name> }* ")" <param_type_spec> ::= <base_type_spec> | <string_type> | <wide_string_type> | <scoped_name> An operation declaration consists of:
The type of the operation's return result. An identifier that names the operation in the scope of the interface in which it is defined. A parenthesized, comma separated parameter list that specifies zero or more parameter
naming scope, the names only need to be unique in the same list. Each parameter declaration must have a directional attribute that informs the framework the direction in which the parameter is to be passed. This is different than a parameter definition in a C++ method. The directional attributes are as follows:. in out inout The parameter is passed from caller to callee The parameter is passed from callee to caller The parameter is passed in both directions
It is expected that an implementation will not attempt to modify an in parameter. The ability to even attempt to do so is language-mapping specific. The effect of such an action is undefined. The following are valid operation definitions.
interface A { SCAResult meth1 ( ); SCAResult meth2 ( out SCAInt32 val ); SCAResult meth3 ( in SCABool ival, out SCAInt32 oval, inout SCAString ioval ); }; An IDL operation may have an optional raises clause. The raises clause defines what type of exceptions the method may throw. The syntax is defined as follows: <raises_expr> ::= "raises" "(" <scoped_name> {"," <scoped_name> }* ")" Where <scoped_name> is any valid IDL exception specification as show in section SCA Exception Declaration. You may also specify one of the built-in exceptions, SCASystemException, SCAUserException or SCAException, in the raises clause. The following shows an example of the raises clause. interface A { SCAResult meth1 ( )raises (Exception1, Exception2); SCAResult meth2 ( )raises (SCAException); };
Forward declarations
It is valid for interface-defined types to be passed as parameters to operations. Occasionally, interfaces are mutually dependent to each other; each one is expecting a parameter of the other's type. This can present a problem because the IDL compiler is a one-pass compiler and all types must be defined before being used. In this case a forward declaration can be used to resolve the problem. A forward declaration declares the name of an interface without defining it. This interface can then be used as a parameter in another interface definition. Multiple forward declarations of the same interface name are legal. It is illegal to inherit from a forward-declared interface that has not yet been defined.
module Example { interface A; interface B { SCAResult meth ( in A inf ); }; interface A { SCAResult meth ( in B inf ); }; };
// Forward declaration of A // Full declaration of B // Use forward declared A // Full declaration of A // Use fully declared B
Interface inheritance
An interface can be derived from another interface, which is then called a base interface of the derived interface. A derived interface, like all interfaces, may declare new constants, types, and operations. In addition, unless redefined in the derived interface, the elements of a base interface can be referred to as if they were elements of the derived interface. The name resolution operator "::" may be used to refer to a base element explicitly. This permits reference to a name that has been redefined in the derived interface. A derived interface may redefine any of the types or constants that have been inherited. A derived interface may not redefine operations that have been inherited. An interface is called a direct base if it is mentioned on the interface definition and an indirect base if it is not a direct base but is a base interface of one of the interfaces that is inherited from. An interface may be derived from any number of base interfaces, which is often referred to as multiple inheritance. The order of derivation is not significant except that is should never be changed once it has been set. An interface may not be specified as a direct base interface of a derived interface more than once, but it may be an indirect base interface more than once. Consider the following valid examples:. interface interface interface interface interface A { ... }; B: A { ... }; C: A { ... }; D: B, C { ... }; E: A, B { ... }; // // // // Direct base of A Direct base of A Multiple indirect bases of A A is both a direct and indirect base
References to base interface elements must be unambiguous. A reference to a base interface element is ambiguous if the name is declared as a constant or type in more than one base interface. Ambiguities can be resolved by qualifying a name with its interface name. Consider the following examples:
interface A { typedef SCAInt32 L1; SCAResult meth1(in L1 l_1); }; interface B { typedef SCAInt16 L1; SCAResult meth2(in SCAInt32 l); }; interface C: B, A { typedef L1 L2; // Error: L1 typedef A::L1 L3; // OK: A::L1 SCAResult meth1 ( in L3 val1, // OK: L3 is in B::L1 val2 // OK: B::L1 ); };
Overloading of operation definitions is not allowed. This means it is illegal to inherit from two interfaces containing the same operation name, or to redefine an operation in the derived interface. This is required because operation names are used at run-time with dynamic interfaces and in scripting and must be unique. Also, not all implementation languages support operation overloading. interface A { SCAResult meth1(); }; interface B: A { SCAResult meth1(in long times); };
SCAIService interface
The SCA Framework requires that all interfaces inherit, either directly or indirectly form the SCAIService interface. The SCAIService interface defines the methods used to support interface navigation, reference counting and runtime introspection. You need to include the file that defines this interface, "SCA/Service.idl", in your IDL file unless it is already included in another include file that you are using. The following is the definition of the SCAIService interface.
Module SCA { interface SCAIService { SCAVoid addReference(); SCAVoid releaseReference( out SCAVoidPtr pPublisher ); SCAResult getInterface( in SCAString sName, out SCAVoidPtr pIface ); SCAString getImplName(); SCAInt32 getInstanceID(); }; }; The following example shows two valid SCA interface definitions. The first interface directly inherits from the SCAIService interface and the second one indirectly inherits from it. #include "SCA/Service.idl" module Test { interface SCAITest1 : SCA::SCAIService { SCAResult meth1(); }; interface SCAITest2: SCAITest1 { SCAResult meth2(); }; Since every interface must inherit from the SCAIService interface, the IDL compiler will generate all the code that is normally required to implement its methods.
Exceptions
The IDL language supports exception declarations containing zero or more named members of arbitrary type, including user constructed complex types. The grammar for the exception is similar to the struct type with several differences. Exceptions are allowed to inherit from other exceptions but inheritance is not supported on structures. Also it is possible to define an exception type with zero members. The following is the grammar for the exception type. <except_dcl> ::= "exception" <identifier> [ <except_inherit> ] "{" <member>* "}" <except_inherit> ::= ":" <scoped_name> <member> ::= <type_spec> <declarators> ";" <declarators> ::= <declarator> { "," <declarator> }? <declarator> ::= <simple_declarator> | <complex_declarator> <simple_declarator> ::= <identifier> <complex_declarator> ::= <array_declarator> | <dynarray_declarator> [ <except_inherit> ] "{" <member>* "}" The <identifier> in the <except_dcl> specification introduces the name of the new legal data type. These identifiers may appear only in an exception inheritance <except_inherit> clause or a raises clause in an interface operation definition described in section Operation Declaration. Exception identifiers are not allowed to be used for any other purpose. The following is an example of SCA IDL exception definition.. exception MyException : SCAUserException { SCAInt32 id; SCAString value; }; exception MyException2 : MyException { SCAString extradata; };
SDL Specifications
The SCA Framework SDK delivery includes many IDL files which define types supported by the framework. But, most of these types are no different than the types you define in the components you deliver. There are a few exceptions to this. The framework also provides a number of special types, described in this section, that are treated differently for the following reasons.
Some types are provided for convenience because they tend to be useful to all developers. In this
case it is better to use the framework provided types rather than defining your own for the purpose of consistency.
Some of these types are provided because they utilize special language specific mappings to
provide the required functionality. In many of these cases the framework provided special implementation classes for these types in each supported language. The following special types can be thought of as new basic types provided by the SCA Framework. The IDL compiler treats them the same as any other IDL provided type but they are so fundamental to the workings of the framework that it provides special implementations for each. module SCA { // Type for holding return values and error messages struct SCAResult { SCAInt32 errorCode; SCAInt32 messageTable; SCAInt32 messageID; SCAAnySequence params; }; // UUID definition for SCA Types struct SCAUUID { SCA::SCAUInt64 part1; SCA::SCAUInt64 part2; }; }; The following exception types are predefined by the SCA framework. .
module SCA { // Base for all SCA exceptions exception SCAException{}; // Base for all User defined exceptions exception SCAUserException: SCAException{}; // Base for all System defined exception exception SCASystemException: SCAException { SCAInt32 id; }; }; The following sequence and dynamic array types are provided by the framework. These are provided for convenience and to since many components required similar definitions it is more consistent if everyone uses the same ones.
module SCA { // Standard sequence types typedef sequence<SCAInt8> SCAInt8Sequence; typedef sequence<SCAUInt8> SCAUInt8Sequence; typedef sequence<SCAInt16> SCAInt16Sequence; typedef sequence<SCAUInt16> SCAUInt16Sequence; typedef sequence<SCAInt32> SCAInt32Sequence; typedef sequence<SCAUInt32> SCAUInt32Sequence; typedef sequence<SCAInt64> SCAInt64Sequence; typedef sequence<SCAUInt64> SCAUInt64Sequence; typedef sequence<SCAReal32> SCAReal32Sequence; typedef sequence<SCAReal64> SCAReal64Sequence; typedef sequence<SCAChar> SCACharSequence; typedef sequence<SCAWChar> SCAWCharSequence; typedef sequence<SCAString> SCAStringSequence; typedef sequence<SCAWString> SCAWStringSequence; typedef sequence<SCABool> SCABoolSequence; typedef sequence<SCAAny> SCAAnySequence; typedef sequence<SCATypeCode> SCATypeCodeSequence; // Define some standard dynamic array types typedef SCAInt8 SCAInt8DynamicArray[]; typedef SCAUInt8 SCAUInt8DynamicArray[]; typedef SCAInt16 SCAInt16DynamicArray[]; typedef SCAUInt16 SCAUInt16DynamicArray[]; typedef SCAInt32 SCAInt32DynamicArray[]; typedef SCAUInt32 SCAUInt32DynamicArray[]; typedef SCAInt64 SCAInt64DynamicArray[]; typedef SCAUInt64 SCAUInt64DynamicArray[]; typedef SCAReal32 SCAReal32DynamicArray[]; typedef SCAReal64 SCAReal64DynamicArray[]; typedef SCAChar SCACharDynamicArray[]; typedef SCAWChar SCAWCharDynamicArray[]; typedef SCAString SCAStringDynamicArray[]; typedef SCAWString SCAWStringDynamicArray[]; typedef SCABool SCABoolDynamicArray[]; typedef SCAAny SCAAnyDynamicArray[]; typedef SCATypeCode SCATypeCodeDynamicArray[]; };
CDL Specifications
The SCA IDL language allows for the definition of SCA services. The definition of services should always be placed in source files with the extension of ".sdl" and these SDL files should only contain service definitions. Only one service definition is allowed in each SDL file. The IDL compiler uses the service definitions in SDL files to generate sample implementation code skeletons for a service in one of the supported languages. See the IDL mapping section for your language for the complete details. The IDL compiler also uses the service definitions to generate support code that links the developer's implementation code to the SCA Framework. This code is automatically generated, compiled and linked with the SCA services you build. When designing the implementation for a SCA service, the developer is free to structure the code any way they wish. Usually the implementation of a service will be split among several different classes. In some cases the IDL compiler needs to understand this structure. In general, the following types of classes might be used.
The Top-Level class that is instantiated when an instance of the service is requested. This class
must implement at least one SCA interface. This class is referred to as the Top-Level service class.
Additional classes may be instantiated that also implement SCA interfaces. Pointers to instances
of these classes, in the form of interface references, may then be passed outside of the service to other SCA services. These classes are referred to as Sub-Service classes.
Additional classes may be instantiated that do not implement SCA interfaces. Because these
classes do not implement any SCA interfaces, pointers to them cannot be passed outside of the service. They can only be used internally as part of the implementation of the service. Of these three types of classes, the IDL compiler only needs to be aware of the first two because they each implement SCA interfaces. Since interface references to any instance of these classes may be passed outside of the service, appropriate support code is generated to control the life cycle of these instances to insure that they will be deleted when no longer referenced. When coding SDL definitions, the general rule is that any class in the implementation of a service that implements a SCA interface must be declared in the SDL file. The declaration must also include, either directly or indirectly, all of interfaces that each class implements. Because interfaces can inherit from other interfaces, if the SDL specifies that a class implements a specific interface, then the class must also implement all interfaces that it is derived from. Because of this it is not required that you specify every base interface in your SDL definitions. It is only required that you include the most derived interfaces. The structure of the Top-Level service class and Sub-Service classes is very similar but do differ because of the way instances of them are created. An instance of the Top-Level class is only triggered when a SCA Framework request is made for a new instance of the service. There is no provision for this type of request to include any parameters that can be passed to the service instance when it is constructed. SubService classes, however, can only be instantiated from within the implementation code of a service. It is reasonable to assume that there will be a need to initialize these classes so this information can also be provided in the definition of Sub-Service classes.
The grammar of a SCA service definition is as follows: <service> ::= <service_dcl> "{" <service_body> "}" <service_dcl> ::= "service" <service_name> < service_name > ::= <identifier> | < service_name > "." <identifier> <service_body> ::= <service_entry>+ <service_entry> ::= <option_dcl> ";" | <interface_ref> ";" | <sub_service> ";" <option_dcl> ::= <option>+ <option> ::= "singleton" | "delegate" | "inherit" | "aggregates" | "aggregated" <interface_ref> ::= "interface" <scoped_name> <scoped_name> ::= <identifier> | "::" <identifier> | <scoped_name> "::" <identifier> <sub_service> ::= <subservice_dcl> "{" <sub_service_body> "}" <sub_service_dcl> ::= "subservice" <identifier> [ <parameter_dcls> ] <parameter_dcls> ::= "(" <param_dcl> { "," <param_dcl> }* ")" | "(" ")" <sub_service_body> ::= <sub_service_entry>+ <sub_service_entry> ::= <interface_ref> ";" A service definition consists of these elements:
The keyword service The fully qualified dotted service name Definition of the service body enclosed by the characters "{" and "}".
service A.B.C.MyService {}; The fully qualified dotted service name serves several purposes. For complete details on these see section IDL/SDL/CDL Names, Namespaces and Directories. The body of a service definition consists of the following elements in any order.
Zero or more service option specifications. One or more interface references for the interfaces that the Top-Level service class will
implement. The interface names must be normally scoped names that resolve to previously defined interfaces.
Zero or more Sub-Service definitions.
service A.B.C.MyService1 { interface Inf1; }; Since the service class must also implement all base classes of the specified interface, in reality this service class will implement a minimum of two interfaces. The second interface is SCA::SCAIService which is the common base class for all SCA interfaces. It may also implement additional interfaces depending on the actual definition of the Inf1. You may also specify more the one interface for the service class. service A.B.C.MyService2 { interface Inf1; interface Inf2; }; You may need to also specify some options for the service. service A.B.C.MyService3 { singleton; delegate; interface Inf1; }; A Sub-Service definition consists of these elements.
The keyword subservice. An optional parenthesized initialization parameter list for the Sub-Service class that is passed to
The grammar for the optional parameter list for a Sub-Service is the same as for an interface operation and is described in section Operation Declaration. Generally, the only parameter types that are allowed in the parameter list are those that can be defined in the IDL. But, in some languages it is possible to pass non-IDL pointer types using the special SCAVoidPtr type. The Sub-Service body contains one or more interface references for the interfaces this Sub-Service class will implement. The interface names must be normally scoped names that resolve to previously defined interfaces.
service A.B.C.MyService1 { interface Inf1; subservice MySub1 { interface Inf2; }; }; The following example shows a second subservice class which includes optional parameters. service A.B.C.MyService2 { interface Inf1; subservice MySub1 { interface Inf2; }; subservice MySub2( in SCAInt32 value, in SCAVoidPtr ptr ) { interface Inf3; interface Inf4; }; };
Service Options
In the definition of a SCA service there are several options that can be specified which will affect the type of code generated by the IDL compiler.
singleton: Only one instance of a singleton service may exist at any given time. Once an instance
of the service has been created, additional calls to get a copy of the service will return the same instance. For normal non-singleton services, each call to get a copy of the service will return a new instance of the service.
delegate or inherit: The default code generated for a SCA service will use an implementation that
inherits from a base class generated by the IDL compiler. This base class will then inherit from the interface class and also provide all the necessary links to the SCA Framework. There may be situations where this form of implementation is inconvenient because of other requirements in your classes. To resolve this, the delegation form of implementation can be used. With delegation, the skeleton classes generated by the IDL compiler do not inherit from a base or interface classes. Instead the base class is replaced with a separate or tie class which inherits from the interface class and provides all the necessary links to the SCA Framework. When a new instance of the service is requested, an instance of the tie class is constructed and returned. The tie class will internally construct a separate instance of your implementation class when it is initialized. Interface calls to the methods in the tie class are then delegated to the methods in the implementation class.
service by reusing implementation from another service. The aggregates option specifies that this service class will use aggregation. The services that implement the interfaces that will be aggregated must be defined with the aggregated option. For complete details on aggregation see the section on implementation reuse.
aggregated: This option specifies that the interfaces implemented by this service class may be
aggregated by another service which is defined with the aggregates option. Not all languages support all of these options. See the sections on the IDL mappings for the language you will be using for the complete details on which options are supported.
The body of a component definition consists of the following elements in any order.
Zero or more component option specifications. One or more service references for the services that the component will contain.
component A.B.C.MyComponent1 { service Service1; service SCA.Util.Service2; }; You may need to also specify some options for a component.
component A.B.C.MyComponent1 { service Service1; service SCA.Util.Service2; }; The fully qualified dotted component name serves several purposes. For complete details on these see section IDL/SDL/CDL Names, Namespaces and Directories. The service names used may be either a short name or the fully qualified dotted name. If a short name is used there must be no ambiguities when resolving it to a service definition based on the following rules.
If there is only one service in the entire source tree with the same short name, that that service
definition is used.
If there is more than one service in the entire source tree with the same short name, there must be
only one in the sub-tree of the source tree that is rooted in the directory that contains the CDL file for the component.
Component Options
In the definition of a SCA component there are several options that can be specified which will affect the type of code generated by the IDL compiler.
embedded: Normally, each SCA component will be packaged by the build system into a separate
package that is appropriate for the language it is implemented in. For example a component implemented in C++ is linked into a shared library and a Java component is packaged in a jar file. Under some conditions it may be desirable to not do this packaging. This allows you to manually package the files with other parts of your program as required. See the section on Embedded Components for examples of when this may be useful. Not all languages support all of these options. See the sections on the IDL mappings for the language you will be using for the complete details on which options it supports.
Run the SCA/IDL compiler. Usage: idl [options] file, [file, file, ...] One of the following options must be given: -w -s -h : Generates glue part of interfaces, services and components : Generates skeleton for user's part of a service : Print this message
Additional Options: -b backend -c -e -f -Dxxx -i path -nf -o path : : : : : : : : Name of back-end module to run Keep comments from IDL files in interface headers An error is generated if any of the files already exist List the files that will be generated, do not generate them Add pre-preprocessor define Add path to the list of search directories for includes Do not warn about unresolved forward declarations Base directory where output files are written Default is the current working directory Base directory where XML output files are written Default is the same as specified with the "-O" parameter Do not put interface header files in relative sub-directories specified by IDL module statements. Instead they will be put directly in the current working directory or the directory specified with the "-o" option. Do not print the IDL compiler version number on stdout Generate tie or delegated service implementation Pass option "xxx" directly to the back-end module Generate TypeCode information for IDL defined types Do not generate TypeCode information for IDL defined types If "-tc" set, generate XML TypeCode information If "-tc" set, generate embedded TypeCode information
: : : : : : :
Debug options: -dump -p -v : Dump the parsed IDL then exit, without running back-end : Only run the pre-processor, sending its output to stdout : Verbose output to trace compilation stages
The extension of the input file is used to determine the type of files that will be generated: .cdl -> Generate component implementation (-w) .sdl -> Generate service implementation (-w) or skeletons (-s) .idl -> Generate interface headers (-w)
The "-s" option is used when you want the IDL compiler to generate skeleton code for the
implementation of a SCA service. This option only has affect when processing SDL files. The files generated will always have an extension of ".new" appended to their name so they will not accidentally overwrite any existing implementation files. This option is normally used when you are first starting to develop a SCA service. You first write the IDL and SDL definitions for the service and then use the IDL compiler to generate the skeleton code. You may also want to rerun the compiler at a latter time if you make significant changes to your IDL and/or SDL files and need to update your existing implementation. Normally you do not directly run the IDL compiler with this option. Instead you will use the genskeleton command which provides additional features for generate your skeletons.
The "-w" option is used when you want the IDL compiler to generate the code that is required to
link the developer's implementation code to the SCA Framework. This option is normally only used by the SCA Build System and applies to all three IDL file types.
APPS_LOCAL directory. This is required because the IDL compiler cannot use the IDL files located in your source directory. This is because the relative paths for the IDL files that must be included must use the directory structure in the APPS_LOCAL tree and not the directory structure used in the source tree.
The IDL compiler is run to build the skeletons from the SDL file.
Since the build system must be run first, this script will only work if you are located in a properly configured source tree. This means there must be a SConscript file in the current directory and there must be a SConstruct file in either the the current directory or one of its parents. The skeleton files generated by the IDL compiler normally have an extension of ".new" appended to the files names so they will not overwrite any existing versions. If none of the files currently exist, then the new versions will have the ".new" extension removed. If any of the files do exist, the newly generated files will keep their ".new" extension unless "-r" option is provided. In this case, the existing versions of the files will be saved by adding an extension of ".old", and the new versions will then replace them.
Summary
Introduction
Mapping for Enumerated Types Mapping for Structures Mapping for Arrays 110 116 119 120 109
Mapping for SCATypeCode Mapping for SCAAny Mapping for SCAResult Mapping for Constants Mapping for Interfaces Mapping for Exceptions Mapping for SCA Services 121
Introduction
The IDL language provides a language independent definition of SCA interfaces and data types. In order to actually use these definitions, there must be a set of rules, commonly known as a mapping; that describes how these types are represented in a particular language. This chapter explains the mapping that SCA uses for the C++ language. For every type defined in IDL, the IDL compiler will generate the code required to expose the proper C++ definition of the type. In addition to the actual C++ definition of the type, there is also some support code generated which is used by the SCA Framework to manage instances of each type. An example of this is the bridging of instances of the type between C++ and the other languages supported by the SCA Framework.
The SCA C++ mapping uses a set of overloaded bi-directional marshalling operators to
manipulate data in instances of the SCAAny type and for some bridging operations. For each user defined type the IDL compiler will generate these required operator definitions. The format for these is different for each different type and will be shown in the various mapping sections below. For the basic and predefined types that are delivered with the SCA Framework, these operators are predefined in the delivered header files.
For each type the IDL compiler will generate some support code for accessing the
SCATypeCode created at runtime by the SCA Framework. This code will be described in the SCATypeCode mapping section latter in this chapter. The code that is generated by the IDL compiler is stored in one of several files depending on whether it is an interface or a user defined type definition.
All of the user defined types, defined outside of interfaces, in the same .idl file will be store in a
single header file. The name of the file will be xxxTypes.h for the IDL file named xxx.idl. The relative directory under the include directory in the delivery tree where the generated file is stored is taken from the namespace of the first type defined in the file.
For each interface defined in the IDL file, two header files will be generated, one for the smart
pointer definition and one for the full definition of the abstract interface class. The relative directory under the include directory in the delivery tree where the generated files are stored is taken from the namespace that the interface is defined in. Consider the following simple IDL file to demonstrate these rules. IDL file mytest.idl module SCA { module Test { enum MyENUM { VALUE1, VALUE2 }; Module Test2 { Struct MyStruct { SCAInt32 data; } ; Interface SCAITest1 : SCAIService { };
}; Interface SCAITest2 : SCAIService { }; }; }; The IDL compiler will generate the following type file for this definition.
MyEnum and MyStruct smart pointer for SCAITest1 interface for SCAITest1 smart pointer for SCAITest2 interface for SCAITest2
Although it is legal to include type and constant definitions inside an interface body, it is a discouraged practice. As a result the affect on the mappings described in this section will not be discussed.
All of the mappings for the basic types are distinguishable and unique with respect to overloading. That is, one can safely write overloaded C++ functions for SCAInt8, SCAUInt8, SCAInt16, SCAUInt16, SCAInt32, SCAUInt32, SCAInt64, SCAUInt64, SCAReal32, SCAReal64, SCAChar, SCAWChar and SCABool types and each of these will be unique. Remember that overloading is not permitted in interface operations in IDL but it can be used in service implementation code. This is also a requirement to allow type-safe processing of data in a SCAAny type.
IDL typedef SCAInt32 Matrix[5][5]; C++ class Matrix { public: typedef SCAInt32 value_type; typedef size_t size_type; Matrix() {} size_type size1() const { return 5; } size_type size2() const { return 5; } value_type* operator[](size_type idx) { return data[idx]; } const value_type* operator[](size_type idx) const { return data[idx]; } private: value_type data[5][5]; }; inline SCAMarshaller& operator >>= (const Matrix& val, SCAMarshaller& stream){ stream.setSize(SCA::TypeCodeForType< Matrix >::get()); for(Matrix::size_type i=0; i<5; i++) for(Matrix::size_type j=0; j<5; j++) val[i][j] >>= stream; return stream; } inline SCAMarshaller& operator <<= (Matrix& val, SCAMarshaller& stream){ for(Matrix::size_type i=0; i<5; i++) for(Matrix::size_type j=0; j<5; j++) val[i][j] <<= stream; return stream; } Only one and two dimensional arrays are support by the C++ mapping.
Dynamic arrays
Dynamic arrays are implemented in C++ with several template class types. The following is the definitions for the one and two dimensional dynamic array. The ArrayData class is the actual object that stores the data for an instance of either a one or two dimensional dynamic array. It adds reference counting to the data to allow more efficient handling of it. template <typename T> class ArrayData { public: typedef T value_type;
typedef size_t size_type; typedef value_type& reference; typedef const value_type& const_reference; typedef value_type* pointer; typedef const value_type* const_pointer; friend class ArrayPtr1<value_type>; friend class ArrayPtr2<value_type>; friend class SCAMemoryMarshaller; ArrayData(pointer data, size_type size1, size_type size2, DestroyFunc destroyFunc, SCATypeCode contTC); ~ArrayData(); SCAVoid addReference(); SCAVoid releaseReference(); private: value_type* m_data; unsigned long m_refcnt; size_type m_size1; size_type m_size2; DestroyFunc m_destroyFunc; SCATypeCode m_contTC;
};
ArrayPtr1 is the template class for a one dimensional dynamic array. The actual instance of a dynamic array objects are an instance of this class. It contains an instance of the ArrayData class and manages the reference count on it. The ArrayPtr1 class provides the appropriate operator overloads to act like a normal C++ array. It also provides a number of additional methods for managing the ArrayData data it points to. template<typename T> class ArrayPtr1 { public: typedef T value_type; typedef ArrayData<value_type> arraydata; typedef typename arraydata::size_type size_type; typedef typename arraydata::reference reference; typedef typename arraydata::const_reference const_reference; typedef typename arraydata::pointer pointer; typedef typename arraydata::const_pointer const_pointer; friend class SCAMemoryMarshaller; ArrayPtr1(); ArrayPtr1(value_type* data, size_type size, DestroyFunc destroyFunc=NULL, SCATypeCode contTC=SCATypeCode()); ArrayPtr1(const ArrayPtr1& val) ~ArrayPtr1(); void set(pointer data, size_type size, DestroyFunc destroyFunc=NULL, SCATypeCode contTC=SCATypeCode());
void setTypeCode(SCATypeCode contTC); void clear(); size_t size() const; bool empty() const; bool shared() const; pointer data() const; ArrayPtr1<value_type>& operator=(const ArrayPtr1& val); const_reference operator[](size_type index) const; reference operator[](size_type index); private: arraydata* m_arraydata; }; ArrayPtr2 is the template class for a two dimensional dynamic array. The actual instance of a dynamic array objects are an instance of this class. It contains an instance of the ArrayData class and manages the reference count on it. template<typename T> class ArrayPtr2 { public: typedef T value_type; typedef ArrayData<value_type> arraydata; typedef typename arraydata::size_type size_type; typedef typename arraydata::reference reference; typedef typename arraydata::const_reference const_reference; typedef typename arraydata::pointer pointer; typedef typename arraydata::const_pointer const_pointer; ArrayPtr2(); ArrayPtr2(value_type* data, size_type size1, size_type size2, DestroyFunc destroyFunc=NULL, SCATypeCode contTC=SCATypeCode()); ArrayPtr2(const ArrayPtr2& val); ~ArrayPtr2(); void set(pointer data, size_type size1, size_type size2, DestroyFunc destroyFunc=NULL, SCATypeCode contTC=SCATypeCode()); void setTypeCode(SCATypeCode contTC); void clear(); size_t size1() const; size_t size2() const; bool empty() const; bool shared() const; pointer data() const; ArrayPtr2<value_type>& operator=(const ArrayPtr2& val); const_pointer operator[](size_type index) const; pointer operator[](size_type index); private: arraydata* m_arraydata; };
The generated code for dynamic array types must also be handled differently because the C++ compiler treats two different instantiation of a template with the same parameter type the same when distinguishing operator overloads. This would make it difficult to support the insertion of the dynamic arrays into a SCAAny value in a type-safe manner. To allow for the type-safe handling of dynamic arrays, each IDL defined sequence is mapped to a light weight C++ class that inherits from the template class. The following shows an example of the generated code for a one dimensional dynamic array type. IDL typedef SCAInt32 DynDate[]; C++ class DynDate : public DynamicArray::ArrayPtr1< SCAInt32 > { public: typedef SCAInt32 value_type; typedef DynamicArray::ArrayPtr1<value_type> base; typedef base::size_type size_type; DynDate() : base() {} DynDate(value_type* data, size_type size, DynamicArray::DestroyFunc desfunc=0) : base(data,size,desfunc) {}; }; inline SCAMarshaller& operator >>= (const DynDate& val, SCAMarshaller& stream) { stream.packDynamicArray(&val); return stream; } inline SCAMarshaller& operator <<= (DynDate& val, SCAMarshaller& stream) { stream.unpackDynamicArray(&val); return stream; } The following shows an example of the generated code for a two dimensional dynamic array type. IDL typedef SCAInt32 DynMatrix[][]; C++ class DynMatrix : public DynamicArray::ArrayPtr2< SCAInt32 > { public: typedef SCAInt32 value_type; typedef DynamicArray::ArrayPtr2<value_type> base; typedef base::size_type size_type; DynMatrix() : base() {}
};
DynMatrix(value_type* data, size_type size1, size_type size2, DynamicArray::DestroyFunc desfunc=0) : base(data,size1,size2,desfunc) {};
inline SCAMarshaller& operator >>= (const DynMatrix& val, SCAMarshaller& stream) { stream.packDynamicArray(&val); return stream; } inline SCAMarshaller& operator <<= (DynMatrix& val, SCAMarshaller& stream) { stream.unpackDynamicArray(&val); return stream; } The details of using dynamic arrays are an advanced topic and are discussed in detail in the Dynamic SCA chapter of the Advanced SDK manual.
copy of the data and only when one copy is changed is a copy of the actual data made The following example uses the framework provided SCAInt32Sequence to show how this works. First the code declares an instance of the sequence, values1, and fills it with data. SCAInt32Sequence values1; for ( int i=0; i<1000; i++ ) values1.push_back(i); Then a new instance of the sequence, values2, is allocated and set equal to the first instance. This operation will not cause a new copy of the data in the sequence to be made. Instead both instances, values1 and values2, will be referencing the same data. vector<int> values2 = values1; for ( int i=0; i<1000; i++ ) cout << values2.r_at(i) << endl; Then an entry in the sequence is changed using the values2 instance. At this time a copy of the data will be made and only the value in the second copy will be changed. Now when the sequence instance values1 is printed it will contain the original values but the values2 instance will contain the modified data. values2[5] = 10; for ( int i=0; i<1000; i++ ) cout << values1.r_at(i) << endl; for ( int i=0; i<1000; i++ ) cout << values2.r_at(i) << endl; In order for the copy on write behavior to work correctly, the various methods in the SCASequence class must know when the data in the sequence is being read or when it is being written. The standard C++ vector class does not provide this ability because certain methods can be used for both purposes as shows by the following example. vector<int> values(10); values.at(5) = 10; int ival = values.at(5); To solve this problem, the SCASequence provides two different versions of these methods, one for reading and one for writing. The version for reading uses the standard std::vector name with an r_ prefix and the writing version uses a w_ prefix. SCAInt32Sequence values(10); values.w_at(5) = 10; int ival = values.r_at(5);
The following table shows the standard methods in the STL vector class and their corresponding methods in the SCASequence class which are different. All of the other methods are the same. std::vector SCASequence read SCASequence - write begin end rbegin rend address at front back r_begin r_end r_rbegin r_rend r_address r_at r_front r_back w_begin w_end w_rbegin w_rend w_address w_at w_front w_back
The mapping for sequences types must be handled differently because the C++ compiler treats two different instantiation of a template with the same parameter type the same when distinguishing operator overloads. This would make it difficult to support the insertion of these sequences into a SCAAny value in a type-safe manner. To allow for the type-safe handling of sequences, each IDL defined sequence is mapped to a light weight C++ class that inherits from the template class. IDL typedef SCASequence<Node> NodeSequence; C++ class NodeSequence : public SCA::SCASequence< Node > { public: NodeSequence(size_type n, const Node& value = Node()) : SCA::SCASequence< Node >(n,value) { } NodeSequence() : SCA::SCASequence< Node >() { } template <class InputIterator> NodeSequence(InputIterator first, InputIterator last) : SCA::SCASequence< Node >(first,last) { } }; inline void swap(NodeSequence& x, NodeSequence& y) { x.swap(y); } inline SCAMarshaller& operator >>= (const NodeSequence& val, SCAMarshaller& stream){ SCAUInt32 len = static_cast< SCAUInt32 >(val.size()); len >>= stream; for(SCAUInt32 i=0; i<len; i++) val.r_at(i) >>= stream; return stream;
} inline SCAMarshaller& operator <<= (NodeSequence& val, SCAMarshaller& stream){ SCAUInt32 len; len <<= stream; val.resize(len); for(SCAUInt32 i=0; i<len; i++) val.w_at(i) <<= stream; return stream; }
The first requirement covers the typical usage of the SCAAny type, the insertion of typed values into the SCAAny and the type-safe extraction of the values. The second requirement covers situations like a requirement to process a SCAAny value that holds data of a type that is unknown when the service was built. In this case the receiver must be able to determine information about what type of data the SCAAny contains so it can be processed correctly. To achieve these requirements, the definition of a SCAAny contains a pair of values that includes a SCATypeCode value, which describes what type of data is contained, and the actual value of the data. Using the type code value, the C++ mapping of the SCAAny can enforce type safety and also allow for the handling of types not known at compile time. If the value stored in a SCAAny instance is a type that is referenced counted, like an interface pointer or a SCASequence, the reference count will be correctly incremented when the value is inserted into the instance and decremented when the SCAAny instance is destroyed or overwritten. It is therefore safe for the SCAAny to hold only a reference to value and not a copy of it. To decrease the chances of creating a SCAAny with a mismatched SCATypeCode and value, the C++ operator overloading facility is utilized. Specifically, for each distinct type in an IDL specification, overloaded operators to insert and extract values of that type are used. Overloaded operators are used instead of functions definitions to avoid namespace pollution. The usage of these insertion and extraction operators is described below.
// Templated constructor to intialize SCAAny with a value template<typename t_type> explicit SCAAny(const t_type& val); // Destructor ~SCAAny(); // Copy constructor SCAAny(const SCAAny& val); // Assignment operator SCAAny& operator = (const SCAAny& val); // Special insertion operator for a SCAAny iself SCAVoid operator <<= (const SCAAny& val); // Templated insertion operator for all other values. template<typename t_type> SCAAny& operator <<= (const t_type& val); // Special extraction operator for a SCAAny iself. True is // returned if and only if the extraction is successful SCABool operator >>= (SCAAny& val) const; // Templated extraction operator for all other values. True is // returned if and only if the extraction is successful template<typename t_type> SCABool operator >>= (t_type& val) const; // Routines to directly process dynamic arrays in a Any SCAAny& insertArray(const SCATypeCode tcval, SCAVoid* values, SCAUInt32 numEnt1, SCAUInt32 numEnt2, const SCATypeCode tcArray=SCATypeCode(), const DynamicArray::DestroyFunc destroyFunc=NULL); SCABool extractArray(SCATypeCode& tcval, SCAVoid** values, SCAUInt32& numEnt1, SCAUInt32& numEnt2) const; // Data extraction and testing SCAVoid dump() const; SCATypeCode getType() const; SCATypeCodeID getTypeID() const; SCAString getTypeDesc() const; SCAUInt32 getLength() const; SCABool sameTypeCode(const SCATypeCode& tcval) const; SCABool empty() const; SCAVoid flush();
}; }
seqval.push_back(2.3); anyval <<= seqval; A SCAAny can even hold another SCAAny instance. // Insert an SCAAny value to SCAAny instance SCAAny anyval1(SCAInt32(100)); anyval <<= anyval1;
The values in a SCAResult instance can be used by the SCA Framework to format and dispatch messages in the language appropriate for the current user. For more information on using SCAResult for error processing see the Error Handling chapter of this manual.
bool equals(const SCAResult& ); // Assignment operators SCAResult & operator=( const SCAResult& ); SCAResult & operator=( const SCAInt32 eid ); // Return the information of error SCAInt32 getTableID() const; SCAInt32 getMessageID() const; SCAInt32 getErrorCode() const; SCAAnySequence getParams() const; SCABool hasParams() const; SCABool isOk() const; SCABool isSystemError() const; // Marshalling insertion and extraction operators SCAMarshaller& operator >>= (SCAMarshaller& stream) const; SCAMarshaller& operator <<= (SCAMarshaller& stream);
}; }
Two predefined SCAResult values are provided when you do not need to return more detailed information to the caller. namespace SCA { const SCAResult SCASuccess(0); const SCAResult SCAError(0XFFFFFFFF); } Examples of the usages of these are shown in the following sections.
// Add parameters to SCAResult value rstat.addParam(SCAInt8(1)); rstat.addParam(SCAInt16(12)); rstat.addParam(SCAInt32(123)); rstat.addParam(SCAInt64(1234)); rstat.addParam(SCAUInt8(2)); rstat.addParam(SCAUInt16(23)); rstat.addParam(SCAUInt32(234)); rstat.addParam(SCAUInt64(2345)); rstat.addParam(SCAReal32(12.34)); rstat.addParam(SCAReal64(123.456)); rstat.addParam('A'); rstat.addParam("TestString"); rstat.addParam(L"TestWideString"); rstat.addParam(true);
the smart pointer name will be SCAIReader and it will be stored in a header file with the name SCAIReaderSPtr.h.
An abstract class with the name composed of the interface name followed by the string
Interface. In this example the name of the C++ class will be SCAIReaderInterface and it will be stored in a header file with the name SCAIReader.h. This file will always include the file containing the smart pointer definition so you do not need to explicitly include both. Each of the generated header files is completely defined. This means they will include any required header files to allow a successful compile whenever it is used. The definitions are split between different header files because the smart pointer definition can act very similar to the forward definition of a C++ class. When you do not need the detailed information about the methods in an interface you can include the header file for the smart pointer, SCAIReaderSPtr.h, instead of the header file with the full interface class definition, SCAIReader.h. This can result in a substantial reduction in the size of the expanded source files because they do not need to include the definitions of all the types that are used as parameters in the interface methods. The IDL compiler makes extensive use of the behavior to keep down the size of the expanded skeleton files it generates. This means that the generated header files for the full interface definition will only include the smart pointer definitions for any other interfaces that appear only as parameter values. There is a side effect when only including the smart pointer definition that needs to be understood. The stub files for your implementation class will compile correctly as generated by the IDL compiler. But, as
soon as you add a call to a method on an interface that is passed in as a parameter, you may get a compiler error. Unfortunately these errors tend to be very cryptic and difficult to understand because they are related to template expansions. If you get one of these cryptic errors, just include the header file with the full definition of the interface appearing in the error and it will usually fix the problem. To illustrate this problem consider the following portion of a simple interface definition. interface SCAITest1 : SCA::SCAIService { SCAVoid test1( in SCAITest2 inf ); }; The code for the test1 method in the generated implementation stub would look something like this. This code will compile as generated with no errors. SCA::SCAVoid TestService::test1(const SCAITest2 inf) { } But if you then add a call to a method in the SCAITest2 interface a compilation error may occur. SCA::SCAVoid TestService::test1(const SCAITest2 inf) { inf->test2(); } To fix the error, just add an include statement for the SCAITest2.h header file.
const SCAUUID& SCASmartPointer< SCAIReaderInterface >::getUUID(){ static SCAUUID uuid = {0x0d291f4bfd5331e3,0xa1fcefd01e371d1f}; return uuid; } } #endif The smart pointer definition contains the following pieces of information.
The expansion of the SCASmartPointer template for the SCAIReaderInterface abstract class
which has the name SCAIReader. The smart pointer is used by the client to access the methods in the interface.
A template specialization of the getInfName method of the SCASmartPointer class which
appropriate reference counting calls are made to automatically manage the lifecycle of the objects pointed to.
Navigating or switching between interfaces implemented by the same service object can be
made using standard C++ assignment or casting operators instead of requiring special framework calls.
Allows you to determine if two different interface references point to the same underlying
implementation object using standard C++ comparison tests. The following shows some examples of how using smart pointers simplify the handling of interfaces. Declarations of smart pointer instances are the same as instances of any C++ class. Note that no * is used as would be the case with a normal C pointer. SCAITest1 spTest1; SCAITest2 spTest2; The conversion of one smart pointer type to another smart point type is also referred to as interface navigation. With smart pointers, the syntax for interface navigation is the same as normal C++ casts or conversions. try { spTest2 = static_cast<SCAITest2>(spTest1); spTest2 = (SCAITest2)spTest1; spTest2 = spTest1; } catch(SCAIException ex) { cout << Interface cast failed = << e.what() << endl; } It is important to remember that you can only navigate from one interface to another interface if the underlying implementation object supports both interfaces. If the underlying object does not implement the interface that you wish to navigate to, a SCAException will be thrown. As a result you should always include interface casts in a try/catch block to catch any errors. The smart pointer implementation provides a number of different operator overloads that can be used. Two smart pointers are defined to be equal if they point to the same underlying implementation object. The normal C++ equality operators can be used for this test. Less then and greater then operators have no meaning and are not defined. if ( spTest1 == spTest2 ) ...;
if ( spTest1 != spTest2 ) ...; The C++ pointer-to-member operator is used to make a call to a method in the interface. spTest1->doSomething(); A special value, SCA::NULLSP, is provided which is a used to represent a null interface pointer. The use of the normal C++ NULL value or a 0 value will not work and will generate a compilation error. The following shows how a smart pointer instance can be reset. If the instance currently contains a pointer to an implementation object, then the reference count on that object will be decremented before the pointer is set to null. spTest1 = NULLSP; The NULLSP value can also be used to test for an unassigned smart pointer or you can just test its value. if if if if ( ( ( ( spTest1 == NULLSP ) ...; spTest1 != NULLSP ) ...; !spTest1 ) ...; spTest1 ) ...;
Several methods are provided which allow you to interrogate information about the contents of the smart pointer. // Get the name of the interface the smart pointer is for cout << Interface name is << spTest1.getInfName() << endl; // Get the UUID for the interface the smart pointer is for SCAUUID uuid = spTest1.getUUID(); // Get the raw pointer that is held by the smart pointer void* ptr = spTest1.getInfPtr();
SCAException class
The base for all IDL defined exceptions is SCA::SCAException. It is mapped to the following C++ class. Only those methods intended for external use have been shown here. Other methods required by the framework to manage exceptions and marshal them between different languages have not been shown. namespace SCA { struct SCA_EXPORT SCAException { //Constructors and destructors SCAException(SCABool deleteOnThrow=false); SCAException(const SCAException ©); virtual ~SCAException() throw(); //Method to print out description of exception SCAString what() const throw(); //Method to get SCATypeCode for this exception virtual SCATypeCode getTypeCode() const throw(); //Method to get raw text string for this exception SCAString getText() const throw(); //Method to set raw text string for this exception SCAVoid setText(const SCAString text) throw(); //Creates an SCAException object static SCAException* create(); //Throws this exception virtual void throwit(); //Optional exception description SCAString m_text; }; }
SCAUserException class
The SCA::SCAUserException class adds no new data or method. It is provided as a base for all user defined exceptions.
namespace SCA { struct SCA_EXPORT SCAUserException: public SCAException { //Constructors and destructors SCAUserException(SCABool deleteOnThrow=false); SCAUserException(SCAString text, SCABool deleteOnThrow=false); virtual ~SCAUserException() throw(); //Method to get SCATypeCode for this exception virtual SCATypeCode getTypeCode() const throw(); //Creates an SCAUserException object static SCAException* create(); //Throws this exception virtual void throwit();
}; }
SCASystemException class
The SCA::SCASystemException class should only be used internally by the SCA Framework. It adds an error ID to the base SCAException. namespace SCA { struct SCA_EXPORT SCASystemException: public SCAException { //Constructors and destructors SCASystemException(SCABool deleteOnThrow=false); SCASystemException(SCAInt32 id, SCAString text, SCABool deleteOnThrow=false); virtual ~SCASystemException() throw(); //Method to get SCATypeCode for this exception virtual SCATypeCode getTypeCode() const throw(); //Creates an SCASystemException object static SCAException* create(); //Throws this exception virtual void throwit(); //System exception ID SCAInt32 id;
}; }
IDL exception ReaderException : SCAUserException { SCAString name; SCAString error; }; C++ struct ReaderException : public SCAUserException { ReaderException(SCABool deleteOnThrow=false) : SCAUserException(deleteOnThrow) {} virtual ~ReaderException() throw() {} static SCAException* create() { return new ReaderException(true); } virtual void throwit() { if ( m_deleteOnThrow ) { ReaderException exc = *this; delete this; exc.setThrow(); throw exc; } else { this->setThrow(); throw *this; } } virtual SCATypeCode getTypeCode() const throw() { return getCachedTypeCode("SCA.FileReader.ReaderException"); } SCAString name; SCAString error; };
SCAINode spNode = spReader->getNode(123); } catch (SCAException& e) { cout << "Exception: " << e.what() << endl; } In this example, if the getNode method throws an exception there is a potential problem. Notice that the getSCAService call to load the service and the only references to it, spReader and spNode, are all inside the try block. This means that when the method throws an exception, and the execution flow leaves the try block to enter the catch block, the destructors for the smart pointers spReader and spNode will be called. Since these are the only references to the service, the shared library for the service may be unloaded by the SCA Kernel at this point. This is a problem because the code in the catch block requires access to the implementation of the exception object which would be no longer available. This can trigger a crash in the catch block. To keep this from happening you should always throw the exception using the throwit method. This will trigger some additional logic in the SCA Kernel that will insure that no shared libraries are unloaded until all of the SCA exception objects that have been thrown have been deleted. For a complete discussion of how exceptions are used in the SCA Framework see the Error Processing chapter of this manual.
};
}; }; #endif The following is the SDL for an example service that will implement these interfaces. #ifndef FILEREADER_SDL_INCLUDED #define FILEREADER_SDL_INCLUDED #include "SCA/FileReader/FileReader.idl" module SCA { module FileReader { module Impl { service SCA.Test.FileReader { interface SCA::SCAIReader; subservice NodeImpl ( in SCA::Node node ) { interface SCA::SCAINode; }; }; }; }; }; #endif
#ifndef SCA_FILEREADER_IMPL_FILEREADER_H_INCLUDED #define SCA_FILEREADER_IMPL_FILEREADER_H_INCLUDED #include "FileReaderBase.h" namespace SCA { namespace FileReader { namespace Impl { class FileReader : public FileReaderBase { public: // Constructor and Destructor FileReader(SCAIFileReaderFactoryAccess* factoryAccess); virtual ~FileReader(); // Methods for interface SCA.FileReader.SCAIReader virtual SCAVoid readModel(const SCAString name); virtual SCAINode getNode(const SCAInt32 id);
};
} } } #endif The following is the implementation file FileReader.cpp that is generated. #include "FileReader.h" namespace SCA { namespace FileReader { namespace Impl { // Constructor FileReader::FileReader(SCAIFileReaderFactoryAccess* factoryAccess) : FileReaderBase(factoryAccess) { } // Destructor FileReader::~FileReader() { } SCAVoid FileReader::readModel(const SCAString name) { } SCAINode FileReader::getNode(const SCAInt32 id) { } } } } The C++ implementation will be put in a namespace that is defined by the SDL module statements. In this example it will be SCA::FileReader::Impl.
The implementation classes generated are fairly simple, but there are a couple of requirements for these that are imposed by the SCA Framework. These are the requirements when using the default or inheritance form of implementation.
The class must inherit from the base class generated by the IDL compiler. The name of the base
class will be xxxBase where xxx is the name of the service class.
The class can have only one constructor and it must have a single argument of
SCAIxxxFactoryAccess* where xxx is the name of the top-level service class. This pointer is used by the base class to access the SCA Framework.
The base class constructor must be explicitly called from the class constructor. The class must implement each of the operations defined in the interfaces it supports and any
interfaces that inherit from them. The exception to this rule is the methods in the SCAIService interface do not need to be implemented. As long as these requirements are followed, the developer is free to make any desired modifications to these implementation classes. The base class, FileReaderBase, generated by the IDL compiler during the build process, does the following.
Inherits from the abstract base class for each interface implemented by the service object. In this
introspection methods in the SCAIService interface that every SCA service must implement.
Provides complete access to the SCA Framework facilities through the service access interface. Provides helper functions for creating new instances of subservice objects using any
initialization parameters defined in the SDL through the service access interface. If the FileReader service used in this example implemented additional interfaces, the only changes to the generated code would be the addition of the method definitions for the operations in the new interfaces. Implementation for subservice class The SDL for the FileReader service also includes the definition of a subservice NodeImpl which takes a Node constructor argument. A separate C++ class, NodeImpl, will be generated for the subservice class. The format of this class is identical to the FileReader class except for the addition of an extra argument on the class constructor and the interface methods it implements. The following is the header file generated for this class. #ifndef SCA_FILEREADER_IMPL_NODEIMPL_H_INCLUDED #define SCA_FILEREADER_IMPL_NODEIMPL_H_INCLUDED #include "NodeImplBase.h" namespace SCA { namespace FileReader { namespace Impl { class NodeImpl : public NodeImplBase
public: // Constructor and Destructor NodeImpl(SCAIFileReaderFactoryAccess* factoryAccess, const Node& node); virtual ~NodeImpl(); // Methods for interface SCA.FileReader.SCAINode virtual SCAInt32 getID(); virtual SCAReal32 getX(); virtual SCAReal32 getY(); virtual SCAReal32 getZ();
};
} } } #endif Subservice classes have the same requirements imposed by the SCA Framework as top-level classes except the restrictions on its constructor are relaxed. Subservice classes may still only have one constructor defined but it is possible for it to have additional user defined arguments. Only subservice classes can have user defined constructor arguments. This is because instances of the top-level FileReader class are instantiated by the IDL generated factory class in response to getService calls made by the clients of the service. There is currently no way for clients to passes constructor arguments through the getService call. On the other hand, instances of subservice objects can only be generated by the implementation code in the service. In this case the implementation is free to pass any desired arguments to the constructors. It is even possible to pass argument types that are not supported in IDL by using the SCAVoidPtr IDL type in the SDL definition.
virtual SCAIService getService(const SCAString name, const SCAString attributes, SCAResult& rstatus); virtual SCAIService getService(const SCAString name, SCAResult& rstatus); // Get system interfaces virtual SCAIServiceProvider getSCAIServiceProvider(); virtual SCAIServicePublisher getSCAIServicePublisher(); virtual SCAIServiceFactory getSCAIServiceFactory(); // Get a subservice object virtual SCAIService getNodeImpl(const Node& node);
};
} } } #endif The ServiceAccess interface is made available to the implementation classes using the m_serviceAccess variable defined in the base class. The ServiceAccess interface provides three overloaded getService methods that should be used by the implementation code if it needs to load other SCA services. Here is an example of using these. SCAIService spSvc; SCAResult rStat; spSvc = m_serviceAccess->getService(Test.Service.Name); spSvc = m_serviceAccess->getService(Test.Service.Name,rStat) if ( rStat ) return rStat; For each subservice class defined in the SDL, the ServiceAccess interface will also include a helper method that can be used to create instances of it. The arguments to the method will match the constructor arguments specified in the SDL for the class. These simplify the task of creating subservice object instances because the implementation code does not need to worry about the Factory Access interface that is always required as a constructor argument. The name of each helper method will be getXxx where Xxx is the name of the subservice class. The following shows how we can use this helper method in a sample implementation of the getNode method in the SCAIReader interface. This interface is implemented by the FileReader class. SCAINode FileReader::getNode(const SCAInt32 id) { // Find and initialize the node with the requested ID Node node = // Return a new subservice object for this node SCAINode spNode = m_serviceAccess->getNodeImpl(node); return spNode;
Notice that class no longer inherits from a base class and as a result the interface methods are no longer declared virtual. Because there is no requirement that the implementation inherit from an IDL generated base class, you are free to use any inheritance structure you require. Also note that the m_serviceAccess variable must now be a member of the implementation class because there is no longer a base class for it to reside in. The following is the implementation file FileReader.cpp that is generated for the delegation form of implementation.
#include "FileReader.h" namespace SCA { namespace FileReader { namespace Impl { // Constructor FileReader::FileReader(SCAIFileReaderServiceAccess* serviceAccess) { m_serviceAccess = serviceAccess; } // Destructor FileReader::~FileReader() { } SCAVoid FileReader::readModel(const SCAString name) { } SCAINode FileReader::getNode(const SCAInt32 id) { } } } }
Singleton Services
It is also possible to indicate that the service is a singleton in the SDL. The use of this keyword has no affect on any of the C++ implementation skeletons generated for a service. The processing of singleton services is handled entirely in the factory support code that is generated when the service is built. It is important to remember that even so the generated skeletons are the same; the implementation code for a singleton service may need to be different. Because multiple clients may be sharing the same instance of the service, the code needs to make sure this is done in a safe manner.
Aggregation
The C++ mapping also supports the aggregates and aggregated keywords in the SDL. The affect of these on the mapping for service objects is an advanced topic that is covered later in this manual.
Embedded Components
The C++ mapping supports the embedded option in the CDL. When this option is specified, the build system compiles the source for the component in normal fashion but it will not link the object files into a separate shared library. Instead you are allowed to include the generated object files where ever you would like in the application. Also since the SCA framework will no longer be loading the share library, a different initialization scheme is required. See section on embedded components in the Advanced SDK manual for complete details.
Introduction
Mapping for Unsigned Data Types Mapping for String Types 158
Mapping for Enumerated Types Mapping for Structures Mapping for Arrays 160
159
Mapping for SCATypeCode Mapping for SCAAny Mapping for SCAResult Mapping for Constants Mapping for Interfaces Mapping for Exceptions Mapping for SCA Services 168
Introduction
The IDL language provides a language independent definition of SCA interfaces and data types. In order to actually use these definitions, there must be a set of rules, commonly known as a mapping that describes how these types are represented in a particular language. This chapter explains the mapping that SCA uses for the Java language. The following table summarizes the data type mapping between IDL and Java types. The following sections of this chapter will provided the details on each mapping. IDL Type SCA::SCAInt8 SCA::SCAUInt8 SCA::SCAInt16 SCA::SCAUInt16 SCA::SCAInt32 SCA::SCAUInt32 SCA::SCAInt64 SCA::SCAUInt64 SCA::SCAReal32 SCA::SCAReal64 SCA::SCAChar SCA::SCAWChar SCA::SCAString SCA::SCAWString SCA::SCABool SCA::SCAAny SCA::SCATypeCode SCA::SCAVoid SCA::SCAResult sequence enum struct array const byte short short int int long long long float double char char java.lang.String java.lang.String boolean SCA.SCAAny SCA.SCATypeCode void SCA.SCAResult class (extends java.util.Vector) enum class [] or class (that wraps []) interface.value Java Type
For every type defined in IDL, the IDL compiler will generate the code required to expose the proper Java definition of the type. In addition to the actual Java definition of the type, there is also some support code generated for each interface which is used by SCA Framework to make interface calls from Java to services implemented in the other supported languages. When building a Java application or component, the SCA SCons build system will compile the Java definitions for all of the known IDL types and store them in a single jar file APPS/lib/java/IDLTypes.jar. See the section on the SCA Kernel interactions with the JVM later in this chapter for more details on this.
The following structure definition illustrates this mapping. IDL: struct Node { SCAInt32 id; SCAReal32 x; SCAReal32 y; SCAReal32 z; }; Java: public class Node { public int id; public float x; public float y; public float z; }
The following example illustrates this mapping. IDL struct Time { SCAUInt8 hour; SCAUInt8 minute; SCAUInt8 second; }; Java public class Time { public short hour; public short minute; public short second; } Because of the mapping of IDL unsigned data to Java signed data, care must be taken when dealing with the unsigned SCA data types in the Java code.
When calling an interface method implemented in a non-Java service, the Java number may be
truncated if its value is out of the supported range for the IDL type.
When calling an interface method implemented in a non-Java service and the Java value is
negative, the converted IDL unsigned value will be a large positive value.
If a non-Java client passes a large SCAUint64 value to an interface implemented in Java and its
value does not fit in a Java long, then the convert Java value will be a very large negative value.
dateArray[0] = 12; System.out.println("Modified data is " + dateArray[0] + "/" + dateArray[1] + "/" + dateArray[2]); Two dimensional IDL arrays are handled in a similar manner. IDL typedef SCA::SCAInt64 Matrix[5][5]; Java public class Matrix { public Matrix(){ data=new long[size1][size2]; } public void setElementAt(long e, int index1, int index2){ data[index1][index2]=e; } public long elementAt(int index1, int index2){ return data[index1][index2]; } public long[][] getArray(){ return data; } public long[] getRow(int index1){ return data[index1]; } private long[][] data; public static final int size1=5; public static final int size2=5; } If an IDL array appears as a member of a structure, it is mapped directly to the member of the Java defined structure. IDL struct Node2 { SCAInt32 id; SCAReal32 location[3]; }; Java public class Node2 { public int id; public final float[] location= new float[3]; }
Dynamic Arrays
Dynamic Arrays are mapped to Java classes which are very similar to fixed size arrays except they take in the size of the array as an argument of the constructor. Dynamic Arrays cannot be declared inside structures and they are not a substitute for a java.util.Vector since they do not allow resizing of the array at runtime. Dynamic arrays exist primarily to provide more optimized code in other languages supported by the SCA framework. IDL typedef Node NodeArray[]; Java public class NodeArray { public NodeArray(int s1){ size=s1; data=new Node[size]; } public void setElementAt(Node e, int index){ data[index]=e; } public Node elementAt(int index){ return data[index]; } public Node[] getArray(){ return data; } public final int size; private final Node[] data; } Two dimensional dynamic arrays are handled in a similar manner.
Since generated sequence classes inherit from java.util.Vector, they are used the same way as any Java Vector object. // Initialize contents of the sequence SCAReal64Sequence seq = new SCAReal64Sequence(); seq.add(1.0); seq.add(2.0); seq.add(3.0); // Print contents of the sequence for ( int i=0; i<seq.size(); i++ ) System.out.println("seq[" + i + "] = " + seq.get(i));
public interface SCAITimeConvert extends SCA.SCAIService { TimeDef convert(TimeDef time); } Notice how the HourValue, MinuteValue, SecondValue, LocalTime and GMTTime alias types have been unwound and the SCA basic types or IDL defined types have been used instead.
The first requirement covers the typical usage of the SCAAny type, the insertion of typed values into the SCAAny and the type-safe extraction of the values. In Java the type-safety is ensured by the SCAAny class and the Java JVM. When you try to insert a value into a SCAAny, the class will make sure that the data being inserted is an IDL defined type and consistent with the SCA type description. If it is not, then a SCASystemException will be thrown. When you try to extract a value from the SCAAny, the JVM will attempt to cast it to the type you requested. If the value can be converted then the extraction will succeed. If it cannot be converted then a Java RuntimeException exception will be thrown. The second requirement covers situations like the need to extract data from the SCAAny when you do not know the type of data it contains. In this case the receiver must be able to determine information about what type of data the SCAAny contains. To achieve this, SCAAny contains a pair of values that includes the actual value of the data and a description of its type. The type information can then be inspected to determine the details on the value stored in the SCAAny instance.
public public public public public public public public public public public public public public public public public public public public public public public public public public public public public
void setSCAUInt32(long data); long getSCAUInt32(); void setSCAInt64(long data); long getSCAInt64(); void setSCAUInt64(long data); long getSCAUInt64(); void setSCAReal32(float data); float getSCAReal32(); void setSCAReal64(double data); double getSCAReal64(); void setSCAChar(char data); char getSCAChar(); void setSCAString(String data); String getSCAString(); void setSCAWChar(char data); char getSCAWChar(); void setSCAWString(String data); String getSCAWString(); void setSCABool(boolean data); boolean getSCABool(); void setSCAAny(SCAAny data); SCAAny getSCAAny(); void setSCATypeCode(SCATypeCode data); SCATypeCode getSCATypeCode(); void setSCAResult(SCAResult data); SCAResult getSCAResult(); void setSCAObject(Object data); void setSCAObject(Object data, String typeString); Object getSCAObject();
// Flush the contents public void flush(); // Methods for getting information about the contents public String type(); public boolean empty(); public String toString(); // SCAAny data private Object m_data; private String m_type; } Examples of using the SCAAny are show in the following sections.
To create a new SCAAny value that contains a value you can use the constructor which takes a Java Object and an optional string description of the type. Which form you use depends on the way the SCA type is mapped in Java. If the SCA type maps to a Java class that is generated by the IDL compiler, then you only need to provide the instance in the constructor. This includes types like sequences, structures, enumerations, array and interfaces. The following shows an example of this. // Create a SCAAny instance which contains a structure value Node node = new Node(); node.id = 123; node.x = 1.0; node.y = 2.0; node.z = 3.0; SCA.SCAAny any = new SCA.SCAAny(node); But, if the SCA type maps to a native Java type that cannot be uniquely mapped to a SCA type then you will need to add the type description to explicitly specify the type. An example of this is a Java String value which can be mapped to either a SCAString or a SCAWString value. // Create a SCAAny instance which contains a SCAString value SCA.SCAAny any = new SCA.SCAAny(new String(test),SCA.SCAString); Because the SCAAny will only hold an object that inherits from the Java Object type, you cannot create a new SCAAny with a primitive type directly. Instead you must use its corresponding wrapper classes as shown in this example. Since the wrapper classes do have unique SCA mappings, you must also include the type description in this case. // Create a SCAAny instance which contains a SCAInt32 value SCA.SCAAny any = new SCA.SCAAny(new Integer(123),SCA.SCAInt32);
seq.add(2.0); anyval.setSCAObject(seq); As before, since a SCA sequence has a unique mapping to a Java class, you do not need to add the type description. But if the mapping is not unique you will need to. // Insert a SCAString value into a SCAAny instance anyval.setSCAObject(new String(value),SCA.SCAString); // Insert a SCAInt32 value into a SCAAny instance anyval.setSCAObject(new Integer(123),SCA.SCAInt32); A SCAAny can even hold another SCAAny instance. // Insert an SCAAny value to SCAAny instance SCA.SCAAny anyval2 = new SCA.SCAAny(new Long(123),SCA.SCAInt64); anyval.setSCAAny(anyval2);
// Extract a SCAInt32 from a SCAAny instance try { int int32val = anyval.getSCAInt32(); } catch (RuntimeException ex) { System.out.println("RuntimeException:" + ex.toString()); } For non-basic types, you will need to use the getSCAObject method to extract the value. This value must be cast to the desired Java type as shown in this example.
// Extract a Node structure value from a SCAAny instance try { Node node = (Node)anyval.getSCAObject(); System.out.println("Extracted value: id=" + node2.id + " x=" + node.x + " y=" + node.y + " z=" + node.z); } catch (RuntimeException ex) { System.out.println("RuntimeException:" + ex.toString()); } There may be cases where the SCAAny value may hold one of a number of different types and you do not know at compilation time which one it is. In this case you can use the type method to determine what type it contains so you can choose the correct get method. The following example shows how several extractions can be used to handle different possible types of values that may be in the SCAAny instance.
// Extract an unknown type from a SCAAny instance int int32val; float real32val; if ( anyval.type() == "SCA.SCAInt32" ) { int32val = anyval.getSCAInt32(); System.out.println("int32val = " + int32val); } else if ( anyval.type() == "SCA.SCAReal32" ) { real32val = anyval.getSCAReal32(); System.out.println("real32val = " + real32val); } else { System.out.println("Unsupported type " + anyval.type()); }
The values in a SCAResult instance can be used by the SCA Framework to format and dispatch messages in the language appropriate for the current user. For more information on using SCAResult for error processing see the Error Handling chapter of this manual.
public boolean equals(SCAResult sr ); // Return the parameters public SCAAnySequence getParams(); // Return true if has parameters, return false otherwise. public boolean hasParams(); // Return true if the error code == 0 public boolean isOk(); // Return true if it is a system error public boolean isSystemError(); // Print raw contents of SCAResult public void dump(); // Return a formatted message public String toString(); // Predefined SCAResult values public final static SCAResult SCASuccess = new SCAResult(0); public final static SCAResult SCAError = new SCAResult(0X7FFFFFFF);
Two predefined SCAResult values are provided when you do not need to include more detailed information to the callers. // Return error return SCA.SCAResult.SCAError; // Return success return SCA.SCAResult.SCASuccess; Examples of the usages of these are shown in the following sections.
System.out.println("SCAResult value is " + anyval.toString()); // Print contents using the dump method anyval.dump();
} public T value;
If the IDL parameter is one of the SCA basic types then the SCA.Holder class must hold its corresponding Java box type and not the primitive type. The following table shows the corresponding box type for each SCA IDL type. IDL Type SCA::SCAInt8 SCA::SCAUInt8 SCA::SCAInt16 SCA::SCAUInt16 SCA::SCAInt32 SCA::SCAUInt32 SCA::SCAInt64 SCA::SCAUInt64 SCA::SCAReal32 SCA::SCAReal64 SCA::SCAChar SCA::SCAWChar SCA::SCABool Java primitive type byte short short int int long long long float double char char boolean Java box type java.lang.Byte java.lang.Short java.lang.Short java.lang.Short java.lang.Integer java.lang.Integer java.lang.Long java.lang.Long java.lang.Float java.lang.Double java.lang.Character java.lang.Character java.lang.Boolean
When calling a method with output parameters, you must first create instances of the SCA.Holder class and pass them as the parameter as shown in this example.
SCA.Holder<Float> x = new SCA.Holder<Float>(); SCA.Holder<Float> y = new SCA.Holder<Float>();; SCA.Holder<Float> z = new SCA.Holder<Float>();; inf.getNodeCoordinates(id,x,y,z); System.out.println(X= + x.value()); System.out.println(Y= + y.value()); System.out.println(Y= + z.value()); If any of these parameters was specified with the inout direction, the value in the holder class should be set before the interface method is called.
SCAException interface
The base for all IDL defined exceptions is SCAException which is mapped to the java interface SCA.SCAException.
package SCA; public interface SCAException { // Method to return description of the exception String what(); // Method to get SCATypeCode for this exception SCATypeCode getTypeCode(); // Method to get raw text string for this exception String getText(); // Method to set raw text string for this exception void setText(String text); }
SCAUserException class
The SCAUserException exception adds no new data to SCAException and should be used as the base for all user defined exceptions. It is mapped to the predefined Java class SCA.SCAUserException which implements the SCA.SCAException interface.
package SCA; public class SCAUserException extends Exception implements SCAException { // Constructor public SCAUserException(); // Methods to return description of exception public final String what(); public final String toString(); // Method to get SCATypeCode for this exception public final SCATypeCode getTypeCode(); // Method to get raw text string for this exception public final String getText(); // Method to set raw text string for this exception public final void setText(String text); // Method to set exception type protected final void setType(String type);
SCASystemException class
The SCASystemException exception should only be used internally by the SCA Framework and adds an error ID member to SCAException. It is mapped to the predefined Java class SCA.SCASystemException which implements the SCA.SCAException interface. Note that SCA.SCASystemException inherits from Javas RuntimeException exception, so it is treated as an unchecked exception by the java runtime. package SCA; public class SCASystemException extends RuntimeException implements SCAException { // Constructors public SCASystemException(); public SCASystemException(int iid); public SCASystemException(int iid, String text); // Methods to return description of the exception public final String what(); public final String toString(); // Method to get SCATypeCode for this exception public final SCATypeCode getTypeCode(); // Method to get raw text string for this exception public final String getText(); // Method to set raw text string for this exception public final void setText(String text); // Method to set exception type protected final void setType(String type); // Method to return exception traceback public static String getTrace(Throwable e); // Exception data private String m_type; protected String m_text; public int id; }
};
SCAString error;
Java public class ReaderException extends SCA.SCAUserException { public ReaderException(); public String name; public String error; }; The following code shows an example of how you throw a SCA exception in Java. ReaderException ex = new ReaderException(); ex.name = TestInput.dat; ex.error = The file does not exist; throw ex; And the exception can be caught as follows. try { // Code that triggers an exception } catch (ReaderException ex) { System.out.println("ReaderException caught for"); System.out.println(File: + ex.name); System.out.println(Error: + ex.error); }
Use of the raises clause is especially important in Java, since a method that throws an exception should specify it in a throws clause. The SCASystemException exception is an unchecked Java exception so it can always be thrown and users do not need to include it in the raises clause. Other exceptions that may be thrown must be one of the exceptions in the raises clause to inherit from one of them.
}; }; }; #endif The following is the SDL for an example service that will implement these interfaces. #ifndef FILEREADER_SDL_INCLUDED #define FILEREADER_SDL_INCLUDED #include "SCA/FileReader/FileReader.idl" module SCA { module FileReader { module Impl { service SCA.Test.FileReader { interface SCA::FileReader::SCAIReader; subservice NodeImpl ( in SCA::FileReader::Node node ) { interface SCA::FileReader::SCAINode; }; }; }; }; }; #endif
setServiceProvider(provider);
// Methods for interface SCA.FileReader.SCAIReader public final void readModel (String name) throws SCA.FileReader.ReaderException { //implementation goes here } public final SCA.FileReader.SCAINode getNode (int id) throws SCA.FileReader.ReaderException { //implementation goes here }
The implementation classes generated are fairly simple, but there are a couple of requirements for these imposed by the SCA Framework. These are the requirements when using the default or inheritance form of implementation. The class must inherit from the base class generated by the IDL compiler.
The class can have only one constructor and it must have a single argument of
SCAIServiceProvider type. This argument is used by the base class to access the SCA Framework.
The base class constructor must be explicitly called from the class constructor. The class must implement each of the operations defined in the interfaces it supports except for
SCAIService. As long as these requirements are met, the developer is free to make any desired modifications to these implementation classes. The base class FileReader_base, which will be generated by the IDL compiler when it is run during the build process, provides the following.
Inherits from the respective interface for each interface implemented by the service class. In this
introspection methods in the SCAIService interface that every SCA service class must implement.
Provides access to the SCA Framework facilities.
If the FileReader service used in this example implemented additional interfaces, the only changes to the generated code would be the addition of the method definitions for the operations in the new interfaces. The SDL for the FileReader service also includes the definition of a subservice NodeImpl which takes a Node constructor argument. A separate Java class, NodeImpl, will be generated for the subservice class. The format of this class is identical to the FileReader class except for the addition of an extra argument defined in the SDL file on the class constructor and the interface methods it implements.
Following is the NodeImpl.java class generated by the genskeleton command package SCA.FileReader.Impl; public class NodeImpl extends NodeImpl_base { // Constructor public NodeImpl (SCA.Framework.SCAIServiceProvider provider, SCA.FileReader.Node node) { super(); setServiceProvider(provider); } // Methods for interface SCA.FileReader.SCAINode public final int getID () { //implementation goes here } public final float getX () { //implementation goes here } public final float getY () { //implementation goes here } public final float getZ () { //implementation goes here }
Only subservice classes can have user specified constructor arguments. This is because instances of the top-level FileReader class are instantiated by the IDL generated factory class in response to getService calls made by the clients of the service. There is currently no way for clients to passes constructor arguments through the getService call. On the other hand, instances of subservice classes can only be generated by the implementation code in the service. In this case the implementation is free to pass any desired arguments to the constructors. The following is an example implementation for the getNode method which returns an instance of the SCAINode interface. The SDL definition for this service specifies that the SCAINode interface is implemented by the NodeImpl subservice class. That means that the getNode method needs to allocate an instance of the NodeImpl class and return the SCAINode interface on it. The constructor for the NodeImpl class, generated by the genskeleton utility, requires two parameters. The first parameter is the SCAIServiceProvider interface which is available in the m_provider variable in the base class. The second argument is an instance of the Node structure that was specified as a constructor argument in the SDL. SCAINode FileReader::getNode(const SCAInt32 id) { // Find and initialize the node which cooresponds to ID Node node =
// Return a new subservice class instance for this node NodeImpl spNode = new NodeImpl(m_provider,node); return (SCAINode)spNode;
Singleton Services
It is also possible to indicate that the service is a singleton in the SDL. The use of this keyword has no affect on any of the Java implementation skeletons generated for a service. The processing of singleton services is handled entirely in the factory support code that is generated when the service is built. It is important to remember that even so the generated skeletons are the same; the implementation code for a singleton service may need to be different. Because multiple clients may be sharing the same instance of the service, the code needs to make sure this is done in a safe manner.
Aggregation
The support for aggregation is requested with the aggregates and aggregated keywords in the SDL. Currently aggregation is not supported in Java.
Embedded Components
Support for embedded components is requested with the embedded option in the CDL. Currently embedded components are not supported in Java.
Java application launcher utility. In this case the application launcher is responsible for initializing the JVM and the normal command line parameters and environment variables it supports are used to provide any user defined options for the JVM.
If the SCA application is written in a language other than Java, then the JVM will be initialized
by the SCA Kernel when the first Java service is loaded. In this case, the SCA Kernel JVMConfig configuration parameter is used to provide any user defined options for the JVM.
to the Java class path using either CLASSPATH environment variable or the classpath or cp command line parameter for the Java application launcher utility.
If the SCA application is written in a language other than Java, then the SCA kernel will
initialize the JVM using the JVMConfig configuration variable. If no JVMConfig variable is provided, then it will automatically provide one that includes an IDLTypes.jar archive which is located relative to the SCA resource directory which is specified with the Resource configuration value. JVMConfig=-Djava.class.path=Resource/../lib/java/IDLTypes.jar
If the SCA application is written in a language other than Java and the JVMConfig
configuration variable is provided, then the location of the IDLTypes.jar archive must be manually included in it. For further details on setting the JVMConfig and other SCA configuration parameters please refer to the SCA Kernel documentation.
Mapping for Enumerated Types Mapping for Structures Mapping for Arrays 200
Mapping for SCATypeCode Mapping for SCAAny Mapping for SCAResult Mapping for Constants Mapping for Interfaces Mapping for Exceptions Mapping for SCA Services 206 211 215 216
Introduction
The IDL language provides a language independent definition of SCA interfaces and data types. In order to actually use these definitions, there must be a set of rules, commonly known as a mapping that describes how these types are represented in a particular language. This chapter explains the mapping that SCA uses for the .NET languages. Since .NET has a unified type system, the SCA types can be used in any .NET language. The following table summarizes the data type mapping between IDL, CLR, C# and Visual Basic types. The following sections of this chapter will provide the details on each mapping. IDL Type SCA::SCAInt8 SCA::SCAUInt8 SCA::SCAInt16 SCA::SCAUInt16 SCA::SCAInt32 SCA::SCAUInt32 SCA::SCAInt64 SCA::SCAUInt64 SCA::SCAReal32 SCA::SCAReal64 SCA::SCAChar SCA::SCAWChar SCA::SCAString SCA::SCAWString SCA::SCABool SCA::SCAAny SCA::SCATypeCode SCA::SCAResult sequence enum struct array interface exception SByte Byte Int16 UInt16 Int32 UInt32 Int64 UInt64 Single Double Char Char String String Boolean SCA.SCAAny SCA.SCATypeCode SCA.SCAResult class that inherits from List enum struct [] or[][] interface class that inherits from System.Exception CLR Type C# Type sbyte byte short ushort int uint long ulong float double char char string string bool VB Type SByte Byte Short UShort Integer UInteger Long ULong Single Double Char Char String String Boolean
See the HelloWorld Application chapter of this manual for examples on how SCA applications can be written in different .NET languages. In that chapter both a C# and Visual Basic version of the application is shown. To reduce the amount of sample code, this chapter will only show examples in the C# language. For every type defined in IDL, the IDL compiler will generate the code required to expose the proper CLR definition of the type. In addition to the actual CLR definition of the type, there is also some support code generated for each interface which is used by SCA Framework to make interface calls from .NET to services implemented in the other supported languages. When building a .NET application or component, the SCA SCons build system will compile the CLR definitions for all of the known IDL types and store them in a single assembly APPS/WINNT/bin/IDLTypes.dll.
The following structure definition illustrates this mapping. IDL: struct Node { SCAInt32 id; SCAReal32 x; SCAReal32 y; SCAReal32 z; }; C#: public struct Node { public int id; public float x;
Dynamic Arrays
Dynamic Arrays also use normal CLR arrays types. Since the size of a dynamic array is not fixed in the IDL, you can use any appropriate size when you allocate the CLR array type. Dynamic arrays exist primarily to provide more optimized code in other languages supported by the SCA framework.
public public public public public public public public public public public public public public public public public public public public public public public public public public public public public
void setSCAUInt32(uint data); uint getSCAUInt32(); void setSCAInt64(long data); long getSCAInt64(); void setSCAUInt64(ulong data); ulong getSCAUInt64(); void setSCAReal32(float data); float getSCAReal32(); void setSCAReal64(double data); double getSCAReal64(); void setSCAChar(char data); char getSCAChar(); void setSCAString(String data); String getSCAString(); void setSCAWChar(char data); char getSCAWChar(); void setSCAWString(String data); String getSCAWString(); void setSCABool(bool data); bool getSCABool(); void setSCAAny(SCAAny data); SCAAny getSCAAny(); void setSCATypeCode(SCATypeCode data); SCATypeCode getSCATypeCode(); void setSCAResult(SCAResult data); SCAResult getSCAResult(); void setSCAObject(Object data); void setSCAObject(Object data, String typeName); Object getSCAObject();
// Flush the contents public void flush(); // Methods for getting information about the contents public String type(); public bool empty(); public override String ToString(); // Data private Object m_data; private String m_type; } }
To create a new SCAAny value that contains a value you can use the constructor which takes a CLR Object and an optional string description of the type. Which form you use depends on the way the SCA type is mapped in .NET. If the SCA type uniquely maps to a CLR type, then you only need to provide the object in the constructor. Types like sequences, structures, enumerations, interfaces and all basic types except characters and strings fall into this category. The following shows several examples of this. // Create a SCAAny instance which contains a structure value Node node = new Node(); node.id = 123; node.x = 1.0; node.y = 2.0; node.z = 3.0; SCA.SCAAny any = new SCA.SCAAny(node); // Create a SCAAny instance which contains a SCAInt64 value SCA.SCAAny any2 = new SCA.SCAAny((long)123); But, if the SCA type maps to a native CLR type that cannot be uniquely associated to a SCA type then you will need to add the type description to explicitly specify the type. An example of this is a CLR String value which can be mapped to either a SCAString or a SCAWString value. If the type description is not specified when required a SCASystemException will be thrown. // Create a SCAAny instance which contains a SCAString value SCA.SCAAny any = new SCA.SCAAny(new String(test),SCA.SCAString);
As before, since a SCA sequence has a unique mapping to a CLR class, you do not need to add the type description. But if the mapping is not unique you will need to. // Insert a SCAString value into a SCAAny instance anyval.setSCAObject(new String(value),SCA.SCAString); A SCAAny can even hold another SCAAny instance. // Insert an SCAAny value to SCAAny instance SCAAny anyval2 = new SCAAny((long)123,"SCA.SCAInt64"); anyval.setSCAAny(anyval2);
The values in a SCAResult instance can be used by the SCA Framework to format and dispatch messages in the language appropriate for the current user. For more information on using SCAResult for error processing see the Error Handling chapter of this manual.
public bool equals (SCAResult sr); // Return the parameters public SCAAnySequence getParams(); // Return true if has parameters, return false otherwise public bool hasParams(); // Return true if the error code ==0 public bool isOk(); // Returns true if it is a system error public bool isSystemError(); // Print raw contents of SCAResult public void dump(); // Returns a formatted message public override string ToString(); // Predefined SCAResult values public static SCAResult SCASuccess = new SCAResult(0); public static SCAResult SCAError = new SCAResult(0X7FFFFFFF);
Two predefined SCAResult values are provided when you do not need to include more detailed information to the callers. // Return error return SCA.SCAResult.SCAError; // Return success return SCA.SCAResult.SCASuccess; Examples of the usages of these are shown in the following sections.
// Test if SCAResult has a non-zero error if ( !rstat.isOk() ) // Process error The get methods can be used to extract the error, table and message values. // Extract the error, tableID and messageID values int error = rstat.getErrorCode(); int tableid = rstat.getTableID(); int messageid = rstat.getMessageID(); The hasParams and getParams methods can be used to determine if the SCAResult has parameters and to process them. The parameter values are returned in a SCAAnySequence. // If the SCAResult has parameters, print them if ( rstat.hasParams() ) { for ( int i=0; i<params.size(); i++ ) { SCA.SCAAny param = params.elementAt(i); Console.WriteLine("Param " + i + " = " + param.ToString()); } }
SCAException interface
The base for all IDL defined exceptions is SCAException which is mapped to the SCA.SCAException class. namespace SCA{ public class SCAException: System.Exception { // return a string to explain what message the exception carries public string what(){} //Method to get SCATypeCode for this exception public SCATypeCode getTypeCode(){} //Method to get raw text string for this exception public string getText(){} //Method to set raw text string for this exception public void setText(string text){}
SCAUserException class
The SCAUserException exception adds no new data to SCAException and should be used as the base for all user defined exceptions. It is mapped to the SCA.SCAUserException class which inherits from the SCA.SCAException class. namespace SCA { public class SCAUserException: SCAException { public SCAUserException(){} } }
SCASystemException class
The SCASystemException exception should only be used internally by the SCA Framework and adds an error ID member to SCAException. It is mapped to the SCA.SCASystemException class which inherits from the SCA.SCAException class. namespace SCA { public class SCASystemException: SCAException{
//Constructors public SCASystemException() {} public SCASystemException(int iid) {} public SCASystemException(int iid, string text) {} public int id;
// Code that triggers an exception } catch (ReaderException ex) { Console.WriteLine("ReaderException Caught for); Console.WriteLine(File: + ex.name); Console.WriteLine(Error: + ex.error); }
IDL interface SCAIReader : SCAIService { SCAVoid readModel( in SCAString name ) raises (ReaderException); }; C# interface TestInterface{ public void testEx(); } Since the CLR does not support checked exceptions, the presence of a raises clause in the IDL has no affect on any of the generated code. But, when implementing components in .NET it is still important that the IDL definition of the interfaces that they implement contain the appropriate raises clauses. This is because the various SCA language bridges do check the exceptions thrown and will only pass those that have been specified. If an exception is thrown that is not specified in the raises clause, it will be converted to a SCASystemException. The SCASystemException exception is an unchecked exception that can always be thrown. Users do not need to include SCASystemException or SCAException in the raises clause.
The generated skeleton can then be expanded with the code required to implement the desired behavior for the various interface operations.
During the build process, various base or tie classes are generated which are used to link the
developer generated implementation code to the SCA Framework. This is done to reduce as much as possible the amount of code that must be written by the developer to implement a service. These classes also provide a level of isolation between the services implementation code and the SCA Framework. This allows for future changes to be made in the interaction between the base or tie classes and the SCA Framework without affecting the developers implementation code. The following is the IDL file for an example interface definition that will be used in this section. #ifndef SCA_FILEREADER_FILEREADER_IDL_INCLUDED #define SCA_FILEREADER_FILEREADER_IDL_INCLUDED #include "SCA/Service.idl" module SCA { module FileReader { struct Node { SCAInt32 id; SCAReal32 x; SCAReal32 y; SCAReal32 z; }; exception ReaderException : SCAUserException { SCAString name; SCAString error; }; interface SCAINode; interface SCAIReader : SCA::SCAIService { SCAVoid readModel( in SCAString name ) raises(ReaderException); SCAINode getNode( in SCAInt32 id ) raises(ReaderException); }; interface SCAINode : SCA::SCAIService { SCAInt32 getID(); SCAReal32 getX(); SCAReal32 getY(); SCAReal32 getZ();
}; }; }; #endif The following is the SDL for an example service that will implement these interfaces. #ifndef FILEREADER_SDL_INCLUDED #define FILEREADER_SDL_INCLUDED #include "SCA/FileReader/FileReader.idl" module SCA { module FileReader { module Impl { service SCA.Test.FileReader { interface SCA::FileReader::SCAIReader; subservice NodeImpl ( in SCA::FileReader::Node node ) { interface SCA::FileReader::SCAINode; }; }; }; }; }; #endif
{ }
} } } The implementation classes generated are fairly simple, but there are a couple of requirements for these imposed by the SCA Framework. These are the requirements when using the default or inheritance form of implementation.
The class must inherit from the base class generated by the IDL compiler. The class can have only one constructor and it must have a single argument of
SCAIServiceProvider type. This argument is used by the base class to access the SCA Framework.
The class must implement each of the operations defined in the interfaces it supports except for
SCAIService. As long as these requirements are met, the developer is free to make any desired modifications to these implementation classes. The base class FileReader_base, which will be generated by the IDL compiler when it is run during the build process, provides the following.
Inherits from the respective interface for each interface implemented by the service class. In this
introspection methods in the SCAIService interface that every SCA service class must implement.
Provides access to the SCA Framework facilities.
If the FileReader service used in this example implemented additional interfaces, the only changes to the generated code would be the addition of the method definitions for the operations in the new interfaces.
using System.Collections.Generic; namespace SCA { namespace FileReader { namespace Impl { public class NodeImpl: NodeImpl_base , SCA.FileReader.SCAINode { // Constructor public NodeImpl (SCA.Framework.SCAIServiceProvider provider, SCA.FileReader.Node node) { setServiceProvider(provider); } // Methods for interface SCA.FileReader.SCAINode public int SCA.FileReader.SCAINode.getID () { //implementation goes here } public float SCA.FileReader.SCAINode.getX () { //implementation goes here } public float SCA.FileReader.SCAINode.getY () { //implementation goes here } public float SCA.FileReader.SCAINode.getZ () { //implementation goes here }
} } } }
Only subservice classes can have user specified constructor arguments. This is because instances of the top-level FileReader class are instantiated by the IDL generated factory class in response to getService calls made by the clients of the service. There is currently no way for clients to pass constructor arguments through the getService call. On the other hand, instances of subservice classes can only be created by the implementation code in the service. In this case the implementation is free to pass any desired arguments to the constructors.
SCAINode FileReader::getNode(const SCAInt32 id) { // Find and initialize the node which cooresponds to ID Node node = // Return a new subservice class instance for this node NodeImpl spNode = new NodeImpl(m_provider,node); return (SCAINode)spNode;
using SCA.FileReader; namespace SCA { namespace FileReader { namespace Impl { public class FileReader { // Constructor public FileReader (FileReader_tie tie) { m_tie = tie; } // Methods for interface SCA.FileReader.SCAIReader public void readModel (string name) { //implementation goes here } public SCA.FileReader.SCAINode getNode (int id) { //implementation goes here } protected FileReader_tie m_tie; } } } } Notice that class no longer inherits from a base class and as a result the interface methods names are not longer in the scope of the interface. Because there is no requirement that the implementation inherit from an IDL generated base class, you are fee to use any inheritance structure you required. The class also saves a reference to the tie class which is required to access the ServiceAccess interface that it implements. Since a subservice was also defined in the SDL file, NodeImpl is also created as a delegated subservice. A separate tie class named NodeImp_tie is created for the subservice which creates an instance of the NodeImpl generated by the genskeleton command. Therefore, when creating an instance of the subservice, the user will have to create an instance of the tie class. The following illustrates how the getNode method would be implemented when delegation is used. public SCA.FileReader.SCAINode getNode (int id) { // Find and initialize the node which cooresponds to ID Node node = // Return a new subservice class instance for this node NodeImpl_tie spNode = new NodeImpl_tie(m_tie.getServiceProvider(), node) return (SCAINode)spNode;
Singleton Services
It is also possible to indicate that the service is a singleton in the SDL. The use of this keyword has no affect on any of the implementation skeletons generated for a service. The processing of singleton services is handled entirely in the factory support code that is generated when the service is built. It is important to remember that even so the generated skeletons are the same; the implementation code for a singleton service may need to be different. Because multiple clients may be sharing the same instance of the service, the code needs to make sure this is done in a safe manner.
Aggregation
The .NET mapping also supports the aggregates and aggregated keywords in the SDL. The affect of these on the mapping for service objects is an advanced topic that is covered in a separate manual.
Embedded Components
Support for embedded components is requested with the embedded option in the CDL. Currently embedded components are not supported in .NET.
Introduction
Mapping for Enumerated Types Mapping for Structures Mapping for Arrays 238
240 241 242 243 244 248 252 253 258 261 262
Mapping for Sequences Mapping for Type Aliases Mapping for TypeCode Mapping for SCAAny Mapping for SCAResult Mapping for Constants Mapping for Interfaces Mapping for Exceptions Mapping for SCA Services
Accessing IDL Type Definitions from Python Running Python Scripts 267
264
Introduction
Python scripting in the SCA Framework allows a SCA enabled application to execute one or more Python scripts at runtime. Users can also access SCA services from the Python command line. In both cases, the Python scripts can load SCA services and invoke their methods. SCA interfaces can also be implemented in Python scripts. Python is a type-less language which means that you do not declare variables to be of a specific type. Instead, any Python variable can assume any type at runtime. In addition to this, the SCA mappings of types in many cases directly use native Python types. As a result many of the type names defined in IDL never appear in Python. Instead you just use the native Python types as appropriate. But because Python does no type checking, you have to be careful when calling a SCA interface to make sure the arguments are Python objects of the correct type. If you try to pass a Python object to a SCA interface that cannot be converted to the required SCA type, then the SCA language bridge will throw a SCASystemException exception. The following table summarizes the data type mappings between IDL and Python types. The following sections of this chapter will provided the details on each mapping. IDL Type SCA::SCAInt8 SCA::SCAUInt8 SCA::SCAInt16 SCA::SCAUInt16 SCA::SCAInt32 SCA::SCAUInt32 SCA::SCAInt64 SCA::SCAUInt64 SCA::SCAReal32 SCA::SCAReal64 SCA::SCAChar SCA::SCAString SCA::SCAWChar SCA::SCAWString SCA::SCABool SCA::SCAAny SCA::SCAVoid SCA::SCAResult plain integer plain integer plain integer plain integer plain integer long integer long integer long integer float float string of length 1 string Unicode string of length 1 Unicode string plain integer SCA.SCAAny class None SCA.SCAResult class Python Type
IDL Type sequence array list list of the array size (1D array)
Python Type
list of lists of array size (2D array) enum struct interface exception plain integer SCA defined structure type SCA defined interface type or Python class inheriting from SCAIServiceBase SCA.SCAException class or SCA defined exception type
These conversions use the functions provided by the Python interpreter and are quite liberal. For example you they will convert a Python plain integer, long integer, floating point or even a string value to a C long value as long as the conversion makes sense. The following examples show some valid types of conversions that would be acceptable when making a call to an interface method that has an input SCAInt32 value. inf.method(123) inf.method(long(123)) inf.method (123.0) inf.method ('123') But the following calls would throw a SCASystemException exception because they do not represent a valid integer value. inf.method (123.45) inf.method ('123.45') The following table shows the Python types and value ranges that can be converted to each SCA basic type. IDL Type IDL Type SCA::SCAInt8 SCA::SCAUInt8 SCA::SCAInt16 SCA::SCAUInt16 SCA::SCAInt32 SCA::SCAUInt32 SCA::SCAInt64 Python Types Python Types integer, long, float, string integer, long, float, string integer, long, float, string integer, long, float, string integer, long, float, string integer, long, float, string integer, long, float, string Legal Range Legal Range -128,127 0,255 -32768,32767 0,65535 -2147483648,2147483647 0,4294967295 -9223372036854775808, 9223372036854775807 SCA::SCAUInt64 integer, long, float, string 0,18446744073709551615
SCA::SCAReal64
-1.797693134862315710308, 1.797693134862315710308
SCA::SCAWString string, Unicode string SCA::SCABool integer, long, float, string, True, False True = 0 False != 0 After making the actual call to the interface method, any output values must then be converted back to a Python value. This conversion is much simpler because the SCA types are already strongly typed and no value checking is required. The following table shows the Python type that will be created for each value type. IDL Type SCA::SCAInt8 SCA::SCAUInt8 SCA::SCAInt16 SCA::SCAUInt16 SCA::SCAInt32 SCA::SCAUInt32 SCA::SCAInt64 SCA::SCAUInt64 SCA::SCAReal32 SCA::SCAReal64 SCA::SCAChar SCA::SCAString SCA::SCAWChar Python Type plain integer plain integer plain integer plain integer plain integer long integer long integer long integer floating point floating point string string Unicode string
The following example shows how these attributes can be used. Python print '\nStructure name is',node.__name__ print '\n',node.__doc__ print '\nStructure members are',node.__members__ Would produce the following output: Structure name is SCA.Test.Node Structure SCA.Test.Node { SCA.SCAInt32 id SCA.SCAReal32 x SCA.SCAReal32 y SCA.SCAReal32 z }
Structure members are ['id', 'x', 'y', 'z'] Because Python is type-less, each member in the structure is defined as a generic Python object. When initializing the structure, you need to be careful that it can be converted to the required SCA type when the structure passes through a SCA language bridge. If it cannot, then a SCASystemException exception will be thrown at that time. The following IDL contains a structure with an array member to demonstrate this. IDL module SCA { module Test { struct Node2 { SCAInt32 id; SCAReal32 loc[3]; }; }; }; Python node = SCA.Test.Node2() node.id = 123 node.loc = [2.345,3.456,4.567] When using structures, you should remember that Python assignment statements only generate a new reference to the data. As a result, the change to the node2.id value in the following example will also cause the value of the node1 reference to change. node1 = SCA.Test.Node() node1.id = 1 node2.id = node1 node2.id = 999 To make the node2 value a separate copy of the data so any changes to it will not affect the node1 value, you can use the following code. node1 = SCA.Test.Node() node1.id = 1 node2.id = SCA.Test.Node(node1) node2.id = 999
The first requirement covers the typical usage of the SCAAny type, the insertion of typed values into the SCAAny and the type-safe extraction of the values. Because of the type-less nature of Python, many of the type-safe features of the SCAAny provided in other languages are not supported in Python. More details on what type-safe features are supported are discussed later in this section. The second requirement covers situations like the need to extract data from the SCAAny when you do not know the type of data it contains. In this case the receiver must be able to determine information about what type of data the SCAAny contains. To achieve this, SCAAny contains a pair of values that includes the actual value of the data and a description of its type. The type information, contained in a SCATypeCode value, can then be inspected to determine the details on the value stored in the SCAAny instance.
# Flush the SCAAny def flush(self): # Methods to format contents def dump(self): def __str__(self): Examples of using the SCAAny are show in the following sections.
The following shows some examples of this. # Create a SCAAny instance which contains a SCAInt32 value any = SCA.SCAAny(123) # Create a SCAAny instance which contains a SCABool value any = SCA.SCAAny(True)
# Create a SCAAny instance which contains a structure value node = SCA.Test.Node() node.id = 123 node.x = 1.0 node.y = 2.0 node.z = 3.0 any = SCA.SCAAny(node) If the Python value is not one of the types that supports an automatic mapping or if you want a different mapping then you will need to add the type description to explicitly specify the type. An example of this is a Python plain integer value which will normally map to a SCAInt32 value but you wish to make it a SCAInt64 value instead. # Create a SCAAny instance which contains a SCAInt64 value any = SCA.SCAAny(123,'SCA.SCAInt64') Another example of this would be inserting a Python list that has no automatic mapping defined. # Create a SCAAny instance which contains a SCA.Test.Date value date = [9,1,2009] any = SCA.SCAAny(date,'SCA.Test.Date') When both a Python object and the type description are given, some limited checking will be done to ensure that the two are consistent. For example for an array a check is made to be sure the Python object is a list and that its length is correct but the individual entries in the list will not be checked. The complete checking will be performed when the data is actually used when making an interface method call through a SCA language bridge.
The values in a SCAResult instance can be used by the SCA Framework to format and dispatch messages in the language appropriate for the current user. For more information on using SCAResult for error processing see the Error Handling chapter of this manual.
# Compare all fields of SCAResult data, excluding parameters def equals(self, obj): # Return the parameters def getParams(self): # Return true if has parameters, return fals otherwise def hasParams(self): # Return true if the error code == 0 def isOK(self): def SCABool(self): # Return true if it is a system error def isSystemError(self): # Print the raw contents of the SCAResult def dump(self): # Return a formatted message def __str__(self): Two predefined SCAResult values are provided to indicate a simple success or error when you do not need to include more detailed information to the callers. // Return error return SCA.SCAError // Return success return SCA.SCASuccess Examples of the usages of these are shown in the following sections.
# If the SCAResult has parameters, print them if rstat.hasParams(): params = rstat.getParams() for (i,param) in zip(range(len(params)),params): print 'Param',i,'=',str(param) Miscellaneous SCAResult methods The SCAResult also implements the __str__ and dump methods which allow you to get or print the contents. The __str__ method will attempt to format a message for the SCAResult value using the MessageDispatcher service which is described in the Error Handling chapter of this manual. The dump method will just print the raw data contained in the SCAResult value. # Print a string representation of the SCAAny print 'SCAResult value is ' + str(rstat) # Print contents using the dump method rstat.dump()
proxy class.
Interfaces implemented in Python are mapped to a normal Python class that inherits from the
SCA.SCAIServiceBase class. Since interfaces are either mapped to an instance of a generic proxy class or to a user provided Python class, the IDL interface names are not used in Python and are not exposed. The only required references to interface names are using string types which contain their name. The following is a simple interface definition that will be used in this section to demonstrate the use of SCA interfaces in Python. IDL module SCA { module Test { typedef SCAInt32 Date[3]; interface SCAITestInterface : SCA::SCAIService { SCAInt32 testInt32(in SCAInt32 inval, out SCAInt32 outval, inout SCAInt32 inoutval); Date testDate(in Date inval, out Date outval, inout Date inoutval); SCAInt32 testNoArg(); }; }; };
The following example shows how these attributes can be used assuming you have an instance of a Python mapping to the SCA.Test.SCAITestInterface interface. Python # Variable sp points to a SCAITestInterface interface print sp.__doc__ for meth in sp.__methods__: print 'method ',meth Would produce the following output: SCAPyInterface object for interface SCA.Test.SCAITestInterface method addReference method releaseReference method getInterface method getImplName method getInstanceID method testInt32 method testDate method testNoArg
Some SCA types map to mutable Python types. Examples of these are arrays and sequences which map to Python lists. Even so these types of arguments could be mapped to take advantage of the fact that they are mutable, this is not done. If this was done then a different set of mapping rules would be used for different parameter types and this would be confusing. As a result, even for SCA types which map to mutable Python types, a parameter with a inout direction should still appear in both the argument list and the return tuple. This is shown with the following example. # Test with mutable arguments indate = [11,12,13] inoutdate = [31,32,33] (retdate,outdate,inoutdate) = splist.testDate(indate,inoutdate) print 'Call results: inval=%s outval=%s inoutval=%s retval=%s' % (indate,outdate,inoutdate,retdate) If the total number of return values and arguments with a direction of out or inout is only one, then the single value will be returned directly instead of in a tuple of length one. This is demonstrated in the following example. # Test with single value return retval = splist.testNoArg() print 'Call results: retval=%d' % retval You can also use keyword arguments when calling interface methods. In this case the order of the arguments is not important, but you need to make sure that a value for each required argument is supplied. # Test with keyword arguments ival = 1 ioval = 3 (rval,oval,ioval) = splist.testInt32(inoutval=ioval,inval=ival) print 'Call results: inval=%d outval=%d inoutval=%d retval=%d' % (ival,oval,ioval,rval)
framework.
The initialization routine in the implementation class must explicitly call the initialization
routine of the SCA.SCAIServiceBase class. The arguments for this call must be a list with the names of interfaces that the class will implement.
The class must implement each method in each of the interfaces that are implemented. This also
includes methods in any interfaces that they inherit from. You do not need to implement the methods in SCA.SCAIService because these are implemented in SCA.SCAIServiceBase class. The following example shows a simple Python class that will implement the listener interface. class MyPyImpl(SCA.SCAIServiceBase): def __init__(self): interfaces = ['SCA.Test.SCAITestListener'] SCA.SCAIServiceBase.__init__(self,interfaces) def notify(self,msg): print 'MyPyImpl::notify -',msg The __init__ routine in SCA.SCAIServiceBase checks to make sure that the Python class implements each of the required interface methods. If any of the methods are missing it will throw an exception.
When implementing interface methods, you need to use the same rules for handling arguments that was described above. Only arguments with directions of in and inout should appear as parameters to the method and the method should return a tuple which contains the return value and any arguments with directions of out or inout. You also need to make sure that the values in the return tuple can be converted to the required SCA types. The following example shows how an instance of this interface can be created and passed to the test service. sp = SCA.getService('ServiceName','SCA.Test.SCAITestProcessor') myinf = MyPyImpl() sp.registerListener(myinf) sp.process()
SCAException exception
The base for all IDL defined exceptions is SCAException which is mapped to the Python class SCA.SCAException. class SCAException: # Supported attributes text = None typecode = None def __init__(self): def what(self): def getTypeCode(self): def getText(self): def setText(self,text): def __str__(self):
SCA::SCAResult process() raises(NoListenerException); }; }; }; When calling the process method in Python, we can now catch the exception as follows. try: sp.process() except SCA.Test.NoListenerException, exc: print 'NoListenerException:',exc.what() print 'id =',exc.id Because we caught the actual SCA.Test.NoListenerException exception we have full access to the data members defined in it. In this case this is the id data member. It is also possible to catch the SCA.SCAException type which is the base SCA exception. But in this case we would not have access to any data members defined in the actual exception. try: sp.process() except SCA.SCAException, exc: print 'SCAException:',exc SCA exceptions do not inherit from Python's Exception type so you cannot catch them with an except clause using it. There may also be situations where you need to throw a SCA exception from a Python script. This is unusual and normally will only occur if you are implementing an interface in Python. The following Python code will implement the SCAITestProcessor interface that was described before. In the implementation of the process method, an exception will be thrown if the listener interface was never registered. class MyPyImpl(SCA.SCAIServiceBase): def __init__(self): interfaces = [] interfaces.append('SCA.Test.SCAITestProcessor') SCA.SCAIServiceBase.__init__(self,interfaces) m_spListener = None def registerListener(self,spListener): self.m_spListener = spListener def process(self): if not self.m_spListener: exc = SCA.Test.NoListenerException() exc.setText('Listener interface was never registered') exc.id = 123 raise exc self.m_spListener.notify('This is test notification from Python')
The getService function is used to load SCA services. The following is an example of how it is used. import SCA svc = SCA.getService('ServiceName') (ret,sp) = svc.getInterface('SCA.Test.SCAITestProcessor') It is also possible to combine the two calls into one as shown below. import SCA sp = SCA.getService('ServiceName','SCA.Test.SCAITestProcessor') The loadTypes function will be described in the next section.
when the SCA module is imported. Examples of these types include SCAAny, SCAResult and SCAException.
Some types are mapped to normal Python types. These include basic types, arrays and
sequences.
Some types required special Python classes to be created to represent the SCA type in Python.
an IDL definitions. Examples of these include enumerations and IDL defined constants. The last two categories are important because they both required the SCA Python Bridge to create Python objects at runtime which represent the IDL defined type. When using these types, it is important to understand what actually triggers the creation of these Python objects. The SCA Python Bridge will normally process these definitions when a proxy object is created for a SCA interface and only the types referenced in that interface will be created. The following IDL definitions will be used to demonstrate this. Two interfaces are defined and each one references a separate structure definition. There is also a constant definition included. IDL module SCA { module Test { const SCAInt32 ERROR_1 = 1; struct Struct1 { SCAInt32 val; }; interface SCAITestInterface1 : SCA::SCAIService { void method1(in Struct1 sval1); }; struct Struct2 { SCAInt32 val; }; interface SCAITestInterface2 : SCA::SCAIService { void method2(in Struct2 sval2); }; }; }; The following Python code illustrates when the various types will become available in the Python script. def TestVariables(): vars = [] vars.append('SCA.Test.Struct1')
vars.append('SCA.Test.Struct2') vars.append('SCA.Test.ERROR_1') for var in vars: try: exec 'val = %s' % var print '%s defined' % var except: print '%s not defined' % var import SCA print '\nStart of test' TestVariables() print '\nLoad service' svc = SCA.getService('ServiceName') TestVariables() print '\nCall to getInterface for SCA.Test.SCAITestInterface1' (ret,sp) = svc.getInterface('SCA.Test.SCAITestInterface1') TestVariables() print '\nCall to getInterface for SCA.Test.SCAITestInterface2' (ret,sp) = svc.getInterface('SCA.Test.SCAITestInterface2') TestVariables() print '\nCall to SCA.loadTypes for SCA.Test' SCA.loadTypes('SCA.Test',True) TestVariables() Running this script generates the following output Start of test SCA.Test.Struct1 not defined SCA.Test.Struct2 not defined SCA.Test.ERROR_1 not defined Load service SCA.Test.Struct1 not defined SCA.Test.Struct2 not defined SCA.Test.ERROR_1 not defined Call to getInterface for SCA.Test.SCAITestInterface1 SCA.Test.Struct1 defined SCA.Test.Struct2 not defined SCA.Test.ERROR_1 not defined Call to getInterface for SCA.Test.SCAITestInterface2 SCA.Test.Struct1 defined SCA.Test.Struct2 defined SCA.Test.ERROR_1 not defined Call to SCA.loadTypes for SCA.Test SCA.Test.Struct1 defined
SCA.Test.Struct2 defined SCA.Test.ERROR_1 defined Notice that at the start of the test none of the types defined in the IDL are available. This is even true after the getService call. This is because the getService call returns a SCA.SCAIService interface and none of the desired types are references by it. Only after the getInterface call for SCA.Test.SCAITestInterface1 do we get some types defined. In this case only the structure SCA.Test.Struct1 is defined because it is the only type referenced by this interface. The second structure, SCA.Test.Struct2, is not defined until the second getInterface call for SCA.Test.SCAITestInterface2 is made. Notice that even after all of the getInterface calls, the constant value SCA.Test.ERROR_1 is still not defined. This is the normal case for constants because they are usually not directly referenced by any of the methods in an interface so the SCA Python Bridge will never define them. To resolve these issues, you must explicitly trigger the loading of these SCA type definitions using the loadTypes function in the SCA module. SCA.loadTypes('SCA.Test',True) The first argument to the loadTypes call is the IDL namespace for the types you want defined. The second argument is a boolean value. If it is true then all the type in the specified namespace and recursively all of the namespaces it contains will be defined. If false then only the types in the specified namespace will be defined. It is also possible to use the loadTypes call to force the definition of types before they would normally be available. For example in the above example, if the loadTypes call is done immediately after the SCA module is imported then all of the types will be defined and available immediately. Python import SCA print '\nImmediate call to SCA.loadTypes for SCA.Test' SCA.loadTypes("SCA.FileReader",True) TestVariables() Will generate this output Immediate call to SCA.loadTypes for SCA.Test SCA.FileReader.Struct1 defined SCA.FileReader.Struct2 defined SCA.FileReader.ERROR_1 defined
This section will briefly describe both of these methods. For complete details you should consult the Scripting chapter in the SCA SDK Advanced Features manual.
Introduction
Internationalization is the process of adding capabilities in the software applications so that they can be adapted to various locales (languages and regions) without re-building them. Localization is the actual process of translating and formatting the text and other GUI components. The SCA Framework provides the following localization capabilities.
XML Message Tables The text from messages, dialog boxes and widgets is stored in XML message files. Parameter Substitution Support for number, date, time and currency formats. Language Customization The substitution parameters are indexed, so the order could be different in different
languages. This is necessary to support different writing directions, grammar and composition.
Text Translation Service Provides the interfaces to load the contents of the XML message files and format messages.
Message Files
The message files supported by the SCA framework are UTF-8 encoded XML files that allows a simple one-to-one mapping from a text ID to the respective message text. The text includes place holders to allow parameter substitution and formatting of number, date, time and currency values.
Locale String
<localeLanguageCode>_<localeCountryCode> The locale string contains the locale value, which is a 2-character language code (as defined by ISO 6391), optionally followed by an underscore and a 2-character country identifier (as defined by ISO 3166). For example "en" for English, "en_GB" for British, "fr" for French, "fr_CA" for Canadian French etc. This format is also used in XML documents as a value for the xml:lang attribute (as described in http://www.faqs.org/rfcs/rfc3066.html )
Only the first file found using the above order is loaded. Example: baseName= "TextTranslation", localeLanguageCode = "fr" localeCountryCode= "CA" Search order: RESDIR/Locale_fr_CA/TextTranslation_fr_CA.xml RESDIR/Locale_fr/TextTranslation_fr.xml RESDIR/TextTranslation_fr_CA.xml RESDIR/TextTranslation_fr.xml RESDIR/Locale_en/TextTranslation_en.xml RESDIR/TextTranslation_en.xml
but specifying it in the file again allows for easier conversion to other formats. [optional] <text>: A translation unit. It contains the text node in the translated language. It has the following attributes:
id: Specifies the string id that is used to retrieve the message [mandatory] comment: Can be used to explain the context of the text [optional] author: If the text was not written by the author specified in the <sim_office_resource > author
<text>: This node describes the dialog's caption; it has the same attributes as the <text> tag
described above. The id attribute is optional here! If provided, it is used to identify the context.
<help>: A help text for the dialog.
Text Formatting
The formatting place holders are embedded in the text. Since the argument order can change in different languages, each argument is prefixed by the index of its entry in the argument sequence that is passed to the getFormattedText method. The index is 1-based. The format of this prefix is %<index>:, so the first argument would be %1:, the second %2: and so on. This index reference is followed by the data formatting information. Currently, there are three different formatting styles, which are distinguished by the first character:
code a A b B c d h I j m M p s u w W x X y Y Z
Example:
Meaning abbreviated weekday name (e.g. Fri) full weekday name (e.g. Friday) abbreviated month name (e.g. Oct) full month name (e.g. October) the standard date and time string day of the month, as a number (1-31) hour, 24 hour format (0-23) hour, 12 hour format (1-12) day of the year, as a number (1-366) month as a number (1-12). Note: some versions of Microsoft Visual C++ may use values that range from 0-11. minute as a number (0-59) locale's equivalent of AM or PM second as a number (0-59) week of the year, (0-53), where week 1 has the first Sunday weekday as a decimal (0-6), where Sunday is 0 week of the year, (0-53), where week 1 has the first Monday standard date string standard time string year in decimal, without the century (0-99) year in decimal, with the century time zone name
Text: "Today is a %1:#A in %1:#B, the time is %1:#X", Arg1 = 1130514894 Formatted Text: "Today is a Friday in October, the time is 5:54:54 PM".
Output a
Example 392 3.9265e+2 3.9265E+2 392.65 392.65 392.65 610 sample 7235 7fa 7FA B800:0000
Scientific notation (mantise/exponent) using e character Scientific notation (mantise/exponent) using E character Decimal floating point Use the shorter of %e or %f Use the shorter of %E or %f Signed octal String of characters Unsigned decimal integer Unsigned hexadecimal integer Unsigned hexadecimal integer (capital letters) Pointer address Nothing printed. The argument must be a pointer to a signed int, where the number of characters written so far is stored. A % followed by another % character will write % to stdout. Description
Left-justify within the given field width; Right justification is the default (see width subspecifier). Forces to precede the result with a plus or minus sign (+ or -) even for positive numbers. By default, only negative numbers are preceded with a - sign. If no sign is going to be written, a blank space is inserted before the value. Used with o, x or X specifiers the value is preceded with 0, 0x or 0X respectively for values different than zero. Used with e, E and f, it forces the written output to contain a decimal point even if no digits would follow. By default, if no digits follow, no decimal point is written. Used with g or G the result is the same as with e or E but trailing zeros are not removed.
Left-pads the number with zeroes (0) instead of spaces, where padding is specified (see width sub-specifier).
width #
Description Minimum number of characters to be printed. If the value to be printed is shorter than this number, the result is padded with blank spaces. The value is not truncated even if the result is larger. The width is not specified in the format string, but as an additional integer value argument preceding the argument that has to be formatted. Description For integer specifiers (d, i, o, u, x, X): precision specifies the minimum number of digits to be written. If the value to be written is shorter than this number, the result is padded with leading zeros. The value is not truncated even if the result is longer. A precision of 0 means that no character is written for the value 0. For e, E and f specifiers: this is the number of digits to be printed after the decimal point. For g and G specifiers: This is the maximum number of significant digits to be printed. For s: this is the maximum number of characters to be printed. By default all characters are printed until the ending null character is encountered. For c type: it has no effect. When no precision is specified, the default is 1. If the period is specified without an explicit value for precision, 0 is assumed.
.precision .number
.*
The precision is not specified in the format string, but as an additional integer value argument preceding the argument that has to be formatted. description The argument is interpreted as a short int or unsigned short int (only applies to integer specifiers: i, d, o, u, x and X). The argument is interpreted as a long int or unsigned long int for integer specifiers (i, d, o, u, x and X), and as a wide character or wide character string for specifiers c and s. The argument is interpreted as a long double (only applies to floating point specifiers: e, E, f, g and G).
modifier h l L
Example:
Text: "%1:%d + %2:%g %3:%s" Arg1 = 1, Arg2 = 2.5, Arg3 = "is equal to..." Formatted Text: "1 + 2.5 is equal to...". Sample1: English Message File.
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE SIMOFFICERES> <!-Copyright (c) 2005, MSC.Software Corporation. All Rights Reserved. MSC PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. --> <sim_office_resources version="1.0" xml:lang="en" comment="Translation Test" author="[email protected]"> <text id="color" comment="material" author="[email protected]">Color</text> <text id="help">Help</text> <text id="formatTest"> This is a formatting test: string '%1:%ls', long: %2:%d, double: %3:%1.3g </text> <text id="currencyTest">The price is %1:$</text> <text id="dateTimeTest">Today is %1:#A %1:#B - %1:#x %1:#X</text> <module id="mod_test"> <text id="m1">Text in mod_test::m1.</text> <text id="m2">Text in mod_test::m2.</text> </module> </sim_office_resources> Sample2: German Message File. <?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE SIMOFFICERES> <!-Copyright (c) 2005, MSC.Software Corporation. All Rights Reserved. MSC PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. --> <sim_office_resources version="1.0" xml:lang="de" comment="Translation Test" author="[email protected]"> <text id="color" comment="material" author="fred@acme_translation.com">Farbe</text> <text id="help">Hilfe</text> <text id="formatTest"> Dies ist ein Formatierungs-Test: Zeichenkette '%1:%ls', long: %2:%d, double: %3:%1.3g </text> <text id="currencyTest">Der Preis betrgt %1:$</text> <text id="dateTimeTest">Heute ist %1:#A %1:#B - %1:#x %1:#X</text> <module id="mod_test"> <text id="m1">Der Text von mod_test::m1.</text> <text id="m2">Der Text von mod_test::m2.</text> </module> </sim_office_resources>
SCA::Framework::SCAITextTranslationFactory Interface
The SCAITextTranslationFactory is used to create new translation tables and provides access to the common settings object. Member Functions SCAResult SCAResult SCAResult getTextTranslationSettings (out SCAITextTranslationSettings settings) createTextTranslationTable (in SCAWString fileBaseName, out SCAITextTranslationTable newTable) setDefaultFallbackTranslationTable (in SCAITextTranslationTable defaultTable)
SCAResult getTextTranslationSettings (out SCAITextTranslationSettings settings ) Returns the singleton instance of SCAITextTranslationSettings (which is created by the framework on startup).
Parameters:
settings
Returns:
SCAITextTranslationSettings object
Returns SCASuccess on success, else returns an error. SCAResult createTextTranslationTable ( in SCAWString fileBaseName, out SCAITextTranslationTable newTable ) Creates a new translation table instance.
Parameters:
fileBaseName newTable
Base-name of the message file, which is expected to reside inside the components resource directory. Newly created text translation table.
Sets the default Translation Table. All translation tables created after the default table has been set will have their fallback set to the default table. Existing tables are not affected.
Parameters:
defaultTable
Returns:
SCA::Framework::SCAITextTranslationSettings Interface
The SCAITextTranslationSettings has methods to set and get the current locale. Member Functions SCAResult SCAResult SCAResult getLocale (in LocaleCategory category, out SCAString locale) getNativeLocale (in LocaleCategory category, out SCAString nativeLocaleName) setLocale (in LocaleCategory category, in SCAString locale)
SCAResult getLocale (in LocaleCategory category, out SCAString locale ) Inquires the currently used locale string.
Parameters:
category
Returns:
Returns SCASuccess on success, else returns an error. SCAResult getNativeLocale (in LocaleCategory category, out SCAString nativeLocaleName ) This method is used to inquire the locale name in a format that can be used with native locale functions like setlocale or std::locale. For example the locale names are different on Windows and UNIX.
Parameters:
category nativeLocaleName
Returns:
Returns SCASuccess on success, else returns an error. SCAResult setLocale (in LocaleCategory category, in SCAString locale ) Sets the locale string for the given category.
Parameters:
category locale
Returns:
The LocaleCategory is an enum defined below. enum LocaleCategory { LC_All, // All categories. When inquiring, it returns the locale last // used to set LC_All. LC_Messages,// Text translation. Determines the message file to load. LC_Collate, // String handling. LC_CharType,// Character handling. LC_Numeric, // Formatting of numbers. LC_Monetary,// Formatting of monetary values. LC_Time // Formatting of time and date. };
Locale String
The locale string contains the locale value, which is a 2-character language code (as defined by ISO 6391), optionally followed by an underscore and a 2-character country identifier (as defined by ISO 3166). For example "en" for English, "en_GB" for British, "fr" for French, "fr_CA" for Canadian French etc. This format is also used in XML documents as a value for the xml:lang attribute (as described in http://www.faqs.org/rfcs/rfc3066.html )
Mixed Locales
The text translation service supports mixed locales by allowing the assignment of different locales to different categories. The mixed locales can be set either by making multiple calls to the 'setLocale' method or by making a single call using LC_ALL category and a mixed locale strring. The mixed locale string has the following format: "CATEGORY1=locale1; CATEGORY2=locale2; locale3;locale4..." The optional category is in uppercase and the separation character is semicolon ";" For example the following call to setLocale will select German messages with US-English numeric format and Japanese for all other categories, SCAITextTranslationSettings spSettings; spFactory->getTextTranslationSettings( spSettings ); spSettings->setLocale(LC_All, "LC_MESSAGES=de;LC_NUMERIC=en_US;ja");
SCA::Framework::SCAITextTranslationTable Interface
The SCAITextTranslationTable makes the contents of a Message File available to the client applications. The text translation table allows a simple one-to-one mapping from a text ID to the respective text. There is separate message file for each locale. Changing the locale requires re-loading of the message file. When an exact match is not possible, (i.e. locale is set to "fr-CA", but translations exist only for the "fr" locale), then only the first two characters (which always indicate the language) are compared. If even then no match can be found, the default will be English ("en").
Member Functions SCAResult SCAResult SCAResult SCAResult SCAResult SCAResult getText (in SCAString textID, out SCA::SCAWString text) getTextByIntId (in SCAInt32 textID, out SCA::SCAWString text) getModuleText (in SCAString moduleName, in SCAString itemName, in GuiMessageKind kind, out SCA::SCAWString text) getFormattedText (in SCAString textID, in SCAAnySequence args, out SCAWString text) getFormattedTextByIntId (in SCAInt32 textID, in SCAAnySequence args, out SCAWString text) getFormattedModuleText (in SCAString moduleName, in SCAString itemName, in GuiMessageKind kind, in SCAAnySequence args, out SCAWString text) getModuleTextByComment (in SCAString moduleName, in SCAString commentName, in SCAString itemName, in GuiMessageKind kind, out SCA::SCAWString text) getFormattedModuleTextByComment (in SCAString moduleName, in SCAString commentName, in SCAString itemName, in GuiMessageKind kind, in SCAAnySequence args, out SCAWString text) setFallbackTable (in SCAITextTranslationTable table) setFixedLocale (in SCAString locale)
SCAResult
SCAResult
SCAResult SCAResult
SCAResult getText (in SCAString textID, out SCA::SCAWString text ) Gets the simple unformatted text, which does not require any arguments.
Parameters:
textID text
Returns:
String that identifies the message. Contains the simple text on return.
Returns SCASuccess on success, else returns an error. SCAResult getTextByIntId (in SCAInt32 textID, out SCA::SCAWString text ) Same as getText(), but instead of string the input is an integer textID.
Parameters:
textID text
Returns:
Integer value that identifies which message should be looked up. Contains the simple text on return.
Returns SCASuccess on success, else returns an error. SCAResult getModuleText ( in SCAString in SCAString in GuiMessageKind moduleName, itemName, kind,
out SCA::SCAWString text ) Gets simple GUI text from <dialog> or <module> sections of the message file.
Parameters:
Module name ( dialog name). Item name (widget name). Sub-item type (GMK_Text, GMK_Help). Contains the simple text on return.
Returns SCASuccess on success, else returns an error. SCAResult getFormattedText (in SCAString textID,
Gets the formatted text. The section "Text Formatting" discusses the formatting in detail.
Parameters:
String that identifies the message. List of message arguments used for formatting. Could be NULLSP if no arguments are provided. Contains the translated and formatted text on return.
Returns SCASuccess on success, else returns an error. SCAResult getFormattedTextByIntId (in SCAInt32 textID,
in SCAAnySequence args, out SCAWString text ) Same as getFormattedText(), but instead of string the input is an integer textID. See also: getFormattedText
Parameters:
Integer value that identifies which message should be looked up. List of message arguments used for formatting. Could be NULLSP if no arguments are provided. Contains the translated and formatted text on return.
Returns SCASuccess on success, else returns an error. SCAResult getFormattedModuleText (in SCAString moduleName, in SCAString itemName, in GuiMessageKind kind, in SCAAnySequence args, out SCAWString text ) Gets the formatted GUI text from <dialog> or <module> sections of the message file. The section "Text Formatting" discusses the formatting in detail.
Parameters
Module name ( dialog name). Item name (widget name). Sub-item type (GMK_Text, GMK_Help). List of message arguments used for formatting. Could be NULLSP if no arguments are provided. Contains the translated and formatted text on return.
Returns SCASuccess on success, else returns an error. SCAResult getModuleTextByComment (in SCAString moduleName, in SCAString commentName,
in SCAString itemName, in GuiMessageKind kind, out SCA::SCAWString text ) Gets simple GUI text from <dialog> or <module> sections of the message file. Uses the combination of comment and id as the key. The key is of the format (comment_id)
Parameters:
Module name ( dialog name). String that identifies the widget. Item name (widget name). Sub-item type (GMK_Text, GMK_Help). Contains the translated and formatted text on return.
Returns SCASuccess on success, else returns an error. SCAResult getFormattedModuleTextByComment (in SCAString moduleName, in SCAString commentName, in SCAString itemName, in GuiMessageKind kind, in SCAAnySequence args, out SCAWString text ) Gets formatted GUI text from <dialog> or <module> sections of the message file. Uses the combination of comment and id as the key. The key is of the format (comment_id). The section "Text Formatting" discusses the formatting in detail.
Parameters:
Module name ( dialog name). String that identifies the widget. Name of the widget for which contains the text that is searched. Sub-item type (GMK_Text, GMK_Help). List of message arguments used for formatting. Could be NULLSP if no arguments are provided. Contains the translated and formatted text on return.
Returns:
Returns SCASuccess on success, else returns an error. SCAResult setFallbackTable ( in SCAITextTranslationTable table ) This method sets the fallback translation table. The fallback table is searched if a text ID is not found in this table. When the table is created by the SCAITextTranslationFactory, the fall back table is set to the default translation table.
Parameters:
Returns SCASuccess on success, else returns an error. SCAResult setFixedLocale (in SCAString locale ) Sets a fixed locale for the translation table which overrides the locale set in the SCAITextTranslationSettings instance. Passing an empty string resets the override and re-attaches the locale to the global SCAITextTranslationSettings instance.
Parameters:
Returns SCASuccess on success, else returns an error. Using the service The following C++ code snippets demonstrate how the text translation service should be used. The intention is to demonstrate the usage of methods. It uses the sample translation tables presented earlier in this chapter. The real code should contain proper exception and SCAResult handling.
SCA::SCAResult result; // Get an instance of the SCAITextTranslationFactory SCA::Framework::SCAITextTranslationFactory spFactory; spFactory = getService("SCA.Framework.TextTranslation",""); if (spFactory == SCA::NULLSP){ std::cerr << "ERROR:Unable to load the TextTranslation service" << std::endl; return SCA::SCAError; } // Set the Locale to German "de" SCA::Framework::SCAITextTranslationSettings spSettings; result = spFactory->getTextTranslationSettings(spSettings); if (!result.isOk()){ std::cout << "Could not get SCAITextTranslationSettings" << std::endl; return SCA::SCAError; } result = spSettings->setLocale(SCA::Framework::LC_All,"de"); // Create a text translation table by loading the messages from the file // $SCA_RESOURCE_DIR/TranslationSample_de.xml SCA::Framework::SCAITextTranslationTable spTable; result = spFactory>createTextTranslationTable(L"TranslationSample",spTable); // Get the simple text without any place holders SCA::SCAWString text; result = spTable->getText("help",text); std::printf("%ls\n",text.c_str()); // Get the text formatted using the supplied arguments for the place holders SCAAnySequence args; args.push_back(SCA::SCAAny(SCA::SCAString("TestString"))); args.push_back(SCA::SCAAny(SCA::SCAInt32(1234))); args.push_back(SCA::SCAAny(SCA::SCAReal32(123.456))); result = spTable->getFormattedText("formatTest",args,text); std::printf("%ls\n",text.c_str()); // Get Module text result = spTable->getModuleText("mod_test","m1", SCA::Framework::GMK_Text,text); std::printf("%ls\n",text.c_str()); result = spTable->getModuleText("DeformPlotMetaDataFrame", "Qt Linguist context", SCA::Framework::GMK_Text,text); std::printf("%ls\n",text.c_str());
Language Support Files The text translation service uses several xml files to store language and country settings. These files must be located in the applications resource directory. At startup LocaleLanguagesMap.xml and LocaleCountriesMap.xml files are parsed to load the settings. Since these settings are global they are read only once and are available to all instances of the TextTranslationSettings object. The country map is only required for Windows.
LocaleLanguagesMap.xml.
<?xml version="1.0" encoding="utf-8"?> <!-- for list of language/country strings, see http://msdn2.microsoft.com/en- us/library/cdax410z(VS.71).aspx --> <LanguagesMap version="1.0"> <language code="zh" defaultCountry="cn">chinese;chinesesimplified;chs;chinese-traditional;cht</language> <language code="cs" defaultCountry="cz">csy;czech</language> <language code="da" defaultCountry="dk">dan;danish</language> <language code="nl" defaultCountry="nl">dutch;nld;belgian;dutchbelgian;nlb</language> <language code="en" defaultCountry="us">english;australian;ena;englishaus;canadian;enc;english-can;english-nz;enz;eng;englishuk;uk;american;american english;american-english;englishamerican;english-us;english-usa;enu;us;usa</language> <language code="fi" defaultCountry="fi">fin;finnish</language> <language code="fr" defaultCountry="fr">fra;french;frb;frenchbelgian;frc;french-canadian;french-swiss;frs</language> <language code="de" defaultCountry="de">deu;german;dea;germanaustrian;des;german-swiss;swiss</language> <language code="el" defaultCountry="gr">ell;greek</language> <language code="hu" defaultCountry="hu">hun;hungarian</language> <language code="is" defaultCountry="is">icelandic;isl</language> <language code="it" defaultCountry="it">ita;italian;italianswiss;its</language> <language code="ja" defaultCountry="jp">japanese;jpn</language> <language code="ko" defaultCountry="kr">kor;korean</language> <language code="no" defaultCountry="no">norwegian</language> <language code="nb" defaultCountry="no">nor;norwegianbokmal</language> <language code="nn" defaultCountry="no">non;norwegiannynorsk</language> <language code="pl" defaultCountry="pl">plk;polish</language> <language code="pt" defaultCountry="pt">portuguese;ptg;portuguese-brazil;ptb</language> <language code="ru" defaultCountry="ru">rus;russian</language> <language code="sk" defaultCountry="sk">sky;slovak</language> <language code="es" defaultCountry="es">esp;spanish;esm;spanishmexican;esn;spanish-modern</language> <language code="sv" defaultCountry="se">sve;swedish</language> <language code="tr" defaultCountry="tr">trk;turkish</language> </LanguagesMap>
LocaleCountriesMap.xml.
<?xml version="1.0" encoding="utf-8"?> <!-- for list of language/country strings, see http://msdn2.microsoft.com/en-us/library/cdax410z(VS.71).aspx --> <CountriesMap version="1.0"> <country code="au">aus;australia</country> <country code="at">aut;austria</country> <country code="be">bel;belgium</country> <country code="br">bra;brazil</country> <country code="ca">can;canada</country> <country code="cn">china;chn;pr china;pr-china</country> <country code="cz">cze;czech</country> <country code="dk">dnk;denmark</country> <country code="fi">fin;finland</country> <country code="fr">fra;france</country> <country code="de">deu;germany</country> <country code="gr">grc;greece</country> <country code="hk">hkg;hong kong;hong-kong</country> <country code="hu">hun;hungary</country> <country code="is">iceland;isl</country> <country code="ie">irl;ireland</country> <country code="it">ita;italy</country> <country code="jp">jpn;japan</country> <country code="kr">kor;korea</country> <country code="mx">mex;mexico</country> <country code="nl">nld;holland;netherlands</country> <country code="nz">nzl;new zealand;new-zealand;nz</country> <country code="no">nor;norway</country> <country code="pl">pol;poland</country> <country code="pt">prt;portugal</country> <country code="ru">rus;russia</country> <country code="sg">sgp;singapore</country> <country code="sk">svk;slovak</country> <country code="es">esp;spain</country> <country code="se">swe;sweden</country> <country code="ch">che;switzerland</country> <country code="tw">twn;taiwan</country> <country code="tr">Turkey tur;turkey</country> <country code="gb">gbr;britain;england;great britain;uk;united kingdom;united-kingdom</country> <country code="us">usa;america;united states;unitedstates;us</country> </CountriesMap>
10
Error Processing
Introduction 292 294
Using SCA Exceptions for Error Handling Using SCAResult for Error Handling MessageDispatcher Service 316 305
Introduction
Error handling is a crucial part of any programming project. SCA supports the two most common forms for handling errors.
The interface methods throw exceptions. The interface methods return error codes.
Both methods have their own advantages over the other and users should choose the one that is most appropriate to their application. Exceptions tend to be easier to implement than error codes. Consider the example where routine A calls B, B calls C and C calls D and then D produces an error code. In this case C has to check the code, return its own code, which would be checked by B which would return its own code which would finally be checked by A. Whereas with exceptions, if D throws the exception, then a simple try/catch block in A can catch it and no special code is required in any of the intermediate routines in the call stack. Furthermore, many programmers get lazy and do not always do the appropriate checking of error codes. With error codes this can be disastrous because the error is lost and the caller will continue as if nothing happened. But with exceptions, the errors will always be triggered. If the programmer does not include the appropriate try/catch block for the exception, it will still be propagated up to the next routine in the call stack until someone eventually catches it. On the other hand, error codes tend to be more efficient then exceptions. To properly handle exceptions, the compilers need to generate special support code in every routine to correctly process them. This tends to make the generated code larger and adds overhead. Also, error codes provide for more sophisticated processing of messages. This includes more flexible formatting of error messages including parameter substitution and the ability to internationalize the messages to any language supported by the application. Exceptions and error codes are two different general error handling approach. The mixing of the two can lead to confusion and should be avoided. The following conventions are recommended for best practice.
If an interface method defines a raises clause for any explicit exceptions, then the method should
not have a raises clause and it should not throw any exceptions.
Throwing user-defined exceptions is preferred over the throwing of any of the SCA provided
base exceptions.
Normally the same error handling method should be chosen for all interfaces implemented by a
given service. Even if the SCAResult form of error handling is chosen, it is important to realize that calls to SCA interface methods may still throw SCASystemException exceptions. This may occur if the call crosses languages boundaries or accesses remote components. In this case the SCA language bridges may need to report an error when trying to marshal the call. This is generally not a problem because these types of errors are usually catastrophic in nature and there is no recovery logic available. As a result the individual routines do not need to catch these SCASystemException exceptions. Instead they can be caught in a global try/catch block at the top of the applications call stack so the application can terminate cleanly.
Both SCA exceptions and the SCAResult forms of error handling described in this chapter are available in all of the languages supported by the SCA framework. The exact syntax of using them vary from language to language but their general functionality is similar. As a result this chapter will only show examples using the C++ language. You should consult the IDL Mapping chapters of this manual for the complete details of using these features in each of the supported languages.
IDL #ifndef SDK_ERRORHANDLING_EXCEPTIONS_FILEREADER_IDL_INCLUDED #define SDK_ERRORHANDLING_EXCEPTIONS_FILEREADER_IDL_INCLUDED #include "SCA/Service.idl" module SDK { module ErrorHandling { module Exceptions { exception ReaderException : SCA::SCAUserException { SCA::SCAString modelName; }; interface SCAIReader : SCA::SCAIService { void readModel( in SCA::SCAString name ) raises(ReaderException); }; }; }; }; #endif
contain other information required by the SCA Framework but these should generally not be used for other purposes. The SCASystemException Exception SCASystemException is used to handle internal SCA framework errors that cannot be conveniently reported in any other manner. Even if a method does not have a raises clause in its definition, the SCASystemException may always be thrown. The IDL definition of the SCASystemException is as follows. IDL module SCA { exception SCASystemException : SCAException { SCAInt32 id; }; }; This exception contains an integer error value that is used to contain one of the following SCA system errors. SCA System Error ID 2000000010 2000000020 2000000030 2000000040 2000000050 2000000060 2000000070 2000000080 2000000090 2000000100 2000000110 2000000120 2000000130 2000000140 2000000150 2000000160 2000000170 2000000180 SCA System Error Description Unknown error Invalid interface Invalid method Invalid Parameter(s) Undefined type code Communication failure Data conversion error Memory error Implementation not available Invalid exception thrown Error reading type codes Error in CORBA Rpc bridge Error in C++ bridge C++ interface cast failed XML TypeCode processing error Error in python bridge Marshalling error SCABinary processing error
SCA System Error ID 2000000190 2000000200 2000000210 2000000220 2000000230 2000000240 2000000250 2000000260 2000000270 2000000280 2000000290 2000000300 2000000310
SCA System Error Description Internal Error Error in Java bridge SCAAny marshalling error SCA Kernel initialization error Invalid global service provider SCA Framework configuration error Error in .NET bridge SCADynAny error Corruption in SCASequence memory getSCAService error Error terminating the SCA Kernel Error initializing embedded component Error terminating embedded component
Most of these errors are generated during the marshalling of data through SCA language bridges. Many are unexpected problems which should never occur but there are others that can easily occur because of the nature of some of the SCA language mappings. For example the Python language only supports 32 and 64 bit integers. As a result types like the SCAInt16 are mapped to a 32 bit integer. When an interface method is called that has a SCAInt16 argument, the value in the 32 bit Python integer is checked to make sure it will fit in the required SCA type. If it is too large then a SCASystemException will be generated with one of the above codes. It is the responsibility of the Python code to make sure that this condition never happens. For the complete definitions of these errors see the SCA/SystemError.h header file delivered with the SCA Framework. The SCAUserException Exception The SCAUserException is the (direct or indirect) base for all user defined exceptions. It has the following IDL definition. IDL module SCA { exception SCAUserException: SCAException { }; }; This exception does not contain any data member. Its purpose is to only define the base class for user defined exceptions.
Exception API
The syntax for using SCA exceptions and the API they provide is different for each supported language. The following example code shows how an instance of the ReaderException can be created and thrown in C++. C++ ReaderException except; except.modelName = name; except.setText("The requested model \""+name+ "\" could not be located."); except.throwit(); Notice that the actual exception is thrown using the throwit method it provides and not using the normal C++ throw statement. This is an important rule which is discussed in detail in the IDL to C++ Mapping chapter of this manual. The exception can be caught and processed like this. try { spReader->readModel("Test.Model"); } catch (SCAException& e) { cout << e.what() << endl; } For language specific exception APIs, please refer to the respective Language Mapping chapters of this manual.
classes. In this case the SCA exception that is inherited from is the one that determines if it will pass through the language bridges. When these exceptions are thrown, only the data defined in SCA exception definitions will be passed. Any data in the non SCA exception class will be lost.
The SCASystemException always pass through the language bridges.
All other exceptions are blocked by the language bridge and a SCASystemException is thrown
instead. In this case any data contained in the original exception will be lost. For an example of these rules, consider the following exception hierarchy. IDL exception exception exception exception exception SCAException { }; SCAUserException : SCAException { }; TestExcept1 : SCAUserException { }; TestExcept2 : TestExcept1 { }; TestExceptX: SCAUserException { };
The following table shows which combination of exceptions specified on the raises clause in the IDL and the actual exception thrown are allowed to pass through the SCA language bridges. The exceptions that are not allowed to pass will be converted to a SCASystemException. Raises clause SCAUserException SCAUserException SCAUserException SCAUserException TestException1 TestException1 TestException1 TestException1 TestException2 TestException2 TestException2 TestException2 Thrown TestException1 TestException2 TextExceptionX None SCA Exception TestException1 TestException2 TextExceptionX None SCA Exception TestException1 TestException2 TextExceptionX None SCA Exception Does bridge pass Yes Yes Yes No Yes Yes No No No Yes No No
Intra Language Propogation Rules Normally when making interface calls between a caller and callee written in the same language, the SCA framework is not involved. As a result the rules for using exceptions in this case are the normal rules that apply to the language being used. Exception Catching Rules The rules covering which exceptions will be caught by which catch statement is completely a function of the language you are catching the exception in. It does not matter which language the exception was thrown in. This is because when an exception is marshaled through a SCA language bridge it will be rethrown by the bridge in the language of the caller. The exception that is rethrown is either the original exception thrown if it passes the propagation rules discussed above or else a SCASystemException. In
either case it is now a native exception in the language of the caller and the rules for this language are used. Most languages have a similar set of rules governing which exceptions will be caught by which catch statement. Normally, a catch statement, or whatever the equivalent statement is in the language you are using, will catch an exception that is the same as the one thrown or if it is a base class of the one thrown. Using the same exception hierarchy that was used above the following table shows how these rules typically work. Catch Statement SCAUserException SCAUserException SCAUserException TestException1 TestException1 TestException1 TestException2 TestException2 TestException2 TestExceptionX TestExceptionX TestExceptionX Exception Thrown TestException1 TestException2 TextExceptionX TestException1 TestException2 TextExceptionX TestException1 TestException2 TextExceptionX TestException1 TestException2 TextExceptionX Will it be Caught Yes Yes Yes Yes Yes No No Yes No No No Yes
SCASystemException.
When you throw an SCA user-defined exception, always define the basic text field that is
provided by the SCAException. This way there will be some information about the exception regardless of the exception class that is actually caught.
In your code, only add catch statements for exceptions that you expect to catch and have some
SCA exceptions inherit from this one, this catch block will catch any SCA exception that was not explicitly caught by any other catch block. The text field of this exception can then be used to provide some useful information.
namespace SDK { namespace ErrorHandling { namespace Exceptions { class FileReader : public FileReaderBase { public: // Constructor and Destructor FileReader(SCAIFileReaderFactoryAccess* factoryAccess); virtual ~FileReader(); // Methods for interface SDK.ErrorHandling.Exceptions.SCAIReader virtual SCA::SCAVoid readModel(const SCA::SCAString name);
};
} } } #endif
FileReader.cpp #include "FileReader.h" namespace SDK { namespace ErrorHandling { namespace Exceptions { // Constructor FileReader::FileReader(SCAIFileReaderFactoryAccess* factoryAccess) : FileReaderBase(factoryAccess) { } // Destructor FileReader::~FileReader() { } SCA::SCAVoid FileReader::readModel(const SCA::SCAString name) { bool error; // Locate the model . . . . // Return error if model could not be located if ( error ) { ReaderException except; except.modelName = name; except.setText("The requested model \""+name+ "\" could not be located."); except.throwit(); } // Process the model . . . .
// Return error processing the model if ( error ) { ReaderException except; except.modelName = name; except.setText("An error was encountered reading model \""+ name+"\"."); except.throwit(); } // Return return;
} } } The example client application that uses this version of the FileReader service is shown next.
Client.cpp #include <iostream> using namespace std; #include <SCA/SCAKernel.h> #include "SCA/Framework/SCAIMessageDispatcher.h" #include <SDK/ErrorHandling/Exceptions/SCAIReader.h> using namespace SCA; using namespace SDK::ErrorHandling::Exceptions; int main() { try { cout << "Test using exceptions for error processing" << endl; // Initialize the SCA Kernel initializeSCAKernel(1); // Load the FileReader service SCAString svcName = "SDK.ErrorHandling.SCAResult.FileReader"; SCAIReader spReader = getSCAService(svcName); // Read the model try { spReader->readModel("Test.Model"); } catch (SCAException& e) { cout << e.what() << endl; } // Clean up spReader = NULLSP;
// Terminate the SCA Kernel terminateSCAKernel(); } catch (SCAException& e) { cout << e.what() << endl; } return 0;
There may be a unique set of error codes for each method or a single set for the entire service or component. You should consult the documentation of the service you are using for definitions of the error codes that it may return.
Message table ID The message table ID is a unique value that is assigned by the
MessageDispatcher service when a message table is registered. In order to use a message in a message table, the message table must be first registered with the MessageDispatcher service. This registration is the responsibility of the service that will be returning SCAResult values that reference the message table.
Message number Message number in the message table referenced by the message table ID. Message parameters Optional values for parameters that will be used to format the requested
messages. The use of the message information in the SCAResult is optional. When used, the messages in SCAResult values may also include parameter values that will be substituted during the formatting of the message. The following types of parameters are supported in a SCAResult. SCA Type SCAInt8 SCAUInt8 SCAInt16 SCAUInt16 SCAInt32 SCAUInt32 SCAInt64 SCAUInt64 SCAReal32 SCAReal64 SCAChar SCAWChar SCAString SCAWString SCABool
You should consult the appropriate IDL mapping chapter for the language you are using for the exact syntax for adding parameter values to a SCAResult instance.
with the framework and saves the message table ID that is returned.
The client calls the readModel interface method. The readModel method tries to read the model but encounters an error. It formats a SCAResult
value and returns it to the caller. The SCAResult value includes an error code value, the message table ID, the message number and any optional parameter values.
The client checks the error code in the SCAResult value to determine if there is an error. The client uses the MessageDispatcher to format the message contained in the SCAResult
value. The formatted message is either dispatched to any registered message listeners or returned to the client.
<!-Copyright (c) 2009, MSC.Software Corporation. All Rights Reserved. MSC PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. --> <sim_office_resources version="1.0" xml:lang="en" comment="Message Table for FileReader Service"> <text id="101" comment="MODEL_LOCATE_MSG" > Das angeforderte Modell "%1:%s" konnte nicht gefunden werden. </text> <text id="102" comment="MODEL_READ_MSG" > Beim Einlesen des Modells "%1:%s:" trat ein Fehler auf. </text> </sim_office_resources> If you look at the IDL for FileReader example earlier in this chapter, you will see a set of IDL constants were also defined for each message. These can be used when creating SCAResult values instead of using hardcoded values. The use of these constants is a recommended practice because it provides a consistent location to document the messages and makes maintenance easier. IDL // Message ids const SCAInt32 MODEL_LOCATE_MSG = 101; const SCAInt32 MODEL_READ_MSG = 102; In order to use a message table, the table must be first registered with the MessageDispatcher service. Normally a service will do this registration in its constructor and save the message table IDL for later use. When registering the message table you used the portion of the filename for the table which does not include the language information. For our example, the name used for the registration would be FileReaderMsgTable. The following code shows how the the message table is registered. C++ #include "SCA/Framework/SCAIMsgTableManager.h" // Register our message table SCA::Framework::SCAIMsgTableManager spManager; spManager = getService("SCA.Framework.MessageDispatcher"); spManager->addTable(L"FileReaderMsgTable",m_msgTableID); In this example code the message table ID returned is m_msgTableID which should be saved for later use when it is necessary to format a SCAResult value.
The SCAResult may contain both an error code value and the information to format a message.
There may be times when specific error code values are not required. Instead it is only necessary to indicate if the method call succeeded or failed. In this case you can use one of the two predefined SCAResult values provided by the SCA framework.
SCA::SCASuccess SCA::SCAError
If the readModel method in our example only needed to return a generic error indication it could use the predefined SCAError value as follows. C++ return SCAError; Normally, this is not the ideal solution because it does not provide any details to the caller about what the error actual was. As a result it is usually a better solution if you at least return an error code value that represents the error that occurred. If you look at the example IDL again, you will notice that a set of constant values were also defined for the error codes that can be returned. Depending on how you have set up your error code values and message IDs, you may choose to use a single set of constant values for both. // Error codes const SCAInt32 MODEL_LOCATE_ERR = 1; const SCAInt32 MODEL_READ_ERR = 2; The use of these constants is a recommended practice because it provides a consistent place where the possible error code values can be documented for the users of the service. Additionally, the use of IDL defined constants reduces maintenance of the code. If you need to change error values they only need to be changed in the IDL file and not in every implementation and client file that uses them. The readModel interface routine could now create a SCAResult instance containing only an error code if that was appropriate. In this example we are using the IDL defined constants to indicate the error code value. C++ SCAResult rstat = SCAResult(MODEL_READ_ERR); return rstat; Normally, it is more desirable to also include message information that your callers can use to format and display a message which described the details of the error. The following code adds a message to the SCAResult value. The message requires one parameter which is the name of the model that cannot be read. C++ SCAResult rstat;
rstat = SCAResult(MODEL_READ_ERR,m_msgTableID,MODEL_READ_MSG); rstat.addParam(name); return rstat; If the readModel method did not encounter any errors, then it should return the predefined SCASuccess value to indicate that the operation was successful. C++ return SCASuccess; The exact syntax for using the SCAResult data type is a function of the language you are using. Although the basic concepts are the same in all of the languages the exact syntaxes are a bit different. For complete details you should consult the IDL mapping chapter in this manual for your language.
#endif The new versions of the implementation files for the service are as follows. Once again only the error processing logic of the readModel method has been included. FileReader.h #ifndef SDK_ERRORHANDLING_SCARESULT_FILEREADER_H_INCLUDED #define SDK_ERRORHANDLING_SCARESULT_FILEREADER_H_INCLUDED #include "FileReaderBase.h" namespace SDK { namespace ErrorHandling { namespace SCAResult { class FileReader : public FileReaderBase { public: // Constructor and Destructor FileReader(SCAIFileReaderFactoryAccess* factoryAccess); virtual ~FileReader(); // Methods for interface SDK.ErrorHandling.SCAResult.SCAIReader virtual SCA::SCAResult readModel(const SCA::SCAString name); private: // Message table ID assigned by the MessageDispatcher SCA::SCAInt32 m_msgTableID; }; } } } #endif FileReader.cpp #include "FileReader.h" #include "SCA/Framework/SCAIMsgTableManager.h" #include "SDK/ErrorHandling/SCAResult/FileReaderTypes.h" namespace SDK { namespace ErrorHandling { namespace SCAResult { // Constructor FileReader::FileReader(SCAIFileReaderFactoryAccess* factoryAccess) : FileReaderBase(factoryAccess) { // Register our message table SCA::Framework::SCAIMsgTableManager spManager; spManager = getService("SCA.Framework.MessageDispatcher"); spManager->addTable(L"FileReaderMsgTable",m_msgTableID); } // Destructor FileReader::~FileReader()
{ } SCA::SCAResult FileReader::readModel(const SCA::SCAString name) { SCA::SCAResult rstat; bool error; // Locate the model . . . . // Return error if model could not be located if ( error ) { rstat = SCA::SCAResult(MODEL_LOCATE_ERR, m_msgTableID, MODEL_LOCATE_MSG); rstat.addParam(name); return rstat; } // Process the model . . . . // Return error processing the model if ( error ) { rstat = SCA::SCAResult(MODEL_READ_ERR, m_msgTableID, MODEL_READ_MSG); rstat.addParam(name); return rstat; } // Successful return return SCA::SCASuccess;
} } }
if ( !rstat.isOk() ) { cout << "Error encountered" << endl; return 1; } But the disadvantage of this approach is there is no indication of what the error may have been. To add some additional information, the interface method may return different error code values which may be checked. C++ if ( rstat == MODEL_LOCATE_ERR ) { cout << "Error encountered locating the model" << endl; return 1; } else if ( rstat == MODEL_READ_ERR ) { cout << "Error encountered reading the model" << endl; return 1; } But ideally the service will also include message information in the SCAResult value that can be used to format a message. In this example we use the MessageDispatcher to format the message and print it. The second blank argument to the getMessage call is the language the message should be formatted in. In this case the blank value means the current default language setting. C++ if ( rstat ) { SCA::Framework::SCAIMessageDispatcher spDispatcher; spDispatcher = getSCAService("SCA.Framework.MessageDispatcher"); SCAWString wstr; spDispatcher->getMessage(rstat,"",wstr); wcout << wstr << endl; return 1; } It is also possible to dispatch the message to any registered message listeners. See the section on the MessageDispatcher later in this chapter for more details on message listeners. In this example the dispatchMessage method is used to dispatch the message. The second argument of zero in the call is severity level you can set. This value is passed to the message listeners and they can use it as required. C++ if ( rstat ) { SCA::Framework::SCAIMessageDispatcher spDispatcher; spDispatcher = getSCAService("SCA.Framework.MessageDispatcher"); spDispatcher->dispatchMessage(rstat,0); return 1; } The following is simple C++ client application that uses the FileReader service and does the appropriate handling of any errors. In this example we are using the getMessage method to format the error message and printing it out.
Client.cpp #include <iostream> using namespace std; #include <SCA/SCAKernel.h> #include "SCA/Framework/SCAIMessageDispatcher.h" #include <SDK/ErrorHandling/SCAResult/SCAIReader.h> #include <SDK/ErrorHandling/SCAResult/FileReaderTypes.h> using namespace SCA; using namespace SDK::ErrorHandling::SCAResult; int main() { try { cout << "Test using MessageDispatcher to format errors" << endl; // Initialize the SCA Kernel initializeSCAKernel(1); // Load the FileReader service SCAString svcName = "SDK.ErrorHandling.SCAResult.FileReader"); SCAIReader spReader = getSCAService(svcName); // Read the model SCAResult rstat = spReader->readModel("Test.Model"); if ( rstat ) { SCA::Framework::SCAIMessageDispatcher spDispatcher; spDispatcher = getSCAService("SCA.Framework.MessageDispatcher"); SCAWString wstr; spDispatcher->getMessage(rstat,"",wstr); wcout << wstr << endl; } // Clean up spReader = NULLSP; // Terminate the SCA Kernel terminateSCAKernel(); } catch (SCAException& e) { cout << e.what() << endl; } return 0;
If this application was run, and the readModel method returned an error indicating it could not locate the model, the following is what the output of the formatted message would look like if the default language was English. The requested model "Test.Model" could not be located. If the default language was German, we would get the following. Das angeforderte Modell "Test.Model" konnte nicht gefunden werden.
It is also possible to use a message listener and then used the dispatchMessage method to dispatch the error. This technique will be shown in the next section.
MessageDispatcher Service
The MessageDispatcher is a messaging service that formats the message information stored in SCAResult values and optionally dispatches them. The MessageDispatcher service provides three functions each of which is handled by a different interface.
SCAIMsgTableManager - Acts as a front-end to the TextTranslation service discussed in the
Messages and Internationalization chapter of this manual and manages instances of client registered message tables and their locales
SCAIMsgListenerManager - Manages instances of client registered message listeners SCAIMessageDispatcher - Formats and optionally dispatches messages
The MessageDispatcher service uses message listeners to process the dispatched messages. The main reason for message listeners is to allow an application to compartmentalize the handling of messages into a single piece of code instead of spreading it around the application. A message listener is a SCA object which implements the SCAIMessageListener interface. This code must be provided by the clients of the MessageDispatcher and instances of the SCAIMessageListener interface must be registered by the client if the dispatching of messages is used. Each message listener can specify its own locale that it wants any message formatted in or it may choose to use the current system default locale. An application may utilize as many message listeners as it needs. Normally a different listener would be used for each different type of processing that is required for dispatched messages. For example there may be one listener that handles the logging of messages to a log file and a different listener that informs the user of the errors by displaying them in a message box. The MessageDispatcher is tightly coupled with the SCAResult data type. The SCAResult type provides the data structure where the information required for formatting the message is stored. This include the message number, message table ID and any optional message parameters. The MessageDispatcher service ignores the error code data value in the SCAResult instant. It is also possible to use the MessageDispatcher service to format general messages and not just error messages. To do this you just create a SCAResult value with the appropriate message information. In this case the error ID information has no meaning. The same interface methods in the SCAIMessageDispatcher interface are then use to format and optionally dispatch the message. The following sections describe each of the three interfaces and shows examples of how they are used.
SCA::Framework::SCAIMsgTableManager Interface
The SCAIMsgTableManager interface allows a client service to register message tables with the MessageDispatcher.
Inherits SCA::SCAIService. Member Functions SCAResult addTable (in SCAWString fileBaseName, out SCAInt32 tableId)
SCAResult addTable
fileBaseName, tableId
Method to add a message table to the Messaging service so it can be used in a SCAResult value. If the table has already been added, then the table ID that was previously assigned for it will be returned. Parameters:
fileBaseName tableId
Message table root name which does not include locale information. Table ID that can be used to create a SCAResult value.
SCAResult Status of request which should always be a SCASuccess In order to use a message table, the table must be first registered with the MessageDispatcher service. Normally, a service will do this registration in its constructor and save the message table ID for later use. To register the message table you use the portion of the filename for the table which does not include the language information. In our example the complete name of the message table names were FileReaderMsgTable_en.xml and FileReaderMsgTable_de.xml so the name for addTable call would be FileReaderMsgTable. The following code shows how the message table is registered using this interface. C++ #include "SCA/Framework/SCAIMsgTableManager.h" // Register our message table SCA::Framework::SCAIMsgTableManager spManager; spManager = getService("SCA.Framework.MessageDispatcher"); spManager->addTable(L"FileReaderMsgTable",m_msgTableID); The addTable method returns an integer message table ID, m_msgTableID in this example, which should be saved. It is required when creating SCAResult instances which reference messages defined in this table. Once a table has been registered the table ID will be valid for the duration of the current execution.
It is also important to understand that the addTable method will always return a SCASuccess value. This is true even if there are no message tables available that match the name provided. This is because the registering of a table does not trigger the processing of the actual XML file for the message table. At the time of registration it is not known what language will actually be used to format any of its messages and there may be multiple message tables available. The actual language is not determined until a message is formatted so the processing of the XML file must be delayed until that time. If the message table cannot be found or there are errors reading it at this time, the formatting operation will instead return a message indicating why the desired message could not be formatted. It will also include the message number and table name so you can still determine what the original error was. The following is an example of what one of these messages would look like. Error formatting message 101 in table FileReaderMsgTable XML file for message table FileReaderMsgTable for locale "en_US.1252" or "en" does not exist.
SCA::Framework::SCAIMsgListenerManager Interface
The SCAIMsgListenerManager interface allows a client service to add and remove message listeners. Inherits SCA::SCAIService. Member Functions SCAResult SCAResult addListener (in SCAIMessageListener msgListener, in SCAString locale) removeListener (in SCAIMessageListener msgListener)
SCAResult addListener
( in SCAIMessageListener in SCAString )
msgListener, locale
[in] [in]
msgListener locale
Interface to the listener that is to be added. Locale string for this message listener. All messages dispatched to this listener will be formatted in this locale. The value can be an empty string which means the listener uses the current default locale.
Returns:
SCAResult removeListener
( in SCAIMessageListener
msgListener
[in] Returns:
msgListener
SCAResult Status of request which should always be SCASuccess The SCAIMsgListenerManager allows clients to add and remove message listeners. Each message listener can specify the language that it wants the messages formatted in. It is not required for all registered listeners to use the same locale. The following example shows how message listeners can be added and removed. In this case we will request that the messages received by the listener be formatted in German. C++ // Get instance of message listener SCAIMessageListener spListener = . . .; // Register our message listener using the German locale SCA::Framework::SCAIMsgListenerManager spManager; spManager = getSCAService("SCA.Framework.MessageDispatcher"); spManager->addListener(spListener,"de"); // Application does its thing // Remove our message listener spManager->removeListener(spListener); This example does not show how the message listener was actually implemented. Later in this section a number of examples of this will be provided.
SCA::Framework::SCAIMessageDispatcher Interface
The SCAIMessageDispatcher interface is used to format messages. The formatted messages can either be dispatched to the registered message listeners or returned to the caller.
Inherits SCA::SCAIService. Member Functions SCAResult SCAResult SCAResult setDefaultLocale (in SCAString locale) dispatchMessage (in SCAResult rStat, in SCAInt32 severity) getMessage (in SCAResult rStat, in SCAString locale, out SCAWString outString)
SCAResult dispatchMessage
( in SCAResult in SCAInt32 )
rStat, severity
Method to format a message described by a SCAResult value and dispatch it to all registered listeners. The message may need to be formatted several times if multiple message listeners have been added which requested different locales.
Parameters:
[in] [in]
rStat severity
The SCAResult value to format The severity code for the message. This value is passed directly to the message listeners.
Returns:
SCAResult getMessage
Method to format a message described by a SCAResult value and return it to the caller.
Parameters:
The SCAResult value to format The locale string that the message will be formatted in. The value can be an empty string which means the current default locale is used. Formatted message value
SCAResult setDefaultLocale
( in SCAString
locale )
Method to set the default locale for all message listeners that are added with a blank locale specified. Calls to this routine will affect message listeners that were previously added as well as those that are added in the future.
Parameters:
[in]
Returns:
locale
Locale string
SCAResult Status of request The SCAIMessageDispatcher interface is used by the client to request the formatting of a message. The message can be either returned directly to the caller or can be dispatched to all registered message listeners. The first example shows how the message can be formatted and returned to the caller. In this example the current default locale is used for the message translation. C++ SCAResult rstat = sp->someMethod() if ( rstat ) { SCA::Framework::SCAIMessageDispatcher spDispatcher; spDispatcher = getSCAService("SCA.Framework.MessageDispatcher"); SCAWString wstr; spDispatcher->getMessage(rstat,"",wstr); wcout << wstr << endl; return 1; } It is also possible to dispatch the message to all register message listeners using the dispatchMessage method. The second parameter in this call is the message severity. The severity value is passed directly to the message listeners and not used by the MessageDispatcher itself. Each application can define its own mechanism of interpreting the severity code or it can choose to ignore it altogether. One common
way of handling the severity value is to use an IDL enum definition to contain the valid values. In the FileReader example we have used the following values for the severity of a message. IDL enum SeverityType { SDK_INFORMATION, SDK_WARNING, SDK_ERROR } These values can be used in the call to the dispatchMessage method. C++ SCAResult rstat = sp->someMethod() if ( rstat ) { SCA::Framework::SCAIMessageDispatcher spDispatcher; spDispatcher = getSCAService("SCA.Framework.MessageDispatcher"); spDispatcher->dispatchMessage(rstat,SDK_ERROR); return 1; } The dispatchMessage call may result in the requested message being formatted more than once. This could happen if multiple message listeners are registered and they require different languages. The SCAIMessageDispatcher interface can also be used to change the default locale setting. C++ SCA::Framework::SCAIMessageDispatcher spDispatcher; spDispatcher = getSCAService("SCA.Framework.MessageDispatcher"); spDispatcher->setDefaultLocale("de"); It is important to remember that this default setting will affect all message listeners that are currently registered which requested the default locale as well as any added in the future. Message listeners currently registered that requested a specific locale are not affected.
SCA::Framework::SCAIMessageListener Interface
The SCAIMessageListener interface is used to implement message listeners that can be used with the MessageDispatcher service. Inherits SCA::SCAIService. Member Functions SCAResult doPublishMessage (in SCAWString str, in SCAInt32 severity)
SCAResult doPublishMessage
( in SCAWString in SCAInt32 )
str, severity
Method used by the MessageDispatcher to dispatch messages to the listeners that have been registered with it.
Parameters:
[in] [in]
str severity
Message to be dispatched. The severity level that was provided by the code that requested the message to be dispatched.
Returns:
SCAResult Status of request The SCAIMessageListener interface is used in the implementation of the message listeners that the MessageDispatcher uses to dispatch messages. It must be implemented by the client application that is using the MessageDispatcher service. Once an application has obtained an instance of the SCA object that implements this interface, it must then register it with the MessageDispatcher service. Once a message listener has been added then all messages dispatched through the MessageDispatcher will be sent to the message listener until it is removed. There are a number of different techniques that can be used to implement the SCAIMessageListener interface.
The interface can be implemented in a normal SCA service. The interface can be implemented in a SCA service contained in an embedded component. The interface can be implemented using techniques provided by the individual language
mappings. For example in C++ the SCAServiceObjectImpl templates could be used and in Python the SCAIServiceBase class could be used. Examples of the first and last of these methods are provided later in this section. For more details on using embedded components and the SCAServiceObjectImpl templates see the SCA SDK Advanced Features manual. The SCAIServiceBase class is described in the Python Mapping chapter of this manual. The SCAIMessageListener interface has a single method. doPublishMessage ( in SCAWString str, in SCAInt32 severity ); When implementing the SCAIMessageListener interface, the doPublishMessage method must be implemented. This method will be called by the MessageDispatcher when there is a message to process. The first argument is the formatted text string for the message in the language specified when the listener
was registered. The second input is the severity code that is provided by the call that originally dispatched the message. Each application can define its own mechanism of interpreting the severity code or it can choose to ignore it altogether.
};
int main() {
try { cout << "Test using local listener to format messages" << endl; // Initialize the SCA Kernel initializeSCAKernel(1); // Get instance of message listener Listener* pListener = new Listener(); SCAIMessageListener spListener = (SCAIMessageListener)pListener; // Register our message listener SCAIMsgListenerManager spManager; spManager = getSCAService("SCA.Framework.MessageDispatcher"); spManager->addListener(spListener,""); // Load the FileReader service SCAString svcName = "SDK.ErrorHandling.SCAResult.FileReader"; SCAIReader spReader = getSCAService(svcName); // Read the model SCAResult rstat = spReader->readModel("Test.Model"); if ( rstat ) { SCAIMessageDispatcher spDispatcher = spManager; spDispatcher->dispatchMessage(rstat,SDK_ERROR); } // Clean up spReader = NULLSP; spManager = NULLSP; // Terminate the SCA Kernel terminateSCAKernel(); } catch (SCAException& e) { cout << e.what() << endl; } return 0;
module SDK { module ErrorHandling { interface SCAILogger : SCA::SCAIService { SCA::SCAResult openLog( in SCA::SCAString fileName, in SCA::SCABool append ); SCA::SCAResult writeLog( in SCA::SCAString msg ); SCA::SCAResult closeLog( ); }; }; }; #endif Logger.sdl #ifndef LOGGER_SDL_INCLUDED #define LOGGER_SDL_INCLUDED #include "SDK/ErrorHandling/Logger.idl" #include "SCA/Framework/MessageListener.idl" module SDK { module ErrorHandling { service SDK.ErrorHandling.Logger { interface SCA::Framework::SCAIMessageListener; interface SCAILogger; }; }; }; #endif Logger.cdl #ifndef LOGGER_CDL_INCLUDED #define LOGGER_CDL_INCLUDED #include "Logger.sdl" component SDK.ErrorHandling.Logger { service Logger; }; #endif The implementation for the logging service is as follows. To make the example code smaller and easier to understand, the detailed error checking that would normally be part of the implementation has been omitted.
Logger.h #ifndef SDK_ERRORHANDLING_LOGGER_H_INCLUDED #define SDK_ERRORHANDLING_LOGGER_H_INCLUDED #include "LoggerBase.h" #include <iostream> #include <fstream> namespace SDK { namespace ErrorHandling { class Logger : public LoggerBase { public: // Constructor and Destructor Logger(SCAILoggerFactoryAccess* factoryAccess); virtual ~Logger(); // Methods for interface SCA.Framework.SCAIMessageListener SCA::SCAResult doPublishMessage(const SCA::SCAWString str, const SCA::SCAInt32 severity); // Methods for interface SDK.ErrorHandling.SCAILogger SCA::SCAResult openLog(const SCA::SCAString fileName, const SCA::SCABool append); SCA::SCAResult writeLog(const SCA::SCAString msg); virtal SCA::SCAResult closeLog(); private: std::ofstream m_file; }; } } #endif Logger.cpp #include "Logger.h" #include <SCA/StringUtility.h> #include <time.h> using namespace std; using namespace SCA; namespace SDK { namespace ErrorHandling { // Constructor Logger::Logger(SCAILoggerFactoryAccess* factoryAccess) : LoggerBase(factoryAccess) { } // Destructor Logger::~Logger() {
} SCA::SCAResult Logger::doPublishMessage(const SCA::SCAWString str, const SCA::SCAInt32 severity) { SCAString msg = StringUtility::stringFromWString(str); m_file << "Error dispatched with severity of " << severity << endl; m_file << msg << endl; return SCASuccess; } SCA::SCAResult Logger::openLog(const SCA::SCAString fileName, const SCA::SCABool append) { // Open the log file if ( fileName.empty() ) { return SCAError; } else { if ( append== true ) { m_file.open(fileName.c_str(), ios_base::out| ios_base::app); } else { m_file.open(fileName.c_str(), ios_base::out| ios_base::trunc ); } } // Add the current date and time time_t rawtime; struct tm * timeinfo; time ( &rawtime ); timeinfo = localtime ( &rawtime ); m_file << "Log file opened on " << asctime(timeinfo); } return SCASuccess;
SCA::SCAResult Logger::writeLog(const SCA::SCAString msg) { m_file << msg << endl; return SCASuccess; } SCA::SCAResult Logger::closeLog() { m_file.close(); return SCASuccess; } } } The following is a sample client that uses the logging service to save all error messages in a log file. It is similar to our previous example except it loads and initializes an instance of the logging service and uses
it for the message listener instead of providing its own implementation. The client will also use the logging feature to save messages indicating the flow of the program. Client.cpp #include #include #include #include #include #include #include #include #include using using using using
<iostream> <SCA/SCAKernel.h> <SCA/Framework/ServiceObjectImpl.h> "SCA/Framework/SCAIMessageDispatcher.h" "SCA/Framework/SCAIMsgListenerManager.h" "SCA/Framework/SCAIMessageListener.h" <SDK/ErrorHandling/SCAResult/SCAIReader.h> <SDK/ErrorHandling/SCAResult/FileReaderTypes.h> <SDK/ErrorHandling/SCAILogger.h> SCA; SCA::Framework; SDK::ErrorHandling; SDK::ErrorHandling::SCAResult;
int main() { try { cout << "Test using Logger service to process errors" << endl; // Initialize the SCA Kernel initializeSCAKernel(1); // Get instance of error logger service and open a log file SCAILogger spLogger; spLogger = getSCAService("SDK.ErrorHandling.Logger"); spLogger->openLog("Test.log",false); // Register our message listener SCA::Framework::SCAIMsgListenerManager spManager; spManager = getSCAService("SCA.Framework.MessageDispatcher"); SCAIMessageListener spListener = spLogger; spManager->addListener(spListener,""); // Get SCAIMessageDispatcher interface for future error processing // Load the FileReader service SCAString svcName = "SDK.ErrorHandling.SCAResult.FileReader"; spLogger->writeLog("Getting "+svcName+" service"); SCAIReader spReader = getSCAService(svcName); // Read the model SCAString modelName = "Test.Model"; spLogger->writeLog("Reading model "+modelName); SCAResult rstat = spReader->readModel(modelName); if ( rstat ) { SCAIMessageDispatcher spDispatcher = spManager; spDispatcher->dispatchMessage(rstat,SDK_ERROR); }
// Clean up spReader = NULLSP; spManager->removeListener(spListener); spManager = NULLSP; spLogger->closeLog(); spLogger = NULLSP; // Terminate the SCA Kernel terminateSCAKernel(); } catch (SCAException& e) { cout << e.what() << endl; } return 0;
11
Multi-Threaded Applications
Introduction Thread Safety 332 333 338 339 341
Threading Infrastructure
Introduction
Multithreading is a widespread programming and execution model that allows multiple threads to exist within a single process. These threads share the process resources but are able to execute independently. The threaded programming model provides developers with a useful abstraction of this concurrent execution. The advantage of a multithreaded program is it allows it to operate faster on computer systems that have multiple CPUs because the threads can execute concurrently. When building multi-threaded applications, there are several different aspects of the problem that need to be addressed.
The various components of the application that are going to be run concurrently in different
threads must be thread-safe. This means that internal data structures in the components must be protected in such a way that they cannot be corrupted when several different threads are accessing them concurrently. This is usually done by protecting the data with various synchronization primitives such as mutexes to lock data structures against concurrent access and atomic operations.
The application must be able to create and manage multiple threads. Typically you also want a
way of creating and managing data that is unique for each thread and is not shared. The SCA Framework handles each of these aspects differently which is described in the following sections.
Thread Safety
The following portions of the SCA Framework are thread-safe.
Core services like ServiceManager, SharedLibraryManager and TextTranslation IDL generated support code like the base and factory classes created for services implemented in
C++
Implementation for framework defined types like SCAAny, SCAResult and SCATypeCode
Utility services like the XML reader and regular expression processor are not thread safe. These services are not singletons and each user typically gets their own instance.
SCA/Threading/Threads.h // // Threading support used in SCA Kernel // #ifndef SCA_FRAMEWORK_SCATHREADS_H_INCLUDED #define SCA_FRAMEWORK_SCATHREADS_H_INCLUDED namespace SCA { namespace Threading { // // Single access spin mutex/lock // class SpinLock; class SpinMutex { friend class SpinLock;
}; class SpinLock { public: SpinLock(); SpinLock(SpinMutex&); ~SpinLock(); void acquire(SpinMutex&); void release(); private: SpinLock(const SpinLock&); SpinLock& operator=(const SpinLock&); }; // // Single access recursive mutex/lock // class RecursiveLock; class RecursiveMutex { friend class RecursiveLock; public: RecursiveMutex(); ~RecursiveMutex(); }; class RecursiveLock { public: RecursiveLock(); RecursiveLock(RecursiveMutex&); ~RecursiveLock(); void acquire(RecursiveMutex&); void release(); private: RecursiveLock(const RecursiveLock&); RecursiveLock& operator=(const RecursiveLock&); }; // // Thread safe atomic increment and decrement operation // inline inline inline inline AtomicAdd(volatile long& value, long addend); AtomicIncrement(volatile long& value); AtomicDecrement(volatile long& value); AtomicCompareExchange(volatile long& value, long exchange, long compare); template <typename T> inline T AtomicLoadAcquire(const volatile T& value); template <typename T, typename V> long long long long
inline void AtomicStoreRelease(volatile T& value, const V rhs); // // Thread safe Atomic counter class // class AtomicCounter { public: typedef long value_type; // Default constructor inline AtomicCounter() : value(0); // Conversion operator inline operator value_type() const; // Assignment operator inline value_type operator=(value_type rhs); // Assignment operator inline AtomicCounter& operator=(const AtomicCounter& rhs); // Addition operator inline value_type operator+=(value_type rhs); // Subtraction operator inline value_type operator-=(value_type rhs); // Prefix increment operator inline value_type operator++(); // Prefix decrement operator inline value_type operator--(); // Postfix increment operator inline value_type operator++(int); // Postfix decrement operator inline value_type operator--(int); // Counter value value_type value; private: // No copy constructor defined inline AtomicCounter(const AtomicCounter&); }; // // Thread safe Atomic pointer class // template <typename T> class AtomicPointer { public: typedef T* value_type; // Default constructor inline AtomicPointer() : pointer(0); // Conversion operator inline operator value_type() const; // Assignment operator inline value_type operator=(value_type rhs); // Indirect access member operator inline value_type operator->() const;
};
// Pointer value value_type pointer; private: // No copy constructor defined inline AtomicPointer(const AtomicPointer&);
} } #endif The following example shows a simple class that uses a recursive lock and mutex to only allow one thread at a time access to some shared data.
#include <map> #inclued <SCA/SCA.h> class SafeMap { public: // Method to set value in shared data void setMap(string key,int value) { SCA::Threading::RecursiveLock lock(m_mutex); m_mapData[key] = value; } // Method to get data from shared data int getMap(string key) { SCA::Threading::RecursiveLock lock(m_mutex); return m_mapData[key]; } private: // Map data that must be protected std::map<string,int> m_mapData; // Mutex for protecting map data SCA::Threading::RecursiveMutex m_mutex; }; The following example shows the use of an atomic counter to implement reference counting in a class.
class TestClass { public: . . . void addReference() { m_refCount++; } void releaseReference() { if(--m_refCount == 0) delete this;
Threading Infrastructure
The SCA Framework leaves the issues relating to the starting and management of individual threads to the application. The reason for this is that only the application itself has the knowledge required to decide how threads should be used and the framework does not want to impose any restrictions relative to this. As a result the SCA Kernel does not provide any infrastructure to create and manage threads. Also internally the SCA Kernel does not use or have any expectation about the availability of multiple threads. Another reason the SCA Framework has chosen not to provide any thread management infrastructure is you typically do not want to use more than one threading management package in a single application. The use of more than one can cause over subscription of threads because the different packages do not know about each other which could cause performance issues. Since many of the applications that may want to use the features of the SCA Framework may already have their own threading support, it could cause problems if the framework used a different package.
SCAResult runMultiThreadBatchTest
args, output
args
output The test must not write to stdout, instead all output should be appended to a string variable and returned through this parameter.
of output.
Creates and starts 20 threads with each calling the runMultiThreadBatchTest method. The output from the test that is run in each thread is compared against the baseline output.
For the threading infrastructure, this sample application uses the Intel TBB library to start and manage threads. For the details on using the library the Intel TBB documentation should be consulted. Client.cpp #include <iostream> using namespace std; #include <SCA/SCAKernel.h> #include "SCA/Framework/SCAIMultiThreadBatchTest.h" #include <SCA/SCAKernel.h> #include <SCA/StringUtility.h> using namespace SCA; using namespace SCA::Framework; #include <tbb/task.h> #include "tbb/task_scheduler_init.h" // // Global variable for counting complete thread tasks // SCA::Threading::AtomicCounter tasksDone; // // Task for driving a thread instance // class TestThread : public tbb::task { public: TestThread(SCAIMultiThreadBatchTest spTest, SCAStringSequence args, SCAString& result) : m_spTest(spTest), m_args(args), m_result(result) { }; ~TestThread()
};
{ }; tbb::task* execute() { m_spTest->runMultiThreadBatchTest(m_args,m_result); ++tasksDone; return NULL; } private: SCAIMultiThreadBatchTest m_spTest; SCAStringSequence m_args; SCAString& m_result;
int main() { try { cout << "Initializing the SCA Kernel" << endl; initializeSCAKernel(1); // Load test service SCAIMultiThreadBatchTest spTest; spTest = getSCAService("SDK.ThreadSafety.ThreadTester"); // Get baseline results SCAString baseline; SCAStringSequence sqTestArgs; sqTestArgs.push_back("0"); spTest->runMultiThreadBatchTest(sqTestArgs,baseline); cout << "*** Baseline results" << endl << baseline << endl; // Initialize the TBB library const int numThreads = 20; tbb::task_scheduler_init init(numThreads); // Create a list of tasks tbb::task_list tasks; SCAString results[numThreads]; for ( SCAInt32 i=0; i<numThreads; i++ ) { cout << "Allocating task for thread " << i+1 << endl; sqTestArgs[0] = StringUtility::stringFromInt32(i+1); TestThread& test = *new(tbb::task::allocate_root()) TestThread(spTest,sqTestArgs,results[i]); tasks.push_back(test); } // Start all the tasks and wait for them to finish cout << "Starting all threads" << endl << flush; tbb::task::spawn_root_and_wait(tasks); cout << flush; // Make sure all threads finished if ( tasksDone != numThreads )
cout << "***Error: Only " << tasksDone << " of " << numThreads << " threads finished" << endl; // Check each thread result for ( SCAInt32 i=0; i<numThreads; i++ ) { if ( results[i] != baseline ) { cout << "*** Thread " << i+1 << " results are incorrect" << endl; cout << results[i] << endl; } else { cout << "*** Thread " << i << " results are correct" << endl; } } // Clean up spTest = NULLSP; // Terminate kernel terminateSCAKernel(); } catch( const SCAException& exc ) { cout << "Caught SCAException: " << exc.what() << endl; } } return 0;
We also need to implement a thread-safe SCA service to be tested. In this example it is a simple service that implements the SCAIMultiThreadBatchTest interface. To demonstrate a use of synchronization primitives, the test service will keep a std::map data structure. A single instance of this map will be shared by each thread. The runMultiThreadBatchTest method makes a series of modifications and queries on this data. Since the std::map data structure is not thread safe itself, a locking primitive is used to gain exclusive use of the structure every time it is accessed. The following SDL and CDL files are used to define the service. We do not need to create an IDL file because the only interface the service is implementing is defined in an IDL file that is delivered with the SCA Framework. ThreadTester.sdl #ifndef THREADTESTER_SDL_INCLUDED #define THREADTESTER_SDL_INCLUDED #include "SCA/Framework/BatchTest.idl" module SDK { module ThreadSafety { service SDK.ThreadSafety.ThreadTester { interface SCA::Framework::SCAIMultiThreadBatchTest; }; }; };
#endif
ThreadTester.cdl
#ifndef THREADTESTER_CDL_INCLUDED #define THREADTESTER_CDL_INCLUDED #include "ThreadTester.sdl" component SDK.ThreadSafety.ThreadTester { service SDK.ThreadSafety.ThreadTester; }; #endif The following is the implementation for the service.
ThreadTester.h #ifndef SDK_THREADSAFETY_THREADTESTER_H_INCLUDED #define SDK_THREADSAFETY_THREADTESTER_H_INCLUDED #include "ThreadTesterBase.h" #include "SCA/Threading/Threads.h" using namespace SCA; using namespace SCA::Threading; #include <map> using namespace std; namespace SDK { namespace ThreadSafety { class ThreadTester : public ThreadTesterBase { public: // Constructor and Destructor ThreadTester(SCAIThreadTesterFactoryAccess* factoryAccess); virtual ~ThreadTester(); // Methods for interface SCA.Framework.SCAIMultiThreadBatchTest SCAResult runMultiThreadBatchTest(const SCAStringSequence& args, SCAString& output); private: // Private methods for manipulating map data void setMap(string,int); int getMap(string);
// Map data that must be protected map<string,int> m_mapData; // Mutex for protecting map data RecursiveMutex m_mutex;
}; } }
#endif
ThreadTester.cpp #include "ThreadTester.h" #include "SCA/StringUtility.h" namespace SDK { namespace ThreadSafety { // Constructor ThreadTester::ThreadTester(SCAIThreadTesterFactoryAccess* factoryAccess) : ThreadTesterBase(factoryAccess) { } // Destructor ThreadTester::~ThreadTester() { } // Method to test multi-threaded access to shared map data SCAResult ThreadTester::runMultiThreadBatchTest( const SCAStringSequence& args, SCAString& output) { int count = 0; for ( int j=0; j<50; j++ ) { for ( int i=0; i<20; i++ ) { string key = "Key" + args[0] + "_" + StringUtility::stringFromInt32(i+1); setMap(key,(100*j+i+1)); } for ( int i=0; i<20; i++ ) { string key = "Key" + args[0] + "_" + StringUtility::stringFromInt32(i+1); count += getMap(key); } } output = StringUtility::stringFromInt32(count); return SCASuccess; } // Method to set value in map
void ThreadTester::setMap(string key,int value) { RecursiveLock lock(m_mutex); m_mapData[key] = value; } // Method to get value from map int ThreadTester::getMap(string key) { RecursiveLock lock(m_mutex); return m_mapData[key]; } } } The following is the output from the test. For this test 20 threads were used. The results of the test show that each of the tests generates the same result as the single threaded baseline call. This demonstrates that shared data structures are being correctly synchronized. As a simple test of this you can comment the locking calls in the setMap and getMap methods of the ThreadTester class and rerun the tests. You will probably get incorrect results or even a crash because of corruption in the data structures being modified. Initializing the SCA Kernel SCA Kernel 4.8.0 successfully initialized *** Baseline results 353000 Allocating task for thread 1 Allocating task for thread 2 Allocating task for thread 3 Allocating task for thread 4 Allocating task for thread 5 Allocating task for thread 6 Allocating task for thread 7 Allocating task for thread 8 Allocating task for thread 9 Allocating task for thread 10 Allocating task for thread 11 Allocating task for thread 12 Allocating task for thread 13 Allocating task for thread 14 Allocating task for thread 15 Allocating task for thread 16 Allocating task for thread 17 Allocating task for thread 18 Allocating task for thread 19 Allocating task for thread 20 Starting all threads *** Thread 0 results are correct *** Thread 1 results are correct *** Thread 2 results are correct *** Thread 3 results are correct *** Thread 4 results are correct *** Thread 5 results are correct *** Thread 6 results are correct *** Thread 7 results are correct
*** *** *** *** *** *** *** *** *** *** *** ***
Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread
8 results are correct 9 results are correct 10 results are correct 11 results are correct 12 results are correct 13 results are correct 14 results are correct 15 results are correct 16 results are correct 17 results are correct 18 results are correct 19 results are correct
It is important to remember that because of the nondeterministic nature of multi-threaded programs, a single test like this does not guarantee the correctness of the code. A more reliable testing procedure is to use a tool that is specifically designed to analyze the execution flow of a multi-threaded application and detect potential race conditions. This tool can be used in conjunction with the SCA provided test driver to better determine the correctness of the code.
12
Versioning
Introduction 350 351
Component Metadata
Introduction
The SCA Framework does not currently support a formal versioning policy. Work is currently in progress to do this which will be available in a future release of the framework. The SCASCons build system does currently provide a facility that allows you to attach metadata to SCA components and offers several ways to access this data. This feature can be used to provide version information that users can use to implement their own versioning scheme if desired. Currently the component metadata is only supported in components written in C++.
Component Metadata
Each time a C++ SCA component is built; the SCASCons Build System will automatically add build date and build time information to it. This data can be extracted programmatically from inside the components code or externally directly from the shared library object. You can also add your own customized metadata to the component. This is done by defining a set of construction variables with special names of BUILDINFO_xxx. These variables can then be set to your desired value like any other construction variable. See the SCASCons Build System chapter of this manual for details on how construction variables are defined and set. The following shows an example of how build metadata can be added to a source tree. First we must update the SConstruct file in the root of the source code to define a new construction variable that will hold each piece of customized metadata. In this example we have defined four pieces of data. SConstruct # # Component metadata # Norm('BUILDINFO_MajorVersion','Major version number',None) Norm('BUILDINFO_MinorVersion','Minor version number',None) Norm('BUILDINFO_BuildVersion','Build version number',None) Norm('BUILDINFO_ComponentName','Component name',None) The values of the construction variables are normally set in the SConopts file in the root of the source tree. It is also possible to set these variables on the scons command line or in the SConscript files in the various directories in the tree. SConopts # # Set component metadata # BUILDINFO_MajorVersion = 4 BUILDINFO_MinorVersion = 8 BUILDINFO_BuildVersion = 0 BUILDINFO_ComponentName = "MessageDispatcher" Each time a component is built the build date, build time and the current values of any user defined metadata construction variables will be stored in its shared library. The easiest way to extract this metadata is with the scautil utility that is provided with the SCA Framework. Using the buildinfo option and the name of the shared library for the component, it will print the value of each piece of metadata.
Command: cd /Kernel-V4-008/WINNT/bin Command: scautil -buildinfo SCA/Framework/MessageDispatcher.dll Build Information for SCA/Framework/MessageDispatcher.dll Build Date = Sep 3 2009 Build Time = 12:31:14
= = = =
8 4 0 MessageDispatcher
You can also access the metadata programmatically from code inside the component. Note that this will only work from code inside the component because the symbols used are not exported from the components shared library. The following sample code shows how this can be done for a sample component. #include "MessageDispatcherBuildInfo.h" using namespace SCA::BuildInfo; SCAString SCAString SCAString SCAString SCAString SCAString cout cout cout cout cout cout cout << << << << << << << date = SCA_Framework_MessageDispatcher_BuildDate(); time = SCA_Framework_MessageDispatcher_BuildTime(); minor = SCA_Framework_MessageDispatcher_MinorVersion(); major = SCA_Framework_MessageDispatcher_MajorVersion(); bver = SCA_Framework_MessageDispatcher_BuildVersion(); name = SCA_Framework_MessageDispatcher_ComponentName();
"MessageDispatcher component build metadata" << endl; " Build Date " << date << endl; " Build Time " << time << endl; " Major version " << major << endl; " Minor version " << minor << endl; " Build version " << bver << endl; " Component Name " << name << endl;
One routine is generated for each piece of build information. These routines are always in the SCA::BuildInfo namespace and their names are of the form xxx_getyyy where xxx is the fully qualified component name with the periods replaced with underscore characters and yyy is the name of the BuildInfo variable. In this example, one of the generated routine names is SCA_Framework_MessageDispatcher_getComponentName. This is generated from the fully qualified component name, SCA.Framework.MessageDispatcher, and the BuildInfo variable name, ComponentName. A header file, MessageDispatcherBuildInfo.h, is also generated which declares these routines and should to be included in any routine that is using them. Note that the BuildDate and BuildTime data is always provided by default and does not need to be added by the user. If you want to expose this data to users of the component, you can define and implement an interface with appropriate methods to retrieve it. Currently all BuildInfo construction definitions are global to the source tree they are defined in. This means that every component built in the same source tree will have the same build metadata variables. But, it is possible to define different values to the variables for each component in the SConscript file in the directory where the component is built. Also, the build metadata is currently managed at a component level and not a service level.
Chapter 13: Configuring and Using the SCA Kernel SCA Framework Users Guide
13
Introduction
354 355
Initializing and Terminating the Kernel Kernel Configuration Variables Kernel Configuration File 361 359
364 365
Changing the Prefix for Environement Variable Names Building Applications using the SCA Kernel Running Applications using the SCA Kernel The SCA Services Catalog Other Configuration Files Service Manager 373 377 370 372 366 367
Introduction
The SCA Framework provides an infrastructure to facilitate the design, coding and building of applications. All access to these facilities is through a set of common services and interfaces provided by the framework. The SCA Kernel portion of the framework provides the runtime core functionality required by the framework. This includes the loading, unloading and lifecycle management of services and shared libraries and the processing of messages and events. The SCA Kernel is composed of the following services.
ServiceManager which is responsible for loading and unloading of services. SharedLibraryManager which is responsible for loading and unloading of shared libraries. TextTranslation which provides the core message processing functions. EventManager which provides facilities for event processing.
The TextTranslation service is described in the Messaging and Internationalization chapter in this manual and the EventManager is described in the SCA SDK Advanced Features manual. The rest of these services are discussed in this chapter. The SCA Kernel is delivered in the SCAKernel shared library. There is also a SCAKernelUtil shared library that is part of the kernel. The utility library is statically linked against the SCAKernel shared library and all SCA components.
C++ SCA::SCAInt32 terminateSCAKernel() SCA::SCAInt32 terminateSCAKernel(SCAStringSequence& orphans) Java void SCA.SystemProvider.unloadSCA() C# void SCA.SystemProvider.unloadSCA () Visual Basic Sub SCA.SystemProvider.unloadSCA() Python None available Each of these calls will throw a SCASystemException exception if errors occur trying to terminate the kernel. It is not required to explicitly terminate the SCA Kernel in your application. The required termination processing will automatically occur when the application terminates. You can explicitly terminate the kernel at an earlier point if you want to release any resources it is using. It is important to understand that the call to terminate of the SCA Kernel does not force the deleting of any SCA service objects instances that may still exist. The fact the instances still exist means that there are active references to them from somewhere else in the application. If the instances were forcibly deleted with active reference to them this could cause the application to crash at a later time. Instead these instances are left alone but they are disconnected from the SCA ServiceManager. This way the ServiceManager and other services in the kernel can still release their resources and terminate. When the active references to the left over service objects are finally remove, then they will automatically delete themselves. This process is called orphaning. Some of languages provide a termination call that will optionally provide a list of any orphaned services.
cout << "Initializing the SCA Kernel" << endl; initializeSCAKernel(1); . . . cout << "Terminating the SCA Kernel" << endl; terminateSCAKernel(); } catch(SCAException& e) { cout << "Error: " << e.what() << endl; } return 0;
Java import SCA.*; import SCA.SystemProvider.*; public class Client{ public static void main (String args[]) { try{ System.out.println("Intitialize the SCA Kernel"); SCA.SystemProvider.loadSCA(); . . . System.out.println("Terminate the SCA Kernel"); SCA.SystemProvider.unloadSCA(); } catch (SCASystemException e) { System.out.println("Error: " + e.what()); } } } C# using System; using SCA; class ClientCS { static void Main(string[] args) { try { Console.WriteLine("Initialization the SCA"); SCA.SystemProvider.loadSCA(); . . . Console.WriteLine("Terminate the SCA"); SCA.SystemProvider.unloadSCA(); } catch (SCAException e) { Console.WriteLine("Error: " + e.what()); } } }
Visual Basic Imports SCA Imports System Module ModuleMain Sub Main(ByVal args As String()) Try System.Console.WriteLine("Initialize the SCA Kernel") SystemProvider.loadSCA() . . . System.Console.WriteLine("Termination the SCA Kernel") SystemProvider.unloadSCA() Catch e As SCAException System.Console.WriteLine("Error: " + e.what()) End Try End Sub End Module
Python try: print "Initializing the SCA Kernel" import SCA except SCA.SCAException, e: print "Error:",e.what() . . .
The SCA_ prefix of each environment variable in the above table can be changed by the application. The API for this is discussed in a later section of this chapter. The following table provides the description and default value for each configuration variable. Variable Name KernelLibPath Description and Default Value Defines the path where the SCA Kernel shared library was loaded from. This value is determined internally by the kernel and cannot be set by the application. Defines a set of paths for the location of the SCA resource directories. The default uses the KernelLibPath configuration value and is directory KernelLibPath/../../res. Defines a set of paths for the files or directories where the SCA service catalog information is obtained. The default value is the file SCAServiceCatalog.xml in each of the directories specified in the Resource configuration value. At least one of these must exist.
Resource
Catalog
Description and Default Value Defines a set of paths for the directories the XML parser service will search for XML files. The search order used by the XML parser is to first look in the directories defined by this value and then to look in the directories defined by the Resource configuration value. If the requested XML file is not in any of these directories it looks in the current directory. Defines a set of paths for the root locations for loading Java components. The default value is the directory RESOURCE/../lib/java where RESOURCE is each of the directories specified in the Resource configuration value. Defines a set of configuration options for the Java virtual machine. Multiple options are separated by commas. See the Java Mapping chapter of this manual for a discussion of when this value is used and what its default is. Defines a set of paths for the root locations of C++ and .NET components. The default value is taken from the appropriate system environment library variable on the current platform (for example, PATH on WINDOWS and LD_LIBRARY_PATH on Linux). Defines a set of debug output options. Multiple options are separated by commas. The default is to generate no debug output. Specifies if thread safety is required for SCA Kernel. The default value is on.
JavaPath
JVMConfig
LibraryPath
Debug Locking
the value of the specified environment variable. E = NVAL where NVAL is the value of environment variable N. A configuration error will occur if the specified environment variable is not set.
While not required, the convention is for all temporary variable names to be enclosed in percent
signs such as %T% so they are easily distinguished from real configuration values. The value of these variables will be substituted verbatim for all occurrences of the name in the remaining entries in the current session.
The temporary variable %SCAKERNEL_LIBPATH% is a reserved name that stands for the
directory where the shared library for the SCA Kernel was loaded from. It is determined by the SCA Kernel at runtime.
win32 win64 aix alpha hpux irix hpuxipf solaris linux64 linux32 linuxipf The following is an example of a simple setting of a configuration variable. This example sets the value of the Locking variable to off. <var name="Locking" value="off"/> The configuration process supports the concept of temporary variables. Temporary variables can be used to create a value that can subsequently be used in the setting of several configuration values. This way the logic does not need to be repeated in more than one configuration entry. Temporary variables may also be used to include the settings for system environment variables in the configuration settings. The convention for temporary variable names is to enclose them in percent signs like %APPLOC%. While this is not a requirement, it is a good practice because it reduces the chance that the name of the temporary variable will appear elsewhere in a configuration value which could cause undesired substitutions. Every occurrence of the temporary variable name in all of the entries in the current session will be substituted with its value. Note that this substitution occurs in every entry, even those that appear before the definition of the temporary variable in the configuration file. To include the setting of a system environment variable in the configuration you must first set a temporary variable with its value. Only temporary variable names can be set to the value of an environment variable. You cannot directly set an actual configuration variable. The following is a sample configuration file entry that sets the temporary variable %APPLOC% to the current value of the environment variable MY_APPS_LOCAL. <env name=%APPLOC% value="MY_APPS_LOCAL > Temporary variable names can also be set in the configuration file. In this example the %TYPEJAR% temporary variable is set to a string value. In this case the string value used contains an instance of another temporary variable and its value will be substituted during the process. <tmp name="%TYPEJAR%" value="%APPLOC%/lib/java/IDLTypes.jar"/>
The special reserved temporary variable of %SCAKERNEL_LIBPATH% is set by the SCA Kernel to the directory where the actual SCA Kernel shared library was loaded from. This value can then be used to set other values in the configuration file. <tmp name="%APPSYS%" value="%SCAKERNEL_LIBPATH%/../.."/>
set DSYSTEM = $ISYSTEM/WINNT # set path = ( $DLOCAL/bin $DLOCAL/lib $DSYSTEM/bin $DSYSTEM/lib $path ) # setenv SCA_RESOURCE_DIR "$ISYSTEM/res" # $DLOCAL/bin/ClientCS.exe Python #! /bin/csh # set ILOCAL = D:/Builds/Initialization/Apps set DLOCAL = $ILOCAL/WINNT # set ISYSTEM = D:/Kernel-WINNT/Apps-Opt set DSYSTEM = $ISYSTEM/WINNT # setenv PYTHONPATH "$ISYSTEM/lib/python;$DSYSTEM/bin" setenv PYTHONHOME /ThirdParty/Python/2.5.0-1/WINNT # set path = ( $DLOCAL/bin $DLOCAL/lib $DSYSTEM/bin $DSYSTEM/lib $path ) set path = ( $PYTHONHOME/libs $path ) # setenv SCA_RESOURCE_DIR "$ISYSTEM/res" # $PYTHONHOME/python ClientPy/Client.py With the exception of Python, the only change to these scripts to use a XML configuration files would be to remove the setting of the SCA_RESOURCE_DIR environment variable. The initialization for the SCA Kernel in the source code for the application would also have to be changed to include the name of the XML configuration file. For this simple example, the following XML configuration file is equivalent to the previous environment variable examples. SCAConfig.xml <?xml version="1.0"?> <SCA> <session os="win32"> <var name="Resource" </session> <session os="win64"> <var name="Resource" </session> <session os="linux32"> <var name="Resource" </session> <session os="linux64"> <var name="Resource" </session> </SCA>
initializeSCAKernel(1,"SCAConfig.xml"); The API to initialize the SCA Kernel in Python does not provide the ability to specify the name of a XML configuration file. In Python, you must include this information on the command line that runs the Python interpreter. In the above sample script for Python we would use the following line to accomplish this.
<?xml version="1.0" ?> <SCA> <component language="C++" library="SCAKernel" name="SCAKernel"> <service name="SCA.Framework.EventManager" /> <service name="SCA.Framework.ServiceManager" /> <service name="SCA.Framework.SharedLibraryManager" /> <service name="SCA.Framework.ResourceManager" /> <service name="SCA.Framework.TextTranslation" /> </component> <component language="C++" library="SCA/Framework/MessageDispatcher" name="SCA.Framework.MessageDispatcher"> <service name="SCA.Framework.MessageDispatcher" /> </component> </SCA>
Message Files
Application defined messages are stored in XML files that are installed as part of the build process. These XML files are stored in the directories specified by the Resource configuration value or in a subdirectory of them. See the Messages and Internationalization chapter of this manual for complete details on these XML files and how they are located.
Service Manager
The ServiceManager service provides important interfaces for managing the available services. These interfaces expose methods to load services and to query kernel information and available services. This section describes these interfaces and provides examples to illustrate their usage.
SCAResult getService
sName sAttr
Fully qualified name of the service to load Additional keyword=value attributes to specify additional information about the service to be loaded
Return values:
SCAResult
The SCAIServiceProvider interface implemented in the ServiceManager is the only way to load a SCA service. The various language bridges and mappings provide different APIs to load services. For example the C++ language mappings provides the routine m_serviceAccess.getService()for loading services when
inside other SCA services or the routine getSCAService when external to a SCA service. But all of these APIs eventual use the SCAIServiceProvider interface to do the actual loading of the service.
SCA::Framework::SCAIKernelInfo Interface
SCAIKernelInfo is used to query the Kernel version and other information
Member Functions SCAResult SCAResult SCAResult getMajorVersion (out SCAString sMajorVersion) getMinorVersion (out SCAString sMinorVersion) getBuildVersion (out SCAString sBuildVersion)
sBuildVersion
Returns:
Build version
Returns SCASuccess on success, else returns an error. SCAResult getMajorVersion ( out SCAString sMajorVersion ) Returns the major version .
Parameters:
sMajorVersion
Returns:
Major version
Returns SCASuccess on success, else returns an error. SCAResult getMinorVersion ( out SCAString sMinorVersion ) Returns the minor version.
Parameters:
sMinorVersion
Minor version
Returns:
Returns SCASuccess on success, else returns an error. The following example demonstrates how to get the Kernel version and build date using the SCAIKernelInfo interface.
#include <SCA/Framework/SCAIKernelInfo.h> #include <SCA/StringUtility.h> namespace std; using namespace SCA; using namespace SCA::Framework; // Get Kernel Version and build date SCAIKernelInfo spInfo; spInfo = getSCAService("SCA.Framework.ServiceManager"); SCAString sMajor,sMinor,sBuild; spInfo->getMajorVersion(sMajor); spInfo->getMinorVersion(sMinor); spInfo->getBuildVersion(sBuild); cout << "SCA Kernel version is " << sMajor << "." << sMinor << "." << sBuild << endl; SCAString sDate,sTime; spInfo->getBuildDate(sDate); spInfo->getBuildTime(sTime); cout << "SCA Kernel was built on " << sDate << " at " << sTime << endl; spInfo = NULLSP;
SCA::Framework::SCAIServiceCatalog Interface
SCAIServiceCatalog is used to manage the service catalog
Member Functions SCAResult SCAResult getAvailableServices (out SCAStringSequence sqServices) getServiceInfo (in SCAString sName, out ServiceInfo info)
SCAResult getAvailableServices ( out SCAStringSequence sqServices ) Gets the list of available services.
Parameters:
sqServices
Available services
Returns:
Returns SCASuccess on success, else returns an error. SCAResult getServiceInfo ( in SCAString sName, out ServiceInfo info ) Gets the service information.
Parameters:
Returns SCASuccess on success, else returns an error. This example demonstrates how to get the list of services and query information on them using the SCAIServiceCatalog interface. #include #include #include #include <iostream> <SCA/Framework/SCAIKernelInfo.h> <SCA/Framework/SCAIServiceCatalog.h> <SCA/StringUtility.h>
using namespace std; using namespace SCA; using namespace SCA::Framework; // Query the available services and get service information SCAIServiceCatalog spCatalog; spCatalog = getSCAService("SCA.Framework.ServiceManager"); SCAStringSequence services; spCatalog->getAvailableServices(services); for ( size_t i=0; i<services.size(); i++ ) { SCAString name = services.r_at(i); ServiceInfo info; SCAResult rstat = spCatalog->getServiceInfo(name,info); SCAStringSequence parts; parts = StringUtility::split(info.genericName,"/"); cout << setiosflags(ios::left) << setw(50) << name << setw(25) << parts.r_at(parts.size()-1) << setw(8) << info.language << setw(5) << info.loadCount << endl; }; spCatalog = NULLSP;
SCAResult findFunction
handle to the library (obtained via loadLibrary) name of the function whose pointer is requested pointer to symbol
SCAResult getLibraryInfo
getLibraryInfo returns the generic library name and the real shared library object name Parameters:
handle to the library (obtained via loadLibrary) generic library name real shared object name
SCAResult loadLibrary
sName, iHandle
SCAResult releaseLibrary
( in SCAInt32
iHandle
releaseLibrary decrements the reference count on the shared library. The library may be unloaded if there are no other references to it Parameters:
iHandle Returns:
status of request
SCAVoid setReleaseQueueSize
( in SCA::SCAUInt32
size
setReleaseQueueSize change the release buffer size to the given size Parameters:
size
then used to find the list of paths to search for the library. If LibraryPath is not set then the following platform specific environment variables are used. Platform Windows AIX IRIX Environment Variable LIB LIBPATH LD_LIBRARY64_PATH LD_LIBRARY_PATH Other Linux/Unix LD_LIBRARY_PATH
The relative or library only name is then appended to each search path. If the search path entry is a relative directory name, it is made absolute by adding the current working directory to the name. Internally the SharedLibraryManager always uses absolute paths when loading libraries. This is done for several reasons.
This keeps the number of directories that must be tried to load the shared library to a minimum.
Since most shared libraries for SCA components are in subdirectories and not the root directory, you would have to include each subdirectory in the system search path list. But since the SharedLibraryManager assembles the absolute directories itself, you can give it a name relative to the root directory and it will only need to try to load from the single subdirectory required.
The operating system rules for how the system search path for loading shared libraries is used is
different on the various platforms supported by SCA. By bypassing the Operating System's processing of the system search path it is possible to provide a consistent loading behavior across all platforms.
SharedLibraryManager example
The following example shows using the SharedLibraryManager to load and call a function in a shared library. In this example we will be calling the SCA_CompInfo entry in the SCA Kernel shared library to extract all the build information that is stored in the library.
using namespace std; using namespace SCA; using namespace SCA::Framework; // Load the SharedLibraryManager Service SCAISharedLibraryManager spLib; spLib = getSCAService("SCA.Framework.SharedLibraryManager"); // Load the SCA Kernel shared library SCAString kernelLibPath = getSCAEnvValue("KernelLibPath"); SCAString kernelLib = kernelLibPath + "/SCAKernel"; SCAInt32 iHandle; SCAResult rStatus = spLib->loadLibrary(kernelLib,iHandle); if( rStatus ) { cout << "Error: Could not load library " << kernelLib << endl; return 1; } // Get the address of the "SCA_CompInfo" symbol SCAVoid* pSym; rStatus = spLib->findFunction(iHandle,"SCA_Comp_Info",&pSym); if( !rStatus ) { SCAString sValue; SCAString (*pfunc)(SCAString) = (SCAString(*)(SCAString))pSym; cout << "Build Information for " << kernelLib << endl << endl; // Get the build date sValue = pfunc("BuildDate"); if ( sValue.length() > 0 ) cout << " Build Date = " << sValue << endl; // Get the build time sValue = pfunc("BuildTime"); if ( sValue.length() > 0 ) cout << " Build Time = " << sValue << endl; // Get the other build information entries sValue = pfunc("?"); SCAStringSequence varNames = StringUtility::split(sValue,","); for ( size_t iLoc=0; iLoc<varNames.size(); iLoc++ ) { sValue = pfunc(varNames[iLoc]); if ( sValue.length() > 0 ) cout << " " << left << setw(17)
} } else { cout << "No build information was available library" << kernelLib << endl; } spLib->releaseLibrary(iHandle); spLib = NULLSP;
14
Utility Services
Introduction XML Parser 384 385
Introduction
This chapter discusses the following utility services that are provided as part of the SCA Framework.
XML Parser
XML Parser
The XML Parser is an efficient and high performance SCA compliant XML reader/writer utility component. It is provides a set of services and interfaces to facilitate the parsing, generating, manipulating, and validating of XML documents. It consolidates various features of different XML Parsers by using a standard set of interfaces in a uniform manner across different products within the Simulation enterprise umbrella. XML Parser is implemented as a wrapper around libxml2, which is a third party XML library. The XML Parser supports the following parsing mechanisms. 1. Tree based parsing - Document Object Model (DOM) This mechanism provides access to the information stored in the XML document as ahierarchical object model. The DOM parser creates a tree of nodes based on the structure and information in the XML document. Once the document is loaded, the user can manipulate the data by traversing the tree. 2. Event driven parsing - Simple API for XML (SAX) This mechanism provides access to the information stored in the XML document through a set of predefined events. The parser fires a sequence of these events depending on the tags encountered while reading an XML document. The user needs to provide event handlers to intercept and process the events. 3. XSLT style sheet processor XSLT is a declarative XML language that allows you to translate XML files into arbitrary text output using a style sheet. The XSLT processor provides the functions to perform the required transformation. The XML parser is composed of the following services.
SCA.MXP.MXPDOMParser The DOM parser SCA.MXP.MXPSAXParser The SAX parser SCA.MXP.MXPXSLT The XSLT style sheet processor
In the following sections we will discuss the interfaces supported by the XML Parser and provide some usage examples. DOM Parser Interfaces
Chapter 15: SCA Utility Program and Testing Components SCA Framework Users Guide
15
Introduction
Introduction
This chapter describes the functionality available in the SCA Framework for testing of services and components. In particular it discusses the SCA utility program scautil and the TestRun environment routine of the SCA SCons build system. The combination of these two can be used to test loading of components, query information and run tests. The tests could be run either manually or as part of the build process.
There is no script provided with the SCA Framework to run the scautil program because it would be difficult to provide one that was general enough to be helpful. But, the scautil is just a normal SCA application and it requires the same configuration as any SCA application. The details of this are discussed in SCA Kernel chapter of this manual. As an example, the following simple C shell script could be used to run the delivered version of the Framework. #! /bin/csh # set ISYSTEM = D:/SCAKernel-V4-007 set DSYSTEM = $ISYSTEM/WINNT # set path = ( $DSYSTEM/lib $DSYSTEM/bin $path ) # setenv SCA_SERVICE_CATALOG "$ISYSTEM/res/SCAServiceCatalog.xml" # setenv SCA_RESOURCE_DIR "$ISYSTEM/res" # $DSYSTEM/bin/scautil.exe $* Typically, you would need to modify this to also point to the APPS directory for the components you have built. The syntax for running scautil is a follows. Syntax: scautil [arg1] [arg2] [arg3] . . . The rest of this section describes the usage of the scautil program.
Run a Script
The script option can be used to run a script using the runScript method of the ScriptBroker service. Usage: scautil -script file Example: scautil -script test.py
Usage: scautil -testload [-noinfo] library_name This option also checks if any of the known symbols that a SCA component might export from a library exist and outputs this information. If the optional argument noinfo is specified as the second argument, the library is loaded but no information will printed except for the appropriate errors if the load is unsuccessful. Example: scautil -testload SCAKernel.dll Output: SCA Kernel 4.7.0 successfully initialized Symbol SCA_Comp_Info was found at address 008869D0
Output: SCA Kernel 4.7.0 successfully initialized Kernel Build Information Build Date Build Time Major Version Minor Version Source Label = = = = = Jul 28 2009 18:16:09 4 7 V4-007
Example: scautil -buildinfo C:/SCAKernel-V4-007/WINNT/bin/SCAKernel.dll Output: Build Information for SCAKernel.dll Build Date Build Time MajorVersion MinorVersion BuildVersion BuildLabel ProductName ProductLabel = = = = = = = = Jul 28 2009 18:16:09 4 7 0 V4-007 SCA Kernel SCA
Every SCA component will have information with the date and time of the build. See the SCA Build System Guide for the details on how you can add other build information to a component.
( in SCAStringSequence
args )
args
baseline command
args aliases
Description Python script that is used to modify each line of output from the test run before it is compared to the baseline output. Normally the test is also made dependent on the Service catalog to make sure any catalog updates are done before the test is run. If this argument is set true, then this dependency is not established. Any extra paths for loading libraries to be used when running the test. These paths may be absolute or relative to the bin or lib subdirectory, depending on the current platform, in the APPS_LOCAL_MACH and APPS_SYSTEM_MACH directories. Python script that will be run before the actual test is run. It can be used to do any special setup processing that the run requires. Python script that will be run after the actual test is run. It can be used to do any required clean up processing after the test. Title which is printed out for test File or list of files that the test is dependent on Alias or list of aliases that the test is dependent on The baseline file is run through a macro preprocessor before it is used. The value of this option is a Python dictionary that contains the variables and their values which are referenced in the preprocessor commands. For example: env = {} env['VAR1'] = True env['VAR2'] = False env.TestRun(...,preprocess=env)
loadpaths
addtodefaults
If true, the targets of the test are added to the list of default targets. This way the test will run if no command line targets are provided. The normal behavior is the test will only run if it is requested by a target on the command line.
aliases="MemManagerTest") To select the test you want to run, you use the alias name on the build command. scons KernelUseTest scons KernelUseTest MemManagerTest
# Run the test and check the results env.TestRun(program="UtilTest", baseline="UtilitiesTest.txt", loadpaths="File/Utilities", alias=UtilitiesTest) In this example we are using a program, UtilTest, built in the current directory to run the tests on a nonSCA shared library, Utilities. The shared library is built in a different directory so we set up a dependency between the program and the shared library to make sure it will be built when needed. The TestRun routine specifies that we are using a program to run our tests. In this case, it is not necessary to specify the command to run since it is the same as was specified in the program argument. We have also used the loadpaths argument so the loader can find the shared library. This is required since the File/Utilities directory, where the shared library is built, is not in the default load path.
A new string value for the line A list of string values to replace the original one. This list may be empty.
the build is run, even so the actual test may not be executed during the current build.
The SConscript file is always completely executed before any targets are built which means in
this case before any tests are run. As a result there is no way to perform any cleanup operations in SConscript file. The proper way to perform these types of operations is to use the setup and cleanup arguments in the TestRun command. Each of these should point to a Python routine which takes a single argument which is the SCons environment for the test. The setup routine, if provided, will be run immediately before the test is run and the cleanup routine will be run immediately after the test is run. The following shows an example of how these can be used. import os # Setup routine SetupFunc(env): # Create a test input file try: file = open(Test.data, 'w') for i in range(1024): file.write(" ") file.close() except: pass # Cleanup routine CleanupFunc(env): try: os.remove(Test.data) except: pass # Run the test env.TestRun(component="Test.CatalogTest", command="scautil", args="-test Test.CatalogTest.Test", baseline="CatalogTest.txt", setup=SetupFunc, cleanup=CleanupFunc, aliases="CatalogTest")
TESTRUN_LOADPATH Replacements to the default Apps Local and Apps System locations that are added for the test command. These are in addition to any paths that are specified using the loadpaths argument to the TestRun command. The following example shows how these can be used. # Setup special environment for the test env['TESTRUN_DEBUG'] = "ShrLibLoad=all" pylib = os.path.join(env['APPS_LOCAL'],"lib","python") scripts = os.path.join(env['APPS_LOCAL'],"res","scripts") env['TESTRUN_ENV'] = { "PYTHONHOME":env['PYTHON_SYSTEM'], "PYTHONPATH":pylib+os.pathsep+scripts } # Run the test env.TestRun(component="Test.PythonTest", command="scautil", args="-test Test.PythonTest.Test", baseline="PythonTest.txt", aliases="PythonTest")
16
Introduction
404 405
Introduction
The SCA Build System is used to build SCA Framework components. Together, the build system and the SCA IDL compiler make the development of SCA components easier for the programmer by automating many of the required steps in the coding and build process. The SCA build system utilizes the SCons utility to do the builds. SCons is an open source software build tool that is an improved cross-platform replacement for the classic Make utility. SCons is implemented as a Python script and set of modules. Because SCons configuration files are actually executed as Python scripts, build customization can be done using the Python language. The SCons system has been customized for the SCA environment and provides the following functionality.
Automatic traversal of the source tree processing all directories containing a SConscript file Setting up appropriate processing for every file in a directory with a supported file type Automating the building and management of SCA components
This section provides a basic overview of the SCA build system and describes the steps for building a SCA service. For a complete description of the build system, see the document SCA Build System Guide.
Construction Variables
The SCA build system uses construction variables containing string values that are substituted into command lines or used by the builder functions. The construction variables may contain paths, compiler flags and other build options. The following places can be used to specify the construction variables. 1. The SConscript files in each directory 2. Using command line arguments 3. The SConopts.user option file in the users home directory 4. The SConopts option file in the root directory of the source tree Examples: APPS_DIR = "MyComponent-015" SCA_OBJECT = C:/Builds/HelloWorld APPS_SYSTEM = C:/SCA/SCAKernel-V4-007 APPS_LOCAL = C:/Builds/HelloWorld/Apps Note that these examples show the setting of configuration variables on the command line and the SConopts files. The syntax for setting configuration variables in the SConscript file is discussed later.
The source tree is also where you must run the build command. The root of the source tree is determined by the presence of a file named SConstruct. If you start the build in a subdirectory within the source tree with the D option, the build system will traverse up the directory tree until it finds a SConstruct file to determine where the root of the source tree is located. Object The object directory contains transitory files created during the build that can be deleted afterwards if desired. Each build run will only rebuild files that are out of date in the object directory. The location for the object directory is defined by the SCA_OBJECT construction variable, and it has the same directory structure as the source tree. Apps System The Apps System directory contains all of the components that make up the release of the product on which your component is based. This is where the SCA Framework is located as well as any other components that your component uses. It does not contain your component. The tree is organized in a structure optimized for running the application. The types of files listed below are stored in both the Apps System and Apps Local directory trees.
IDL files C++ header files generated from the IDL files Dynamically linked shared library for the component Resource files that are required by the component
The location of the Apps System directory is defined by the APPS_SYSTEM (absolute path) construction variable or the APPS_DIR (relative path) construction variable. Apps Local The Apps Local tree has the same structure as the Apps System and only contains the components that you have built. This is where you will find the build results for your component. Some of the files in this directory are copied from the source tree, some from the object tree and some are generated directly in the tree. The location of the Apps Local directory is defined by the APPS_LOCAL (absolute path) construction variable. If APPS_LOCAL variable is not defined, then the default location is used which is under the object directory. Third Party tree The build system provides facilities for including support for third party packages. Third party packages are non-SCA components that are required to build and run an application. Support for Mozilla, Qt, and Python packages is supplied by default. Support for additional libraries can be added. This support requires that the third party package be installed in a directory structure that is understood by the build system.
The basic directory hierarchy for the third party tree is: ThirdParty/Package/Version/Platform
This structure allows support for multiple versions of each package on each platform type. The version identifiers in the above tree are just directory names and can be numeric, alphabetic or any combination of both. Multiple packages can be installed under the same third party tree or they may be installed in different trees. Depending on how the version of the package is located at run time, the ThirdParty, PackageX or VersionX directory levels many not be required in the directory tree. Tools Directory The Tools tree contains the SCA Build System and the other tools required for building SCA Components. These are some of the utilities included.
genskeleton - Generate skeletons for service implementations idl - SCA IDL Compiler scons - SCA Build system
This location could be added to the operating systems path environment variable to make it easy to run the tools. Rules for locating Apps System and Third Party Trees There is a common set of rules that are used to locate the Apps System and third party directory trees. These rules allow you to specify the full path to the directory or only the directory name and the build
system will determine its full path. The table below shows what construction variables can be used. The X field in the variable names can be APPS or a third party package name. Variable X_SYSTEM X_DIR X_BASE Description Full path to the location of the X directory tree. Directory name of the X tree. Name of the base directory for locating the X_DIR directory for package X.
SCA_THIRDPARTY_BASE Name of the base directory for locating X_DIR directories for third party packages only. SCA_BASE Name of the base directory for locating X_DIR directories for the APPS and third party packages.
To locate APPS_SYSTEM, either the APPS_SYSTEM or the APPS_DIR variable must be specified. If the APPS_SYSTEM variable is used, it already contains the full path to the desired directory and the search is done. However, if the APPS_DIR variable is used, the following locations are searched to determine the full path.
If APPS_BASE is defined: APPS_BASE/APPS_DIR. If SCA_BASE is defined: SCA_BASE/APPS_DIR. ../APPS_DIR relative to the location of the SCons script running.
Locating third party directories follows the same general rule except the third party directory tree structure is used. For the third party package X, either the X_SYSTEM or the X_DIR variable must be specified. If the X_SYSTEM variable is used, it already contains the full path to the desired directory and the search is done. In this case it should point to the specific platform specific directory for the desired version of the package. For example, the Qt package on Windows could be located using the following value. QT_SYSTEM = C:/SCA/ThirdParty/Qt/3.3.2/WINNT If the X_DIR variable is used, the following locations are searched to determine the full path. The MACH value in these rules is substituted with the appropriate value for the platform you are building for.
If X_BASE is defined: X_BASE/X_DIR/MACH. If SCA_THIRDPARTY_BASE is defined: SCA_THIRDPARTY_BASE/X/X_DIR/MACH. If SCA_BASE is defined: SCA_BASE/ThirdParty/X/X_DIR/MACH. ../ThirdParty/X/X_DIR/MACH relative to the location of the scons script running.
Rules for locating Apps Local and object trees The location of the Apps Local and Object directory trees is controlled by the construction variables SCA_OBJECT and APPS_LOCAL.
The following table shows the possible combinations for SCA_OBJECT and APPS_LOCAL and how they affect the location of these trees. SCA_OBJECT Not set APPS_LOCAL Not set Location of Apps Local Tree Source/Apps Location of Object Tree Source/ObjectSubTree e.g. /source/WINNT_SRC_DEBUG Set Not set Set Not set Set Set SCA_OBJECT/Apps APPS_LOCAL APPS_LOCAL SCA_OBJECT/ObjectSubTree Source/ObjectSubTree SCA_OBJECT/ObjectSubTree
The ObjectSubTree could be MACH_SOURCE_DEBUG or MACH_SOURCE_OPT depending on debug or optimized build. For example on Windows if the Source directory is TestComp, then ObjectSubTree could be WINNT_TESTCOMP_DEBUG or WINNT_TESTCOMP_OPT. The APPS_LOCAL construction variable is not normally set in the users SConopts.user options file. Normally its location is set using the SCA_OBJECT construction variable.
Configuration files
There are four main types of configuration files that control the configuration of the build which are described below. The build system uses a directory hierarchy model so the appropriate configuration files must be present at the appropriate places in the source tree for the whole directory structure to be processed. An important point to remember is that the build system uses these files to create the build environment. The build system only propagates the HOME environment variable into the build environment from the list of environment variables defined for the user. None of the other environment variables set by the user are propagated. The reason for this is to maintain a standard build environment independent of the user settings. There are also some operating system environment variables that individual system commands required that are also automatically propagated into the build environment. An example of this is the TMP variable that is required by the Windows linker. SConstruct This is the master configuration file and must exist in the root of the source tree. It is used to initiate build processing by identifying the root of the source tree and specifying any special build system configuration options for it. The SConstruct file provides customizations and extensions to the build system itself. Examples of these types of customizations might be adding user specific construction variables, environment routines and third party packages. Because the SConstruct file is run at an early stage of the build process, the rules for coding this file are different than the two options files. Setting values for construction variables
should not be done in this file. Only new construction variable names and their default values can be declared. Customized values for these variables should be set in the normal manner in the SConopts or SConopts.user files or on the command line. After the initial setup, this file only needs to be updated when new build configuration options are needed, such as adding a third party software dependency. An example SConstruct file with one third party software configuration is provided below.
# Main SCons configuration file for the SCA build system. from SCASCons.Configure import * #================== Perform local customization here ================== # # Set up for LibXML processing # def IncludeLibXML2(env, compile=True,extraincs=[], link=True,extralibs=[]): # Set compilation related values if compile: libXMLInc = os.path.join("$LIBXML_SYSTEM", "include") env.Append( CPPPATH = libXMLInc ) for inc in env.Split(extraincs): env.Append( CPPPath = os.path.join( libXMLInc, inc )) # Set link related values if link: if env["MACHINE"] == "LX8664": env.Append( LINKFLAGS=os.path.join("$LIBXML_SYSTEM", "lib", "libxml2.so.2") ) else: env.Append(LIBPATH=os.path.join("$LIBXML_SYSTEM", "lib")) env.Append(LIBS="libxml2") for lib in env.Split(extralibs): env.Append(LIBS=lib) # Initialize the package ThirdPartyPackage('libxml', 'libxml2 support', IncludeLibXML2) #==================================================================== == import SCASCons.Setup SCASCons.Setup.Setup() There is a sample SConstruct file, in the Runtime/lib/python/SCASCons subdirectory of the Tools tree, with a list of all the available commands.
SConopts This file is in the root of the source tree and contains build options, i.e. construction variable settings, unique to this source tree. The file should only set options that are specific to the source tree as they will be treated as default values for every user working in the tree. Examples of this type of information might be SCA Framework version, build information, and third party versions. After the initial setup, this file only needs to be updated when new build configuration options are needed, such as using different version of the SCA Framework. The SConopts.user file, which is described next, contains options that are unique to users but not to the source tree. The same types of items can go into either of the files, but they are separated for the reasons described. An example of the SConopts file is provided below. # Source tree specific build options file import SCASCons # Apps System directory location relative to APPS_BASE. APPS_DIR = "SCAKernel-V4-007" # Third party option for LibXML. if SCASCons.MACHINE == 'WINNT': LIBXML_DIR = "2-2.6.27-2" else: LIBXML_DIR = "2-2.6.27" SConscript One of these configuration files must exist in each directory that is to be processed. If a directory does not have a SConscript file, then processing will stop at that point and the directory and all of its subdirectories will be skipped. This small file can also contain special build instructions for the files in its directory. If there are no special requirements, as in this example, then the default version of this file still needs to be present. Import("env_base") env = env_base.Copy() #================= Perform local customization here ================= #==================================================================== retval = env.ProcessDir(env_base) Return('retval') SConopts.user This optional file exists in the users home directory and contains user-specific build options used for any builds that they perform. Options in this file have precedence over options in the SConopts file, and unlike the SConopts file, this file may change often depending on the users environment.
User specific options should be put in each users SConopts.user file. Examples of this information might be temporary object locations and output requests. You can also override any of the settings in the SConopts file. You may wish to do this if you want to build against a different version of a third party package for example. Be careful about the settings you put in this file because they will affect every build you as a specific user run no matter which source tree you are processing. Before using the SCA build system, you need to make sure your personal build options file is setup correctly. The file is a Python script that is run at the start of the build process. An example of the SConopts.user file is provided below. import sys import os # Set location for object tree root = os.path.basename(os.getcwd()) if sys.platform == win32: SCA_OBJECT = C:/Builds/ + root else: SCA_OBJECT = /tmp/rich/Builds/ + root # Set reduced command output COMMANDPRINT = short Normally you should only define the locations of the SCA_OBJECT and optionally the APPS_LOCAL directory trees in your SConopts.user file. The APPS_SYSTEM directory is defined in the SConopts file, which resides in the root of the source tree. If APPS_LOCAL directory is not defined, then the default location is used and is under the object directory.
The example configuration files can be found in the SCASCons directory which is located in the Runtime/lib/python/SCASCons subdirectory of the Tools tree. These configuration files should be modified as required for the source tree. In particular, the SConopts file should be modified to specify the correct version of the SCA Framework that will be used. Next, you need to create a SConscript file in each directory in the source tree that needs to be processed. If required you may modify these files to do special processing in their directory. For details on the available customization options for these files, see the document SCA Build System Guide.
As the SCA Build System processes the source tree, the SConscript file in each directory is executed. The first two lines of each SConscript file should be the following.
Import("env_base") env = env_base.Copy() These two lines make a copy of the base construction environment that will be used in this directory. A copy of the construction environment is made in each directory so any changes made to it in the current directory will not affect processing in any other directory. You should not make any changes to the base construction variable, env_base, directly. If you wish to have change you make in the current directory be propagated down to all of its subdirectories you should use the env.Propagate command.
Adds directories to the list of include paths searched by the C preprocessor just like -I arguments
If the directory contains header files in which you want to include all dependency scanning and testing then they should be added with the CPPATH construction variable. Notice how the -I preprocessor argument is not used in this case. It will be added automatically when the values are added to the CCFLAGS variable by SCons. env.Append(CPPPATH=["$QT_SYSTEM/include"]) If the directory contains packages that change very seldom and you do not wish to do any dependency scanning or testing then they should be added directly to the CCFLAGS construction variable. In this case, the -I flag is now required because these arguments are sent directly to the compiler. env.Append(CCFLAGS=["-I$QT_SYSTEM/include"]) Available construction variables The following are some of the commonly used construction variables that you might set in the SConscript files. CCFLAGS CPPDEFINES CPPFLAGS CPPPATH CXXFLAGS FORTRANFLAGS LIBPATH LIBS LINKFLAGS BUILDTYPE COMMANDPRINT C and C++ compiler options C preprocessor defines C preprocessor options C preprocessor include paths C++ compiler options FORTRAN compiler options Link library paths Link libraries Link program options Optimized or debug build selection Level of build output desired
For a complete list of the available construction variables you can run the following command from inside any SCons source tree. scons h
Installing files The SCons will automatically install certain file types into the APPS_LOCAL directory when they are found in the source tree. Examples of these include IDL files and any XML files. The following routine can be used to explicitly install other files that are not supported. env.InstallFile(files,installdir,aliases=None,newname=None) Propagating commands to subdirectories As discussed before, changes made to the values of construction variables are local to the directory they are made in. Sometimes you may wish for these changes to apply for the current directory and all of it subdirectories. The env.Propagate routine allows you to do this. env.Propagate(routine,*args,**keywords) The arguments to this routine are the name of any other environment routine and its arguments. For example, the following will change the C compiler flags in the current directory and all subdirectories. # Change only in current directory env.Append(CCFLAGS=["-wd810"]) # Change in current directory and all subdirectores env.Propagate(env.Append,CCFLAGS=["-wd810"]) Skipping files and directories A number of routines are provided which allow you to skip files or directories that the build system would normally process. env.SkipFiles(names) env.SkipSubDirs(dirs) env.SkipThisDir()
SCons Command
The simplest way to run SCons is from the root of the source tree. The following command will cause the entire tree to be built. scons Or to build only selected components you simply add the name of the component. scons HelloWorldCPP
You can also use other targets on this command. A common example would be if you are working in the source directory for a component, for example HelloWorldCPP, and you wanted to rebuild the HelloWorldCPP shared library. You can use the following command to accomplish this. scons D HelloWorldCPP
SConscript file.
The build processes each file in the directory and creates build actions for it if the file type is
supported. The file is ignored if the type is not supported. As the build actions are created, they are linked together into a global dependency tree containing all of the relationships of the files in the source tree and the targets that they generate. As each directory is processed, a list of object files created is maintained. If the build system determines that an object library, shared library or main program is to be built, this list of object files is used. If none of these is built in the directory, then the list of object files is passed back to the parent directory to be added to its list of object files. This way, whenever an object library, shared library or main program is built, it will contain all of the object files from the current directory and any unused objects from its subdirectories. Dependency processing SCons automatically scans the source files for any implicit dependencies they have and adds them to the global dependency tree. Examples of these are the include files a C++ source file references. Executing build commands After all of the dependencies have been determined, SCons checks if any of the dependencies are out of date. By default, MD5 checksums are used to determine when a dependency has changed instead of file timestamps. This is so clock differences between file servers do not affect the build process and allow SCons to rebuild only the minimal files required. It is important to note that no targets are built during configuration file and dependency processing phases. The build commands such as env.BuildProgram and env.BuildSharedLibrary found in SConscript files are only used to create entries in the global dependency tree. The actual targets are not built until the final phase of the build, using the information in the dependency tree. This is important because any Python code you add to SConscript file will be executed before any actual targets are built.
There is no way to control the exact order in which these targets are built. The only thing that can be assured is that all dependencies for a target will be built before the target.
It is also sometimes desirable to see the actual values of various construction variables that will be used for the build. This can be done by using the name of the construction variable as the target on the SCons command line. scons CPPATH This command will not perform a build, but will instead traverse the source tree and show the default value for the CPPATH construction variable and any directory that has a different value. The printing of construction variables values is only triggered when all of the targets on the command line are upper case. If any of them contain lower case characters then they are assumed normal build targets and a normal build is performed.
of debug or opt. This variable can be specified on the command line but is normally only used in the SConopts or SConopts.user options file. BUILDTYPE=debug BUILDTYPE=opt For convenience, special command line only options are supported to control the type of build. scons scons scons scons debug=yes debug=no opt=yes opt=no
There is no difference between specifying debug=yes or opt=no, on the command line. They will both generate a debug compile. Similarly, debug=no and opt=yes will both generate an optimized compile. It should also be noted that the debug and optimize compile options only change the compiler options that are used. No special preprocessor defines are generated to indicate which is being used. If these are desired, you need to add them yourself. This was done because large amounts of undesired debug output can be generated if all programmers are triggering off the same preprocessor values
Index
SCA Framework Users Guide (DEV)
A
IN DE X Index
CDL, 49 CDL Specifications, 89 Code skeletons, 28 Component metadata, 351 Configuration files, 409 Constant expressions, 55 Constants, 70 Creating client, 35
Define, interface, 21 Defining component, 26 Directory trees, 405 Dynamic arrays, 111
IDL, 49 IDL compiler command, 96 IDL Langauage, 11 IDL specification, 57 IDL to .Net mapping for arrays, 201 mapping for basic types, 196 mapping for constants, 215 mapping for enumerated types, 199 mapping for exceptions, 217 mapping for Identifiers, 194 mapping for interfaces, 216 mapping for modules, 195 mapping for SCA Components, 227 mapping for SCAAny, 206 mapping for SCAResult, 211 mapping for SCATypeCode, 205 mapping for sequences, 203 mapping for string types, 198 mapping for structures, 200 mapping for type aliases, 204
Genskeleton command, 28
IDL to C++ mapping for arrays, 110 mapping for basic types, 106 mapping for constants, 130 mapping for enumerated types, 108 mapping for exceptions, 137 mapping for identifiers, 104 mapping for interfaces, 131 mapping for modules, 105 mapping for SCA components, 150 mapping for SCA services, 141 mapping for SCAAny, 121 mapping for SCAResult, 127 mapping for SCATypeCode, 120 mapping for sequences, 116 mapping for string types, 107 mapping for structures, 109 mapping for type aliases, 119 IDL to Java mapping for arrays, 161 mapping for Basic Types, 156 mapping for constants, 177 mapping for enumerated types, 159 mapping for exceptions, 180 mapping for identifiers, 154 mapping for interfaces, 178 mapping for modules, 155 mapping for SCA components, 189 mapping for SCA services, 184 mapping for SCAAny, 168 mapping for SCAResult, 173 mapping for SCATypeCode, 167 mapping for sequences, 164 mapping for string types, 158 mapping for structures, 160 mapping for type aliases, 166 mapping for unsigned data types, 157
IDL to Python mapping for arrays, 240 mapping for basic types, 234 mapping for constants, 252 mapping for enumerated types, 237 mapping for exceptions, 258 mapping for identifiers, 232 mapping for interfaces, 253 mapping for modules, 233 mapping for SCA components, 262 mapping for SCA services, 261 mapping for SCAAny, 244 mapping for SCAResult, 248 mapping for sequences, 241 mapping for structures, 238 mapping for type aliases, 242 mapping for TypeCode, 243 SCA module, 263 IDL type definitions from Python, 264 IDLTypes.jar, 190 Interface body, 79 Interface header, 78 Interface inheritance, 79 Interface, programming, 7 Interfaces and operations, 78 Interfaces, define, 21
INDEX 425
Message files, 271 MessageDispatcher, 316 Metadata, 351 Multi-Threaded application, 341 Multi-Threaded services, 339
O P
Operation declaration, 79
Platform support, 16 Prefix changing, 365 Preprocessing, 56 Programming, 7 Python scripts, 267
SCA-IDL compiler, 96 SCAIService interface, 83 SCAResult for error handling, 305 SCASequence, 66 SCAString, 66 SCAWString, 67 SCons, 418 SConscript, 414 Scoping rules, 76 ScriptBroker, 267 SDL, 24, 49 SDL Specifications, 86 Sequences versus arrays, 68 Service manager, 373 Service options, 92 Source files, 49
Q R
Qualified names, 75
Template types, 66 Testing SCA components, 393 Text translation service, 278 Thread safety, 333 Threading infrastructure, 338 Types, 158 IDL, 58
SCA component, 13 SCA component declaration, 94 SCA exceptions for error handling, 294 SCA framework, 14 language support, 15 platform support, 16 SCA framework / JVM interaction, 190 SCA interfaces, IDL language, 11 SCA kernal, 366 SCA kernal using, 366 SCA kernel, 13 SCA overview, 5 SCA services, 12 SCA services catalog, 370 SCA utility program, 389 SCAAny class, 121