ObjectStore C++ API User Guide

Chapter 11

Component Schemas

Component schemas allow you to incorporate one or more DLLs in an ObjectStore application.

A component schema is a schema describing class types for a particular DLL. A DLL schema is to a DLL as an application schema is to an ObjectStore application.

Like a DLL, a DLL schema can be loaded and unloaded dynamically at run time.

This chapter introduces two new terms: program schema and complete program schema.

Component Schema and Application Schema

A component schema, also referred to here as a DLL schema, is a schema that is contained within a DLL. It plays the same role for the DLL as an application schema plays for an application. Like a DLL, a DLL schema can be loaded and unloaded dynamically at run time. Unlike an application schema, multiple DLL schemas can be in effect at the same time in a single program.

Differences Between an Application Schema and a Component Schema

A component (DLL) schema is a type of application schema with some additional properties. The file name extension .adb is used for both application schemas and DLL schemas. DLL schemas are generated by ossg just as application schemas are.

A program schema is an application or DLL schema. A complete program schema is all the loaded program schemas for an application.

When a type name is defined by more than one program schema, all definitions of the type must be the same.

Component schemas differ from application schemas in these ways:

Generation of DLL schemas is fundamentally the same as for application schemas. See Generating an Application or Component Schema in ObjectStore Building C++ Interface Applications for further information.

All ObjectStore utilities that can be used on application schemas can be used for component schemas.

Uses for Component Schemas

Some typical uses of DLL schemas are as follows:

How to Use Component Schemas

An example of four ways to use component schemas is given in the ObjectStore examples directory. The example can be found in these locations:

The example illustrates four ways to use DLLs with ObjectStore.

The first example, flights, uses a standard application schema.

The second example, flights2, uses a component schema generated for the DLL flight_cs with the schema source file macro OS_SCHEMA_DLL_ID( ). The component schema is loaded and unloaded automatically.

The third example, flights3, also uses a component schema in the same way as the previous example and additionally illustrates how a DLL can be dynamically loaded with an application. This example uses automatic load reporting, as shown in the following excerpt:

      ...
      const char *flight_cs_id = "DLL:flight_cs";  // DLL Identifer
...
...
      const char *flight_cs_symname = "flight_db_component"; 
      os_DLL_handle dll_handle = os_null_DLL_handle;
      os_boolean caught_except = false;

      // Load the flight_cs component. 
      TIX_HANDLE(err_DLL_not_loaded) {
            // This is equivalent to calling `dlopen' on Solaris2 or 
            // `LoadLibrary' on WIN32 platforms.  objectstore::load_DLL
            // is provided as a convenience for application developers.
      dll_handle = objectstore::load_DLL(flight_cs_id, true); 
      }
      TIX_EXCEPTION {
            caught_except = true;
      }
      TIX_END_HANDLE
      if (caught_except || (dll_handle == os_null_DLL_handle)) {
            cout << "Error: " << tix_handler::get_report() << `\n';
            cout << "Cannot load component: " << flight_cs_id << `\n';
            return 2;
      }

      // Look up the component's entry point
      caught_except = false;
      TIX_HANDLE(err_DLL_symbol_not_found) {
            // Same as calling `dlsym' on Solaris2 or `GetProcAddress' on 
            // WIN32 platforms.  Provided as a convenience for application 
            // developers.
      cfp = (int(*)(const char*))
      objectstore::lookup_DLL_symbol(dll_handle, 
                                          flight_cs_symname);
      } 
      TIX_EXCEPTION {
            caught_except = true;
      }
      TIX_END_HANDLE
      if (caught_except || !cfp) {
            cout << "Error: " << tix_handler::get_report() << `\n';
            cout << Cannot locate symbol: " << flight_cs_symname <<`\n';
      return 3;
       }
...
The fourth example, flights4, has the features of the previous example, but does not use automatic loading of the component schema. Instead it shows how to manually control how component schemas are loaded and unloaded. In the schema source file you see

// Component schemas need a DLL identifier so we define
// one using this schema macro.  
OS_SCHEMA_DLL_ID("DLL:flight2")

// Shut off automatic load/unload reporting.
OS_REPORT_DLL_LOAD_AND_UNLOAD(false)

// Tell ossg to use this variable name for the schema info.
OS_SCHEMA_INFO_NAME(flight_cs2_dll_schema_info)
main.cpp file
And in the main.cpp file:

...
      const char *flight_cs_id = "DLL:flight_cs2";  // DLL Identifier
      os_DLL_schema_info *flight_cs2_sch_inf = NULL;
      os_schema_handle *cs2_sh; 
...
...
      // Via a macro in the schema source file, we arranged for the 
      // schema generator to place a symbol name that we specified to 
      // be the name of the schema info structure 
      //(os_DLL_schema_info *).  
      // We simply look up the symbol in the DLL to get at it.
      caught_except = false;
      TIX_HANDLE(err_DLL_symbol_not_found) {
            flight_cs2_sch_inf = (os_DLL_schema_info *)
            objectstore::lookup_DLL_symbol(dll_handle, 
                              "flight_cs2_dll_schema_info");
      } 
      TIX_EXCEPTION {
            caught_except = true;
      }
      TIX_END_HANDLE
      if (caught_except || !flight_cs2_sch_inf) {
            cout << "Error: " << tix_handler::get_report() << `\n';
            cout <<"Can't locate symbol: flight_cs2_dll_schema_info\n";
      return 4;
      }

      // Automatic load reporting has been disabled for this component
      // schema(flight_cs2).  Which means that the schema for this DLL 
      // does not automatically load when put in use.  So we must 
      // manually set up the loading process here. 

      // Tell ObjectStore that this DLL has been loaded.
      cs2_sh = &flight_cs2_sch_inf->DLL_loaded();
...
Unload the DLL
After using the component, explicitly unload the DLL:

...
      //
      // Unload the component
      //
      // Tell ObjectStore that this DLL is about to be unloaded.
      flight_cs2_sch_inf->DLL_unloaded();

      objectstore::unload_DLL(dll_handle);
...

Building Component Schemas

Solaris
On Solaris platforms, to build an application with static linking, use a command of the following form:

CC foo.cpp foolib.cpp -ldl -o foo
To build an application with dynamic linking, use a command of the following form:

CC -G foolib.cpp -o foolib.so
CC foo.cpp foolib.so -ldl -o foo
To build an application with run-time library loading, use a command of the following form:

CC -G foolib.cpp -o foolib.so
CC foo.cpp -ldl -o foo
To build with dynamic linking and an explicit call to the library,

CC -G foolib.cpp -o foolib.so
CC foo.cpp foolib.so -ldl -o foo

DLL Loading and Unloading

Typically DLLs are loaded and unloaded by means of operating system calls. ObjectStore provides two additional methods to load and unload DLLs into ObjectStore applications.

A platform-independent interface:

An interface that enables ObjectStore to automatically load DLLs when a database is put into use:

These check whether the DLL is loaded or queued to load. If not, they call objectstore::load_DLL(), which checks each DLL ID and loads it.

See the ObjectStore C++ API Reference for more information on these interfaces.

DLL Load and Unload Reporting

When a DLL that has a DLL schema is loaded or unloaded, it must report that fact to ObjectStore so ObjectStore can load or unload the DLL schema. You do this reporting by calling the functions described below. In general, the actual schema loading or unloading is deferred until a later time.

In the simplest case, calls to these functions are automatically generated by ossg and inserted into the DLL as initialization and termination functions. The application developer does not have to do anything to implement this reporting. If you want to explicitly control this, specify in the schema source file that the automatic calls should be disabled and then write code that makes the calls. For example, you could set some ObjectStore parameters or the pathname of the DLL schema database before calling DLL_loaded(). This code could be in DLL initialization and termination functions, or could be in entry points to the DLL that are called according to the developer's specific protocol. See the ObjectStore C++ API Reference for further information on these functions:

DLL Identifiers

The DLLs that use component schemas must be assigned a DLL identifier. A DLL identifier is a generic way of identifying a DLL or catalog of DLLs. This identifier can be recognized by all platforms sharing a database that depends on the DLL.

A DLL identifier is a string of the form prefix:suffix where the prefix is a string that identifies the catalog of DLLs to be used and the suffix is something meaningful to that catalog. A colon can also appear in the suffix, but the first colon in an identifier is always interpreted as a separator.

ObjectStore provides the following built-in DLL identifier prefixes:

On Windows, the conversion appends .dll. On Solaris, the conversion appends .so.

You can also create other DLL identifier prefixes if needed.

See the following for more information on uses of DLL identifiers:

Schema Generation Macros

OS_REPORT_DLL_LOAD_AND_UNLOAD

Default: true

OS_REPORT_DLL_LOAD_AND_UNLOAD( os_boolean)
Reports that a DLL has been loaded or unloaded.

Component schema source file macro
When os_boolean is true, automatic reporting of DLL loading and unloading is enabled. To do this ossg generates code that calls os_DLL_schema_info::DLL_loaded() and os_DLL_schema_info::DLL_unloaded() from the DLL's initialization and termination functions.

OS_SCHEMA_DLL_ID

OS_SCHEMA_DLL_ID( string)
Component schema source file macro
Optional. For use in generating component schema, specifies the DLL identifier of the DLL. This macro can be used multiple times, for example, to specify different platform-specific DLL identifiers for different platforms. Do not conditionalize these calls on the platform - you want all the DLL identifiers to be recorded in any database that depends on this DLL.

You must call OS_SCHEMA_DLL_ID at least once in a DLL schema source file to distinguish it from an application schema.

OS_SCHEMA_INFO_NAME

OS_SCHEMA_INFO_NAME( name)
Use with component schema source file
Required for component schema. For use with component schema, generates a variable extern os_DLL_schema_info name; that is, the os_DLL_schema_info of this DLL. The default is to generate the schema information with a static name. Call this if you are going to call os_DLL_schema_info::DLL_loaded() yourself, so you can get access to the os_DLL_schema_info.

Use with application schema source file
This macro also works in an application schema, in which case the type of the variable is os_application_schema_info instead of os_DLL_schema_info.

Creating a DLL Identifier Prefix

In some circumstances you might need to create a DLL identifier using a prefix other than the ObjectStore-supplied prefixes DLL: and file:. Some examples of DLL catalogs for which there could be prefixes are

To inform ObjectStore how to understand a DLL prefix, create an instance of a subclass of os_DLL_finder and call its register_ function with the prefix string. Be sure to unregister the prefix before deleting the instance. It is customary for each subclass of os_DLL_finder to know the prefix that it implements and have a constructor and destructor that call register_ and unregister respectively.

You cannot register a DLL finder from a static constructor that could be called before objectstore::initialize() has been called. You must call objectstore::initialize() before doing anything with DLL finders, even registering them.

The argument to the get() function is a DLL identifier, not a prefix. The function finds the prefix by searching for a colon.

Each subclass of os_DLL_finder must provide an implementation of load_DLL that interprets the suffix part of the DLL identifier and calls the appropriate operating system API (or calls another os_DLL_finder) to load the DLL.

Each subclass of os_DLL_finder must provide an implementation of equal_DLL_identifiers_same_prefix that compares two DLL identifiers that are both implemented by this finder and returns true if they are equal.

To compare two DLL identifier strings, call the static function os_DLL_finder::equal_DLL_identifiers(). It takes care of getting the prefixes, looking up the finder, and calling equal_DLL_identifiers_same_prefix. Looking up objects by DLL identifier calls os_DLL_finder::equal_DLL_identifiers() to compare identifiers that have the same prefix.

The objectstore::load_DLL() functions call os_DLL_finder::load_DLL() for the finder that implements the prefix of the specified DLL_identifier.

See os_DLL_finder in the ObjectStore C++ API Reference for further information.

Compiler Dope Damage

Compiler dope is additional information added to the run-time layout of an object by the compiler, beyond the nonstatic data members of the object. Compilers use compiler dope for several purposes, most notably to point to a table of virtual function implementations for the object's class. When ObjectStore brings a persistent object into memory from the database, it ensures that the object contains the correct compiler dope for the current program, for the compiler with which the program was compiled. The correct compiler dope for an object can change as a result of loading or unloading a DLL schema, for example, because the compiler dope can point to a virtual function implementation contained in a DLL that is being loaded or unloaded.

Transient dope is the portion of the compiler dope that contains pointers to transient (nonpersistent) memory (that is, pointers to virtual function tables), and thus must be regenerated each time a persistent object is brought into the memory of a new program instance.

Dope damage is said to occur when the compiler dope of a cached persistent object becomes outdated as a result of loading or unloading DLL schemas. ObjectStore automatically detects dope damage when it occurs. You can select whether the response to dope damage when loading a DLL schema is to throw an exception or to repair the damage by regenerating compiler dope in all cached user data pages of affected databases. Note that this operation can take some time, so enable repair only when necessary. Dope damage when unloading a DLL schema occurs if there were any objects with virtual functions implemented in the unloaded DLL, so ObjectStore always repairs this form of damage.

Compiler dope can be damaged during the loading of a DLL schema in the following cases:

You can choose whether ObjectStore throws an err_transient_dope_damaged exception or automatically repairs the damage when dope damage occurs. Repairing the damage incurs overhead. To avoid this, enable the exception for programs that do not expect dope damage to occur. If you expect programs that load and unload DLLs dynamically to create dope damage, set the requirement that dope damage be repaired with the function objectstore::enable_damaged_dope_repair().

See the following in Chapter 2 of ObjectStore C++ API Reference for more information: objectstore::enable_damaged_dope_repair() and objectstore::is_damaged_dope_repair_enabled().

Note that dope damage always occurs when unloading a DLL schema and it is always repaired, regardless of the setting of objectstore::enable_damaged_dope_repair().

Schema Evolution

The ossevol utility can be used to evolve DLL (component) schemas.

Use the following keyword option:

ossevol <workdb> <schemadb> <evolvedb>+ [keyword_option]+
keyword_option ::= -dll_schema pthnames_of_component_schema
You can also use the following member functions of the class os_schema_evolution:

os_schema_evolution::augment_dll_schema_db_names
static void os_schema_evolution::augment_dll_schema_db_names
(const os_charp_collection& dll_schema_db_names);
and

static void os_schema_evolution::augment_dll_schema_db_names
(const char* c);
These two functions add the specified component schema database names to the list of component schema databases to be used for the evolution.

Exceptions

See Component Schema Exceptions in Appendix B of the ObjectStore C++ API Reference.



[previous] [next]

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

Updated: 04/02/98 14:57:08