ActiveX Interface for ObjectStore

Chapter 2

Building OSAX Object Servers

An OSAX object server is an ActiveX object that provides access to ObjectStore databases. It links application-specific methods with the database libraries and implements the COM interfaces required for ActiveX Automation, Scripting, and other capabilities. Once you build an object server, you can distribute it to target computers and register it in the ActiveX system registry, making it accessible to ActiveX controllers.

The following sections provide detailed information for building an OSAX object server, including information about the type description (.ost) file that is used to implement the object server and the osgentyp utility that processes this file.

Building an Object Server

The following procedure outlines the steps for building a typical OSAX object server. As an alternative to this approach, you can use the Microsoft Active Template Library (ATL) to create COM objects; see Chapter 3, Using OSAX with Microsoft ATL. The approach described here is simpler, but does not provide the flexibility of using ATL directly.

To build an object server, perform the following steps:

  1. Use the ObjectStore ActiveX AppWizard to create a skeleton project.

  2. Define the interfaces to your C++ objects in a type description (.ost) file.

  3. Provide implementations of your C++ objects either directly in the project or through an independently created DLL.

  4. Build the project.

The skeleton project produced in Step 1includes a *.def file. This file defines the exported entry points for the DLL that implements the object server. These points are the usual DLL registration entry points. All object server DLLs must export these entry points.

In Step 2, you write a type description (.ost) file, which defines the COM interfaces and object implementations in terms of C++ objects. The ObjectStore ActiveX AppWizard uses a custom build rule to run the osgentyp utility on the .ost file. For more information about the .ost file, see Type Description File.

The osgentyp utility generates the following files:

For more information about the osgentyp utility, see The osgentyp Utility.

The .idl file is processed by Microsoft's idl compiler, midl. The application wizard adds a custom build rule to the project to process the .idl file. The midl compiler generates the following files:

The ObjectStore ActiveX AppWizard invokes the midl compiler with the option for type library marshaling, so you can ignore the marshaling files.

Step 3 is the same as for any ObjectStore application. The application wizard creates an empty schema file and a custom build rule to run the ossg utility on the schema file. If you are using an independent DLL for your C++ objects, your server does not have a schema and you should remove the schema file from your projects. You will need to include the header file for the DLL's API in stdafx.h.

The *_imp.cpp file produced by the osgentyp utility contains the implementations of all the objects in the server. The C++ code uses the ATL interface to OSAX to implement the objects. If you decide later to use ATL directly, you can start with the *_imp.cpp file. For information about using ATL, see Chapter 3, Using OSAX with Microsoft ATL.

After all the C++ and resource files have been compiled, they are linked together along with the registration information and the type library. The final step is to register the server. The osgentyp utility and the application wizard produce a self-registering DLL that can be given to the program regsvr32. The project registers the DLL as the final step of the build process.

Basic Operations Supported by an Object Server

An OSAX object server supports the following operations on databases and transactions:
Object TypeVisual Basic SyntaxOperation
IOSAXObjectStore

ObjectStore.OpenDatabase pathname

Open the specified database.

ObjectStore.BeginTrans

Begin a dynamic transaction.

ObjectStore.CommitTrans

Commit the current transaction.

ObjectStore.Rollback

Abort the current transaction.

IOSAXDatabase

osDB.Value(rootname, type)

Return the value in the named root, which must be of the indicated type.

osDB.Open

Open the database.

osDB.Close

Close the database.

Basic Types Supported by an Object Server

An OSAX object server provides a number of Automation Interfaces to represent ObjectStore types such as databases, collections, and cursors. The contents of databases-essentially, C++ pointers to C++ objects containing C++ data types-are converted to one of the following:

The conversions performed on the various C++ types are listed in the following table:
C++ Member TypeActiveX Data TypeVisual Basic Type
char, int, long

I4

Long

float, double

R4 or R8

Float, Double

char*, signed char*,
unsigned char*, char[]


BSTR (Literal values)

IOSAXString* (Object values)

String

IOSAXString

void*

IDispatch*

Object

Application-specific pointers

Generated interface from typelib, if available; else IDispatch

Generated object type, or Object

NULL (void* == 0)

Null

Nothing

struct,class (embedded)

Generated interface from typelib, if available; else IDispatch*

Generated object type, or Object

os_array*
os_bag*
os_collection*
and subclasses
os_dictionary*
os_set*


IOSAXCollection*

IOSAXCollection or Object

os_database*

IOSAXDatabase*

IOSAXDatabase

os_segment*

IOSAXSegment*

IOSAXSegment

Note: C++ character arrays require special treatment. See Accessing and Creating Character Strings.

Object Server Configuration

An OSAX object server is configured to run in the same process as the client. If you want out-of-process or remote servers, you can write a simple out-of-process server to manage the in-process OSAX servers, or you can use the ATL interface to OSAX to create an OSAX server as an executable or as an NT service.

OSAX object servers use a threading model of Both, which means that they are suitable for single-threaded and multi-threaded clients. Object servers expose each C++ class with a single dual automation interface, making them accessible to scripting languages. The ATL interface to OSAX may be used to expose multiple custom interfaces.

Type Description File

You define an OSAX object server by writing a type description file for processing by the osgentyp utility (see The osgentyp Utility). The type description file is a small source file written in a combination of C++ and idl. The name of the file has the .ost extension.

The type description file defines the COM interfaces and their implementations, and contains the following information:

The type description file begins with a description of the type library, followed by descriptions of the exposed objects. The exposed objects include a combination of the following:

Top-level objects and instance classes have no persistent state; instances are bound to C++ objects. For detailed information about these objects, see OSAX and ATL Concepts in Chapter 3, Using OSAX with Microsoft ATL.

The following sections describe the different specifications to include in the type description file.

Library Specification

The type description file must contain a library specification in the following syntax:

[attributes] library name {library-members}
attributes
Is a comma-separated list of the following:

name
Is the name of type library (TLB) to be built.

library-members
Specifies the library and class information to be generated, consisting of object specifications (see
Object Specification).

Example
The following is the library specification from books.ost in the Books example:

[
      helpstring("ObjectStore OSAX books example 3.0"),
      lcid(0x0000),
      uuid(326D9EC0-4012-11D1-B9C3-0800091AAA11)
      version (3.0),
      objectstore
]
library booksObjectServer
{
      // Objects and classes to be defined here...
}
These elements define a type library with the name booksObjectServer. The helpstring, lcid, uuid, and version attributes are standard IDL. The objectstore attribute instructs osgentyp to link the object server with the OSAX libraries and storage system.

Object Specification

The type description file must contain object specifications in the following syntax:

[attributes] class typename {method-specifications };
[attributes] object coclass {method-specifications };
class typename
Defines an OSAX class and instance class for the C++ typename. The instance class is defined as ItypenameClass, and the instance interface as Itypename. Instances are references to C++ objects of the specified type. You also tell osgentyp that all occurrences of typename should be exposed to the OSAX client using the instance interface.

object coclass
Defines coclass as a COM object and interface that is a top-level object. The top-level object includes methods and properties for accessing interfaces to ObjectStore and instances and instance classes.

attributes
Is a comma-separated list of the following:

method-specifications
Specifies the method or property of the object specification, using the following syntax:

[attributes] return-type MethodName (argument) implementation;
attributes
Is a comma-separated list of any or none of the following:

return-type
Is the C++ type of the value returned by the method. The osgentyp utility generates code that translates the C++ type to an ActiveX type if you have defined a class in the .ost file for the type; see
The osgentyp Utility.

MethodName
Is the name of the method as it appears to the OLE client.

arguments
Is a C++-style argument list of types and variables. The ActiveX interface uses ActiveX types for the C++ types that have classes defined for them in the .ost file. The osgentyp utility generates code that reverse-translates the ActiveX types to their C++ equivalents; see
The osgentyp Utility. The variables in the argument list are available as C++ types for use in the method implementation.

implementation
Tells osgentyp how to implement the OLE method in C++. If no implementation is specified, osgentyp puts the method in the interface and class definition and expects you to provide the code. The following implementations are available. Note that argument-list (where specified) is optional.

Example
The following object specifications are from the Books example. The elements in the first example define an ActiveX object named OSAXBooksServer with a class factory implemented by the object server:

[
      helpstring("ObjectStore OSAX books example"),
      uuid(326D9EC1-4012-11D1-B9C3-0800091AAA11),
      version(3.0),
      appobject,
      progid("OSAX.Books.3","OSAX.Books"),
      factoryuuid(326D9EC5-4012-11D1-B9C3-0800091AAA11), interface(IOSAXBooks)
]
object OSAXBooksServer
The class factory enables ActiveX clients and controllers to refer to the OSAXBooksServer object by name (for example, using CreateObject("OSAX.Books")). This name is specified by the progid attribute. The attribute takes both versioned and unversioned names. The appobject attribute specifies that this object is an appobject for controllers that support appobjects.

The elements in the next example define the top-level properties implemented by the object server:

{
      [propget] 
      IOSAXObjectStore* ObjectStore() class OSAXObjectStore;
      [propget] IBookEltClass* CBookElt() class BookElt;
      [propget] IBookClass* CBook() class Book;
      [propget] IAuthorClass* CAuthor() class Author;
      [propget] IOSAXStringConstructor* CString() class char;
      [propget] 
      BSTR ObjectServerDirectory() function get_server_directory;
};
The first element is the property named ObjectStore. The ObjectStore property supports the OLE interface IOSAXObjectStore, implemented by the C++ class named OSAXObjectStore. This interface and this class are part of the OSAX product. The second property, CBookElt, supports an interface representing the class BookElt. This property is used to type-check the first object retrieved from the database. The third property, CBook, is defined to return a class, so that its constructors and other static methods can be called. The CString property is defined to represent the constructor class for character strings. The ObjectServerDirectory property defines a method implemented by a global C++ function.

The following elements define the object server interface for the C++ class named Book:

[
      uuid(326D9EC6-4012-11D1-B9C3-0800091AAA11),
      classuuid(326D9EC9-4012-11D1-B9C3-0800091AAA11)
]
class Book
{
      [propget]
      char* Name()data name;
      [propget]
      Author* Author()data author;
      [class]
      Book* Create(IOSAXStorage* location, Author* a, char *t) new(location) Book(a, t);
};
The C++ objects of that book (stored in the database) are exposed in ActiveX as object references with two properties, Name and Author. Name uses an OLE interface that osgentyp automatically generates for the C++ type char* to represent the C++ data member named name. Author uses an OLE interface that osgentyp automatically generates for the C++ type Author* to represent the C++ data member named author. The Book class (exposed by the top-level property CBook) defines a constructor exposed as a class method named Create.

The interfaces for the C++ classes named Author and BookElt are defined similarly.

Accessing OSAX Objects

Every OSAX object has an associated context. The OSAX context holds the transaction state associated with object. When a method of an OSAX object is invoked, OSAX makes the object's context current. Any new OSAX objects that are created will use the current context as their context or, if there is no current context, create a new context.

Only one context can use ObjectStore at a time. With OSAX, using ObjectStore means running a method, or being in a transaction. In general, all of your objects will have the same context. The easiest way to ensure this is to have a master object in your object server. Your client creates the master object and then uses the master object to create all other objects. For example, in ASP you can set a session variable to your top-level object so that all pages in the session run in the same context.

You can use the following method to change the context of an object:

When you create an instance container, it will have its own context. However, when you initialize the instance container with an instance, the instance container's context is changed to the context of the instance. Thus, you can safely use instance containers to hold instances. For example, in ASP you can initialize a session variable to an instance container for a database and then store a database instance in the container.

An instance class is normally obtained from a method on the top-level server object with class implementation, so that they pick up the correct context. If an instance class is created with a class factory, you can write a SetContext method for it to put it in the proper context, as follows:

void SetContext(IUnknown* pUnk) function SetContextImpl(pUnk);
Instance classes are given class-wide methods (such as constructors) or methods that return the extent of the class. Instance classes are also used as type objects for some methods that need to know how to associate an OSAX object with an arbitrary C++ pointer.

Instance are only obtained from OSAX. Internally, every instance and instance class in a context has a unique kernel class that obtains instances for C++ pointers. There are two kinds of kernel classes: those with object tables and those without. Kernel classes with object tables are used for instances that reflect C++ identity. This is the default. When the isomorphic attribute of the instance is set as false in the .ost file, the kernel class will not have an object table, so every request for an OSAX instance for a C++ pointer returns a new OSAX instance.

You can use the following method to change the instance of an instance container:

void SetValue(IUnknown* pUnk) function SetDataImpl(pUnk);

Creating Objects

An OSAX object server can provide methods to create new persistent or transient objects, in addition to accessing and modifying existing objects. Here are several ways to do this:

The following sections describe the different approaches to creating objects in more detail.

Expose a Method on One Class That Creates Instances of Another Class

In this approach, one C++ class (for example, Bookshelf) defines a member function that creates an instance of another C++ class. OSAX simply exposes this method on the corresponding types. Following is the C++ and .ost code for this approach:
C++ DefinitionDefinition in .ost File
class Bookshelf {
public:
os_List<Book*> books;
Book *AddBook(char *title);
}
class Bookshelf {
Book *Add(char *title) method     
AddBook(title);
}

Given these definitions, a Visual Basic program could create a new book using syntax like the following:

Dim newBook As IBook
Set newBook = Bookshelf.AddBook(newTitle)
where the Bookshelf object is obtained at a higher level, perhaps as the value of a database root.

Expose a C++ Static Member Function That Creates Instances of Its Class

In this approach, a C++ class defines a static member function (for example, Book::create()) that creates an instance of that C++ class. The [class] attribute in the .ost file directs OSAX to expose that method on the class corresponding to the exposed type (that is, IBookClass). Following is the C++ and .ost code for this approach:
C++ DefinitionDefinition in .ost File
class Book {
public:
   static Book *create(char
*title);
}
class Book {
 [class]
   Book *Create(char *title) method
create(title);
}

Every type exposed by OSAX also has an associated OSAX class. The latter two approaches expose methods on the class associated with an exposed type. The class is used for type checking and also for exposing static members or class methods such as constructors. For example, if you expose a C++ class named Device, it would normally be associated with two OLE interfaces, IDevice and IDeviceClass. Any instance members you wanted to expose would appear on IDevice. Any static members or constructors would appear on IDeviceClass.

Use the [class] attribute in the .ost file to expose class members. Use the class keyword to access the class. For example, the following .ost object specification syntax defines an object server with top-level properties to directly access the class objects for Book and Author:

object OSAXBookExample
{
      IOSAXObjectStore* ObjectStore() class OSAXObjectStore;
      [propget] IBookClass* CBook() class Book;
      [propget] IAuthorClass* CAuthor() class Author;
};
Given these definitions, a Visual Basic program could create and access a new book using syntax like the following:

      Dim newBook As IBook
      Set newBook = CBook.Create(newTitle)
      MsgBox "The new book title is " & newBook.Name

Expose a C++ Constructor That Creates Instances of Its Class

In this approach, a C++ class defines a constructor (for example, Book::Book()) that creates an instance of that C++ class. The [class] attribute in the .ost file directs OSAX to expose that method on the class corresponding to the exposed type (that is, IBookClass). The new keyword makes the implementation of that method call the C++ overloaded new operator for that type. Following is the C++ and .ost code for this approach:
C++ DefinitionDefinition in .ost File
class Author
{
   Author (char *n) { name = n; }
};
 class Author
 {
    [class]
    Author* Create(IOSAXStorage* location,
char *name) new(location)
Author(name);
};

The new keyword used to expose constructors takes a single parameter that specifies where to allocate the new object. This parameter corresponds to the first parameter of the overloaded new operator of the ObjectStore C++ interface. It can be one of the following:

Deleting Temporary C++ Objects

Ordinarily, persistent ObjectStore objects are not deleted when they are unreferenced by any ActiveX object. This behavior might not be appropriate for transient objects. To delete the C++ object when the reference count of the ActiveX object becomes 0, define a method like this in the application object:

void SetCleanup(IUnknown* s) function DeleteDataOnFinalRelease(s, TRUE);
In the controller (for example, Visual Basic), you can use this method to cause an OSAX object to be deleted when its reference count goes to 0. To do this, call SetCleanup(), as in the following example:

Dim X As MyObject
Set X = GetOneOfMyObjects
Call SetCleanup(X)
You can also define a method on your instance to mark it for deletion when on final release, as follows:

void DeleteOnRelease() function DeleteDataOnFinalRelease(TRUE);

Accessing and Creating Character Strings

OSAX treats character strings specially to accommodate the different language semantics of C++ and ActiveX. In C++ and in ObjectStore, character strings are objects with unique identities. In ActiveX and Visual Basic, strings are considered literal values without identity. The OSAX object server effectively provides both behaviors. It exposes C++ char* strings as full-fledged object references of type IOSAXString. Instances of IOSAXString have identity and a default value property to coerce them to their literal ActiveX representations (Unicode BSTR).

Most Automation controllers, when given an IDispatch object in contexts requiring a string, will attempt to call the default value property to obtain the string value. This hybrid behavior is also suitable for exposing character pointers that are not actually strings.

In the .ost file, character strings are declared using the C++ char* data type, as in the following example.
C++ DefinitionDefinition in .ost File
class Author {
   char *name;
   Book *book;
   Author (char* name);
}
class Author {
     [propget] char* Name() data name;
     [propput] void Name(char* n) data name;
     [class]     Author* Create(IOSAXStorage 
*loc, char *n) new(loc) 
Author(n);
};

OSAX represents the character string values at run time using the interface IOSAXString. This interface has a default value property that converts the value to a literal Unicode BSTR. The following Visual Basic excerpt shows a character string property accessed both as a literal value and as an object.

Dim Author, Copy As IAuthor
MsgBox "The author's name is " & Author.Name
Set Copy.Name = Author.Name
The MsgBox statement requires a Visual Basic string for the concatenation of the message text. Therefore, the Author.Name property is automatically converted to a literal string. However, the Author.Name property is actually exposed as an object using the IOSAXString interface. This is so the property can be copied to another object (for example, Copy.Name) without losing its identity. The result is that the persistent object representing Copy contains exactly the same char* character string as the persistent object representing Author.

Creating Persistent Character Strings

OSAX provides a mechanism for creating and initializing persistent and transient strings. OSAX uses either the ANSI or OEM code pages to translate from the Unicode representation used by ActiveX to the 8-bit native C++ representation of char*. The strings are exposed using the IOSAXString interface, and they are created using the IOSAXStringConstructor interface.

An IOSAXStringConstructor interface represents the class object for IOSAXString. It is typically exposed at the top level of an object server, as in the following example. This example .ost object definition provides class objects named CAuthor and CString for the Author objects described in Accessing and Creating Character Strings.

object AuthorExample
{
      [propget] IOSAXObjectStore* ObjectStore() class 
OSAXObjectStore;
      [propget] IAuthorClass* CAuthor() class Author;
      [propget] IOSAXStringConstructor* CString() class char;
};
The CString class used in this example implements three methods:

The following Visual Basic function uses these class objects to create a new persistent Author object. It includes a persistent character string to represent the author name.

Function CreateAuthor(Name As String) As IAuthor
      Dim osName As IOSAXString
      Set osName = CString.Ansi(Name, osDatabase)
      Set CreateAuthor = CAuthor.Create(osDatabase, osName)
End Function

Accessing Collections

To expose ObjectStore collections defined using the template classes os_Set, os_Bag, os_List, and os_Dictionary through OSAX, use the instantiated template class as a normal C++ type. You can use the template classes to specify the type of any argument or return value, as in the following example:
C++ DefinitionDefinition in .ost File
class Bookshelf {
public:
   os_List<Book*> *books;
}
class Bookshelf {
   [propget]
   os_List<Book*> *Books() data books;
}

At run time, all ObjectStore collections are represented using the IOSAXCollection interface. The IOSAXCollection interface provides methods for item lookup, insertion, and removal, as well as iteration and query.

For an example of an application that accesses collections, see the description of the Portfolio example in Chapter 5, The Portfolio Sample Application.

Defining a Collection

To define and expose a collection class in the OSAX .ost type description file, use standard techniques. However, since template instantiation does not exist in ActiveX, you must provide non-templated names for the class.

The following example shows how to expose a collection class in the OSAX .ost type description file, along with its element type. Note that the object declaration provides an accessor CBook for the element type, and CBookList for the collection type. These classes are defined later. The accessors enable a client to call static members and constructors.

object Library
{
      [propget]
      IOSAXObjectStore* ObjectStore()class OSAXObjectStore;
      [propget]
      IOSAXStringConstructor* CString()class char;
      IBookClass* CBook() class Book;
      IBookListClass* CBookList()class os_List<Book*>;
};
The following declaration for the Book class defines an ActiveX interface for it, in the standard manner. Note that the Create() class method is connected to the C++ constructor for the Book class.

class Book
{
      [propget]
      char* Author()data author;
      [propget]
      char* Title()data title;
      [class]
      Book* Create(IOSAXStorage* db, char* title, char* author) new 
(db) Book (title, author);
};
The following declaration for the instantiated collection class os_List<Book*> defines an ActiveX interface for it. OSAX provides the ActiveX interface from the interface for the os_Collection base class. The classinterface() and interface() attributes are used to specify appropriate names for use in the ActiveX domain. A Create class method is defined to provide a type-safe constructor based on the static C++ member os_List::create().

[
      classinterface(IBookListClass),
      interface(IBookList)
]
class os_List<Book*>
{
      [class]
      os_List<Book*>& Create(IOSAXStorage* db) method create (db);
};

Creating a Collection of Objects

The following Visual Basic excerpt shows how to use the interfaces defined in the hypothetical Library object server to create a collection of Book objects. The CreateLibrary routine uses the Create() class method to construct a new CBookList collection in the specified database. This routine also assigns the Create class method to a database root named "Books". Several Books are then created and inserted into the collection.

Sub CreateLibrary()
      Set Root = osDatabase.CreateRoot("Books")
      Set Root.Value = CBookList.Create(osDatabase)
      Set Root.Type = CBookList
      Set Library = Root.Value
      Library.Add CreateBook("Struggling Upward", "Alger, Horatio Jr.")
      Library.Add CreateBook("Peter Pan", "Barrie, James Matthew")
      Library.Add CreateBook("The Wonderful Wizard of Oz", "Baum, L Frank")
End Sub
The following CreateBook routine uses the Create() class method to construct a new CBook object in the specified database. The two arguments are copied from Visual Basic into the database. The standard ANSI code page is used for character conversion.

Function CreateBook(Title As String, Author As String) As IBook
      Dim osTitle, osAuthor As IOSAXString
      Set osTitle = CString.Ansi(Title, osDatabase)
      Set osAuthor = CString.Ansi(Author, osDatabase)
      Set CreateBook = CBook.Create(osDatabase, osTitle, osAuthor)
End Function

Iterating over Collections

The IOSAXCollection interface provides an implementation of the standard COM iteration protocol (IEnumVARIANT), so the normal iteration syntax of the hosting environment can be used. In Visual Basic, the iteration syntax is the For Each...Next statement, as in the following example:

For Each Book in MyBookShelf.Books
  Print Book.Title
Next Book

Querying Collections

The IOSAXCollection interface allows a collection to be queried. Use the ObjectStore collection query language documented in ObjectStore C++ API User Guide.

The IOSAXCollection::Evaluate() function takes one argument, a string representing the query expression, as described in the ObjectStore C++ API User Guide for os_Collection::query. The string is of the form

"type:query-expression"
For example:

Set ResultSet = ASetInstance.MemberSet.Evaluate("A*:Num == 111")
Or, using Visual Basic syntax extensions

Set ResultSet = ASetInstance.MemberSet[A*:Num == 111]
The result is a transient collection of elements of the specified type (A*).

Deleting a Collection

A collection of elements (but not the elements) is deleted when the last reference to the OSAX object is released.

Creating and Using Database Roots

Database roots are explicitly named and explicitly typed objects. They act as entry points to the contents of the database. Typically, a database has one database root. Sometimes, a database has several database roots. The database roots contain objects with properties or methods that lead to other objects in the database, such as a collection or list. Database roots are typed and their values are dynamically type-checked. This is so that all access to persistent objects in the database is type-safe.

You can create a database root two ways: use the ObjectStore C++ API in C++, or use an object server using the exposed IOSAXDatabaseRoot interface. The following excerpt from the Books example shows the creation of a database. It contains one root named Books intended to hold a list of books built of CBookElt objects. Initially, the value of the root is empty (Nothing in Visual Basic, NULL in C++).

Set osDatabase =
        ObjectStore.CreateDatabase(cdFilename.filename)
ObjectStore.BeginTrans
    Dim Root As IOSAXDatabaseRoot
    ` Create the database root, representing an empty list of books
    Set Root = osDatabase.CreateRoot("Books")
    ` Set root type before intializing root value
    Set Root.Type = CBookElt
    Set Root.Value = Nothing
    CreateBookList
ObjectStore.CommitTrans
Once a database root has been created, you can access and update its value using the Value property of the IOSAXDatabase interface. The following excerpt from the Books example shows the insertion of a new Book at the head of the list it maintains, using the Books database root.

Sub InsertBook(Book As IBook)
      Dim osElement As IBookElt
      Dim Head As IBookElt
      Set Head = osDatabase.Value("Books", CBookElt)
      Set osElement = CBookElt.Create(osDatabase, Book, Head)
      Set osDatabase.Value("Books", CBookElt) = osElement
End Sub

Object Server Exceptions

When you perform certain operations in C++, such as trying to open a database that does not exist, ObjectStore signals an exception. The exception might be a C++ exception or a TIX exception, depending on the storage system and the cause of the exception. (ObjectStore uses its own exception mechanism that predates C++ exceptions.) Your OSAX object server must handle the exception, because the ActiveX client cannot interpret the error otherwise.

Signaling Exceptions from an Object Server

C++ exceptions (derived from the class exception) can be caught and translated into ActiveX exceptions. To do this, use the exception attribute in the .ost file. The exception attribute takes one argument, which is the name of the hresult to be returned if a C++ exception is thrown. The exception attribute can be specified for the library, a class, or a method. When specified, it affects all contained methods.

For example, if an exception is thrown in the following method's implementation:

[exception(E_INVALIDARG)]
void SetName(char const * name)function set_my_name (name);
then an ActiveX exception is created with the description of the exception and E_INVALIDARG is returned from the method.

Handling Exceptions from an Object Server

The osgentyp utility inserts macros for general exception handlers in each of your methods. These macros interpret the C++ or TIX exception and convert it to an OLE Automation error. If the error received by an ActiveX client came from ObjectStore, it will have one of the following values, which can be expressed as either a symbolic constant or a hexadecimal value:

In addition to these exceptions, you could receive any of the exceptions that are normally raised by ActiveX.

You can obtain additional information about any exception from the error string.

Transactions and exceptions
When an exception occurs during a transaction, you need to roll back the transaction. At the same time, you should roll back any relevant transient state associated with the transaction. Then you can retry the transaction.

You might find it easiest to structure the work done during a transaction in a subroutine. This subroutine is called by a function that sets up the transaction and an error handler:

Private Sub TransactionExample()
      Dim count As Integer
      count = 0
      On Error GoTo Handle
      os.BeginTrans
            Call TransactionBody
      os.CommitTrans
      Exit Sub
Handle:
      If Err.Number = OSAXETxnAbort Then
            count = count + 1
            If count < 10 Then Resume
            End If
      Err.Raise (Err.Number)
End Sub

The osgentyp Utility

The osgentyp utility reads a type description file and generates the C++ and IDL source code required to implement the object server and its associated type library. osgentyp is typically invoked from a custom build rule attached to type description file in a Visual C++ project. When you use the ObjectStore ActiveX AppWizard to create your projects, the AppWizard creates the custom build rules for you.

Command-Line Syntax

The command-line syntax for invoking osgentyp is:

osgentyp option-list
where option-list is a space-separated list of any of the following:

Generating UUIDs for attributes

The osgentyp utility can automatically generate the UUIDs for the various uuid, classuuid, and factoryuuid attributes used in the type description file. Typically, these UUIDs are assigned when the interface in question is first created, and are rarely changed thereafter. Letting osgentyp manage UUIDs simplifies the administration of the object server by preserving its interface registration entries in the Windows system registry.

To cause osgentyp to generate UUIDs, invoke it with the /u option, as described earlier. The /u option causes osgentyp to read your type description file, generate new UUIDs for every interface, and write the file back out with the new UUIDs in place. Note, however, that any comments you have added to the file and any formatting changes you have made are removed as part of this process. If you want to preserve comments and formatting, rename the file before using the /u option and then manually make changes to the file afterwards. If the name of the type description file is books.ost, the following command line updates the file with UUIDs:

osgentyp /dt books.ost /u books.ost
Once you have inserted the UUIDs in this way, you can make future changes to the file and its interfaces and preserve their registration identity through the development cycle. If you add classes or substantially modify existing interfaces, you can specify the /u option again to supply new UUIDs.



[previous] [next]

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

Updated: 03/18/98 13:55:29