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.
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:
![generated description: cppliteflow](images/gbcp_cppliteflow.png)
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;
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:
-
LC_Persistent_t — is the base class used to generate LCB persistent projection classes. It is the LCB version of Persistent_t (see Generating Proxy Classes).
-
LC_Serial_t — is the base class used to generate LCB serial projection classes.
-
LC_Database — is the LCB version of Database.
-
lc_d_ref<T> — is the reference class template for LCB objects (see Using Proxy Objects). It is the LCB version of d_ref<T>.
-
LC_Batch — is a special batch insert class that provides an alternate interface for high speed inserts.
Connections and Multithreading
An LCB connection uses the following classes:
-
d_connection — is the physical connection handle. This class is used by both LCB and the standard binding.
-
LC_Database — is a subclass of the standard Database class. It is initialized from an open d_connection, just like Database.
-
lc_conn — is the LCB connection class, used in place of the standard tcp_conn class. The connect method, lc_conn::connect() has the same syntax as tcp_conn::connect().
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);
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:
-
It has been returned from openid(), or it has just been created and save() or insert() has been called.
An object is detached when:
-
It has just been returned from create_new()
-
it has just been deleted by calling delete_object()
-
detach() has been called
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();
}
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.
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);
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.
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();
LCB serial classes (projected from Caché classes based on %SerialObjectOpens in a new tab) 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).
LCB projects list properties as std::vector<d_xxx>, where d_xxx is a valid LCB datatype class. For example, a list of %StringOpens in a new tab would be projected as std::vector<d_string>.
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 %StringOpens in a new tab would be projected as std::map<d_string,d_string>.
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 %StringOpens in a new tab array as follows:
Property MyArray As array Of %String(STORAGEDEFAULT = "list");
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.
Invokes the Caché %BuildIndices class method (see %Library.PersistentOpens in a new tab) 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)
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)
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)
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).
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)
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
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()
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.
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>)
The parameters <prop1>...<propN> are properties of the class.
This method does not create index entries (although you can use BuildIndices() after saving).
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()
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)
int id(
char *buf,
size_t bufsiz)
Inserts a new object into the database.
d_status insert(
bool defer_indices = false,
int timeout = -1,
Db_err* err = 0)
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.
Returns true if the projection object is attached.
bool is_attached()
Returns true if the projection object currently has a connection to the database.
bool is_connected()
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)
static lc_d_ref<LCBclass> openid(
LC_Database* db,
const char * ident,
int concurrency = -1,
int timeout = -1,
Db_err* err = 0)
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).
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)
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).
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)
Updates an existing database object.
d_status update(
bool defer_indices = false,
int timeout = -1,
Db_err* err = 0)
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);
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.
-
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.
-
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
-
No lock during create; exclusive lock during update:
#define LC_CONCURRENCY_ATOMIC 1
-
Shared lock during create; exclusive lock during update:
#define LC_CONCURRENCY_SHARED 2
-
Shared lock retained after openid():
#define LC_CONCURRENCY_SHARED_RETAINED 3
-
Exclusive lock retained after openid():
#define LC_CONCURRENCY_EXCLUSIVE 4 #define LC_CONCURRENCY_DEFAULT LC_CONCURRENCY_ATOMIC
Specify concurrency level when opening an object. For example:
d_ref<User_Person> person =
User_Person::openid(db, id, LC_CONCURRENCY_EXCLUSIVE);
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
to
CACHETLIB = -L$(CACHETPATH) -lcachet -lpthread
-
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
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();
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->()
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() ).