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.
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:
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:
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:
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:
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. 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 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
isomorphic(boolean)
Specifies the default value for library members. This attribute is optional. If it is not specified, the default is true.
lcid(lcid)
Specifies the locale ID-a language code for the type library. lcid is a 32-bit value identifying the language and a sort identifier. The first 12 bits of lcid are reserved by Microsoft. The next four bits contain the sort ID. The lower word identifies the language supported. This attribute is optional. If it is not specified, the locale ID defaults to 0x0000.
objectstore
Specifies that the object server uses ObjectStore for persistence. This attribute is required; it causes the ObjectStore header files to be included.
uuid(uuid)
Associates uuid with this library. This attribute is optional. If it is not specified, osgentyp generates one. You can have osgentyp rewrite the .ost file with generated uuids for the missing ones by invoking it with the /u option (see The osgentyp Utility); or you can supply a GUID generated using the Create GUID tool in the Microsoft Developer's Studio.
version(major[.minor])
Specifies the software version number, as assigned by the user. This attribute is required. If minor is not specified, the default is 0.
library-members
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.
[attributes] class typename {method-specifications }; [attributes] object coclass {method-specifications };class typename
object coclass
attributes
appinstance
Specifies that the instance container should be exposed as an appobject. Appobjects are automatically created when Visual Basic starts. Their methods appear at global scope. This attribute is optional and is meaningful only when specified with the object keyword.
classinterface(name)
Specifies name for the class interface. This attribute is optional; if not specified, the default name for the class X is IXClass.
classuuid(uuid)
Associates uuid with the class interface being defined. This attribute is optional; if not specified, osgentyp generates a UUID. You can have osgentyp rewrite the .ost file with generated UUIDs for the missing ones; see the description of the /u command-line option in The osgentyp Utility.
conversion
Specifies that the object is implemented by another object server. You must specify the interface and classinterface attributes in the .ost file. The object server that implements the object must provide instance containers. See the Active Server Pages extreme travel example for an example of this attribute.
exception(hresult)
Catches and translates C++ exceptions (derived from the class exception) into ActiveX exceptions. The exception attribute takes one argument. This argument is the name of the hresult to be returned if a C++ exception is thrown. You can specify the exception attribute for the library, a class, or a method. This attribute is optional; if specified, it affects all contained methods.
factoryuuid(uuid)
Associates uuid with the class factory being defined. This attribute is optional and is meaningful only when defining a class factory; see the progid attribute. If this attribute is not specified, osgentyp generates one. You can have osgentyp rewrite the .ost file with generated UUIDs for the missing ones; see the description of the /u command-line option in The osgentyp Utility. For information about setting the UUID for the instance, see the instancefactoryuuid attribute.
helpstring(string)
Associates string with the generated class for use in browsers. This attribute is optional.
instanceprogid(depName,IndepName)
Specifies that an instance container should be defined-that is, there will be a class factory for the instance. This attribute is meaningful only when defining an instance container. See the progid attribute for the meaning of Depname and Indepname.
instancefactoryuuid(uuid)
Associates uuid with the class factory of the instance container. This attribute is optional and is meaningful only when the instanceprogid attribute has also been specified. If this attribute is not specified, osgentyp generates one. You can have osgentyp rewrite the .ost file with generated UUIDs for the missing ones; see the description of the /u command-line option in The osgentyp Utility.
interface(name)
Overrides the default name for the interface for either an instance or server object. This attribute is optional.
isomorphic(boolean)
Overrides the default value set in the Library Specification. If boolean is true, the C++ object identity implies object identity for instance objects.
progid(DepName,IndepName)
Specifies that, in the class case, the instance container (or, in the object case, the top-level object) should have a class factory registered in the registry and associated with an application name. Two names are specified. DepName includes a version number. IndepName means "get the latest version." The Visual Basic function CreateObject, as well as the As New modifier to a variable declaration, obtain objects through the class factory mechanism. See the instanceprogid attribute for information about creating a class factory for the instance.
uuid(uuid)
In the class case, associates uuid with the instance interface of this class. In the object case, associates uuid with the interface. This attribute is optional. If it is not specified, osgentyp generates one. You can have osgentyp rewrite the .ost file with generated UUIDs for the missing ones; see the description of the /u command-line option in The osgentyp Utility.
version(major[.minor])
Specifies the version number for the interface. For more information, see the version attribute for the Library Specification.
[attributes] return-type MethodName (argument) implementation;attributes
exception(hresult)
Catches and translates C++ exceptions (derived from the class exception) into ActiveX exceptions. The exception attribute takes one argument, hresult, which is the value to be returned if a C++ exception is thrown. You can specify the exception attribute for the library, a class, or a method. When specified, it affects all contained methods.
helpstring(string)
Associates string with the generated class for use in browsers. This attribute is optional.
id(value)
Sets the method ID to value. Some OLE interfaces define method IDs explicitly. See the OLE documentation for further information.
propget
Specifies that the method is a property accessor. As far as the object server is concerned, there is no difference between properties and methods. However, in Visual Basic, a property behaves like a data member. Properties that can be put are also allowed to appear on the left-hand side of an assignment.
propput
Specifies that the method is a property setter. See the OLE documentation for additional information.
propputref
Specifies that the method is a property setter. See the OLE documentation for additional information.
restricted
Specifies that the method is restricted. See the OLE documentation for additional information.
MethodName
Is the name of the method as it appears to the OLE client.
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.
If the class attribute was specified, name is that of a static member. For the object case, it is a global variable.
When more arguments than are needed are specified for the method, they are passed as array-arguments. The final argument to a set method is always the new value.
Specify the order of array-arguments used for array access by following the name with the argument names. Separate the argument names with commas and enclose the list in brackets.
method name [(argument-list)]
The method implementation calls the specified C++ method on the associated C++ object. If the class attribute is specified, this calls the static member function.
The arguments in argument-list are passed to the C++ method. You control their order by following the name with argument-list in parentheses, as they would appear in a C++ method call.
function name [(argument-list)]
This method implementation calls the specified C++ function with the specified arguments. You can use the name this to refer to the associated C++ object. Methods can specify a namespace or class in the function name.
The arguments in argument-list are passed to the C++ function. You control their order by following the name with argument-list in parentheses, as they would appear in a C++ function call.
class name
Use this implementation to return the class object associated with one of your instance objects. The return-type should be a pointer to the appropriate interface type.
new [(where)] name [(argument-list)]
Use this method to allocate a C++ object. where is the database to allocate, and argument-list (if present) is the comma-separated list of constructor arguments.
[ 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 OSAXBooksServerThe 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.
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);
C++ Definition | Definition in .ost File |
---|---|
class Bookshelf { public: os_List<Book*> books; Book *AddBook(char *title); } | class Bookshelf { Book *Add(char *title) method AddBook(title); } |
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.
C++ Definition | Definition in .ost File |
---|---|
class Book { public: static Book *create(char | class Book { [class] Book *Create(char *title) method |
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.
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
C++ Definition | Definition in .ost File |
---|---|
class Author { Author (char *n) { name = n; } }; | class Author { [class] Author* Create(IOSAXStorage* location, |
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);
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.
Dim Author, Copy As IAuthor MsgBox "The author's name is " & Author.Name Set Copy.Name = Author.NameThe 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.
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:
IOSAXStringInterface::OEM
creates a new string in the specified location and initializes it using the supplied ActiveX Unicode string translated to 8-bit representation using the OEM code page. If you do not know whether to use OEM or Ansi, use Ansi.
IOSAXStringInterface::Copy
creates a new string in the specified location and initializes it using the supplied IOSAXString, which is already in 8-bit form.
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
C++ Definition | Definition 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.
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.
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); };
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 SubThe 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
For Each Book in MyBookShelf.Books Print Book.Title Next Book
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*).
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.CommitTransOnce 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
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.
OSAXETixError (&H80041001)
An unspecified exception has occurred. Look at Visual Basic's error object to determine what action to take.
OSAXETixFatal (&H80041002)
A fatal exception occurred and ObjectStore is unusable.
You can obtain additional information about any exception from the error string.
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
osgentyp option-listwhere option-list is a space-separated list of any of the following:
/dt file.ost
Specifies the name of the type description file. If this option is not specified, then the name of the type description file is taken from the argument to the /l option.
/i file.cpp
Specifies the name of the generated C++ file that implements the server. If this option is not specified, then the name of the C++ file is taken from the argument to the /l option.
/ih file.h
Generates class definitions in a separate header file. Use this option if you are implementing a method by hand. If this option is not specified, then the name of the header file is taken from the argument to the /l option.
/l name
Specifies the language-independent component of the type library name. If your type description, DLL, C++, and TLB files all have the same name, you need only specify the /l; name will be supplied as the default for any missing options. Note, however, that you must specify the /c option for any include files.
/t file.idl
Specifies the generated .idl file name that is input to the midl compiler. If this option is not specified, then the name of the .idl file is taken from the argument to the /l option.
/u
Rewrites the .ost file with generated UUIDs for any that are missing. All C and C++ style comments (// ... and /* ... */) will be removed from the rewritten file.
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.ostOnce 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.
Updated: 03/18/98 13:55:29