Calling ObjectScript Methods and Functions from .NET
This chapter describes methods of class IRIS that allow you to call ObjectScript class methods and user-defined functions directly from your .NET application. See the following sections for details and examples:
-
Class Method Calls — demonstrates how to call ObjectScript class methods.
-
Function Calls — demonstrates how to call user defined ObjectScript functions and procedures.
-
Calling Class Library Methods — demonstrates how to pass arguments by reference and check %StatusOpens in a new tab codes.
Class Method Calls
The following methods of class IRIS call ObjectScript class methods, returning values of the type indicated by the method name: ClassMethodBool(), ClassMethodBytes(), ClassMethodDouble(), classMethodIRISList(), ClassMethodLong(), ClassMethodObject(), ClassMethodString(), and ClassMethodVoid(). You can also use ClassMethodStatusCode() to retrieve error messages from class methods that return ObjectScript %StatusOpens in a new tab (see “Catching %Status Error Codes”).
All of these methods take string arguments for className and methodName, plus 0 or more method arguments, which may be any of the following types: int?, short?, string, long?, double?, float?, byte[], bool?, DateTime?, IRISList?, or IRISObject. If the connection is bidirectional (see “Using .NET Inverse Proxy Objects”), then any .NET object can be used as an argument. See “Class IRIS Supported Datatypes” for more information about how the Native SDK handles these datatypes.
Trailing arguments may be omitted in argument lists, either by passing fewer than the full number of arguments, or by passing null for trailing arguments. An exception will be thrown if a non-null argument is passed to the right of a null argument.
The code in this example calls class methods of each supported datatype from ObjectScript test class User.NativeTest (listed immediately after the example). Assume that variable iris is a previously defined instance of class IRIS and is currently connected to the server.
String className = "User.NativeTest";
String comment = "";
try{
comment = "cmBoolean() tests whether two numbers are equal (true=1,false=0): ";
bool boolVal = iris.ClassMethodBool(className,"cmBoolean",7,7);
Console.WriteLine(comment+boolVal);
comment = "cmBytes creates byte array [72,105,33]. String value of array: ";
byte[] byteVal = iris.ClassMethodBytes(className,"cmBytes",72,105,33);
Console.WriteLine(comment+ (new String(byteVal)));
comment = "cmString() concatenates \"Hello\" + arg: ";
string stringVal = iris.ClassMethodString(className,"cmString","World");
Console.WriteLine(comment+stringVal);
comment = "cmLong() returns the sum of two numbers: ";
Long longVal = iris.ClassMethodLong(className,"cmLong",7,8);
Console.WriteLine(comment+longVal);
comment = "cmDouble() multiplies a number by 1.5: ";
Double doubleVal = iris.ClassMethodDouble(className,"cmDouble",10);
Console.WriteLine(comment+doubleVal);
comment = "cmProcedure assigns a value to global array ^cmGlobal: ";
iris.ClassMethodVoid(className,"cmVoid",67);
// Read global array ^cmGlobal and then delete it
Console.WriteLine(comment+iris.GetInteger("^cmGlobal"));
iris.Kill("cmGlobal");
comment = "cmList() returns a $LIST containing two values: ";
IRISList listVal = iris.ClassMethodList(className,"cmList","The answer is ",42);
Console.WriteLine(comment+listVal.Get(1)+listVal.Get(2));
} catch (Exception e){
Console.WriteLine("method failed");
}
To run the previous example, this ObjectScript class must be compiled and available on the server:
Class User.NativeTest Extends %Persistent
{
ClassMethod cmBoolean(cm1 As %Integer, cm2 As %Integer) As %Boolean
{
Quit (cm1=cm2)
}
ClassMethod cmBytes(cm1 As %Integer, cm2 As %Integer, cm3 As %Integer) As %Binary
{
Quit $CHAR(cm1,cm2,cm3)
}
ClassMethod cmString(cm1 As %String) As %String
{
Quit "Hello "_cm1
}
ClassMethod cmLong(cm1 As %Integer, cm2 As %Integer) As %Integer
{
Quit cm1+cm2
}
ClassMethod cmDouble(cm1 As %Double) As %Double
{
Quit cm1 * 1.5
}
ClassMethod cmVoid(cm1 As %Integer)
{
Set ^cmGlobal=cm1
Quit ^cmGlobal
}
ClassMethod cmList(cm1 As %String, cm2 As %Integer)
{
Set list = $LISTBUILD(cm1,cm2)
Quit list
}
}
You can test these methods by calling them from the Terminal. For example:
USER>write ##class(User.NativeTest).cmString("World")
Hello World
Function Calls
Function calls are similar to method calls, but the arguments are in a different order. The function label is specified first, followed by the name of the routine that contains it. This corresponds to the order used in ObjectScript, where function calls have the following form :
set result = $$myFunctionLabel^myRoutineName([arguments])
Functions are supported because they are necessary for older code bases (see “Callable User-defined Code Modules” in Using ObjectScript), but new code should always use method calls if possible. ObjectScript also provides a special keyword to create method wrappers around existing routines without losing efficiency; set the CodeMode keyword equal to call.
The Native SDK methods in this section call user-defined ObjectScript functions or procedures and return a value of the type indicated by the method name: FunctionBool(), FunctionBytes(), FunctionDouble(), FunctionIRISList(), FunctionObject(), FunctionLong(), FunctionString(), or Procedure() (no return value).
They take String arguments for functionLabel and routineName, plus 0 or more function arguments, which may be any of the following types: int?, short?, string, long?, double?, float?, byte[], bool?, DateTime?, IRISList?, or IRISObject. If the connection is bidirection (see Using .NET Inverse Proxy Objects), then any .NET object can be used as an argument. See “Class IRIS Supported Datatypes” for more information about how the Native SDK handles these datatypes.
Trailing arguments may be omitted in argument lists, either by passing fewer than the full number of arguments, or by passing null for trailing arguments. An exception will be thrown if a non-null argument is passed to the right of a null argument.
These methods are designed to call functions in user-defined routines. ObjectScript system functions (which start with a $ character. See “ObjectScript Functions” in the ObjectScript Reference) cannot be called directly from your .NET code. However, you can call a system function indirectly by writing an ObjectScript wrapper function that calls the system function and returns the result. For example, the fnList() function (at the end of this section in ObjectScript Routine NativeRoutine.mac) calls $LISTBUILD.
The code in this example calls functions of each supported datatype from ObjectScript routine NativeRoutine (File NativeRoutine.mac, listed immediately after this example). Assume that iris is an existing instance of class IRIS, and is currently connected to the server.
String routineName = "NativeRoutine";
String comment = "";
comment = "fnBoolean() tests whether two numbers are equal (true=1,false=0): ";
Bool boolVal = iris.FunctionBool("fnBoolean",routineName,7,7);
Console.WriteLine(comment+boolVal);
comment = "fnBytes creates byte array [72,105,33]. String value of the array: ";
Byte[] byteVal = new String(iris.FunctionBytes("fnBytes",routineName,72,105,33));
Console.WriteLine(comment+(new String(byteVal)));
comment = "fnString() concatenates \"Hello\" + arg: ";
String stringVal = iris.FunctionString("fnString",routineName,"World");
Console.WriteLine(comment+stringVal);
comment = "fnLong() returns the sum of two numbers: ";
Long longVal = iris.FunctionInt("fnLong",routineName,7,8);
Console.WriteLine(comment+longVal);
comment = "fnDouble() multiplies a number by 1.5: ";
Double doubleVal = iris.FunctionDouble("fnDouble",routineName,5);
Console.WriteLine(comment+doubleVal);
comment = "fnProcedure assigns a value to global array ^fnGlobal: ";
iris.Procedure("fnProcedure",routineName,88);
// Read global array ^fnGlobal and then delete it
Console.WriteLine(comment+iris.GetInteger("^fnGlobal")+"\n\n");
iris.Kill("fnGlobal");
comment = "fnList() returns a $LIST containing two values: ";
IRISList listVal = iris.FunctionList("fnList",routineName,"The answer is ",42);
Console.WriteLine(comment+listVal.Get(1)+listVal.Get(2));
To run the previous example, this ObjectScript routine must be compiled and available on the server:
fnBoolean(fn1,fn2) public {
quit (fn1=fn2)
}
fnBytes(fn1,fn2,fn3) public {
quit $CHAR(fn1,fn2,fn3)
}
fnString(fn1) public {
quit "Hello "_fn1
}
fnLong(fn1,fn2) public {
quit fn1+fn2
}
fnDouble(fn1) public {
quit fn1 * 1.5
}
fnProcedure(fn1) public {
set ^fnGlobal=fn1
quit
}
fnList(fn1,fn2) public {
set list = $LISTBUILD(fn1,fn2)
quit list
}
You can test these functions by calling them from the Terminal. For example:
USER>write $$fnString^NativeRoutine("World")
Hello World
Calling Class Library Methods
Most of the classes in the InterSystems Class Library use a calling convention where methods only return a %StatusOpens in a new tab value. The actual results are returned in arguments passed by reference. This section describes how to pass by reference and read %StatusOpens in a new tab values.
-
Using Pass-by-reference Arguments — demonstrates how to use the IRISReference class to pass objects by reference.
-
Catching %Status Error Codes — describes how to use the ClassMethodStatusCode() method to test and read %StatusOpens in a new tab values..
Using Pass-by-reference Arguments
The Native SDK supports pass by reference for both methods and functions. To pass an argument by reference, assign the argument value to an instance of class ADO.IRISReference and pass that instance as the argument:
IRISReference valueRef = new IRISReference(""); // set inital value to null string
iris.ClassMethodString("%SomeClass","SomeMethod",valueRef);
String myString = valueRef.value; // get the method result
Here is a working example:
This example calls %SYS.DatabaseQuery.GetDatabaseFreeSpace()Opens in a new tab to get the amount of free space (in MB) available in the iristemp database.
IRISReference freeMB = new IRISReference(0); // set inital value to 0
String dir = "C:/InterSystems/IRIS/mgr/iristemp"; // directory to be tested
Object status = null;
try {
Console.Write("\n\nCalling %SYS.DatabaseQuery.GetDatabaseFreeSpace()... ");
status = iris.ClassMethodObject("%SYS.DatabaseQuery","GetDatabaseFreeSpace",dir,freeMB);
Console.WriteLine("\nFree space in " + dir + " = " + freeMB.value + "MB");
}
catch (IRISException e) {
Console.Write("Call to class method GetDatabaseFreeSpace() returned error:");
Console.WriteLine(e.getMessage());
}
prints:
Calling %SYS.DatabaseQuery.GetDatabaseFreeSpace()...
Free space in C:/InterSystems/IRIS/mgr/iristemp = 8.9MB
Catching %Status Error Codes
When a class method has ObjectScript %StatusOpens in a new tab as the return type, you can use ClassMethodStatusCode() to retrieve error messages. When a class method call fails, the resulting IRISException error will contain the %StatusOpens in a new tab error code and message.
In the following example, the ValidatePassword() method returns a %StatusOpens in a new tab object. If the password is invalid (for example, password is too short) an exception will be thrown and the %StatusOpens in a new tab message will explain why it failed. Assume that variable iris is a previously defined instance of class IRIS and is currently connected to the server.
This example passes an invalid password to %SYSTEM.Security.ValidatePassword()Opens in a new tab and catches the error message.
String className = "%SYSTEM.Security";
String methodName = "ValidatePassword";
String pwd = ""; // an invalid password
try {
// This call will throw an IRISException containing the %Status error message:
iris.ClassMethodStatusCode(className,methodName,pwd);
// This call would fail silently or throw a generic error message:
Object status = iris.ClassMethodObject(className,methodName,pwd);
Console.WriteLine("\nPassword validated!");
} catch (IRISException e) {
Console.WriteLine("Call to "+methodName+"(\""+pwd+"\") returned error:");
Console.WriteLine(e.getMessage());
}
Notice that this example deliberately calls a method that does not use any pass by reference arguments.
To experiment with a more complex example, you can try catching the status code in the previous example (Using pass-by-reference arguments). Force an exception by passing an invalid directory.
The ClassMethodStatusCode() method is used for class method calls. When you are invoking proxy object instance methods (see “Using .NET Inverse Proxy Objects”) the IRISObject.InvokeStatusCode() method can be used in exactly the same way.