1. Create source code.
.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.
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.
PObject
should be defined at file-scope level.
PObject
in your class.
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() { }
If a class is a template class, its arguments must either be another class or a portable elementary type.
Attributes of all persistent classes should be one of the following types.
Elemental type
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.
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
.
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.
o_bool
as an attribute.
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 types are Link<type>
and LinkAny
.
Link vstr types are LinkVstr<type>
and LinkVstrAny
. VERSANT also provides array, set, list, and dictionary collection classes for variable length storage of links.
o_date
, o_interval
, o_time
, o_timestamp
.
const cannot be used as an attribute
const
keyword.
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.
*
) or references (&
) that point or refer to a persistent object. Instead, you should use link syntax for persistent references to persistent objects.
union
or anonymous
union
.
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
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
.
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
|
|
|
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.
Source file type |
Database type |
Vstr<source_type>
|
Vstr<database_type>
|
Link<classname>
|
o_object
|
LinkVstr<classname>
|
Vstr<o_object>
|
|
|
Class<char>
|
Class<o_1b>
|
Class<signed char>
|
Class<o_1b>
|
Class<unsigned char>
|
Class<o_u1b>
|
Class<o_1b>
|
Class<o_1b>
|
|
|
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.
PObject
should have at least one public constructor.
dirty()
in a 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.
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);
.....
}
The following methods require type identification:
Macros
AS()
|
cast object |
assertClass()
|
assert class derivation |
assertSpecies()
|
assert class |
|
cast object |
as()
|
cast object |
class_name()
|
get class name |
deleteobj()
|
delete object |
is_a()
|
is instance of a class |
|
is instance of a class |
as()
|
cast object |
class_name()
|
get class name |
deleteobj()
|
delete object |
|
is instance of a class |
PObject
contains virtual functions, at least one of the virtual functions must be non-inline.
hash()
and compare()
. For specific instructions, see the reference for the Collection type you are using.
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.
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
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
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.
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");
O_NEW_PERSISTENT()
macro. Forms of O_NEW_PERSISTENT()
are:
O_NEW_PERSISTENT( Type )( arguments );
O_NEW_PERSISTENT( Template<Type> )( arguments );
O_NEW_PERSISTENT1( db, Type ) ( arguments );
O_NEW_PERSISTENT1( db, Template<Type> ) ( arguments );
O_NEW_PERSISTENT2( Template<Type1,Type2> )( arguments );
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.
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
:
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.
partPtr
and a link partLnk
:
Part* partPtr;
Link<Part> partLnk;
delete partPtr;
delete (Part*) partLnk;
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.
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.
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."
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
O_NEST
option:
archive()
migrateobj()
migrateobjs()
releaseobj()
resetoct()
restore()
select()
xact()
with O_COMMIT or O_ROLLBACK
zapcods()
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()
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()
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(...);
}
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()
.
Object state methods
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
.
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<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
.
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 |
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
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.
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).
Link<PObject>
lnk
.
A method on PObject
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
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);
o_4b r = lnk.compareobj(lnk2);
Another method on PObject
, Link<type>
, and LinkAny
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();
lnk.deleteobj();
A method on Link<type>
and LinkAny
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();
Following are key data types that you will need to convert:
|
A typed link to an object created with C++/VERSANT. |
|
An untyped link to any object. Convertible to/from Link<type>. |
|
Typed variable length storage of elemental values. |
|
Untyped variable length storage of elemental values. Convertible to/from Vstr<type>. |
|
Typed variable length storage of links to objects created with C++/VERSANT. |
|
Untyped variable length storage of links to any objects. Convertible to/from LinkVstr<type>. |
|
Convertible to/from LinkAny. |
|
Convertible to/from VstrAny. |
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.
Conversions of Links and Pointers
For Run Time Type Checking
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.
AS()
and L_AS()
macros.
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 |
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
PObject
and class B
is derived from class A
:
From |
To |
Kind |
Example |
|
|
down |
|
|
|
up |
|
|
|
up |
|
|
|
up |
|
|
|
down |
|
|
|
down |
|
Type Conversion Using LinkAny
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);
}
LinkVstrAny
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);
}
o_object
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 );
}
o_vstr
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 );
}
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:
Error handling classes are PError
and PResult
.
.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.
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>
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
|
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
|
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
|
.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.
.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>);
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.
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.
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.
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. |
Attribute changes |
Reorder attributes. Change data type of an attribute. |
Class changes |
Add a superclass. Rename a 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.
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.
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.
.cxx
schema implementation file created by the Schema Compile Utility.There are two basic kinds of VERSANT libraries.
System libraries
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.
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.
The names of the UNIX libraries are:
System
Two process library |
archive |
|
|
shared |
|
One process library |
archive |
|
|
shared |
|
Fundamental classes |
archive |
|
|
shared |
|
Collection classes |
archive |
|
|
shared |
|
Fundamental classes |
archive |
|
|
shared |
|
Collection classes |
archive |
|
|
shared |
|
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.
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.
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
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
# 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
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.
For example, in the following, the value of b1
depends upon the byte ordering of int
and double
:
int b1 = * (int*) & a_double_variable;
For example, in the following, the value of n
depends on the compiler used:
class MyClass p;
int n = &p.m1 - &p;
size_of_MyClass
depends on the compiler used:
class MyClass p;
int size_of_MyClass = sizeof(MyClass);
long
double
data type.
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.
.cxx
schema implementation file created by the Schema Compile Utility.There are two basic kinds of VERSANT libraries.
System libraries
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.
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.
The names of the Windows NT libraries are:
System
Two process |
|
One process |
|
Fundamental classes
Two process |
|
One process |
|
Two process |
|
One process |
|
Fundamental classes
Two process |
|
One process |
|
Two process |
|
One process |
|
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
mscvc1p.lib msccls1p.lib libosc.lib
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.
-GX
-GX
compiler option to enable C++ exception handling.
-vmg
-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
-MD
. See the note below.
-DNOMINMAX
-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.
-vmg
option
-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:
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.
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
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.
-DWIN32
and -D_X86_
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.
schcomp
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 normally
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
).
sch2db
utility.For example:
sch2db -y -D mydb myschema.sch
See the chapter "Load Classes" for more information.
Do not capture schemas of C++/VERSANT standard classes.
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> >
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.
.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.
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.
For example, in the following, the value of b1
depends upon the byte ordering of int
and double
:
int b1 = * (int*) & a_double_variable;
For example, in the following, the value of n
depends on the compiler used:
class MyClass p;
int n = &p.m1 - &p;
size_of_MyClass
depends on the compiler used:
class MyClass p;
int size_of_MyClass = sizeof(MyClass);
long
double
data type.
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 use Microsoft specific data types and classes in a database.
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.
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.
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.
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.
schema
.cxx
file will be compiled as part of your normal build procedure.
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++.
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.
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
.
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.
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.
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.
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.
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.
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.
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.
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.
...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
.
The names of the OS/2 libraries are:
System
liboscfe.lib
libosc.lib
Fundamental classes
icccls.lib
icccls1p.lib
iccvcoll.lib
iccvc1p.lib
Fundamental classes
iccchk.lib
iccchk1p.lib
iccvcch.lib
iccvcc1p.lib
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.