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:
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.
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:
-
From the earlier version of Caché, export your existing class
definitions to one or more CDL files.
-
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")
-
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.
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:
-
-
Changes to the Class Dictionary Structure
this causes trouble in applications that directly access the Class Dictionary structure
(either using direct access to the Class Dictionary globals or using undocumented
macros).
-
Stricter Enforcement of Inheritance Rules by the Class Compiler
the Class Compiler now ensures that the signatures of overridden methods match those
within a super class. This means that some code that compiled in prior releases may
get
Signature Mismatch errors when compiled in this version.
To correct these, make sure that the signature (number of arguments and their respective
types) match those of the method they override.
-
-
-
Reoptimized Object Code in Caché
5.0, the byte-codes that represent instructions to the Caché virtual machine
were reoptimized; therefore, all sources need to be recompiled to use the new codes.
In Caché versions up to (and including) 4.1, routine object code had been upward
compatible with later versions of Caché , allowing users to:
-
Compile
.INT or
.MAC routines
using an older version of Caché.
-
Load the object code in a later version using the
%RIMF utility.
Caché 5.0 is different from earlier versions of Caché. To permit
users to use
%RIMF, Caché 5.0 requires that one of the
following be true:
-
The presence of the corresponding source code for routine compilation.
-
The input file is an object code file saved with the
%ROMF utility
in a Caché 5.0 system .
Important:
InterSystems partners who distribute their application in object-code-only format
should pay special attention to this issue.
Object Development Features
There are a number of new features and enhancements related to developing object-based
applications.
The new version of Caché Studio now supports editing of classes as well
as routines. The new features of Caché Studio include:
-
Support for editing class definitions using the full-featured, syntax-coloring
Studio editor.
-
An easy-to-use Class Inspector for rapid inspection and editing of
class definitions.
-
Support for editing methods in Basic and Java.
-
A new set of Wizards to simplify class development including a Web
Services Wizard.
-
Multiuser support for developing with objects.
-
Support for creating CSP class and files.
-
Support for custom extensions via Studio Templates.
-
Support for projects to help manage application development.
-
An interactive, GUI debugger.
-
Context-sensitive help on functions and commands within the Studio
Editor.
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.
The Caché Object Architect is no longer part of Caché. All of
its functionality has been subsumed by Caché Studio.
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:
-
It is based on standard XML making it interoperable with more tools.
-
It is easier to manipulate programmatically making it easier to develop
code generation technologies.
-
It is extensible making it easier to work with future versions of
Caché.
-
It is significantly faster to load and export than CDL 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.
Class Compiler Enhancements
The Caché Class Compiler has been enhanced in the following ways:
-
Compilation speed is much faster.
-
The Class Dictionary structure is stored in a more compact representation.
-
Compiled class definitions use significantly less space.
-
The class compiler generates more compact, efficient code. Classes
now compile into fewer, larger routines than before, eliminating extra inter-routine
calls (which are slower than intra-routine calls).
-
-
When the class compiler generates code, it now places a
z at
the start of each tag name within the routines it generates. For example, a method
called
MyMethod generates a tag name called
zMyMethod.
This change was needed to avoid conflict between method names and reserved words
in Basic (for example, without this change, the code generated for a method called
Print would
have resulted in an error).
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:
-
A new
WebMethod method
keyword that projects class methods as Web Methods accessible via SOAP. There are
additional new keywords to control how such methods are encoded:
SoapBodyUse and
SoapBindingStyle
-
A new class member,
Projection,
that specifies an operation to be performed by the class compiler when it compiles
or deletes a class definition. You can use this mechanism to have Java, C++, or EJB
proxy classes automatically generated whenever you compile a class definition.
-
A new enumerated
ClassType class
keyword that specifies whether a class is persistent, data type, etc. This replaces
the separate boolean class keywords (
Persistent, etc.). The Class
Dictionary upgrade automatically updates class definitions to use this new keyword.
-
A new
Language keyword that specifies the scripting
language used by methods. This is available on both a class-wide and per-method basis.
This allows you to specify Caché ObjectScript, Basic, or Java for methods.
-
A new
ProcedureBlock keyword that specifies that
Caché ObjectScript procedure blocks should be used in generated code. This
is available on both a class-wide and per-method basis.
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.
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 variables introduced within a method are considered private to
that method: you do not need to use the
New command on them and
they are only visible within that method.
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.
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.
With this release, Caché supports a rich set of XML-based features. These
features include:
-
The ability to automatically project object instances as XML documents.
-
The ability to import XML documents into object instances.
-
A fully integrated XML processor (based on SAX) that supports full
XML DTD and Schema validation from within Caché.
-
Automatic support for Web Services and SOAP (an XML-based remote procedure
call mechanism).
-
The ability to import and export class definitions as XML documents.
-
An XML-based, online documentation system developed using Caché.
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.
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.
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:
-
Instantiated objects are automatically destroyed when no variables
refer to them.
-
User applications no longer need to call the
%Close or
%IncrementCount methods.
These methods still exist for compatibility.
-
Applications using Basic for method implementation can interoperate
with Caché ObjectScript code seamlessly.
-
Programming is much simpler; there is no confusion about OREF and
OID value and problems caused by improper reference counting vanish. Performance is
improved as unneeded calls to the
%Close method are eliminated.
Object variables (OREFs) automatically manage a reference countthe 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:
-
Using an OREF value within a
Write statement:
Set oref = ##class(Sample.Person).%New()
Write oref
-
Concatenating a string to an OREF:
Set oref = ##class(Sample.Person).%New()
Set string = oref _ " "
Write string
-
Using an OREF value within a
$List structure:
Set oref = ##class(Sample.Person).%New()
Set list = $LB(oref)
Write $ListGet(list,1)
-
Copying an OREF to a global:
Set oref = ##class(Sample.Person).%New()
Set ^otest = oref
Write ^otest
-
Using an OREF as an array subscript:
Set oref = ##class(Sample.Person).%New()
Set array(oref) = "test"
Write array(oref)
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 has new system-level optimizations
to make object creation faster.
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:
To disable the legacy behavior for
%Close (the system
default) for the current process:
You can also set the legacy behavior for
%Close for
all processes:
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.
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:
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
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.
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:
-
Use a local array to keep track, on the server, of all object references
you have sent to a client.
-
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.