This release of Caché includes many significant enhancements to the Caché Objects technology. These enhancements and their effect on existing objects are outlined in the following sections:
Compatibility and Upgrading
This release of Caché Objects is designed to be, for the most part, source-compatible with prior releases. There are, however, a number of internal changes to the class compiler, class dictionary, and object code format. Because of this, an upgrade requires an in-place conversion of any class definitions as well as the recompilation of all classes.
Note:
If you are upgrading from an earlier version of the v5.0 Developer’s Kit, you must also follow the upgrade steps specified here.
Upgrading Class Definitions
This version of Caché introduces a newer, more compact Class Dictionary format (the structure used to hold class definitions within Caché). Caché provides methods that automatically upgrade your Class Dictionary to the new structure.
Alternatively, you can export your existing class definitions to an external file (such as a CDL file) and reload them after installing the new Caché version. Refer to the next section on Migrating Class Definitions.
If you are upgrading an existing Caché system, you can upgrade any class definitions within a namespace by executing the upgrade command from a Caché command line:
 Do $system.OBJ.Upgrade()
This command updates the class dictionary for all classes within the current namespace. The upgraded classes have to be recompiled before they can be used.
You can recompile all classes within a namespace by executing the compile all command from a Caché command line:
 Do $system.OBJ.CompileAll()
There is also a new command that upgrades the class dictionaries within every namespace:
 Do $system.OBJ.UpgradeAll()
If you want to upgrade and recompile at the same time, you can pass an optional “c” (compile) flag to both the Upgrade and UpgradeAll methods:
 Do $system.OBJ.Upgrade("c")
You can also run the ^%UPDATECLASSES routine which updates all your classes in all your namespaces.
 Do ^%UPDATECLASSES
The ^%UPDATECLASSES routine allows you to update your classes in each namespace one by one to your terminal, or to update all the namespaces at the same time by running several processes in the background.
The $system.OBJ.Upgrade() command performs an upgrade of the current class definitions stored on disk. It does not affect user data; only class definitions. There is no corresponding “downgrade” function. If you wish to preserve the ability to downgrade, then you should export your class definitions to an external file (for example, a CDL file if you are on version 4.x) before upgrading. To downgrade, simply reload the saved class definitions.
Migrating Class Definitions
If you wish to move an object application from an earlier version of Caché to this version, do the following:
  1. From the earlier version of Caché, export your existing class definitions to one or more CDL files.
  2. Load the CDL file or files into the new version of Caché. You can do this from Caché Studio or Caché Explorer, or you can do it from the Caché command line:
     Do $system.OBJ.Load("myfile.cdl","c")
  3. Compile your classes.
    Important:
    To avoid compiler errors, update the Maximum Memory per Process (KB) setting to the highest value (16384 KB) before compiling. This setting is in the Process branch on the Advanced tab of the Caché Configuration Manager. The default setting in earlier Caché versions was 256 KB.
Compatibility
While every attempt has been made to ensure compatibility with prior versions, there are some areas which may cause incompatibilities with prior versions of Caché Objects. These areas include:
Object Development Features
There are a number of new features and enhancements related to developing object-based applications.
Caché Studio
The new version of Caché Studio now supports editing of classes as well as routines. The new features of Caché Studio include:
In order to support class editing and debugging, the new Studio requires server features that are only available in Caché v5.0 and above; therefore, you cannot use this version of Studio with older versions of Caché.
For more information, refer to Using Caché Studio which provides a complete overview of the features and operation of Studio. There is also a Studio FAQ available.
Caché Object Architect
The Caché Object Architect is no longer part of Caché. All of its functionality has been subsumed by Caché Studio.
CDL and XML Files
As part of the effort to add XML support, Caché now supports a new XML-based format for representing class definitions within files. This format offers several advantages over the previous format:
All import/export facilities in Studio now use this format.
You can also store project definitions, routines, globals, and CSP files within these XML files as well.
The previous CDL (Class Definition Language) format is still supported within this release; you can import into and export them from Caché. You should use the new XML format starting with this release.
The import utilities use the file extension to determine whether to load a class definition in the old CDL format (.cdl) or the new XML-based format (.xml).
Exporting Class Definitions for Version 4.x
There are a number of new features in the version 5 object model that are not present in version 4. If you export a CDL file from version 5, by default it may not load on a version 4 system.
If you need to use classes defined on version 5 on version 4, use the $System.OBJ.ExportCDL method (see the %SYSTEM.OBJ class) from the Caché command line and specify a “4” option in the list of flags. If the “4” flag is present then the CDL will not contain any syntax incompatible with a version 4.x system.
Note:
ExportCDL adds comments to the exported CDL file if any incompatible features (such as PROCEDUREBLOCK/WEBMETHOD) are present in the exported classes. ExportCDL does not check for super classes or types not present on a 4.x system nor does it check for LANGUAGE values.
Text-based Class Definitions
Within Caché Studio, as well as within the examples in the documentation, a new, text-based class definition is used. This new class definition is designed to be easier to use than CDL and directly editable within Studio.
For more information on the syntax, refer to the Class Definition Syntax chapter in the Using Caché Studio guide.
Class Compiler Enhancements
The Caché Class Compiler has been enhanced in the following ways:
Note:
These enhancements should have no impact on applications other than increasing their performance with the following exception: If your application makes direct references to the Class Dictionary structure using direct references to the ^oddDEF or ^oddCOM globals, or if it uses the macros that refer to this structure you will have to modify your application to use the documented macros for accessing this information.
Object Model Enhancements
There are a number of enhancements to the Caché object model. These include:
All of these are upwardly compatible with prior versions.
New Class Definition Classes
There is a new set of class definition classes (such as %Dictionary.ClassDefinition) to give you object access to the Caché Class Dictionary. The older class definition classes are still included for compatibility. Refer to Class Definition Classes for details.
Procedure Block Methods
Classes can now make use of the procedure block support within Caché ObjectScript by specifying the ProcedureBlock keyword at the class or method level.
Procedure blocks change the variable scoping rules for methods to use a more natural behavior:
Any new classes you create will use procedure blocks (though you can turn this off). Preexisting classes remain as they were; this change is designed to make existing code compatible with this release.
Object-Based Method Generators
Methods generators can now make use of objects (to provide meta-information and control code generation) and are now much easier to develop. The older style of method generator is still included for compatibility. Refer to Method Generators for details.
New Interface for %ResultSet
The %ResultSet class now gives you access to the values of the current row of a result set via a multidimensional property, Data.
Typical use of the %ResultSet object leads to code that uses a lots of method calls to get each field value. For example this is a typical loop:
   While (result.Next()) {
      Write result.Get("Name")," - ",result.Get("Address"),!
   } 
Each call to Get is a method call which is more expensive than reading a property value. In order to make access to the result set faster, you can use the new multidimensional property Data. The values in this property are indexed by column name, so the above loop becomes:
   While (result.Next()) {
      Write result.Data("Name")," - ",result.Data("Address"),!
   } 
The %ResultSet class still supports the Get method and is backward compatible. In new code you may wish to use the Data multidimensional property.
Simpler Definition of SQL Stored Procedures
Methods projected as stored procedures no longer need to declare the first formal argument as type %SQLProcContext. A new variable, %sqlcontext, can be used in place of a formal argument. If the method does have the first argument declared as type of %SQLProcContext then %sqlcontext is passed as the first argument when the method is invoked by the server as a stored procedure (to ensure compatibility with existing methods). The same requirement for testing for the presence of the %SQLProcContext argument remains as before.
For example:
ClassMethod UpdateProcTest(zip As %String, city As %String, state As %String)
             As %Integer [ SqlProc ] {
   New SQLCODE,%ROWCOUNT,%ROWID,rowcount
   &sql(UPDATE Sample.Person
         SET Home_City = :city, Home_State = :state WHERE Home_Zip = :zip)

  // Return context information to client via %SQLProcContext object
  If ($g(%sqlcontext)'=$$$NULLOREF) {
      Set %sqlcontext.SQLCode = SQLCODE
         Set %sqlcontext.RowCount = %ROWCOUNT
   }
  QUIT 1
}
This change is completely backwards compatible; existing stored procedures can run unchanged.
QHandle as Array for User-defined Class Query Methods
You can now use the QHandle argument, passed to the various methods of a Class Query, as an array. That is, you can store data within sub-nodes of the QHandle variable. These values are guaranteed to be present as the QHandle is passed amongst the various query methods. This change is designed to make it easier to pass objects using QHandle in a way that is compatible with system support for object references.
Existing code will continue to run; in this case you can consider QHandle to be an array with only one, top-level node defined.
XML Support
With this release, Caché supports a rich set of XML-based features. These features include:
For more information on XML support, refer to the Caché XML User's Guide.
For more information on Web Services and SOAP, refer to the Caché SOAP User's Guide.
Support for Basic and Java Methods
This version of Caché lets you define methods in Basic or Java in addition to Caché ObjectScript:
Class MyClass Extends %RegisteredObject {

/// A Basic Method
Method MyMethod() As %Integer [language = basic]
{
   For i = 1 To 10
      person = New Sample.Person()
      person.Name = "John " & i
      person.%Save()
   Next

   Return 1
}

/// A Java method
Method Add(a As %Integer, b As %Integer) As %Integer [language = java]
{
   return a + b;
}
}
Basic methods are compiled into executable Caché code and are executed by the Caché virtual machine (in the exact same manner as Caché ObjectScript). Java methods are automatically placed within the Java classes generated by the Caché Java Binding and are executed within the Java virtual machine (JVM). Caché does not provide a JVM but works with a standard JVM.
For more information on Basic, refer to the Language section of the Release Notes.
For more information on Java support, refer to the Caché Java User's Guide.
Caché Activate ActiveX Gateway
Caché now includes Caché Activate. Activate is an ActiveX Gateway that gives applications the ability to use ActiveX components from within Caché as if they were native objects. In addition there is a Wizard, accessible from the Studio, that automatically generates ActiveX wrapper classes from ActiveX components.
For more information, refer to the Caché Activate User's Guide.
System Support for Object References
This version of Caché further advances its object capabilities by having the Caché runtime engine manage object references. In prior versions, the management of object references, or OREFs, were handled by the object library; the underlying system did not distinguish OREF values from any other value. With System OREF support, the runtime engine recognizes certain values as being OREFs and automatically provides the reference counting and life-cycle management of objects.
Specifically this means that:
Reference Counting
Object variables (OREFs) automatically manage a reference count—the number of items currently referring to an object. Whenever you set a variable or object property to refer to an object, the object's reference count is automatically incremented. When a variable stops referring to an object (if it goes out of scope, is killed, or is set to a new value) the reference count is decremented. When this count goes to 0, the object is automatically destroyed; that is, removed from memory.
For example, consider the following method:
Method Test()
{
  Set person = ##class(Sample.Person).%OpenId(1)

  Set person = ##class(Sample.Person).%OpenId(2)
}
This method creates an instance of Sample.Person and places a reference to it into the variable person. Then it creates another instance of Sample.Person and replaces the value of person with a reference to it. At this point, the first object is no longer referred to and is destroyed. At the end of the method, person goes out of scope (as this method uses procedure blocks) and the second object is destroyed. In prior versions, these objects would not have been closed (unless there was an explicit call to the %Close method).
Another example is iterating over objects within a collection:
Method Total(invoice As Invoice) As %Integer
{
   Set total = 0
   Set count = invoice.LineItems.Count()

   For i = 1:1:count {
      Set lineitem = invoice.LineItems.GetAt(i)
      Set total = total + lineitem.Amount
   }

   Quit total
}
Here we sequentially open each object within the LineItems collection and add its Amount to the running total. Each time an object is assigned to the variable, lineitem, the previous object it referred to is destroyed.
Any local variable or local array node may contain an OREF value. For example:
 Set array(1) = ##class(Sample.Person).%New()
 Set array(2) = ##class(Sample.Person).%New()
 Set array(3) = ##class(Sample.Person).%New()
 Set array(4) = "string"
 Kill array
Here we create three new object instances and place references to them within three nodes of array. When we kill the array, the objects are destroyed (as there are no references to them). You can mix object and non-object values within an array, as with array(4) above.
Using OREF Values as Non-Object Values
If you use an OREF value as a string, it automatically is converted into a string:
 Set object = ##class(Sample.Person).%New()
 Write object      // implicitly convert object to a string
This example implicitly converts object to the string “6@Sample.Person” (the numeric OREF value, an “@” character, and its class name).
An OREF to string conversion happens in any of the following cases:
If you use an OREF value as an integer it is converted to a number:
 Set object = ##class(Sample.Person).%New()
 Write +object      // implicitly convert object to a number
Note:
You cannot convert a string or numeric value back into an object reference.
If you try to use a string or numeric value as an OREF you receive an <INVALID OBJECT> error. For example:
 Set object = ##class(Sample.Person).%New()
 Set object.Name = "Jack"

 Set x = +object      // set x to numeric value of OREF
 Write x.Name         // <INVALID OBJECT>
This is because once a value has been converted to a non-OREF, there is no reference count associated with it. The ability to convert strings or numeric value back into OREFs would lead to very unreliable code; there could be no guarantee that the same object still existed when the conversion occurred.
The %New Method
The %New method has new system-level optimizations to make object creation faster.
The %Close Method
With system OREF support, the %Close method does nothing; it is left in place for compatibility with existing applications.
Legacy Behavior for %Close
In order to simplify the process of upgrading to this release, there is a way to enable the older behavior of the %Close method. By default, %Close does nothing in v5.0.
To enable the legacy behavior for %Close for the current process:
 Do $ZU(68,56,1)
To disable the legacy behavior for %Close (the system default) for the current process:
 Do $ZU(68,56,0)
You can also set the legacy behavior for %Close for all processes:
 Do $ZU(69,56,1)
This takes effect in all processes that are started after invoking this command; You can place this code in your system initialization routine.
With Legacy Support for %Close enabled, the %Close method continues to work as in prior versions; it decrements an object's object-level reference count and removes it from memory when the count reaches 0. Note that in Legacy Mode, objects still contain an object-level reference count in addition to the system-level count. This object-level count is unaffected by system OREF management.
With system OREF support, the following code creates an object instance and then destroys it when all references to it (one in this case) are gone:
 Set obj = ##class(Sample.Person).%New()
 Set obj = "" // object is now destroyed
If an application calls %Close on an object, then the object is destroyed at that point (assuming its reference count goes to 0), and the OREF value used is “inactive”; that is, its value is not used to refer to another object until all references to this OREF are gone.
 Set obj = ##class(Sample.Person).%New()
 Do obj.%Close()   // object is closed; OREF value is "suspended"
 Set obj = ""      // OREF value is removed
Using a suspended OREF value results in a runtime error. This is essentially the same behavior as using an object after it has been closed had in prior versions.
Note:
Legacy Support for the %Close method will be removed in a future (post v5.0) version of Caché. At that point, the %Close method will do nothing at all (and all applications will run slightly faster). After upgrading to v5.0, you should test your applications to make sure that they work correctly with the new behavior for %Close so that upgrades to future versions will be possible. Note that it was never possible to rely on %Close closing an object (as you could never tell if there were outstanding references to the object). Because of this, the vast majority of applications should be able to run with the new %Close behavior with no change.
The $IsObject Function
There is a new function, $IsObject that tests whether are given variable or expression contains a valid object reference or not. It returns one of three possible values:
1 The variable contains an object reference.
0 The variable does not contain an object reference.
-1 The variable contains a reference to an object that has been made “inactive” as a result to a call to the %Close method. This only occurs if you have enabled the Legacy Support for %Close.
Null Value in Chained Property Reference
An enhancement has been made to Caché Objects to avoid the need for checking to see if an embedded object reference has been set before fetching a property of that reference. In many cases, the property value can be treated as the empty string when the object reference has not been set. In this release, a property reference of the form Object.Embedded.Property returns an empty string if Object.Embedded is not set to an existing object reference. In previous releases, this would have resulted in an <INVALID OREF> error.
In Caché ObjectScript terms, the code:
 Write Object.Embedded.Property
is equivalent to:
 Write $SELECT(Object.Embedded'=$$$NULLOREF:Object.Embedded.Property,1:"")
Upgrading to Use System Object Reference Support
For the most part, system OREF support is upward-compatible with the current system. Problems may arise in applications that use OREF values in non-object cases (integers or strings). This is detailed in the following sections.
OREFs in Globals
When an OREF is stored within a global, it is converted to a string value. The following code, which works in previous Caché releases, generates a runtime error in Caché 5.0 because it uses a string value as an object reference:
 Set obj = ##class(Sample.Person).%New()
 Set ^global(1) = obj
 //...
 Set a = ^global(1)
 Write a.Name   // <INVALID OBJECT> error
Using Non-OREF Values as OREFs
As you cannot use a numeric value as an OREF, the following code, which worked in previous versions (if there is an OREF with value of 1), now generates a runtime error:
 Do (1).Method()  // <INVALID OBJECT> error
Using OREFs in Strings and $Lists
When you store an OREF value as part of a string or within a $List structure (a $List structure is a string as far as the system is concerned), the OREF is converted to a string value. The following code, which worked in previous versions, generated a runtime error as it attempts to use a string as an OREF value:
 Set obj = ##class(Sample.Person).%New()
 Set list = $ListBuild(obj)
 // ...
 Set x = $ListGet(list,1)
 Write x.Name   // <INVALID OBJECT> error
If you have cases where you store OREF values within delimited strings or $List structures, you need to modify your code. If this is the case, we recommend converting such code to use local arrays, which can contain OREF values:
 Set obj = ##class(Sample.Person).%New()
 Set array(1) = obj
Using OREF Values Outside of Caché
Certain applications may pass OREF values between a Caché process and an external context. For example, you may send the integer value of a OREF to a client application (such as Java or Visual Basic) and later send the value back to the server expecting to continue to use it as an object reference. This will no longer work, as you cannot convert an integer value back into an OREF.
Note:
If you are using the standard Active X and Java binding provided with Caché in the documented fashion, you will not have any issues with using objects between client and server; Caché automatically manages this case for you.
If you have cases where you pass an object outside of Caché and back in again, we recommend the following technique:
  1. Use a local array to keep track, on the server, of all object references you have sent to a client.
  2. Within this array, store the OREF using the numeric value of the OREF as a subscript (to avoid collisions with other OREF values) and placing the OREF value as the node value:
     Set %myobjects(+obj) = obj
    The object reference stored in this array guarantees that the object remains in existence until you are ready to use it, and provides you with a real OREF.