Chapter 2: Basic Programming

 

Overview

To create a database class or application, the basic steps are:

1.  Create source code.

Write .h declaration and .cxx implementation files for your classes and applications.

In these files, use VERSANT statements as necessary and desired and include VERSANT header files as appropriate.

2.  Create and load schema files.

Create a schema capture file, use the schema capture file to create schema description and implementation files, and load the description files into databases.

3.  Compile and link source code.

Compile and link class and application files.

 

Create Source Code for Classes

This section contains rules and procedures for creating persistence capable classes.

 

Class Derivation

Class derivation from PObject

The PObject class is the root of the VERSANT persistent hierarchy. All classes that will have persistent instances should derive from either the VERSANT PObject class or the PVirtual class, and PObject or PVirtual should be a public base class.

If you are going to compare instances in a collection by value, you should derive from PVirtual, which in turn derives from PObject.

You should include <cxxcls/pobject.h> before any persistent class is defined.

Class definition in a file

Classes derived from PObject should be defined at file-scope level.

Class derivation should avoid multiple copies of PObject

If you are using multiple inheritance, use virtual inheritance where necessary to avoid having multiple copies of PObject in your class.

Class derivation from a link class is not allowed

Do not derive a class from a link, link vstr, bilink, or bilink vstr class.

Class derivation with multiple non-virtual inheritance is not allowed.

There can be no multiple non-virtual inheritance from the same class.

Class virtual inheritance problems

Many compilers have problems understanding multiple virtual inheritance, which means that you may have to use single virtual inheritance in some situations. Also, associated with the use of C++ in general, there are problems with classes with no non-inline virtual methods.

If you create a class that has virtual methods or has any superclass that has virtual methods, and does not have any non-inline virtual methods, then you may see a message at link time stating that there are undefined symbols for:

"__vtbl__yourClassname"

The workaround is to implement a virtual destructor, even if it does nothing:

1.  Declare a virtual destructor as the first method defined in the class <type>.h declaration file.

2.  Implement the destructor as a null operation in the class .cxx implementation file.

For example, for myClass.h:

// Header file "myClass.h"
class myClass : public aBaseClass
{
    public:
        virtual ~myClass();
...

}

Then, in myClass.cxx:

// Implementation file "myClass.cxx"

myClass::~myClass() { }

Template class arguments

C++/VERSANT template classes include the vstr, link, link vstr, bilink, bilink vstr, and collection classes.

If a class is a template class, its arguments must either be another class or a portable elementary type.

 

Attribute Types Allowed

Persistent capable class can have transient attributes and Transient Base types.

Attributes of all persistent classes should be one of the following types.

Elemental type

You can use VERSANT elemental types, such as o_1b, o_u1b, o_2b, o_u2b, o_4b, o_u4b, o_8b, o_u8b, o_float, and o_double, for attributes.

You can also use any conventional elementary types, such as char, int, and float. However, if you use these basic types, there may be portability problems.

Class

You can use any class as an attribute, either a class you create or one of the VERSANT supplemental classes. There are VERSANT supplemental classes for strings, collections, container, and dates.

struct

You can use any struct as an attribute.

Inside a database, there is no difference between a class or struct. Accordingly, in the context of creating persistent classes, the term "class" means either a class or struct.

Fixed size array

You can use fixed sized arrays of either elementary types or classes for attributes. For example: int array[10] and MyClass array[3].

In C++/VERSANT, you can have elemental arrays of up to three dimensions; arrays on non-elemental types are limited to two dimensions.

Boolean type

You can use the VERSANT boolean type o_bool as an attribute.

Vstr of elemental types

You can use the Vstr<type> and VstrAny classes, which VERSANT provides for variable length storage of elementary types.

For string operations, you should use one of the VERSANT string classes instead of Vstr<char>.

Link

You should use links instead of pointers when storing a reference to another object as the values of pointers are not stored in a database.

Link types are Link<type> and LinkAny.

Vstr of links

You should use variable length vstrs of links instead of arrays of pointers when storing multiple object references.

Link vstr types are LinkVstr<type> and LinkVstrAny. VERSANT also provides array, set, list, and dictionary collection classes for variable length storage of links.

Date and Time

You can use the following date and time types as attributes: o_date, o_interval, o_time, o_timestamp.

 

Attribute Types Not Allowed

You should not use the following as an attribute of a persistent class.

const cannot be used as an attribute

You cannot create attributes declared with the const keyword.

enum cannot be used as an attribute

The meaning of the enum type is compiler specific. You should avoid using it, as it may be interpreted either as 2 bytes or 4 bytes, which means that your code will not be portable.

Pointers or references cannot be used as attributes

Persistent classes cannot have attributes containing either memory pointers (*) or references (&) that point or refer to a persistent object. Instead, you should use link syntax for persistent references to persistent objects.

bitfield cannot be used as an attribute

You cannot create attributes containing bitfields.

union cannot be used as an attribute

You cannot create attributes whose type is union or anonymous union.

char cannot be used as an attribute

VERSANT assumes that char is implemented as signed char. Because of variations in how compilers interpret char, we recommend that you use signed char or the VERSANT data type o_u1b instead of char.

long double cannot be used as an attribute

VERSANT assumes that long double is implemented as double. Because of variations in how compilers interpret long double, you should not create attributes with the long double type and should instead use the VERSANT data type o_double.

static attribute values cannot be stored

Static attributes can be used in a class definition, but their values will not be stored to a database.

 

Portable Attribute Types

Class attribute type translation

When you define an attribute of an elementary type, VERSANT may translate it when its values are stored.

For example, to a compiler the char and signed char might be different, while in a database they both have type char.

In general, the databases translations of elementary types mean that, for portability, you should use the VERSANT supplied elementary types o_type whenever possible. In any case, you should consider database type translation when designing a persistent class.

VERSANT makes the following translations of elementary data types:

Source file type

Database type

o_1b char
o_u1b o_u1b
o_2b o_2b
o_u2b o_u2b
o_4b o_4b
o_u4b o_u4b
o_8b o_8b
o_u8b o_u8b
o_float o_float
o_date o_date
o_double o_double
o_interval o_interval
o_time o_time
o_timestamp o_timestamp
char char
signed char char
unsigned char o_u1b
short o_2b
signed short o_2b
unsigned short o_u2b
int o_4b
long o_4b
long long o_8b
__int64 o_8b
signed int o_4b
signed long o_4b
unsigned o_u4b
unsigned int o_u4b
unsigned long o_u4b
unsigned long long o_u8b
unsigned__int64 o_u8b
float o_float
double o_double
long double o_double
type* o_ptr

source_file_type[n]

database_type[n]

 
Note that VERSANT assumes that the long double type is implemented as an eight byte double. If this is not true for your compiler, do not use the long double type in a persistent class whose instances are to be portable.

If a C++ compiler implements char as unsigned char, a program might have different behavior from other systems that implement char as signed char. A rule of thumb is to use o_1b for char or signed char and use o_u1b for unsigned char.

You should not use enum, because its interpretation is compiler specific.

Template argument type translation

For template classes, VERSANT translates the argument type:

Source file type

Database type

Vstr<source_type> Vstr<database_type>
Link<classname> o_object
LinkVstr<classname> Vstr<o_object>

Class<source_type>

Class<database_type>

 
Argument translations for template classes follow the rules for translations of individual data types except for the following cases:

Class<char> Class<o_1b>
Class<signed char> Class<o_1b>
Class<unsigned char> Class<o_u1b>
Class<o_1b> Class<o_1b>

Class<o_u1b>

Class<o_u1b>

 
The argument translations mean, for example, that Class<char>, Class<signed char>, and Class<o_1b> are stored as the same database class Class<o_1b>.

Embedded classes are flattened until they are a series of the above types.

 

Class Constructor

Class public constructor

Classes derived from PObject should have at least one public constructor.

Dirty in a constructor

Do not call dirty() in a constructor.

Throwing exceptions in a constructor

Avoid throwing exceptions in the constructor.

When an exception is thrown in a constructor, there is no easy way to deallocate the memory occupied by the object, as most compilers do not guarantee that operator delete will be called to free the memory allocated for the object.

Since memory for a persistent object is allocated in the application memory cache, throwing an uncaught exception in the constructor may lead to the object being stored in the database as garbage.

If the possibility of an exception being thrown exists, then surround the code with try-catch blocks. For example, consider the following constructor for class A:

A::A()
{
   try
   {
      /* Your initialization code here */
   }
   catch (…)
   {
      delete (PObject *)this;
   }
}

In this case, PObject::operator delete() will take care of the details (navigate to the top of the object) and delete it either from cache or heap (depending on whether the object is persistent or transient) for you.

Class constructor macros may be necessary

A class constructor macro is required if you want to establish the type of a transient instance at run time or use a link to a transient object.

A class contructor macro must be the first statement in the constructor body.

Depending on whether you have a class or a template, and depending on the number of template arguments, the class constructor macro takes a different form. Following are the class constructor macros.

VPP_CLASS_CONSTRUCTOR1()

VPP_CLASS_CONSTRUCTOR1(type);

Constructor macro for non-template classes and template classes with one argument (including bilink, bilink vstr, and collection classes.) Substitute the name of the class for the type parameter.

For example, for class A:

A::A()
{
   VPP_CLASS_CONSTRUCTOR1(A);
   ...
}

VPP_CLASS_CONSTRUCTOR2()

VPP_CLASS_CONSTRUCTOR2(type);

Constructor macro for template classes with two arguments. Substitute the name of the class for the type parameter.

For example, for class A:

template < classB, classC >
A<B,C>::A()
{
   VPP_CLASS_CONSTRUCTOR2(A<B,C>);
   ...
}

vppConstruct()

vppConstruct(this);

Constructor macro for template classes with more than two arguments. This macro must be invoked on a class object.

For example:

template <class X, class Y, class Z>
Object3<X,Y,Z>::Object3()
{
   PClassObject<Object3<X,Y,Z>>::Pointer() ->
   vppConstruct(this);
   .....
}

Methods that require type identification

The following methods require type identification:

Macros

AS() cast object
assertClass() assert class derivation
assertSpecies() assert class

L_AS()

cast object

 
PObject methods and macros

as() cast object
class_name() get class name
deleteobj() delete object
is_a() is instance of a class

is_instance_of()

is instance of a class

 
Link<type> methods

as() cast object
class_name() get class name
deleteobj() delete object

is_instance_of()

is instance of a class

 
 

Virtual Methods

Class virtual methods

If a class deriving from PObject contains virtual functions, at least one of the virtual functions must be non-inline.

Virtual methods may need to be redefined

Per normal C++ programming, virtual methods may need to be redefined when you derive new classes.If you are using VERSANT Collection classes, in your class implementation file, you may also need to implement virtual methods to compare objects, specifically hash() and compare(). For specific instructions, see the reference for the Collection type you are using.

 

Activating Objects

You can activate and deactivate objects from classes that inherit directly or indirectly from PVirtual. This allows you to control actions performed when an object enters or leaves the object cache. See the reference to PVirtual in the "Support Classes, Types, and Macros" chapter of the C++/VERSANT Reference Manual.

 

Transient Attributes

A "persistent capable class" is a class that derives from PObject or PVirtual, which means that instances can be saved in a database. Base classes of persistent capable classes are called "persistent reachable classes."

By default, the states of all non-pointer type attributes in an instance of a persistent capable class are stored when an object is saved to a database, and, by default, attributes of all base classes of persistent capable classes are stored in the database.

You may not want to save the states of all attributes:
•    if it is not necessary,
•    if the attribute type cannot be represented properly in a database, and/or
•    if you have created the class primarily to inherit functionality rather than data.

To exert fine grain control over what attributes of a persistent class are to be stored in a database and which base classes of a persistent capable class should be known to databases, you can use an O_TRANSIENT directive in your schema implementation file. This directive allows you to declare specific attributes and types to be transient. (Schema implementation files are discussed in the following section "Implementation File.")

Transient attributes

To declare attributes of a particular class to be transient, the general form of the O_TRANSIENT directive is :

O_TRANSIENT(class_name, "attribute1", "attribute2",....);

Declaring an attribute as transient means that it will be transient:

•    in instances of the class

•    in instances of derived classes

Transient class

To declare an entire class type to be transient, the general form of the O_TRANSIENT directive is:

O_TRANSIENT( class_name );

When a class type is declared transient attributes of this type are transient:

•    in all classes where this class type is used as an attribute type.

Caution — Please use the transient class directive with care. The implications of declaring a certain class type to be transient is not limited to one persistent capable class but to all persistent capable classes in the application which have attributes of this class type.

Based on the O_TRANSIENT directive, the schema compiler will generate appropriate code for a persistent capable classes that has transient attributes and/or transient base classes. Since transient attributes and base classes are not part of the persistent capable class definition in a database, definitions of persistent capable classes can be changed to add transient attribute or transient base types without affecting the existing database schema or the class signature. In effect, this makes the attribute invisible to the database.

For example consider the header file transient.h.

/* transient.h */
class Transient_Base
{
   int x;
   int y;
   dothis();
   dothat();
};

class Transient_Type
{
   int i;
   int j;
};

class Persistent_Class : public PObject, public Transient_Base
{
private:
     PString note;
     CString Transient_Attr1;
     CString Transient_Attr1;
     Transient_Type Tran_type_attrib;
};

For the persistent capable class Persistent_Class, if attributes Transient_Attr1 and Transient_Attr2 are to be declared transient, insert the following schema compiler directive:

O_TRANSIENT(Persistent_Class, "Transient_Attr1", "Transient_Attr2");

This tells the schema compiler that for the class Persistent_Class, attributes named Transient_Attr1 and Transient_Attr2 are transient. The schema compiler will then generate appropriate code in the schema.cxx file. It is important to note that in all sub classes of Persistent_Class, Transient_Attr1 and Transient_Attr2 will be considered transient. The attribute name is specified as a string literal and should always be in quotes.

Within this application, for all persistent capable classes, attributes of Transient_Type can be considered transient by using the directive:

O_TRANSIENT(Transient_Type);

This directive specifies that an attribute of Transient_type in any persistent capable class should be considered transient.

The schema implementation file, schema.imp, for the above example would look like this:

#include <cxxcls/pobject.h
#include "transient.h"
O_CAPTURE_SCHEMA(Persistent_Class);
O_TRANSIENT(Transient_Base);
O_TRANSIENT(Transient_Type);
O_TRANSIENT(Persistent_Class, "Transient_Attr1", "Transient_Attr2");

 

Create Source Code for Applications

 

Managing Objects

Creating an object

To create a persistent object, use a form of the O_NEW_PERSISTENT() macro. Forms of O_NEW_PERSISTENT() are:

O_NEW_PERSISTENT( Type )( arguments );
O_NEW_PERSISTENT( Template<Type> )( arguments );
O_NEW_PERSISTENT1( dbType ) ( arguments );
O_NEW_PERSISTENT1( dbTemplate<Type> ) ( arguments );
O_NEW_PERSISTENT2( Template<Type1,Type2> )( arguments );

For example:

A* a = O_NEW_PERSISTENT(A)(arguments)
B* b = O_NEW_PERSISTENT(B<int>)(arguments)

The O_NEW_PERSISTENT() and O_NEW_PERSISTENT2() macros will create the new object in the default database. The default database is initially the session workspace specified when you begin a session. Once a session has been started, you can change the default database with set_default_db().

The O_NEW_PERSISTENT1() macro will create the new object in a specified database.

See also the reference to O_NEW_PERSISTENT() in the chapter "Support Classes, Types, and Macros" in the C++/VERSANT Reference Manual.

Deleting an object

To delete a transient or persistent object, you can use the C++/VERSANT deleteobj() method or the C++ delete operator.

It is always safe to use PObject::deleteobj() to delete either a persistent or transient object even if you do not know the exact type of the object to be deleted. Also, deleteobj() can be used to delete an object that is referenced by a link.

If you know the exact type of the object to be deleted, you can use the C++ delete operator normally to delete either a transient or persistent object. Operator delete cannot be used to delete the target of a link.

The delete syntax is the same for both persistent and transient objects. If the object to be deleted is persistent, it will be deleted both from memory and from its database. If the object to be deleted is transient, it will just be deleted from memory.

All normal C++ rules apply when you use operator delete:

The operand of delete should be a pointer, which may require casting.

The C++ compiler will supply the size when delete is called.

You cannot delete a pointer to a constant, but you can delete a null pointer.

Do not use deleteobj() or the delete operator to delete statically allocated objects, stack-allocated objects, or objects that are imbedded in other objects.

If a destructor has been defined for an object, delete will call it.

For example, to delete a pointer partPtr and a link partLnk:

Part*      partPtr;
Link<Part> partLnk;
delete     partPtr;
delete     (Part*) partLnk;

Updating an object

For changes to be saved to a database, you should mark changed objects with the dirty() method in PObject.

If the changed object does not already have a write lock, the dirty() method will try to set a write lock. The dirty() method will cause changes to an object to be written to a database if the transaction ends with a commit or checkpoint commit.

As a matter of object programming practice, dirty() statements should normally be in member functions rather than in application code.

Pinning an object

By default, accessing a persistent object pins it in real or virtual memory, which means that it cannot be swapped to the database.

You can override the default either with explicit pin and unpin methods for individual objects or with pin regions for groups of objects.

For more information, see the topic "Managing Memory" in the VERSANT Concepts and Usage Manual.

Initializing an object

For information on initializing objects with transient data, see the reference to the init() method in PVirtual.

For general information on redefining C++/VERSANT mechanisms that create, update, activate, delete, and fetch objects, see the chapter "Customizing C++/VERSANT."

 

Method Restrictions

Following are usage restrictions on methods.

Session firewall for VERSANT

You must start a session before using VERSANT functions, methods, and persistent objects.

The only exceptions are functions and methods that set session parameters.

Methods for standard sessions only

The following methods can be used only in a standard session that has been started without the O_NEST option:

acquireplock()
checkin()
gcheckout()
monitorobj()
pcheckout()
releaseplock()
savepoint()
undosavepoint()
xact() with O_CHECKPOINT_COMMIT or O_COMMIT_WITH_CLEAN_CODS

The following methods can be called only in a flat transaction in a standard session or in a childless session root transaction of a session started with the O_NEST option:

archive()
migrateobj()
migrateobjs()
releaseobj()
resetoct()
restore()
select()
xact() with O_COMMIT or O_ROLLBACK
zapcods()

Methods for nestable transaction sessions only

The following method can be used only in a nestable transaction session and only if a "restricted" method has not been called. By "restricted" is meant those methods that can be called only in a flat transaction or in a childless session root transaction.

begintransaction()

Methods for shared sessions only

The following methods can be used only in a shared session and only if a "restricted" method has not been called. By "restricted" is meant those methods that can be called only in a flat transaction or in a childless session root transaction.

forkprocess()
joinsession()

 

Invoking Class Methods

An instance of PClass is referred to as a class object. A class object contains the meta-schema information about a class.

Some C++/VERSANT methods, such as some query methods, should be sent to a class object. Class objects are accessed with methods on PClassObject<type>.

Class object syntax

Syntax for a C++/VERSANT class object of class type is:

PClassObject<type>::Object()

If your schema contains a class named Object, you can use the equivalent get_Object() method to access a class object.

To invoke methods on PClass, you can use Object() or get_Object() to send messages to a class object with the dot operator.

PClassObject<type>::Object().method()

For example:

Employee::find_all()
{ return PClassObject<Employee>::Object().select(
    NULL, FALSE, NULL_PREDICATE);

}

Or:

Employee::find_all()
{ return PClassObject<Employee>::get_Object().select(
    NULL, FALSE, NULL_PREDICATE);

}

Class object pointer syntax

Syntax for a pointer to a class object of class type is:

PClassObject<type>::Pointer()

To invoke methods on PClass, you can use Pointer() or get_Pointer() to send messages to a class object with the arrow operator.

For example:

Employee::find_all()
{ PClass* cp = PClassObject<Employee>::Pointer();
    return cp -> select(...);

}

Or:

Employee::find_all()
{ PClass* cp = PClassObject<Employee>::get_Pointer();
    return cp -> select(...);

}

 

Invoking Database Methods

The VERSANT PDOM (Persistent Distributed Object Manager) class provides an interface for database management methods.

Before you can use database methods in PDOM, create a transient instance of PDOM and assign it to a global variable named dom. The dom variable is predefined by the C++ compiler. Database methods are sent to the dom instance using the arrow operator.

For example:

::dom = new PDOM;

::dom->beginsession(DBNAME,NULL);

There should be only one global instance of PDOM named dom in the executable.

There are a few overlapping methods within the PDOM class. These relate to short transaction commits, checkpoint commits, and rollbacks.

For example, to end a short transaction you can use commit(), rollback(), or xact(). If you are a beginning user, you should use the more general form xact() until you are aware of the defaults implied by use of the more simple syntax of commit() and rollback().

 

Invoking Object and Link Methods

To manage objects and links, VERSANT defines three types of methods:

Object state methods

Methods related to the state or type of a C++ object are defined on the PObject class.

For example, the dirty() method, which marks an object as modified, and the is_a() method, which returns the C++ type of an object, are defined only on PObject.

Database status methods

Methods related to the database status of an object are defined both on the PObject class and on the link types, Link<type> and LinkAny.

For example, the deleteobj() method, which deletes an object from a database, is defined on PObject, Link<type>, and LinkAny.

Link as object methods

Methods that treat links as objects are defined on the Link<type> and LinkAny classes.

For example, the is_null() method, which determines whether a link is null, is defined only on Link<type> and LinkAny.

Standard C++ always assumes that an object is in memory and allows you to invoke methods using either object or pointer expressions to specify the object. In C++/VERSANT, for methods defined on PObject, the standard C++ rules apply.

If you have:

An object

Use the dot operator. The object will already be in memory.

A reference

Use the dot operator; the object will already be in memory.

A pointer

Use the arrow operator; the object will already be in memory

 
For example, the VString class derives from PObject, so normal C++ rules apply when you have an object, reference, or pointer:

An object

   VString obj;
   o_u4b n = obj.get_length();

A reference

   VString& ref;
   o_u4b n = ref.get_length();

A pointer

   VString* ptr;
   o_u4b n = ptr ->get_length();

C++/VERSANT adds the concept of links, which are the database equivalent of pointers.

Links allow you to access objects regardless of their location, whether they are in memory, in their creation database, or in a database to which they have been moved.

Although they act like pointers, links are implemented in C++ using the VERSANT classes Link<type> and LinkAny. Link related methods are also defined in the vstr classes LinkVstr<type> and LinkVstrAny.

You can treat links as either pointers or link objects.

Link as a pointer

To invoke a method defined on the target object, treat the link as a pointer and use the arrow operator.

VERSANT will then find the target object, bring it into memory if needed, set the default lock on the object, pin the object in virtual memory, and treat the object as a standard C++ object.

Link as an object

To invoke a method defined on the link itself, treat the link as an object and use the dot operator.

In this case, if the method does not require it, VERSANT will not bring the target object into memory. If the method acts on the target object, VERSANT will treat the object as a database object, which means that C++ semantics may not apply (as in the deleteobj() example below).

The following examples show how to use links as pointers or objects. For the examples, assume that you have defined a link Link<PObject> lnk.

A method on PObject

The dirty() method is defined only on PObject, so you should use the arrow operator. The arrow operator will bring the object into memory if it is not already in memory:

   lnk->dirty();

A method on PObject, Link<type>, and LinkAny

The compareobj() method is defined on both PObject and on the link classes, so you can invoke it by treating a link either as a pointer or as an object. Because the compareobj() does not need to bring the object that is the target of the link into memory in order to make a comparison (it just compares link contents), performance varies depending upon whether the arrow or dot operator is used on a link.

The arrow operator will bring the object into memory if it is not already in memory:

   o_4b r = lnk->compareobj(lnk2);

The dot operator will not bring the object into memory:

   o_4b r = lnk.compareobj(lnk2);

Another method on PObject, Link<type>, and LinkAny

The deleteobj() method is also defined on both PObject and the link classes. The arrow operator brings the object into memory and invokes the C++ destructor:

   lnk->deleteobj();

The dot operator does not bring the object into memory and does not invoke the C++ destructor:

   lnk.deleteobj();

A method on Link<type> and LinkAny

The is_null() method determines whether a link is null and is defined only on the link classes. This means that you should use the dot operator to invoke is_null():

   o_bool r = lnk.is_null();

 

Type Conversion

Converting Data Types

Following are key data types that you will need to convert:

Link<type>

A typed link to an object created with C++/VERSANT.

LinkAny

An untyped link to any object. Convertible to/from Link<type>.

Vstr<type>

Typed variable length storage of elemental values.

VstrAny

Untyped variable length storage of elemental values. Convertible to/from Vstr<type>.

LinkVstr<type>

Typed variable length storage of links to objects created with C++/VERSANT.

LinkVstrAny

Untyped variable length storage of links to any objects. Convertible to/from LinkVstr<type>.

o_object

Convertible to/from LinkAny.

o_vstr

Convertible to/from VstrAny.

 
Conversion Mechanisms

Following are methods and global macros that provide run time type checking and conversion among types. They are available when PObject or a class deriving from PObject is included in your file. They are documented in the C++/VERSANT Reference manual.

AS()

AS( derivedClass, baseClassPtr );

The global macro AS() will convert a type downward, return a pointer to a derived class derivedClass when given a pointer to a base class baseClassPtr, and throw an error if not successful. Use AS() to convert downward if you have a pointer. You can also use AS() to cast a link downwards, but L_AS() is faster if you have a link.

L_AS()

L_AS( derivedClass, baseClassLink );

The global macro L_AS() will convert a type downward, return a pointer to a derived class derivedClass when given a link to a base class baseClassLink, and throw an error if not successful.

as()

void* as( PClass* type ) const;

The as() method in PObject, Link<type>, and LinkAny will return a pointer or link to a derived class when given a pointer to a base class.

A pointer to a base class is cast to the specified type if the pointer is to an instance of a superclass of the type, is of that type, or is convertible to a pointer to an instance of a superclass or that type.

Using the as() method in the case of virtual-base multiple inheritance is the same as using the AS() macro and also performs run time error checking.

Upward conversion for pointers is normally automatic. However, you must explicitly cast derived links upward to base links.

Conversions of Links and Pointers

For Run Time Type Checking

For run time type checking, to convert links and pointers:

•    Use AS() or L_AS(), respectively, to convert downward if you have a pointer or link.
•    Upward conversion for pointers is normally automatic. However, you must explicitly cast your derived links upward to your base links.

For No Run Time Type Checking

For no run time type checking, convert links and pointers with normal casting. This is faster but less safe than using the AS() and L_AS() macros.

Link and Pointer Conversion Examples

In the following examples, assume that class A is derived from PObject and class B is derived from class A:

When the AS() or L_AS() macro is used, types are checked at run time.

From

To

Kind

Example

Safety

A* Link<A> same linkA = ptrA; safe
  B* down ptrB = AS(B,ptrA); safe
  B* down ptrB = (B*)ptrA; unsafe
  Link<B> down linkB = AS(B,ptrA); safe
  Link<B> down linkB = (B*)ptrA; unsafe
  LinkAny up link = (Link<A>)ptrA; safe
         
Link<A> A* same ptrA = linkA; safe
  B* down ptrB = L_AS(B,linkA); safe
  B* down ptrB = (B*)(A*)linkA; unsafe
  Link<B> down linkB = L_AS(B,linkA); safe
  Link<B> down linkB = (LinkAny)linkA; unsafe
  LinkAny up link = linkA; safe
         
B* A* up ptrA = ptrB; safe
  Link<A> up linkA = ptrB; safe
  Link<B> same linkB = ptrB; safe
  LinkAny up link = (Link<B>)ptrB; safe
         
Link<B> A* up ptrA = linkB; safe
  Link<A> up linkA = (LinkAny)linkB; safe
  Link<A> up linkA = L_AS(A,linkB); safe
  B* same ptrB = linkB; safe
  LinkAny up link = linkB; safe
         
LinkAny A* down ptrA = L_AS(A,link); safe
  A* down ptrA = (Link<A>)link; unsafe
  Link<A> down linkA = L_AS(A,link); safe
  Link<A> down linkA = link; unsafe
  B* down ptrB = L_AS(B,link); safe
  B* down ptrB = (Link<B>)link; unsafe
  Link<B> down linkB = L_AS(B,link); safe
  Link<B> down linkB = link; unsafe
 
Conversions of Link Vstrs

There are no macros to convert and type check link vstrs, because it is too expensive to check all elements in a link vstr. Also, since a link vstr is shared when it is assigned to another variable of a different type of link vstr, updating the same vstr from two different views has potential type errors that could not be checked at compile time. Thus, updating a link vstr from two different views is dangerous and should be avoided.

Following are rules for converting link vstrs:

•    To convert down, use normal casting.

•    Same level and upward conversions are automatic.

Link Vstr Conversion Examples

In the following examples, assume that class A is derived from PObject and class B is derived from class A:

From

To

Kind

Example

LinkVstr<A>

LinkVstr<B>

down

lvstrB = (LinkVstrAny)lvstrA;

 

LinkVstrAny

up

lvstrAny = lvstrA;

LinkVstr<B>

LinkVstr<A>

up

lvstrA = (LinkVstrAny)lvstrB;

 

LinkVstrAny

up

lvstrAny = lvstrB;

LinkVstrAny

LinkVstr<A>

down

lvstrA = lvstrAny;

 

LinkVstr<B>

down

lvstrB = lvstrAny;

 
Type Conversion Examples

Type Conversion Using LinkAny

Suppose we have the following C++ function:

LinkAny get_evil_twin(LinkAny );

The following is a C++ wrapper that can be used for objects of class Person:

Link<Person> get_evil_Twin(Link<Person> p)
{
   return  get_evil_twin(p);
}

Type Conversion Using LinkVstrAny

Suppose we have the following C++ api function:

LinkVstrAny get_evil_twins(LinkVstrAny );

The following is a C++ wrapper that can be used for objects of class Person:

LinkVstr<Person> get_evil_Twins(LinkVstr<Person> p)
{
   return  get_evil_twins(p);
}

Type Conversion Using o_object

Suppose we have the following C function:

extern "C" { o_object o_get_evil_twin(o_object p); }

The following is a generic C++ wrapper:

LinkAny get_evil_twin(LinkAny p)
{
   return o_get_evil_twin(p);
}

Following is a C++ wrapper for objects of class Person. It casts to LinkAny as the intermediate between Link<Person> and o_object.

Link<Person> get_evil_Twin(Link<Person> p)
{
   return (LinkAny) o_get_evil_twin( (LinkAny) p );
}

Type Conversion Using o_vstr

Suppose we have the following C function:

extern "C" { o_vstr o_get_evil_twins(o_vstr p); }

Following is a generic C++ wrapper:

LinkVstrAny get_evil_Twins(LinkVstrAny p)
{
   return (VstrAny)o_get_evil_twin((VstrAny)p);
}

Following is a C++ wrapper for objects of class Person. It uses two casts using LinkVstrAny and VstrAny as intermediaries:

LinkVstr<Person> get_evil_Twins(LinkVstr<Person> p)
{
   return (LinkVstrAny) (VstrAny) o_get_evil_twins(
   (VstrAny) (LinkVstrAny) p );
}

 

Manage Sessions

The VSession class allows you to create and manage sessions.

If your intention is to use multiple threads in a single process or if you want to create multiple sessions, then you must create the sessions with instances of VSession. Otherwise, you can start a database session either with VSession() or the beginsession() method on PDOM.

The VSession class derives from PDOM and is available when you include pobject.h.

For an explanation of session types and usage notes, see the following chapters in the VERSANT Concepts and Usage Manual:

"Sessions"
"Optimistic Locking"
"Custom Locking"
"Thread(s) in Session(s)"
"Multiple Processes in a Single Session."

 

Handle Errors

For compilers that provide native exception handling, VERSANT uses the native exception handling provided.

Error handling classes are PError and PResult.

 

Checking Methods

C++/VERSANT support for debugging, consisting of libraries and functions, is collectively called the Check Facility. You can debug with runtime checks by setting breakpoints or by using automatic tracking.

 

Include Header Files

 

Include File Order

In your class and application .h declaration files, you should include header files for VERSANT classes that you use.

You should include files in the following order:

1.  System and C++ include files.

2.  VERSANT include files.

To use VERSANT, the header file for PObject, pobject.h, should be included and be the first VERSANT file listed. You should also include the header files for any other VERSANT classes that you use in your application.

Including pobject.h will provide access to all methods in PClass, PDOM, and PObject that you will need in your application, except for error handling methods.

When including C++/VERSANT files, you should prefix the header file name with cxxcls/ and surround it with angle brackets. The cxxcls/ requirement avoids collision with other library files that you might have with similar names. The general syntax is:

#include <cxxcls/classname.h>

For example:

#include <cxxcls/pobject.h>

3.  Your include files.

 

Include Files

Include file for Primary Classes, Macros and Types

Primary Database Classes

 
PDOM pobject.h
PClass pobject.h
PObject pobject.h

Primary Programming Classes

 
Link<type> pobject.h
LinkAny pobject.h
LinkVstr<type> pobject.h
LinkVstrAny pobject.h
Vstr<type> pobject.h

Macros and Helper Types

 

Macros, such as NULL and AS pobject.h
Helper types, such as o_indextype pobject.h
Storage types, such as o_1b pobject.h

Helper Classes

 

Pattribute pobject.h
PError pobject.h
PLock pobject.h
PPredicate pobject.h
PResult pobject.h
   
 
Include Files for Other Persistence Capable Classes

Container containe.h
PVirtual pvirtual.h
V??Dictionary vdiction.h
V?Array varray.h
V?List vlist.h
V?Set vset.h
VDate vdate.h
VString vstring.h
VTime vtime.h
 
Include Files for Other Support Classes

BiLink<type> bilink.h
BiLinkVstr<type> bilinkvs.h
PString pstring.h
V?ArrayIterator varray.h
V?DictionaryIterator vdiction.h
V?ListIterator vlist.h
V?SetIterator vset.h
VElemental vcommon.h
 

 

Create and Load Schema Files

After creating your .h and .cxx source code files, you need to do the following.

1.  Write a schema implementation file.
2.  Create schema description and implementation files.
3.  Create and load schema files.

 

Implementation File — .imp

Besides normal C++ files, you must create a .imp schema implementation file for later use by schcomp, Versant's Schema Compile Utility. This file will cause the capture of class definition information in a form understandable to VERSANT.

In the schema implementation file:

1.  Include all header files needed for a class or application, even those files that do not define a persistent class.

2.  Use the O_CAPTURE_SCHEMA(type) schema compiler directive for each persistent class and VERSANT template class.

3.  Write an O_TRANSIENT schema compiler directive  for transient classes and/or transient attributes. (The O_TRANSIENT directive is described above in the section "Transient Attributes."

The name of the schema implementation file should follow these rules:

1.  The suffix should be .imp.

2.  The prefix should be different from that of your source code files.

You need to choose a name for the implementation file that is different than the name of any of your .cxx files to avoid later name clobbering. This is necessary, because when you later run the Schema Compile utility, schcomp, on the .imp implementation file, two files are created, one with a .cxx extension and one with a .sch extension. This can cause name clobbering with your C++ implementation file which already has the extension .cxx.

For example, if you have files named example.h and example.cxx, you could name the implementation file example_.imp.

For example:

//example_.imp

#include <cxxcls/pobject.h>
#include "person.h"
#include "employee.h"

O_CAPTURE_SCHEMA(Person);
O_CAPTURE_SCHEMA(Employee);
O_CAPTURE_SCHEMA(VVSet<Employee>);

 

Description and Link Files — .sch, .cxx

Before you can load or change class definitions in a database, you must create:

1.  A .sch schema description file that describes the classes in terms understandable to the database.

2.  A .cxx schema implementation file which is linked with your application.

To create these schema files, process your .imp schema implementation file with the VERSANT Schema Compile Utility, schcomp.

For a complete reference to the schcomp utility, see the chapter "Utilities" in your C++/VERSANT Reference Manual. The basic syntax is:

schcomp [options] file1.imp [ file2.imp ... ]

For options, you can specify any option valid for your compilers C pre-processor. You cannot specify the list of implementation files with wild card characters.

For example, to process example_.imp and create example_.cxx and example_.sch:

schcomp example_.imp

To process myschema_.imp and create myschema_.cxx and myschema_.sch:

schcomp -I'oscp -p'/h myschema_.imp

If an array of a class is embedded in another class, the C++/VERSANT run-time type identification code might call the default constructor for each element in the array. If this occurs and causes problems, insert the following expression in the constructor for the class of the array element:

if (O_DURING_SCAP) return;

The value of O_DURING_SCAP will be set to non-zero values at run time for the purpose of schema capture.

 

Schema Loading

 

Load class definition

Before you can store instances of a class in a database, you must load the definition of the class into this database using the Schema Change Utility. The Schema Change Utility is a command line utility whose name is sch2db.

To load a class definition, the Schema Change Utility requires a .sch database schema file (see above).

For example, to load the classes defined in the file myschema.sch into the database mydb:

sch2db -y -D mydb myschema.sch

For a complete reference to the sch2db utility, see the chapter "Utilities" in your C++/VERSANT Reference Manual.

Once the definition of a class is in one database, you can use the synclass() method to load the definition into other databases. Or, you can just use the sch2db utility on each database involved.

 

Move an object

If you want to move an object to a database in which its class is not defined, you do not have to load the schema into the target database, because a checkin or migration will automatically copy the class definition.

When you try to move an object to a database which already has a definition of its class, VERSANT will compare the class definitions in the source and target databases. If the definitions in the databases are the same, then the checkin or migration will occur. If the definitions in the databases are different, then the checkin or migration will be blocked until you synchronize the definitions by changing the schema in either the source or target database.

 

Change class definition

If you change the definition of a class in your source code, you must update the class definition in the databases involved.

To update a class definition in a database, rerun the Schema Change Utility with the new .sch database schema file.

If you want to update multiple databases, you can run the Schema Change Utility separately on each database, or run it on one database and then reconcile differences in other databases with the synclass() method.

Both the Schema Change Utility and the synclass() method change class definitions without changing the object identifiers for instances of the class. This means that after you have changed a class, you do not have to modify application code that uses links to objects of that class.

After a class has been created, you can make the following changes to the definition of a class using either the Schema Change Utility or the synclass() method:

Attribute changes

Add an attribute.

Drop an attribute.

Rename an attribute.

Inherit a new attribute.

Method changes

Add a method.

Drop a method.

Rename a method.

Inherit a new method.

Change implementation of a method.

Class changes

Add a class at the leaf of an inheritance chain.

Drop a class and its derived classes.

 
Other changes must be made in multiple steps. Changes that require multiple steps include:

Attribute changes

Reorder attributes.

Change data type of an attribute.

Class changes

Add a superclass.

Rename a class.

 
For example, to rename a class, create and load a new class with the desired name and definition, run a program that copies existing objects to the new class, and then drop the old class.

To change the data type of an attribute, rename the attribute, add a new attribute with the new data type, run a program that casts and copies data from the old attribute to the new attribute, and then drop the old attribute.

For example, if you want to change the data type of an attribute from int a to float a, you could use sch2db to rename int a to int ax, run sch2db again to add float a, run a program that selects all instances and sets a=ax, and then run sch2db a final time to drop ax.

Changing the nature of a relationship is similar to changing the data type of an attribute. For example, suppose that you want to change a 1:1 relationships stored in Link<Fruit> fruit to a 1:N relationship to be stored in LinkVstr<Fruit> fruits. You would add the new attribute fruits and use use the link vstr add() method to transfer values.

If you were changing from a 1:N relationship to a 1:1 relationship, you would also have to do some error checking, such as:

if fruits.size() != 1 {//handle error} else {fruit = fruits[0];}

Changing the inheritance ordering in multiple inheritance is not directly supported. It is not clear why this is required, as the C++ compilers consider internal storage layouts to be an implementation detail, not a programming issue.

For extremely complex changes, you might want to create entirely new databases and create a data evolution program that creates new objects in the new databases based upon old objects in old databases. If you do this, remember that the new objects will have new object identifiers, which may break existing programs.

 

Confirm schema for an application

As a matter of practice, even if you have previously loaded a class definition using a class file, you should always verify that the class schema expected by an application is identical to the class schema in the database.

There are several reasons why you should confirm your schema:

•    You may have defined a class for which the database has no definition.

•    The database and application schemas may differ because the database schema is obsolete or someone else has changed a class definition.

•    You already know the definitions are different, and you want the old class and its instances dropped and replaced with a new class definition. This occurs frequently during debugging and testing.

To confirm a schema, create schema files for the application and then attempt to load them into a database with the Schema Change Utility. The Schema Change Utility will then present you with a list of changes that are to be made to the database schema. You should always review the list of changes before committing them to the database.

 

Compile and Link Source Code

The following contains C++/VERSANT compile, link, and class loading instructions.

To compile and link, you need to know the location of VERSANT header files and libraries. See the chapter "Directories and Files" in the System Manual for the structure of VERSANT directories and files.

 

UNIX

 

VERSANT Libraries

To create executables, use your compiler normally to link application files with your choices of VERSANT libraries and also the .cxx schema implementation file created by the Schema Compile Utility.

There are two basic kinds of VERSANT libraries.

System libraries

The system libraries contain the VERSANT Manager and VERSANT Server program modules.

Linking with a VERSANT system library is mandatory for all applications using VERSANT.

You can link your application either with a two process system library (the usual choice) or with a one process system library (for performance reasons).

The system libraries are located in the /lib directory under your platform directory.

Please see the chapter "Managing Memory" in the VERSANT Concepts and Usage Manual for a description of the one and two process model, caution statements and recommendations.

C++/VERSANT libraries

The C++/VERSANT libraries contain both the C++/VERSANT interface and the ODMG C++ interface.

For C++/VERSANT, there are two kinds of libraries: production and checking.

If you use a collection class, you must also link with the collection class production or checking library.

The C++/VERSANT libraries are located in a compiler specific directory under the VERSANT /lib directory.

VERSANT Library Names

The names of the UNIX libraries are:

System

Two process library

archive

lib/liboscfe.a

 

shared

lib/liboscfe.so

One process library

archive

lib/libosc.a

 

shared

lib/libosc.so

 
C++/VERSANT production

Fundamental classes

archive

/lib/compiler/libcxxcls.a

 

shared

/lib/compiler/libcxxcls.so

Collection classes

archive

/lib/compiler/libvcoll.a

 

shared

/lib/compiler/libvcoll.so

 
C++/VERSANT checking

Fundamental classes

archive

/lib/compiler.chk/libcxxcls.a

 

shared

/lib/compiler.chk/libcxxcls.so

Collection classes

archive

/lib/compiler.chk/libvcoll.a

 

shared

/lib/compiler.chk/libvcoll.so

 

 

Library Specification Order

On your command line, you must specify libraries in the following order:

1.  Your libraries.

2.  A C++ collection classes library.

3.  A C++ fundamental classes library.

4.  The mandatory VERSANT system library.

You should never mix checking libraries with non-checking libraries nor mix two-process libraries with single-process libraries when linking, as this will cause the application to fail at runtime.

 

Compile and Link Guidelines

Multi-Thread Linking

You must specify a multiple thread option when you compile and link your application if you want to do any of the following:

•    Run your application and session database in a single process by linking with the single process libraries.

•    Start a session that will use multiple processes (using the O_SHARE parameter.)

•    Start a session that will use multiple threads (using the O_THREAD parameter.)

The only time you do not have to specify a multiple thread option when you link your application is if you only run your application and session database in separate processes by linking with two process libraries and use only a single thread in your application.

Dependency Setting

Following are source code dependencies for classes and applications.

Suppose the following:

C1, C2 — Classes you are defining.

c1.h, c2.h — Declaration files for classes C1 and C2.

c1.cxx, c2.cxx — Implementation files for classes C1 and C2.

myschema.imp — Schema implementation file for classes C1 and C2 (containing O_CAPTURE_SCHEMA and O_TRANSIENT schema compiler directives)

main.cxx — An application file that uses C1 and C2.

Dependencies are:

.sch file — The schema file needs all .imp schema implementation files.

.obj files — The .obj files need the .cxx files (normal C++).

Executable — The executable needs the .obj files (normal C++).

In your makefile, you could express these dependencies as:

myschema.cxx : myschema.imp c1.h c2.h
myschema.obj : myschema.cxx
c1.obj c2.obj main.obj : c1.cxx c2.cxx
main.exe: c1.obj c2.obj mychema.obj

 

Compile and Link Procedures

Use your compiler normally to compile class and application files.

To create executables, use your compiler normally to link application files with your choices of VERSANT libraries and also the .cxx schema implementation file created by the Schema Compile Utility.

Remember, that you must always link with a system library (for example, liboscfe.a) and an interface library (for example, libcxxcls.a).

For example, for the example files described above, compile c1.cxx and c2.cxx and compile and link main.cxx and myschema.cxx.

cl -c -I... c1.cxx
cl -c -I... c2.cxx
cl -c -I... main.cxx
cl -c -I... myschema.cxx

 

A Sample Makefile:

#  Copyright (c) 1998, Versant Object Technology.  All rights reserved.
#  This Makefile is meant for use for only on the Solaris Platform.
# Define Compile and Link Flags.
# The Compiler to be used.
CC= CC
#Compiler Flags.
CFLAGS= -I$(VERSANT_RELEASE_DIR)/h -I. $(DEBUG_FLAG)
#Schema Compiler to be used.
SCHCOMP= $(VERSANT_RELEASE_DIR)/bin/schcomp
#The Directories where Versant Libraries are to be found.
LIB_DIR= -L$(VERSANT_RELEASE_DIR)/lib  -L $(VERSANT_RELEASE_DIR)/lib/sun.4.0
#Linker flags.
LINK_FLAGS= -lvcoll -lcxxcls -losc$(TWO_PROCESS) -Bdynamic -lposix4 -lpthread -lc -lsocket -lnsl -lC
# The main Targets of this Makefile are all and clean.
# Rules are provided about how to build each of the individual
# components
# main.exe, C1.o, C2.o., schema files and main.o
all: main.exe
clean:
   -/bin/rm -rf Templates.DB schema.cxx schema.sch *.o
main.exe:C1.o C2.o schema.o main.o
   $(CC) -o main.exe $(LIB_DIR) $(LINK_FLAGS) C1.o C2.o schema.o Person.o
C1.o:C1.h
   $(CC) $(CFLAGS) -c C1.cxx
C2.o:C2.h
   $(CC) $(CFLAGS) -c C2.cxx
schema.cxx:schema.imp
   $(SCHCOMP) $(CFLAGS) schema.imp
schema.o: schema.cxx
   $(CC) $(CFLAGS) -c schema.cxx
main.o:main.cxx c1.h c2.h
   $(CC) $(CFLAGS) -c main.cxx
 

C++/VERSANT Programming Notes

Hardware platforms and compilers differ in byte-ordering, the size of elemental data types, and the layouts of C++ objects.

If you are managing objects in databases on different platforms, VERSANT will handle data appropriately, but you must handle programming issues when you perform tasks that depend upon machine specific features.

For example, VERSANT will automatically reorder bytes and construct objects when saving and retrieving objects, but you must take care when performing offset manipulation of attributes.

Following are programming requirements related to platform indepencence.

Do not use native elemental types.

If you use native elemental types, be aware of differences in byte-ordering and size of elemental types and C++ classes when sharing objects among different platforms.

For example, in the following, the value of b1 depends upon the byte ordering of int and double:

int b1 = * (int*) & a_double_variable;

Do not perform offset manipulation of attributes.

You should not create code in which values vary by compiler.

For example, in the following, the value of n depends on the compiler used:

class MyClass p;

int n = &p.m1 - &p;

Do not create code that depends upon the size of an object.

The size of an object may vary by compiler. For example, in the following, the value of size_of_MyClass depends on the compiler used:

class MyClass p;

int size_of_MyClass = sizeof(MyClass);

Do not use the long double data type.

The size of long double is 8 on UNIX, 10 for Borland C++, and 8 for Visual C++. For this reason, VERSANT does not support use of long double in persistent classes.

Do not save machine or environment specific information in a database.

 

Windows NT and Windows 95

 

VERSANT Libraries

To create executables, use your compiler normally to link application files with your choices of VERSANT libraries and also the .cxx schema implementation file created by the Schema Compile Utility.

There are two basic kinds of VERSANT libraries.

System libraries

The system libraries contain the VERSANT Manager and VERSANT Server program modules.

Linking with a VERSANT system library is mandatory for all applications using VERSANT.

You can link your application either with a two process system library (the usual choice) or with a one process system library (for performance reasons).

The system libraries are located in the \lib directory under your platform directory.

Please see the chapter "Managing Memory" in the VERSANT Concepts and Usage Manual for a description of the one and two process model, caution statements and recommendations.

C++/VERSANT libraries

The C++/VERSANT libraries contain both the C++/VERSANT interface and the ODMG C++ interface.

For C++/VERSANT, there are two kinds of libraries: production and checking.

If you use a collection class, you must also link with the collection class production or checking library.

The C++/VERSANT libraries are located in the \lib directory under your platform directory.

VERSANT Library Names

The names of the Windows NT libraries are:

System

Two process

liboscfe.lib

One process

libosc.lib

 
C++/VERSANT production

Fundamental classes

Two process

msccls.lib

One process

msccls1p.lib

 
Collection classes

Two process

mscvcoll.lib

One process

mscvc1p.lib

 
C++/VERSANT checking

Fundamental classes

Two process

mscchk.lib

One process

mscchk1p.lib

 
Collection classes

Two process

mscvcch.lib

One process

mscvcc1p.lib

 
Each of the above libraries is an import library which links an application to the corresponding DLL. The import libraries are in your VERSANT lib directory, and the DLLs are in the bin directory.

For example, when linking a 2-process application, you should use:

mscvcoll.lib msccls.lib liboscfe.lib

For example, when linking a single process application, you should use:

mscvc1p.lib msccls1p.lib libosc.lib

 

Library Specification Order

On your command line, you must specify libraries in the following order:

1.  Your libraries.

2.  A C++ collection classes library.

3.  A C++ fundamental classes library.

4.  The mandatory VERSANT system library.

You should never mix checking libraries with non-checking libraries nor mix two-process libraries with single-process libraries when linking, as this will cause the application to fail at runtime.

 

Compile and Link Guidelines

Some compiler options are required.

-GX

You must use the -GX compiler option to enable C++ exception handling.

-vmg

You must use the -vmg compiler option if you are using VERSANT bilinks and bilink vstrs with virtual base class and multiple inheritance. Also, see the note below on OLE integration.

-MD

You must compile all source files with -MD. See the note below.

-DNOMINMAX

If you want to use the C++/VERSANT Standard Template Library support, you must use the compiler option -DNOMINMAX while compiling your source files.

This option is required, because the Standard Template Library header files define inline function templates called "min" and "max", and Visual C++ defines min and max as macros, and this conflict causes compilation errors. The compile-time option -DNOMINMAX disables this macro definition and removes the conflict.

OLE header files and the -vmg option

If your code uses bilinks or bilink vstrs with virtual base classes and multiple inheritance, then you must use the -vmg option when compiling. This causes a problem when the application links with certain OLE libraries, such as when trying to build an OLE automation server. The problem and its resolution are described below.

The -vmg option asks the compiler to use the most general representation for a pointer-to-member data variable. The most general representation is larger than the default representation. Therefore when you have an array of structures containing pointer-to-member data fields, the offsets will be different depending on whether you use -vmg or not.

VERSANT bilinks require the -vmg option, because this option is required whenever there is a pointer-to-member declaration containing a forward reference to a class which has not yet been declared. Since bilinks mutually reference each other's class, it is not possible to have both the classes declared before they are referenced.

The problem with OLE occurs because in the file \msvc20\mfc\include\afxwin.h, the declaration of struct AFX_DISPMAP_ENTRY has the pointer-to-member data field AFX_PMSG pfn. Apparently the OLE libraries are compiled without the -vmg option, so these pointers are of the default size. If the -vmg option is used, the compiler assumes a larger size for struct AFX_DISPMAP_ENTRY, so it generates the wrong offsets when traversing an array of AFX_DISPMAP_ENTRY's, and this causes an application error where the dispatch id is invalid.

The solution is:

1.  Separate out the OLE code and the VERSANT application code, and ensure that the code which references AFX_DISPMAP_ENTRY does not appear in the same source file as code which references VERSANT bilinks.

2.  Compile the OLE source files without the -vmg option, and compile the VERSANT source files with the -vmg option.

If it turns out to be impossible to get the correct combination of options using the command line, then the "pointers_to_members" pragma needs to be used on a file-by-file basis.

For OLE source files:

#pragma pointers_to_members(best_case)

For VERSANT source files:

#pragma pointers_to_members(full_generality)

MSVCRT.DLL and the -MD option

Beginning with Release 4, VERSANT links with the multithreaded dynamic version of the Microsoft Visual C++ runtime library MSVCRT.DLL, as against the static version LIBC.LIB library which was used earlier.

All VERSANT applications also need to link with MSVCRT.DLL. This means that applications must be compiled with the -MD compiler option.

If an application is linked with the static library LIBC.LIB (the -MD option was not used), there may be conflicts due to multiply defined symbols in LIBC.LIB and MSVCRT.DLL. This could happen even if your application uses a third-party library which links with the static library LIBC.LIB.

If you get linker errors due to multiple symbol definitions for C or C++ standard library functions, first check that all your code has been compiled with -MD, then investigate whether you are using any third-party DLL's which are linked with the static library.

Compiler options -DWIN32 and -D_X86_

VERSANT 3.0 for NT required the user to define the token WIN32 when compiling VERSANT applications. For this the user had to pass the option -DWIN32 to the compiler.

In release 4.0 and above, VERSANT uses the token _WIN32 which is predefined by Visual C++ and does not need to be defined on the command line. Users with Microsoft Visual C++ 2.1 and above need not use -DWIN32 when compiling VERSANT programs.

The compiler option -D_X86_ which was required in release 3.0 is no longer needed.

Special "lean and mean" options for schcomp

You may find that the schcomp utility takes an extremely long time to capture class schemas. This happens because schcomp parses all type definitions to find classes which are persistent. The Windows GUI interface header files have large numbers of type definitions, and this slows the performance of schcomp.

To speed the scanning of the Windows GUI interface header files, pass both of the following flags to the schcomp utility:

-DWIN32_LEAN_AND_MEAN -DVC_EXTRALEAN

The standard VERSANT makefiles supplied in the demo and tutorial directories make use of this option.

Compile and Link Procedures

Compile and link normally

Use your compiler normally to compile class and application files.

To create executables, use your compiler normally to link application files with your choices of VERSANT libraries and also the .cxx schema implementation file created by the Schema Compile Utility.

Remember, that you must always link with a system library (for example, liboscfe.a) and an interface library (for example, libcxxcls.a).

Input schema before running application

Before running your application, load your class definitions into a database with the sch2db utility.

For example:

sch2db -y -D mydb myschema.sch

See the chapter "Load Classes" for more information.

 

Capturing Schema

Following are general notes related to schema capture.

Do not capture schemas of C++/VERSANT standard classes.

You should not capture schemas for classes which are already exported by the VERSANT DLLs.

If you put O_CAPTURE_SCHEMA() statements for these classes in your .imp schema implementation files, you will get errors from the compiler or linker.

Following is a list of classes for which schemas have already been captured.

Fundamental classes:

PObject
PVirtual
Container
VString
PBiLink
PBiLinkVstr
VDate
VTime
VCollection
VKey

Collection classes:

VEArray<Link<PObject> >
VEArray<Link<PVirtual> >
VEList<Link<PObject> >
VEList<Link<PVirtual> >
VESet<Link<PObject> >
VESet<Link<PVirtual> >

VEEDictionary<o_1b, Link<PObject> >
VEEDictionary<o_2b, Link<PObject> >
VEEDictionary<o_4b, Link<PObject> >
VEEDictionary<o_u1b, Link<PObject> >
VEEDictionary<o_u2b, Link<PObject> >
VEEDictionary<o_u4b, Link<PObject> >
VEEDictionary<o_float, Link<PObject> >
VEEDictionary<o_double, Link<PObject> >

VEEDictionary<Link<PObject>, o_1b>
VEEDictionary<Link<PObject>, o_2b>
VEEDictionary<Link<PObject>, o_4b>
VEEDictionary<Link<PObject>, o_u1b>
VEEDictionary<Link<PObject>, o_u2b>
VEEDictionary<Link<PObject>, o_u4b>
VEEDictionary<Link<PObject>, o_float>
VEEDictionary<Link<PObject>, o_double>

VEEDictionary<o_1b, Link<PVirtual> >
VEEDictionary<o_2b, Link<PVirtual> >
VEEDictionary<o_4b, Link<PVirtual> >
VEEDictionary<o_u1b, Link<PVirtual> >
VEEDictionary<o_u2b, Link<PVirtual> >
VEEDictionary<o_u4b, Link<PVirtual> >
VEEDictionary<o_float, Link<PVirtual> >
VEEDictionary<o_double, Link<PVirtual> >

VEEDictionary<Link<PObject>, Link<PObject> >
VEEDictionary<Link<PObject>, Link<PVirtual> >
VEEDictionary<Link<PVirtual>, Link<PObject> >
VEEDictionary<Link<PVirtual>, Link<PVirtual> >

The header files for the collection classes are separate.

Source code for the VERSANT collection classes has been placed in the h\cxxcls directory. You must include the appropriate collection class header file in your application and write O_CAPTURE_SCHEMA() statements for each class you use.

Include all needed header files in your schema implementation files.

You should include all .h header files needed by your application in your .imp schema implementation files even if you do not define any new persistent classes. This enables class definitions to be included in the schema implementation files.

 

C++/VERSANT Programming Notes

Hardware platforms and compilers differ in byte-ordering, the size of elemental data types, and the layouts of C++ objects.

If you are managing objects in databases on different platforms, VERSANT will handle data appropriately, but you must handle programming issues when you perform tasks that depend upon machine specific features.

For example, VERSANT will automatically reorder bytes and construct objects when saving and retrieving objects, but you must take care when performing offset manipulation of attributes.

Following are programming requirements related to platform indepencence.

Do not use native elemental types.

If you use native elemental types, be aware of differences in byte-ordering and size of elemental types and C++ classes when sharing objects among different platforms.

For example, in the following, the value of b1 depends upon the byte ordering of int and double:

int b1 = * (int*) & a_double_variable;

Do not perform offset manipulation of attributes.

You should not create code in which values vary by compiler.

For example, in the following, the value of n depends on the compiler used:

class MyClass p;

int n = &p.m1 - &p;

Do not create code that depends upon the size of an object.

The size of an object may vary by compiler. For example, in the following, the value of size_of_MyClass depends on the compiler used:

class MyClass p;

int size_of_MyClass = sizeof(MyClass);

Do not use the long double data type.

The size of long double is 8 on UNIX, 10 for Borland C++, and 8 for Visual C++. For this reason, VERSANT does not support use of long double in persistent classes.

Do not save machine or environment specific information in a database.

Do not use Microsoft specific data types and classes in a database.

To take full advantage of VERSANT compiler independent heterogeneity, we recommend that you do not use Microsoft specific data types when defining persistent classes. This approach will allow you to share objects and code among different compilers and machines.

In general, a layered software architecture is always suitable for applications using a database as a repository. For example, it is a good idea to have a database query accessing layer between a graphic interface and the database.

Accordingly, in the interests of heterogeneity, we recommend that you do not include Microsoft header files such as windows.h in your .imp schema implementation file, either directly or indirectly.

However, if you do want to include Microsoft header files in your schema implementation file, you need to remember that Microsoft header files are not self-contained. In most cases, they need to include windows.h first.

You can use Microsoft Foundation Classes in your application.

In the C++/VERSANT demo directory you will see a sub-directory called hellomfc, which is the HELLO demo program with some additional code to integrate it with the Microsoft Foundation Class (MFC) library.

This example shows how to integrate a VERSANT application with Microsoft MFC class libraries. It is only intended as a supplement to the VERSANT Development Environment, and it is not guaranteed to work as is.

This example requires familiarity with writing MFC applications and using the Microsoft Developer Studio. You may need to modify some of the settings in the Makefile and the IDE Project Settings.

Note particularly the code in the file demo\cxx\hellomfc\hellovw.cpp, which defines some methods which integrate the Hello class with the MFC environment.

Note that MFC datatypes are used only within the application program and are not passed to any VERSANT methods or functions, since VERSANT does not provide persistence for any third-party library datatypes.

For example, CString is type cast to PString whenever it is stored in a database, and then recast to CString when used in the application program.

Most of the other integration changes made are at the application program level without involving any new VERSANT code. The changes should be easy to understand if you have written MFC applications before. It is strongly recommended that you study simpler examples using MFC to understand MFC concepts before using this demo.

There is a readme.txt file in the hellomfc directory, which will describe the sequence of steps required to run the demo from within the Microsoft Developer Studio environment.

Integrate the schema compiler tool with your Visual C++ Environment

In order to build VERSANT application programs within the Microsoft Developer Studio, you need to add the schema compiler tool schcomp as a tool recognized by the environment.

To add schcomp as a tool, click on the Tools option in the Microsoft Developer Studio menubar, and choose Customize. Then, choose Add to add schcomp as a new tool, name it VERSANT Schema Compiler, provide its pathname as VERSANT_ROOT\5_0_6\nt\bin\schcomp.exe, and provide the standard VERSANT compiler options as command line arguments.

In addition to the standard compiler options you must also pass the option -v for "verbose", so that schcomp prints out a message on successful completion of schema capture.

Typically you will pass schcomp arguments of the following form:

-IVERSANT_ROOT\5_0_6\nt\h -GX -vmg -DNOGDI -v $file

Finally, select the option Redirect to output window so that the output of this utility will be seen in the Visual C++ output window.

When you are building an application, you have to generate the schema.cxx file and then compile it by performing the following steps.

1.  Add both the schema.imp file and the schema.cxx file to your project. For example, if your schema implementation file is example_.imp, then add the files example_.imp and example_.cxx to your project.

2.  Open the .imp file and invoke the VERSANT schema capture tool from the Tools menu. The name of the .imp file will be automatically supplied to the schcomp command by the $file macro (which gets substituted by the name of the currently open file), and the corresponding .cxx file will be generated.

After the above, the schema.cxx file will be compiled as part of your normal build procedure.

Integrating schcomp with Microsoft Developer Studio using Custom Build

Another way to integrate the VERSANT schema compiler with Microsoft Developer Studio is to use the Custom Build options in Project Settings.

The Custom Build tab in the Project Settings dialog box is used to specify custom tools for use in your project builds. You can specify tools to run on the output file of a project configuration, or you can specify tools for files which have no default tools.

For example, in Visual C++, you cannot specify a custom build tool for .CPP files because the build system already specifies the compiler as the default tool for .CPP files. If you have an .IMP file, however, which is the input for the Schema compiler, you can specify how to process it and what output it generates.

The set of items you have to modify in the Custom Build tab is described below.

Input File

The input file identifies files for which you want to specify custom tools.

In the "Settings For" pane of the Project Settings dialog, you can select:

•    A single file from an expanded project configuration or more than one file from one or more expanded project configurations.

•    The output file for one or more project configurations by selecting one or more top-level project configuration nodes.

If you select multiple files, the text for this field indicates multiple selections. In the case of a single .IMP file, the text would show the name of the file relative to the project directory (.\SCHEMA.IMP, for instance).

Note — The custom build commands apply only in builds of the project configurations in which you selected the input file or files. So for a new project containing other .imp files, you need to redo the customization.

Description

Specifies the build step, and is displayed on the Build tab of the Output window during the build.

Build Commands

Enter the commands to run.

If you enter more than one command, the build system runs them in order from top to bottom. You can use the directory and file macros in these commands.

Note — You must enter the complete command to run, with all its required options including the input file and the output file or files. You may want to use a directory macro to specify the location for the output file.

For example:

schcomp -Od -Zi -nologo -GX -vmg -G3 -D_DEBUG -DWIN32 \
        -D_X86_=1 -D_AFXDLL -MD -D_WIN32 -DWIN32_LEAN_AND_MEAN \
        -DCHECKING $(InputName).imp

Output Files

Enter the names of the output files that you create with the commands in the Build Commands list.

The build system checks these files during a build to determine whether they are out of date with respect to the input file. If so, the build system builds them. If the output file or files are subsequently used as input files for the build system, you must also add them to the project.

Note — You must specify an output file or files. If you do not, the build system has no way to determine that a file is out of date, and thus will never run the custom build tool.

For example:

$(InputName).cxx OR $(InputName).sch

Directory

Lists directory macros that you can insert at the current insertion point in the "Build Command" or "Output Files" list. Selecting from the list inserts the macro into the command or name that you are currently entering.

In our case we need to generate the SCHEMA.CXX file in the same directory.

Files

Lists filename macros that you can insert at the current insertion point in the Build Command or Output Files list. Selecting from the list inserts the macro into the command or name that you are currently entering.

More information

For more information on this topic, look at the "Specifying Custom Build Tools" help topic in Visual C++.

 

Creating DLLs

In VERSANT Release 4.0, the procedure for building user DLLs containing C++/VERSANT classes involved writing wrapper classes for each of the user's persistent classes. An application would only use these wrapper classes from the DLL, and VERSANT classes or functions themselves could not be used directly because they were not visible outside the DLL. This approach was used, because Microsoft Visual C++ 2.1 did not support the exporting of template class function members or data members, and C++/VERSANT makes extensive use of template classes.

With Visual C++ 4.x and VERSANT Release 5.x, the exporting of template classes has become slightly easier, but the procedure for building DLLs still requires some care. The steps involved are described below.

In the following, assume that you are going to build a DLL called parts.dll, which contains persistent classes named Part1 and Part2.

Step 1: Choose a build macro and an export macro.

You must define a macro which will export your classes when the DLL is being built and which will import the classes when an application is being built which uses the header files for the DLLs. We will call this the "export macro".

The definition of the export macro must be conditional on another macro, which we will call the "build macro".

The build macro name chosen here is BUILD_PARTS_DLL. This macro should be defined while compiling the DLL source files, and should be undefined while compiling the application source files.

The name chosen here for the export macro is O_PARTS_EXPORT.

Step 2: Create a DLL-export header file containing the build and export macros.

In our example you must put the following definition in a common header file which is included by all the other header files. We will call this common header file the DLL-export header file. In this example, let's name the file partexp.h.

// partexp.h
#if defined(BUILD_PARTS_DLL)
#define   O_PARTS_EXPORT   __declspec(dllexport)
#else
#define   O_PARTS_EXPORT   __declspec(dllimport)
#endif
...

There will be more declarations in the DLL-export header file, as described later in this section.

Step 3: Use the export macro in your class declarations.

With the declaration of each of your classes (for example, Part1 and Part2) in your DLL class header files, you must use the export macro to export the class member functions and static data members, as shown below:

// part1.h
#include <partexp.h>
#include <cxxcls/pobject.h>
...

class Part1 : public PObject {
public:
   int partnum;                   // non-static member,
                                  // don't export
   static O_PARTS_EXPORT int part1count; // export static data
                                         // members
   inline char *getPartName();    // inline member function,
                                  // do not export
   O_PARTS_EXPORT int makePart(); // export all non-inline member
                                  // functions
   ...
};

// part2.h
#include <partexp.h>
#include <cxxcls/pvirtual.h>
...

class Part2 : public PVirtual {
public:
   int   partnum;
   static O_PARTS_EXPORT int part2count;

   inline int getCount();
   O_PARTS_EXPORT int makePart();
   ...
};

When the token BUILD_CLASS_DLL is defined, the declaration above will export the class member functions and the class static data members.

When the token BUILD_CLASS_DLL is undefined, the above declaration will import the class. In this case, the linker will expect the class to be defined in a DLL, and the class entry points will be resolved using an import library for that DLL.

In the above example, class Part1 and Part2 derive directly or indirectly from PObject and are captured classes. (Each of these classes have a O_CAPTURE_SCHEMA directive assosciated with them in the schema.imp file.)

If class Part1 was transient or was a derived class of PObject but not captured, then the entire class cannot be marked for export to the dll. It is necessary to apply the O_PARTS_EXPORT macro individually to each of the data members and non_inline methods as in class Part2.

For example consider a class Part3:

class Part3 {
   o_u4b attribute1;
   o_u4b attribute2;
   inline function1(void);
   void funtion2(void);
   void function3(void);
};

In the above, class Part3 is transient and is not a captured class. If during the time the DLL say, parts.dll, was built, the export macro O_PARTS_EXPORT was used as :

class O_PARTS_EXPORT Part3 {
   o_u4b attribute1;
   o_u4b attribute2;
   inline function1(void);
   void funtion2(void);
   void function3(void);
};

...then, if the class Part3 is used as an attribute of a persistent class in any VERSANT based application that imports the class Part3 from parts.dll, you may get unresolved external symbols while linking the application. This is because class Part3 is now a persistent reachable class that needs to be instrumented by the schema compiler during compilation: during which some inline functions are added to the class in the schema.cxx file. If the entire class Part3 was marked for export/import, then refereces to the functions added to the class Part3 are expected to be satisfied by parts.dll. Since these functions were not exported to the DLL when the DLL was originally built, the linker may throw out unresolved external symbol errors and fail.

So, in this case, declare class Part3 as follows:

class Part3 {
   o_u4b attribute1;
   o_u4b attribute2;
   inline function1(void);
   void  O_PARTS_EXPORT function2(void);
   void  O_PARTS_EXPORT function3(void);
};

Schema compiler inserted functions are not expected to be imported from the DLL and references to these functions are satisfied from the schema.obj file.

Step 4: Capture the schemas for all your DLL persistent classes.

Following the standard schema capture procedure as described in the C++/VERSANT manual, use O_CAPTURE_SCHEMA() statements to capture the schemas of all the persistent classes used in your DLL.

In this example, you could create a file called parts_.imp, with the following statements.

// parts_.imp
#include <part1.h>
#include <part2.h>
O_CAPTURE_SCHEMA(Part1);
O_CAPTURE_SCHEMA(Part2);

When you use the C++/VERSANT schcomp utility on parts_.imp, two files will be generated:

parts_.sch — This file contains the class schema information to be loaded into the database using the sch2db utility

parts_.cxx — This file contains the class schema information to be linked into the application.

You should add to the implementation file O_CAPTURE_SCHEMA() statements for any new collection classes which you introduce in your application.

For example if you use VIList<Part1>, then you should put the following statement in parts_.imp:

O_CAPTURE_SCHEMA(VIList<Part1>);

Remember not to capture the schemas of VERSANT classes that are already exported from the C++/VERSANT DLLs. See the above section "Capturing Schema" for a list of classes whose schemas are already captured and should not be captured again.

Step 5: Export the corresponding PClassObject<type> template class.

For each of your persistent classes, VERSANT generates a corresponding template class instance, PClassObject<type>, in which the class layout information is captured in data structures which will be used by the application.

In this example, the template instances PClassObject<Part1> and PClassObject<Part2> will be generated in the file parts_.cxx during the schema capture process. These PClassObject<type> instances needs to be visible outside the DLL also, since the application needs to see them.

To export PClassObject<type> instances from a DLL and import them into an application, VERSANT provides the special macro O_PCLASSOBJECT_EXPORT().

In your DLL-export header file, after defining your export macro, you must add an O_PCLASSOBJECT_EXPORT() statement as shown in the following.

// partexp.h
#include <cxxcls/pobject.h>
#include <cxxcls/template/vlist.h>  // needed for
                                    // exporting VIList

#if defined(BUILD_PARTS_DLL)
#define   O_PARTS_EXPORT   __declspec(dllexport)
#else
#define   O_PARTS_EXPORT   __declspec(dllimport)
#endif

// forward declare class Part1 before exporting its
// PClassObject
class Part1;

// export the PClassObject<> instance
O_PCLASSOBJECT_EXPORT(Part1, O_PARTS_EXPORT);

// forward declare class Part2 before exporting
// PClassObject<VIList<Part2> >
class Part2;

// export the PClassObject<> instance for VIList<Part2>
O_PCLASSOBJECT_EXPORT(VIList<Part2>, O_PARTS_EXPORT);

Note that the macro O_PCLASSOBJECT_EXPORT() takes two parameters. The first parameter is the name of your persistent class, and the second parameter is the name of your export macro. The second parameter is needed so that the PClassObject<Part1> class gets exported or imported in the same way as the Part1 class itself.

Also note that the Part1 class has not been seen by the compiler yet, so it needs to be forward-declared.

Step 6: While compiling your DLL class files, define the build macro.

When compiling the source files containing definitions for your DLL class member functions or class static data, you must define the build macro.

In our example, you would use the compiler option -DBUILD_PARTS_DLL when compiling these source files. This will result in your classes Part1 and Part2 getting exported from the DLL, along with the classes PClassObject<Part1> and PClassObject<Part2>.

Do not use -DBUILD_PARTS_DLL when compiling application source files, i.e. source files outside the DLL which use classes from the DLL, because this will give you several compiler errors, and will not import your persistent classes correctly.

Step 7: Link the file containing PClassObject<type> instances into the DLL

In our example, the PClassObject<Part1> and PClassObject<Part2> classes were generated in the files parts_.cxx. The corresponding object file parts_.obj should be linked into the DLL along with your persistent class object files.

You will now have a DLL which makes use of the C++/VERSANT classes from the C++/VERSANT DLLs and exports its own persistent classes for use by C++/VERSANT applications.

IMPORTANT RULE

Any header file which makes use of classes within the DLL must include the DLL-export header file before using any of the DLL classes.

Sometimes you want to forward-declare a class without including the entire header file where the class is defined. If the class is from a DLL, then you must ensure that the DLL-export header file of that DLL is included before the forward declaration, even if the class header file is omitted.

Following is an example of this rule.

// wrong.h
// Wrong!  Does not include either the DLL-export header
// file <partexp.h> or the class header file <part1.h>
class Part1 ;
class Wrong {
   Link<Part1> toPart1 ;
   ...
};

// correct.h
#include <partexp.h>  // must include partexp.h if
                      // <part1.h> is omitted
class Part1 ;
class Correct {
   Link<Part1> toPart1 ;
};

Failure to follow this rule will lead to compiler errors saying that the class PClassObject<type> cannot be redefined.

This rule is necessary, because when you declare a Link or BiLink to a class, the corresponding PClassObject<type> may get instantiated before the imported declaration is seen. When the imported declaration is seen later, it conflicts with the earlier instantiated type, and the compiler sees this as a class redefinition.

Multi-DLL applications

Although we have used the term "application" above to denote the code which is outside the DLL and makes use of the classes defined in the DLL, the "application" could itself be a DLL which contains its own VERSANT classes. You could build an application which contains several component DLLs, each defining their own persistent classes, and making use of persistent classes from other DLLs.

The steps to be followed when building multiple DLLs remain the same.

Every DLL must have its own unique DLL-export header file with a build macro and an export macro, and every DLL must also have a schema capture file. The build macro should be defined only when building its own DLL and should not be defined at any other time. The PClassObject<type> object files for each DLL should be linked into the DLL along with the other persistent class object files. Your multi-DLL C++/VERSANT application will then work correctly.

Example

The DLL creation procedure has been illustrated in one of the C++/VERSANT demos in the directory ...demo\cxx\hellodll. This demo is a DLL version of VERSANT's standard hello demo. The hello.h file makes use of a build macro BUILD_HELLO_DLL and an export macro O_HELLO_EXPORT. It uses the following statement to export PClassObject<Hello>:

O_PCLASSOBJECT_EXPORT(Hello, O_HELLO_EXPORT);

The Makefile will compile the hellodll.cxx file and the schema.cxx file with the option -DBUILD_HELLO_DLL, and puts the compiled files into hellodll.dll. The application, in this case, is the file helloapp.cxx, which makes use of the persistent class Hello exported by hellodll.dll.

 

OS/2

OS/2 procedures for compiling and linking are similar to those for Windows NT, except that no special effort is needed to create C++ DLLs. The OS/2 C++ compiler handles template classes in DLLs correctly, so that they can be compiled and linked like any other DLL.

The names of the OS/2 libraries are:

System

Two process — liboscfe.lib
One process — libosc.lib

C++/VERSANT production

Fundamental classes

Two process — icccls.lib
One process — icccls1p.lib

Collection classes
Two process — iccvcoll.lib
One process — iccvc1p.lib

C++/VERSANT checking

Fundamental classes

Two process — iccchk.lib
One process — iccchk1p.lib

Collection classes
Two process — iccvcch.lib
One process — iccvcc1p.lib

 

Run Application

Before running an application, you can optionally start the databases that you will use with the startdb utility. Starting a database is optional as an attempt to connect to a database will start that database if it is not already started. Often a formal startup is preferred as it readies the database for use without delay.

The startdb utility requires only the database name as an argument. For example, to start a database named DBNAME, from the system prompt:

startdb DBNAME

After running an application, you can optionally shut down your database with the stopdb utility.

The stopdb utility requires only the database name as an argument. For example, to shut down a database named DBNAME, from the system prompt:

stopdb DBNAME

For information on the startdb and stopdb utilities, see the "Database Utilities" chapter in the System Manual.

 

 

 


This online documentation is confidential and proprietary to Versant Corporation and is licensed to you, or to your organization, pursuant to the terms of an agreement between you and Versant that restricts the use of this documentation. Please refer to the agreement for more information or call Versant at 510-789-1500 with any questions.