Skip to main content
Previous sectionNext section

The Light C++ Binding

The Light C++ Binding (LCB) is most useful in applications where high performance is the primary concern, and class design is relatively simple. It is a special purpose, limited subset of the C++ binding, intended primarily for applications that must load data into a persistent database at very high speed. For example, some applications capture raw real-time data at such a high rate that it must typically be stored it in an in-memory database before it can be processed and transferred to persistent storage. LCB can offer a similar level of performance while also offering failover, which is not possible with an in-memory database.

For basic object manipulation (creating objects, opening objects by Id, updating, and deleting), LCB is ten to twenty times faster than the standard C++ binding.

Constraints on LCB Applications

The most significant tradeoff for this added speed is a limitation in the complexity of the objects to be stored. The primary constraints on LCB applications are as follows:

  • Caché must be installed with the “minimal” or “normal” security option.

  • No dynamic binding (classes must be known at code generation time)

  • Since projected LCB objects do not have corresponding Caché objects in memory on the Server, Caché method calls cannot be used.

  • The default storage structure must be used.

  • No integrity constraints or data validation, except checking for duplicate idkeys during insert (see Error Checking) and checking for duplicates in unique indexes.

  • No registered (transient) classes

  • No transient or calculated properties

  • No collections except lists or arrays of datatype.

  • No streams, relationships, or stored OIDs

  • Only the following Caché datatype classes are supported for properties and indices: %Integer, %Float, %Decimal, %Double, %String, %Date, %Time, %TimeStamp, and %Currency.

  • Idkey properties must be of type %String, %Integer, or %Date

  • Regular and bitmap indices only (no bitslice indices).

  • No triggers (except through SQL)

  • No non-standard LogicalToStorage or StorageToLogical conversions

  • The only supported collation types are SQL string, SQL upper, and exact

See Installing the Light C++ Binding for LCB installation requirements.

Light C++ Binding Architecture

LCB gains much of its improved performance because it provides a much faster way for the C++ application to communicate with the Caché Object Server. LCB does not use the standard binding's client/server architecture, with Caché running as a separate server process. Instead, Caché is loaded as a DLL or shared object, allowing it to execute as part of the application process. The following diagram illustrates this structure:

Light C++ Binding Architecture
generated description: cppliteflow

Both LCB and the standard C++ binding use the Caché C++ Generator and the Caché C++ Binding Library, but there are major differences at runtime:

  • Rather than using TCP/IP to communicate with Caché, LCB uses the Callin API to make intraprocess calls to the Caché kernel and Object Server. Although the server and the C++ application must be on the same machine, Caché ECP can still be used by the application to access data on remote machines.

  • For simple classes, LCB will use Callin functions to perform object loading and filing directly, entirely bypassing the server routines. The Callin interface provides extremely efficient low-level functions for accessing Caché databases.

  • Objects are loaded directly from a Caché database into a corresponding C++ object. The Caché Server does not maintain copies of these objects in memory. The C++ objects are not just proxies for objects on the Server, but contain actual data.

  • Since the C++ objects contain the only in-memory copy of the data, the C++ application can continue to work with them even if there is no connection between the Caché Object Server and the persistent database. This is especially important for multithreaded applications that want to share a connection between two or more threads.

  • Although most LCB properties behave just as they do in the standard binding, getters for non-numeric types return a reference, as optimization. For example, the getter for a string property might have the following signature:

       virtual const d_string& getname() const;
    
    Copy code to clipboard

LCB Classes in the Caché C++ Library

The Caché C++ library implements the Light C++ Binding with a special set of classes, most of which are LCB versions of classes used by the standard binding. The following classes provide the functions that you will need for an LCB application:

Connections and Multithreading

An LCB connection uses the following classes:

The following code fragment demonstrates how these classes are used. Compare the calls in this code to the example in Connecting to the Caché Database. Only the class names have changed.

   #  include "lc_connection.h"
   #  include "lc_database.h"
      Db_err conn_err; 

      d_connection conn =  lc_conn::connect(
         conn_str, user, pwd, timeout, &conn_err); 
      LC_Database db(conn);
Copy code to clipboard
Important:

For all LCB applications, the GLOBALS_HOME environment variable must be set to the root directory of the Caché instance (see “Additional LCB Requirements”). All connection attempts will fail if this environment variable is not set.

Multithreading

LCB is thread-safe, and uses the Callin API to provide parallel multithreading on multiprocessor machines. Performance is similar to that of multithreaded Callin applications. Unlike Callin applications, LCB applications do not require detailed knowledge of class implementations, since the C++ code can be regenerated whenever class definitions change.

A separate database connection (including both d_connection and LC_Database objects) must be used in each thread. If member functions of LC_Database, or of projection objects connected to an LC_Database instance, are called in a different thread than the thread in which the LC_Database object was created, the following exception is thrown: "Database connection may not be shared by multiple threads"

For examples of multithreaded LCB code, see the mttest.cpp and qtest.cpp sample programs located in <cachesys>\Dev\cpp\samples (see Default Caché Installation Directory in the Caché Installation Guide for the location of <cachesys> on your system).

Connections and Multiple Threads

Projection objects can only be connected to one database connection at a time, and can only be used in the thread in which that database connection was created. To use a projection object in more than one thread, use the projection object methods disconnect() and connect(). The is_connected() method can be used to determine the connection state.

  • A projection object is connected when it has been returned by create_new() or openid().

  • A projection object must be detached (see Attaching and Detaching LCB Objects) before being disconnected, and must be disconnected before being (re)connected to a different database connection

  • Using connect() and disconnect() permits one thread to be a factory for projection objects, which are inserted into the database in a different thread.

  • Projection object member functions that access the database enforce thread affinity, but get<name>() and set<name>() functions do not, and are not thread-safe.

  • Separate database connections can be used in parallel in different threads.

Attaching and Detaching LCB Objects

Since the C++ objects contain the only in-memory copy of the data, the C++ application can continue to work with them even if there is no connection between the Caché Object Server and the persistent database. This is especially important for multithreaded applications that want to share an object between two or more threads.

An object is attached when:

An object is detached when:

Transactions and Multithreading

Each thread / database connection has its own transaction context:

  • Threads look just like separate processes to Caché. Two threads may not use the same connection object.

  • Locks acquired by one thread block attempt to acquire the same lock in another thread.

  • Database transaction methods tstart(), tcommit(), and trollback() (see Using Transactions) only affect the calling thread.

Using Objects in LCB

LCB projected objects are different from standard C++ binding objects in a number of ways. It is important to understand these differences in the following situations:

  • Using persistent object references as properties

  • Using classes that inherit from other persistent classes

  • Using embedded serial object properties

  • Using list and array properties

Using Persistent Object References as Properties

When a Caché class uses a persistent class as a property, LCB projects the property as a reference to a persistent object. Each persistent reference property consists of an lc_d_ref that points to the referenced object, and a string representing the id of the object.

Property accessors get and set lc_d_ref<LC_xxx>& where LC_xxx is the client type of the property (for example, LC_User_Person is the client type for Caché class User.Person). Logically, the accessors work as follows:

  • getters — If the object pointer is null and the id string is not null, the getter calls openid() and sets the id string to the returned id, then returns the object pointer. If the object pointer and the id string are both null, it returns a null object pointer.

  • setters — The setter sets the object pointer to the referenced object. If the object has an id, the setter puts it in the id string, or otherwise sets the id string to null/empty.

For example, assume a Caché class Sample.Employee that contains a property Office of persistent class Sample.Office. Each class has a user-assigned idkey (Office on the office's city, and Employee on the employee's name). The following example opens the Office object for the New York office, and the Employee object for John Kent. If Kent is currently assigned to the Boston office, he is reassigned to the New York office:

   lc_d_ref<LC_Sample_Office> o = LC_Sample_Office::openid(db, "New York"); 
   lc_d_ref<LC_Sample_Employee> e = LC_Sample_Employee::openid(db, "Kent, John");
   if (e->getOffice()->getCity() = "Boston")  {
      e->setOffice(o);
      e->save();
   }
Copy code to clipboard

In generated code for classes that contain persistent reference properties, the save() function, and getter and setter functions for persistent reference properties, are generated in the .cpp file rather than inline in the .h file, as is normally the case. This prevents the .h file of a projected class from indirectly including itself due to circular references.

Getter functions for persistent reference properties, unlike other getter functions, are not declared as const, since they must modify the referencing object when they cause it to swizzle.

Using Classes that Inherit from Other Persistent Classes

The Light C++ Binding supports retrieval and update of persistent objects whose classes inherit from other persistent classes. Batch insert is not supported for classes that inherit.

The LCB projection class for a Caché class inherits from the projection class of that Caché class's superclass. With multiple inheritance, this applies only to first class in the list of superclasses. Other superclasses are transparent to LCB, which simply sees their properties as belonging directly to the subclass.

Opening a Subclass Object from its Superclass

An object of a class that inherits from another class can be opened by the openid method of the inherited superclass, provided that the C++ code for the subclass has been generated and linked into the application.

For example, assume a class LC_Sample_Employee that inherits from class LC_Sample_Person. If variable sub_id contains the id of an LC_Sample_Employee object, the following statement can be used to open it:

   lc_d_ref<LC_Sample_Person> newemployee = LC_Sample_Person::openid(db, sub_id); 
Copy code to clipboard

LCB will detect the actual class of the object to be opened, and will transparently open newemployee as an object of subclass LC_Sample_Employee.

Since LCB performs its storage operations in client-side code, it needs to be aware of the actual class of the object. If the generated code for a subclass is not linked into the application, LCB cannot open the object as that class. Instead, it will open the object as the nearest ancestor of the subclass for which linked code is available. In that case, an exception is thrown if the application attempts to update or delete the object, because LCB cannot ensure that all index entries will be properly updated or deleted.

Because LCB transparently opens an object as the most specific-possible subclass, if the same object is opened once from the superclass and once from the subclass, both lc_d_refs will reference the same subclass object in memory.

Note:

Unlike LCB, the standard C++ binding does not have the ability to transparently open an object as a more specific subclass than the class whose openid method was called. However, since the standard binding performs all of the actual storage operations on the Caché Server (which knows the actual class of the object), it is still possible to update or delete objects opened from a superclass that are actually of a subclass.

Using Embedded Serial Object Properties

LCB objects can have embedded serial objects as properties. An LCB serial property has a getter function that returns a pointer to the type of the property. For example, assume that Caché class Sample.Person contains a property Home of serial class Sample.Address. The getter function for the projected serial property would be LC_Sample_Address *getHome(). The following example retrieves the old street from a person's home address, and then assigns a new street value:

   lc_d_ref<LC_Sample_Person> p = LC_Sample_Person::openid(db, 1); 
   d_string oldStreet; 
   d_string newStreet("Broadway"); 
   oldStreet = p->getHome()->getStreet(); 
   p->getHome()->setStreet(newStreet); 
   p->save(); 
Copy code to clipboard

LCB serial classes (projected from Caché classes based on %SerialObject) inherit from LC_Serial_t. The generated code for an LCB serial class consists only of a header file (unlike persistent LCB classes, which have code in both .h and .cpp files). For example, a header file named LC_Sample_Address.h would contain the only projection code generated for Caché class Sample.Address.

Using List and Array Properties

The only collection types that can be used with LCB are lists or arrays of datatype. The following Caché datatype classes are supported: %Integer, %Float, %Decimal, %Double, %String, %Date, %Time, %TimeStamp, and %Currency (see the Reference for Simple Datatype Classes for a discussion of these datatypes).

Long strings must be enabled in order to store lists or arrays larger than 32K. To enable support for long strings system-wide, open the Management Portal and select System Administration > Configuration > System Configuration > Memory and Startup, then select the Enable Long Strings check box. The long string setting can also be overridden temporarily using $ZUTIL(69,69).

Lists of Datatypes

LCB projects list properties as std::vector<d_xxx>, where d_xxx is a valid LCB datatype class. For example, a list of %String would be projected as std::vector<d_string>.

Arrays of Datatypes

LCB projects array properties as std::map<d_string,d_xxx>, where d_xxx is the property's array element datatype. For example, an array of %String would be projected as std::map<d_string,d_string>.

Important:

When defining an array property in a Caché class, the property parameter STORAGEDEFAULT must be set to list. For example, a Caché class might define a %String array as follows:

   Property MyArray As array Of %String(STORAGEDEFAULT = "list");
Copy code to clipboard

The default parameter value, array, is not supported in LCB.

Standard LCB Projection Class Methods

The C++ Generator provides LCB projection classes with a set of methods similar to those generated for standard proxy classes (see Standard Proxy Class Methods). The methods listed in this section are added to all projection classes.

BuildIndices()

Invokes the Caché %BuildIndices class method (see %Library.Persistent) to completely rebuild all indices of the class. It can enhance performance when used after save() or delete_object() (called with defer_indices set to true).

   static InterSystems::d_status BuildIndices(
      InterSystems::LC_Database * db)
Copy code to clipboard
connect()

Connects (or reconnects) a projection object to a database connection. An exception will be thrown if the object is not disconnected.

   void connect(
      InterSystems::LC_Database * db)
Copy code to clipboard
create_new()

Creates a new projection object. The projection object is not saved to the database until save() or insert() is called. Once save() is called, the object is attached. If save() is called again, the object is updated. To cause save() to create a different new object, you must first call create_new() again, or call detach().

   static lc_d_ref<LCBclass> create_new(
      LC_Database* db,
      const_str_t init_val = 0,    // const_str_t is a typedef of const wchar_t*
      Db_err* err = 0)
Copy code to clipboard

The create_new() method inherited from LC_Persistent_t is overridden by a version that returns a reference to the specific class (lc_d_ref<LCBclass> where LCBclass is the name of the projection class).

delete_object()

Deletes an open object from the database. This method allows you to delete an open object from the database without destroying the projection object.

   d_status delete_object(
      bool defer_indices = false,
      int timeout = -1,
      Db_err* err = 0)
Copy code to clipboard

After delete_object() is called, the projection object still contains property data values, but is no longer attached. If the object was locked, the lock is released.

Calling save() right after delete_object() will create a new database object with the same values, except for any properties explicitly set to different values

detach()

Detaches a projection object from an object in database. This is a no-op if the object is not attached). If a retained lock is held on the object, it is released.

   void detach()
Copy code to clipboard

Property values are retained in the projection object. This permits reusing the projection object to create multiple new database objects, avoiding the overhead of calling create_new() and copying properties that have same values in multiple objects.

direct_save()

A very-high-performance alternate interface for creating new objects while avoiding the overhead of projection object instantiation and data conversions. It can only be used to insert new objects, not to update existing objects. direct_save() can be used with a Unicode database, but it only supports ASCII characters in strings.

   static d_status direct_save(
      LC_Database* db,
      const char *<prop1>,
      ..., 
      const char *<propN>)
Copy code to clipboard

The parameters <prop1>...<propN> are properties of the class.

This method does not create index entries (although you can use BuildIndices() after saving).

disconnect()

Disconnects a projection object from a database connection. An exception will be thrown if the object is not detached, or if the object's current database connection was created in a different thread.

   void disconnect()
Copy code to clipboard
id()

Gets an id from an attached object. The *buf parameter can be either a multibyte string or a Unicode string.

   int id(
      wchar_t *buf,
      size_t bufsiz)
Copy code to clipboard
   int id(
      char *buf,
      size_t bufsiz)
Copy code to clipboard
insert()

Inserts a new object into the database.

   d_status insert(
      bool defer_indices = false, 
      int timeout = -1, 
      Db_err* err = 0)
Copy code to clipboard

If called for an attached object, insert() detaches the object, and attempts to insert a new object with the same property values. For a user-assigned idkey, this causes a duplicate idkey exception.

is_attached()

Returns true if the projection object is attached.

   bool is_attached()
Copy code to clipboard
is_connected()

Returns true if the projection object currently has a connection to the database.

   bool is_connected()
Copy code to clipboard
openid()

Opens an existing object, specified by id. Projection object data members are set to current values from the database object, and the projection object is attached to the database object.

   static lc_d_ref<LCBclass> openid(
      LC_Database* db,
      const_str_t ident,         // const_str_t is a typedef of const wchar_t*
      int concurrency = -1,
      int timeout = -1,
      Db_err* err = 0)
Copy code to clipboard
   static lc_d_ref<LCBclass> openid(
      LC_Database* db,
      const char * ident,
      int concurrency = -1,
      int timeout = -1,
      Db_err* err = 0)
Copy code to clipboard

The ident parameter can be either a multibyte string or a Unicode string. The openid() method inherited from LC_Persistent_t is overridden by a version that returns a reference to the specific class (lc_d_ref<LCBclass> where LCBclass is the name of the projection class).

release_shared_lock()

Explicitly releases a shared lock when the projection object has been opened with concurrency mode LC_CONCURRENCY_SHARED_RETAINED (see LCB and Concurrency).

void release_shared_lock()
Copy code to clipboard
save()

Saves the projection object to the database. It must be explicitly called to save a newly-created object to the database, or to save changes to the database.

   d_status save(
      bool defer_indices = false,
      int timeout = -1,
      Db_err* err = 0)
Copy code to clipboard

If the projection object is attached, save() updates the object. If the object is detached, save() creates a new object. In transactions, save() brackets the update with implicit calls to tstart() and tcommit() (see Using Transactions).

set_from_err_list()

Sets the projection object's property values from the error list entry returned by a batch insert (see Using LCB Batch Insert).

   void set_from_err_list(
      const std::pair<d_status,d_binary> & list_entry)
Copy code to clipboard
update()

Updates an existing database object.

   d_status update(
      bool defer_indices = false, 
      int timeout = -1, 
      Db_err* err = 0)
Copy code to clipboard

The object must already be attached. If not, the following exception is thrown: "Object must be opened or inserted before being updated".

Using Queries in LCB Applications

Queries in LCB applications use the same API calls as the regular binding (see Using Queries), and provide similar performance. To run a query in an LCB application, pass an instance of LC_Database as a database argument to the d_query constructor. For example:

   LC_Database *db;
   d_query q(db);
Copy code to clipboard

LCB queries do have some limitations compared to the regular binding:

  • Queries cannot reference or return stream-type properties.

  • Only ad-hoc queries are currently supported, not pre-defined named queries and stored procedures.

  • For authentication and security under UNIX®, users running LCB applications must belong to the cacheusr group, or be running a trusted application (see Running Trusted Applications on UNIX®).

Using LCB Batch Insert

The LC_Batch class provides methods for batch insertion using the Light C++ Binding. The << operator is used to serialize objects and add them to a batch. When a batch is saved with the flush() method, there is a separate implicit transaction for each object, and the transaction is rolled back if there is any error. The get_errors() method returns a list of failed transactions.

Performance is only slightly better than with save(), but a single projection object can be serialized repeatedly with different property values, which may significantly reduce the processing overhead compared to calling create_new() for each insert.

  • Use the << operator to add objects to a batch.

    Once a projection object has been created with create_new() and its properties have been set, it can be serialized into a batch using the << operator. The same projection object can be reused, or different projection objects can be used for each serialization in the batch. In the former case, any properties which are intended to have the same value in every object in the batch only need to be set once.

  • Use clear() to remove an object from the batch.

    If an application does not wish a batch to be saved, after objects have already been serialized into it, the application should call clear(), which resets the batch's number of objects to 0 (but does not reset the error or id lists).

  • Use flush() to save the batch.

    Once all objects to be inserted have been serialized into the batch, the batch is saved by calling its flush() method either directly or indirectly. The close() method calls flush(), and the LC_Batch destructor calls close().

  • After flush() is called, the get_errors() method can return an error list.

    • Each list entry is pairing of error status and object serialization.

    • If there were no errors, size() of the list is 0.

    • The set_from_err_list() method can be used to examine properties of the objects that had errors.

Transaction Handling

If the _do_tx parameter of the LC_Batch() constructor is set to true, there is a separate implicit transaction for each object when a batch is saved. The implicit transaction is committed if the object is saved successfully, or rolled back if there is any error. If the entire batch should be either committed or rolled back, the call to flush() should be bracketed with calls to LC_Database methods tstart() and tcommit() (see Using Transactions). This will cause the entire batch insert to be rolled back if an error is encountered.

Optimization

  • Compile classes with optimization level o2.

    In Visual Studio, go to the Tools Menu > Options > Compile tab and check the Optimize within class and calls to library classes checkbox. This will improve performance 5 to 10 percent.

  • When creating a new LC_Batch object, set the reserve_size parameter appropriately.

    Performance is enhanced by reserving as many bytes as will actually be needed for the batch's serialization buffer. This will avoid the cost of enlarging the buffer and copying data as objects are added to the batch. Specify reserve_size as at least equal to the average object size multiplied by the number of objects to be inserted.

  • Set the do_tx parameter to false.

    Performance is substantially faster if do_tx is set to false (the default). If it is set to true, a tstart() and a tcommit() or trollback() (see Using Transactions) is performed for each insert within the batch.

LCB and Concurrency

LCB supports the standard Caché concurrency model (see Object Concurrency in Using Caché Objects). Use the following constants to specify concurrency level:

  • No locking at all:

    #define LC_CONCURRENCY_NO_LOCKING 0
    
    Copy code to clipboard
  • No lock during create; exclusive lock during update:

    #define LC_CONCURRENCY_ATOMIC 1
    
    Copy code to clipboard
  • Shared lock during create; exclusive lock during update:

    #define LC_CONCURRENCY_SHARED 2
    
    Copy code to clipboard
  • Shared lock retained after openid():

    #define LC_CONCURRENCY_SHARED_RETAINED 3
    
    Copy code to clipboard
  • Exclusive lock retained after openid():

    #define LC_CONCURRENCY_EXCLUSIVE 4 
    #define LC_CONCURRENCY_DEFAULT LC_CONCURRENCY_ATOMIC
    
    Copy code to clipboard

Specify concurrency level when opening an object. For example:

   d_ref<User_Person> person = 
      User_Person::openid(db, id, LC_CONCURRENCY_EXCLUSIVE);
Copy code to clipboard
Note:

You cannot specify a non-default concurrency level when creating a new object (although you can subsequently call openid() to set the desired concurrency level). LCB concurrency always defaults to LC_CONCURRENCY_ATOMIC.

Update Semantics

Calling save() or update() sets all properties of a database object from the projection object, whether or not the C++ application has modified them.

An object is protected from modification by other applications if it was opened with concurrency level SHARED_RETAINED or EXCLUSIVE.

If the object was opened with a lower concurrency level, save() may overwrite properties set by someone else's intervening update, or re-create the object after an intervening delete. This will not corrupt the object or indices, because appropriate locks are taken. You can explicitly call openid() again with a higher concurrency level, to lock an object that wasn't previously locked. This always reloads the current property values from the database.

When updating an object that was opened with a concurrency level lower than SHARED-RETAINED, if index updating is enabled and an object's indexed properties have been modified, the object is locked and the old property values are reloaded from the database. This is necessary so that the old index entries can be deleted.

Optimization and Troubleshooting

For best performance:

  • Avoid use of wchar_t strings if not needed.

  • Avoid unnecessary indices

  • For the initial load, save with defer_indices = true, then build indices at the end.

  • Define properties as %Double rather than %Float when possible.

Workarounds for SUSE 12 Linux Build Problems

Light C++ Binding applications on SUSE 12 Linux systems require one of the following two simple workarounds to build without error, due to a change in ld (the linker) from previous versions of SUSE Linux:

  • Option 1: Add -lpthread to the library specifications in the ld or g++ command line in the makefile used to build the application. For example, if using a makefile based on one of the Master.mak files installed with Light C++ Binding sample applications, change the line:

       CACHETLIB = -L$(CACHETPATH) -lcachet
    Copy code to clipboard

    to

       CACHETLIB = -L$(CACHETPATH) -lcachet -lpthread
    Copy code to clipboard
  • Option 2: Set the environment variable MULTITHREADED to 1 in the environment in which make is invoked. In Bourne shell the syntax is:

       export MULTITHREADED=1
    Copy code to clipboard

Either option resolves the issue, or both options can be used together. The workarounds are made necessary because the linker now requires application makefiles to explicitly specify dependent libraries of other libraries with which they link, even if those other libraries specified the dependent libraries when they were linked. In this case, libcachet.so depends on libpthread.so, so starting with SUSE 12 Linux, both libraries must be explicitly specified.

Detecting “object not found” Errors

If openid() is called with the optional Db_err* parameter and no object with the specified id exists, the Db_err code is set to -3, and msg is set to "object not found". The d_ref to which the result of openid() is assigned is set to null, which can be detected by calling its is_null() member function.

It is the caller's responsibility to either test whether the Db_err code is non-zero, or to test whether the d_ref is null, before dereferencing the d_ref. Dereferencing a null d_ref causes an exception to be thrown, with code -2 and msg "Can not dereference a null d_ref<> value". For example, assume the following code fragment is executed for id "2", and no object with id value 2 exists:

   Db_err openerr; 
      person = User_Person::openid(db, id, concurrency, timeout, &openerr); 
   if (openerr) 
      std::cerr << openerr << ",\n source = " << openerr.get_src() << '\n'; 
   else 
      printf("Object was found\n"); 
   if (person.is_null()) 
      std::wcout << L"Person with id " << id << L" doesn't exist\n"; 
   // Go ahead and dereference the d_ref whether or not it is null 
   d_string name = person->get_name(); 
Copy code to clipboard

Since the object was not found, the output is:

  Error: code = -3, msg = object not found, 
   source = LC_Database::lc_openid_obj 
  Person with id 2 doesn't exist 
  Error: code = -2, msg = Can not dereference a null d_ref<> value, 
   source = abs_d_ref::operator->() 
Copy code to clipboard

Calling the LC_Database and d_connection Destructors

It is important to call the LC_Database and d_connection destructors, in order to cleanly disconnect the application from Caché, causing the license and other resources to be released. The lcbdemo sample application shows an example of this.

If d_connection or LC_Database instances are declared as local variables, their destructors will automatically be called when they go out of scope. But if they are allocated via new and assigned to pointers, they must be explicitly destroyed using C++ "delete".

Using lc_conn::connect

  • lc_conn::connect changes the calling application's working directory

    lc_conn::connect has the side effect (by default) of changing the calling application's current working directory, because it uses ZN to change namespace to the namespace specified in the connect string.

    This behavior can be disabled via a system configuration option in the Management Portal: Configuration->Advanced, ObjectScript: SwitchOSDirectory. Set this to "true" to cause Caché to not switch the OS current working directory when changing the namespace (the name “SwitchOSDirectory” is counter-intuitive). This affects any use of ZN, not just via lc_conn::connect.

  • Avoid signal handling when using lc_conn::connect

    lc_conn::connect uses the Callin API CacheStart() function, which sets handlers for various signals. These handlers may conflict with signal handlers set by the calling application.

  • lc_conn::connect() does not set a SIGINT handler

    No handler is set for SIGINT when lc_conn::connect() invokes CacheStart(). This permits a user application to set its own handler. However, the user's handler should not terminate execution unless it can ensure that all threads which have active LCB connections have terminated them (by explicitly or implicitly destroying the d_connection object for the connection, or by directly calling CacheEnd() ).