ObjectStore Building C++ Interface Applications

Chapter 2

Working with Source Files

This chapter describes the source files you use to build ObjectStore applications.

The topics discussed are

Overview of Source Files

You build an ObjectStore application from the following source files:

Building an ObjectStore application requires the generation of schema information. This is information about the classes of objects the application stores in or reads from persistent memory. ObjectStore generates schema information according to the schema source file that you create. See Creating Schema Source Files.

ObjectStore Header Files

ObjectStore provides header files that you must include in your source code. The ObjectStore features you use determine which header files to include. Be sure to include the files in the given order. You must always include ostore/ostore.hh.
If You Use This Feature Include These Header Files
Any ObjectStore feature

ostore/ostore.hh

Collections

ostore/ostore.hh, ostore/coll.hh

Compactor

ostore/ostore.hh, ostore/compact.hh

Database utilities

ostore/ostore.hh, ostore/dbutil.hh

Metaobject protocol

ostore/ostore.hh, ostore/mop.hh

Relationships

ostore/ostore.hh, ostore/coll.hh, ostore/relat.hh

Schema evolution

ostore/ostore.hh, ostore/manschem.hh, ostore/schmevol.hh

Instantiation Problem with Template Collection Classes

Most AT&T cfront 3.0.1-based compilers have a problem correctly instantiating ObjectStore template collection classes. This is particularly true for HP C++. The problem manifests itself as an error report indicating that one of the following template collection classes is undefined at the time of instantiation:

os_Collection< your_class*>
os_Set< your_class*>
os_Bag< your_class*>
os_List< your_class*>
os_Array< your_class*>
os_Cursor< your_class*>
The problem occurs even if you reference only one of the parameterized types in your application.

ObjectStore defines three preprocessor macros to help you work around this problem:

os_Collection_declare 
os_Collection_declare_no_class 
os_Collection_declare_ptr_tdef 
Use these macros to declare the more common cases of ObjectStore template collection class forward definitions. Doing so works around the instantiation problem.

os_Collection_declare() Macro

The os_Collection_declare macro declares ObjectStore template collection classes that are parameterized by nontemplate classes. For example, if you intend to use os_List<Person*> in your application, you would use a statement of the following form in your source code:

os_Collection_declare(Person);
The macro automatically provides a forward definition of the class Person. There is no need for you to provide a full definition of the class Person at the point in the module where you use the os_Collection_declare preprocessor macro.

os_Collection_declare_no_class() Macro

Because os_Collection_declare is a text-substitution-based preprocessor macro, you cannot use it to work around the instantiation problem for ObjectStore template collection class parameters that are themselves template class instantiations.

In these cases, you must provide a forward definition of the template class and a typedef and use the os_Collection_declare_no_class preprocessor macro instead. For example:

template <int size> class Fixed_Array;
typedef Fixed_Array<5> Fixed_Array_5;
os_Collection_declare_no_class(Fixed_Array_5);
You also use the os_Collection_declare_no_class preprocessor macro to predeclare an ObjectStore template collection class parameterized by a fundamental type. For example, if you intend to use an os_Array<int*> in your application, you would include a statement of the following form in your source module:

os_Collection_declare_no_class(int);

os_Collection_declare_ptr_tdef() Macro

The os_Collection_declare_ptr_tdef preprocessor macro allows you to predeclare an ObjectStore template collection class parameterized by a typedef that names a pointer type. For example, you might define a typedef such as the following:

class Person;
typedef Person * pPerson;
To provide the necessary work-around declarations for the ObjectStore template collection class os_Set<Person*>, you can use a statement of the following form:

os_Collection_declare_ptr_tdef(pPerson);

Required Order of Include Statements

You must ensure that invocations of these macros appear in your source module before <ostore/coll.hh>. The following code works:

#include <ostore/ostore.hh>
os_Collection_declare(Person);
os_Collection_declare(Employer);
#include <ostore/coll.hh>
The following code does not work:

#include <ostore/ostore.hh>
#include <ostore/coll.hh>
os_Collection_declare(Person);
os_Collection_declare(Employer);
ostore/semoptwk.hh
If your ObjectStore C++ application uses ObjectStore template collection classes and includes ostore/mop.hh or ostore/schmevol.hh or both, then you must also include ostore/semoptwk.hh. This header file provides invocations of the os_Collection_declare macro for types used in ostore/mop.hh and ostore/schmevol.hh.

As with your own invocations of the os_Collection_declare macro, the inclusion of ostore/semoptwk.hh must precede the inclusion of ostore/coll.hh in your source module. Also, if you include schmevol.hh or mop.hh, include them before coll.hh. For example:

#include <ostore/ostore.hh>
os_Collection_declare(Person);
os_Collection_declare(Employer);
#include <ostore/semoptwk.hh>
#include <ostore/schmevol.hh>
#include <ostore/coll.hh>
If you are not using ObjectStore template collection classes, it is not necessary for you to explicitly include ostore/semoptwk.hh even if your application uses ostore/mop.hh or ostore/schmevol.hh or both. Similarly, it is not necessary for you to explicitly include ostore/coll.hh, even though the schema evolution and metaobject protocol interfaces use ObjectStore template collection classes. When you are not using ObjectStore template collection classes in your application, the existing structure of the ostore/mop.hh and ostore/schmevol.hh header files is sufficient.

With Visual C++, there are additional considerations for building applications that use collections. See Symbols Missing When Linking ObjectStore Applications.

Determining the Types in a Schema

The schema source file determines the types that are in a schema. In the schema source file, you use a macro to mark the types to be included in the schema. After you run the schema generator, not only are these types in the schema, but any types that are reachable (defined below) from these types are also in the schema.

In other words, the types that you mark plus the types reachable from those types equal the types represented in the schema.

The types that you mark are the types on which you can perform persistent new. However, if you specify the -make_reachable_source_classes_persistent (-mrscp) option when you generate the schema, you can also perform persistent new on types in the schema source file that you did not mark. See -mrscp on the next page. See also Generating an Application or Component Schema.

Which Types to Mark

As a minimum, you should mark the following types:

It is not necessary to mark ObjectStore classes except for collection classes.

Which Types Are Reachable

Type T is directly reachable from class C in each of these situations:

Furthermore, a type that is directly reachable from a directly reachable type is considered to be reachable from the original type. For example, if T is directly reachable from C, and X is directly reachable from T, then X is reachable from C. There are no limits on the chain of reachability.

If You Are Not Sure a Type Is Reachable

When you want a type to be in the schema but you are not sure if the type is reachable, you can do either of the following:

Choosing Whether to Mark Types or Specify -mrscp

If you mark a type, then you can perform a persistent new on that type. If you specify -mrscp (the shortened form of -make_reachable_source_classes_persistent) when you generate the schema, you can perform a persistent new on any type defined in the schema source file. Since the end result is the same, how do you choose whether to mark a particular type or to allow it to be persistently stored only through specification of -mrscp?

For each type that you mark, the schema generator does a certain amount of processing so that the type can be persistently stored. For each type that is reachable, but that is not itself marked, the schema generator does less processing, and the processing is not sufficient to allow persistent storage.

Suppose you specify -mrscp at schema generation time and then during execution you persistently store a type that you did not mark. At run time, additional processing is required so that you can persistently store such a type.

The benefit of specifying -mrscp is that it allows you to perform a persistent new for a type that you did not explicitly mark. The drawback is greater execution time and executable size overhead.

You should mark types that you persistently store. You can specify -mrscp in case you forget to mark a type that you persistently store.

Ensuring a Complete Schema Source File

Omissions in the schema source file can cause run-time errors. For example, you might try to persistently store a type that you did not mark or that is not in the schema. To avoid this, use the osexschm utility to ensure that all relevant types are in the schema.

Creating Schema Source Files

The schema source file specifies the C++ classes that your code reads from or writes to persistent memory. You create the schema source file according to a specified format. The schema source file can contain only valid C++ code. It is good practice to compile your schema source file to verify that it is compilable, but compilation is not required.

When you run the schema generator, you specify the name of the schema source file. Your executable program does not include the schema source file. The schema source file is only for input to the schema generator (ossg).

Schema Source File Format

Before you create the schema source file, determine the types in your application that you are going to mark in the schema source file. Use the information in Determining the Types in a Schema to help you decide. Then follow these steps to create a schema source file. See Schema Source File Examples for sample code.

  1. Create a text file.

  2. In the text file, specify #include to include ObjectStore header files required by the features you use. The required order is on page 15.

  3. Specify #include to include the manschem.hh file provided with ObjectStore.

  4. Specify #include to include the files that define the following types:

  5. Mark certain included types with a call to the macro OS_MARK_SCHEMA_TYPE.

    Use the information on the previous pages to determine which types to mark. The order in which you mark types does not matter.

    Each call is on its own line and has the format

          OS_MARK_SCHEMA_TYPE( type-name); 
    
    OS_MARK_SCHEMA_TYPE is a preprocessor macro. For additional information about OS_MARK_SCHEMA_TYPE() and OS_MARK_SCHEMA_TYPESPEC(), see ObjectStore C++ API Reference, Chapter 4, System-Supplied Macros.

  6. Mark parameterized types with multiple arguments with a call to the macro OS_MARK_SCHEMA_TYPESPEC.

    This macro is similar to OS_MARK_SCHEMA_TYPE in syntax and function, except that you must enclose the type and its arguments in parentheses.

    Each call is on its own line and has the format

          OS_MARK_SCHEMA_TYPESPEC(( type-name< x,y>));
    
  7. Save the schema source file.

Placing Calls to OS_MARK_SCHEMA_TYPE in a Function

In previous releases of ObjectStore, you were required to place the calls to OS_MARK_SCHEMA_TYPE or OS_MARK_SCHEMA_TYPESPEC in a dummy function.

This practice is now discouraged, and becomes obsolete in a future release. Since the default behavior is -skip_function_body_parsing (the -sfbp option), ossg does not see these marked types and prints a warning.

You can override the default using the -parse_function_bodies (-hpfb) option, but in general, placing calls to OS_MARK_SCHEMA_TYPE in a function is not good practice.

Schema Source File Examples

#include <ostore/ostore.hh>
#include <ostore/coll.hh>
#include <ostore/manschem.hh>
#include "ticket.hh" /*defines class ticket*/
#include "passenger.hh" /*defines class passenger*/ #include "schedule.hh" /*defines class schedule*/ OS_MARK_SCHEMA_TYPE(schedule); OS_MARK_SCHEMA_TYPE(ticket); OS_MARK_SCHEMA_TYPE(passenger);
As required, the ostore.hh and manschem.hh files are included. The coll.hh file is included because the application uses collections. For each marked type, the file in which it is defined is included. In this example, three classes, schedule, ticket, and passenger, need to be marked. Each included class is marked on its own line with a call to the OS_MARK_SCHEMA_TYPE macro.

#include <ostore/ostore.hh>
#include <ostore/coll.hh>
#include <ostore/dbutil.hh>
#include <ostore/manschem.hh>
#include "schmdefs.hh"
OS_MARK_SCHEMA_TYPE(drawing);
OS_MARK_SCHEMA_TYPE(view);
OS_MARK_SCHEMA_TYPE(layer);
OS_MARK_SCHEMA_TYPE(coordinates);
This schema source file includes the always required ObjectStore header files (ostore.hh and manschem.hh) along with the coll. hh and dbutil.hh header files, since the application uses collections and database utilities. It then includes the schmdefs.hh file that, in this example, contains the definitions of all classes in the application. The classes defined in schmdefs.hh that need to be marked are drawing, view, layer, and coordinates. Each of these classes is marked on its own line with a call to the OS_MARK_SCHEMA_TYPE macro.

Schema Source File Must Be Compilable

The schema source file can contain only valid C++ code. However, you should not use the schema generator to find errors in your schema source file. It is good practice to make sure that your schema source file compiles before you use it as input to ossg.

This is true even if you are using a C compiler. If you have valid C code that is invalid C++ code, you must modify it or hide it from the schema generator. For example, here is a valid C struct definition:

struct class
{
};
This is invalid C++ because class is a reserved keyword in C++. To hide such code, use comments or the _ODI_OSSG_ macro. See Hiding Code from the Schema Generator for more information about using this macro.

If you are generating persistent C types, for example, structs, and compiling them using a C compiler, then you must include those C types in the schema source file.

Most C constructs (including struct definitions) are valid C++ constructs, but some are not. If invalid C++ constructs appear in your included header files, you might have to use preprocessor directives to ensure that they are not visible in the schema source file. For example, specify the following in the schema source file to isolate the invalid code:

#ifndef    _ _ cplusplus
      /* invalid C++ code */
#endif

OS/2 Notes

Create a schema header file, also called a class definition file. In this file, place the definitions for all classes that you want in the application schema. While only one schema header file is allowed, it can contain #include statements for files that actually contain the class definitions. Include the schema header file in the schema source file. When you invoke ossg to generate the application schema, you specify the -cd option with the name of this class definition file.

The reason for the class definition file is that the icc compiler does not make public a number of symbols that ObjectStore needs to link your persistent data to your program at run time. However, these symbols are available to files that include the class definitions. So the application schema source file produced by the schema generator must include the class definitions for all persistent classes your application might need. Therefore, an OS/2 schema source file contains

Sample OS/2 schema source file
For example, suppose an application's classes are defined in

You must create a single header file that contains

#include "mydefs1.hh"
#include "mydefs2.hh"
#include "mydefs3.hh"
If you name this header file alldefs.hh, you must pass -cd alldefs.hh to ossg. The schema source file would look something like this:

#include <ostore/ostore.hh>
#include <ostore/manschem.hh>
#include "alldefs.hh"
OS_MARK_SCHEMA_TYPE(foo)
OS_MARK_SCHEMA_TYPE(bar)
The schema generator then marks the schema types in the usual way. In addition, it places code in the generated file that arranges for vtbls for all the affected classes to be defined in the schema object file. This means they are accessible to ObjectStore at application run time. (Other platforms provide mechanisms for explicitly instantiating vtbls.)

When You Modify a Source File

Suppose you have already generated the schema for your application, and then you modify a type description. You must regenerate the application schema after changing

You must also regenerate the application schema when you add a library that has a library schema to your application. If you delete a library, you should regenerate the schema to remove the clutter.

You can set up rules in a makefile to automatically regenerate schema when required. Changes to source files have the same compiling and linking implications as they would in any other application.

If you make an incompatible change to the schema, and the old definition is present in the schema database you are regenerating, schema generation fails with an error message identifying the incompatible change. You can choose from three methods for handling this situation:



[previous] [next]

Copyright © 1998 Object Design, Inc. All rights reserved.

Updated: 03/26/98 19:57:41