
Introduction
I want to talk about the use of macros in InterSystems Caché. A macro is a symbolic name that is replaced when the source code is compiled with a sequence of program instructions. A macro can “unfold” into different sequences of instructions for each call, depending on the branches that have been triggered inside the macro and the arguments passed to it. This can be either a static code or the result of running COS. Consider how you can use them in your application.
Compilation
To begin with, to understand where macros are used, a few words about how ObjectScript code is compiled:
- Class compiler uses class definition to generate MAC code
- In some cases, the compiler uses classes as the basis for generating additional classes. You can look at these classes in the studio, but do not change them. This happens, for example, when compiling classes that define web services and web clients.
- The class compiler also generates a class descriptor. Caché uses it at runtime.
- A preprocessor (sometimes called a macro preprocessor, MPP) uses INC files and replaces macros. In addition, it handles embedded SQL in ObjectScript routines.
- All these changes occur in memory, the user code itself does not change.
- Further the compiler creates INT code for ObjectScript routines. This layer is known as an intermediate code. All data access at this level is carried out through global
- INT code is compact and human readable. To view it, press in the studio Ctrl + Shift + V or the button

- INT code is used to generate OBJ code
- The Caché virtual machine uses this code. After it is generated, the CLS / MAC / INT code is no longer required and can be removed (for example, if we want to deliver solutions without source code)
- If the class is stored , the SQL compiler will create the appropriate SQL tables.
Macros
As already mentioned, a macro is a symbolic name that is replaced when a preprocessor is processed with a sequence of program instructions. It is determined using the
#Define command, followed by the name of the macro first (possibly with an argument list) and then the value of the macro:
#Define Macro [(Args)] [Value]Where can macros be defined? Either directly in the code, or in separate
INC files containing only macros.
Include the necessary files to the classes with the
Include MacroFileName command at the very beginning of the class definition — this is the main and preferred method for connecting macros to the class, thus attaching macros to them can be used in any part of the class definition. You can attach the INC file of macros with the
#Include MacroFileName command to the MAC routines or code of individual class methods.
Examples
Example 1
Let us turn to examples of use, we begin with the output line "Hello World". COS code:
Write "Hello, World!"Now we will write a macro HW that displays this line:
#define HW Write "Hello, World!"
It is enough to write in the
$$$ HW code (
$$$ to call the macro, then the macro name follows):
ClassMethod Test ()
{
#define HW Write "Hello World!"
$$$ HW
}
')
And when compiled, it is converted to the following INT code:
zTest1 ( )
public {Write "Hello, World!" }In the terminal when you run this method will be displayed:
Hello, World!
Example 2
In the following example, we use variables:
ClassMethod Test2 ()
{
#define WriteLn (% str,% cnt) For ## Unique (new) = 1: 1:% cnt {## Continue
Write% str ,! ## Continue
}
$$$ WriteLn ( "Hello, World!" , 5)
}
Here the string% str is printed% cnt times. Variable names must begin with%. The ## Unique (new) command creates a new unique variable in the generated code, and the ## Continue command allows you to continue defining the macro on the next line. This code is converted to the following INT code:
zTest2 ( )
public {For % mmmu1 = 1: 1: 5
{Write "Hello, World!" !
} }
In the terminal when you run this method will be displayed:
Hello, World! Hello, World! Hello, World! Hello, World! Hello, World!
Example 3
Let us turn to more complex examples. The
ForEach operator is very useful when iterating over globals; add it:
ClassMethod Test3 ()
{
#define ForEach (% key,% gn) Set ## Unique (new) = $ name (% gn) ## Continue
Set% key = "" ## Continue
For {## Continue
Set% key = $ o (@ ## Unique (old) @ (% key)) ## Continue
Quit:% key = ""
#define EndFor }
Set ^ test (1) = 111
Set ^ test (2) = 222
Set ^ test (3) = 333
$$$ ForEach ( key , ^ test)
Write "key:" , key,!
Write "value:" , ^ test ( key ) ,!
$$$ EndFor
}
Here is how it looks in int code:
zTest3 ( )
public {Set ^ test (1) = 111
Set ^ test (2) = 222
Set ^ test (3) = 333
Set % mmmu1 = $ name ( ^ test)
Set key = ""For {Set key = $ o ( @
% mmmu1 @ (
key ) )
Quit : key = ""Write "key:" , key,!Write "value:" , ^ test (
key ) ,!
} }
What happens in these macros?
- The input is the% key variable in which the current key (subscript) of the iterated global% gn will be written
- Write the name of the global to the new variable (function $ name )
- The key takes the initial, empty value
- Getting an iteration loop
- Using the indirection and $ order function, assign the following value to the key
- Using the postcondition, we check if the key has taken the value "", if yes, then the iteration is completed, we exit the loop
- An arbitrary user code is executed, in this case the key and value are output.
- Cycle is closing
In the terminal when you run this method will be displayed:
key: 1 value: 111 key: 2 value: 222 key: 3 value: 333
If you use
lists and
arrays - the heirs of the
% Collection.AbstractIterator class, you can write a similar iterator already for it.
Example 4
Another possibility of macros is to execute arbitrary COS code at compile time and substitute the result of execution instead of a macro. Create a macro with compile time:
ClassMethod Test4 ()
{
#Define CompTS ## Expression ( "" "Compiled:" _ $ ZDATETIME ($ HOROLOG) _ "" ",!" )
Write $$$ CompTS
}
Which is converted to the following int code:
zTest4 ( )
public {Write "Compiled: 05/19/2015 15:28:45",! }In the terminal when you run this method will be displayed:
Compiled: 05/19/2015 15:28:45
The expression
## Expression executes the code and substitutes the result, the input can be the following elements of the COS language:
Example 5
The preprocessor directives
#If ,
#ElseIf ,
#Else ,
#EndIf are used to select the source code when compiling depending on the value of the expression after the directive, for example this method:
ClassMethod Test5 ()
{
#If $ SYSTEM .Version . GetNumber () = "2015.1.1" && $ SYSTEM .Version . GetBuildNumber () = "505"
Write "You are using the latest version of Caché"
#ElseIf $ SYSTEM .Version . GetNumber () = "2015.2.0"
Write "You are using the latest beta version of Caché"
#Else
Write "Please consider an upgrade"
#EndIf
}
In Caché, version 2015.1.1.505 will be compiled into the following INT code:
zTest5 () public {
Write "You are using the latest version of Caché"
}
And in the terminal will display:
You are using the latest released version of Caché
In Caché, the download from the
beta portal will be compiled into another INT code:
zTest5 () public {
Write "You are using the latest beta version of Caché"
}
And in the terminal will display:
You are using the latest beta version of Caché
And previous versions of Caché will compile the following INT code with a suggestion to upgrade:
zTest5 () public {
Write "Please consider an upgrade"
}
And in the terminal will display:
Please consider an upgrade
This feature can be used, for example, to preserve the compatibility of the client application between the old versions and the new ones, where the new Caché DBMS functionality can be used. This purpose is also served by the preprocessor directives
#IfDef ,
#IfNDef which check for the existence or absence of a macro, respectively.
findings
Macros can simply make your code more understandable, simplifying constructions that are often repeated in code, and also implement part of the application logic at the compilation stage, thus reducing the load in runtime.
What's next?
In the
next article I will talk about a more practical example of using macros - the logging system.
Links
About compilationList of preprocessor directivesList of system macrosClass with examplesPart II. Logging systemThe author is grateful to Daimor
Habrawers ,
Greyder, and another very competent engineer who wished to remain anonymous, for help in writing the code.