Using Dynamic Object Gateways
This chapter describes how to implement Object Gateways using dynamic proxy objects. In previous releases, static proxies had to be generated separately, compiled, and stored in the class library just like any other class (see Importing Java Classes in the previous chapter). Dynamic proxy objects do not require a separate import process before the business operation can start. The business operation simply creates and uses them as needed during runtime.
Introducing Dynamic Gateways — provides an overview of dynamic proxies.
Creating and Using a Dynamic Object Gateway — describes how to use dynamic proxies and gives specific usage examples.
Array Access — describes how .NET arrays are implemented as dynamic proxies.
Gateway Reentrancy — describes how to ObjectScript and .NET code can run safely using the same connection and context.
Example Classes — contains detailed information on the classes used as examples in this chapter.
Introducing Dynamic Gateways
All versions of the Object Gateway can instantiate an external .NET object and manipulate it as if it were a native InterSystems IRIS object. This is accomplished by creating a proxy object that provides all of the same methods and properties as the corresponding .NET object. Any call on the proxy object triggers the corresponding method or property of the .NET object. There are currently two kinds of proxy objects:
Static proxies are standard ObjectScript classes, which must be individually generated and compiled before they can be used. If a new version of the .NET class changes the interface in any way, the corresponding proxy class must be regenerated and recompiled to match it.
Dynamic proxies are created at runtime by introspecting the current version of the .NET class. All dynamic proxies are instances of %Net.Remote.Object. Although each Object instance appears to have the same methods and properties as the corresponding .NET object, it actually uses an internal map of the interface to simulate these features.
In use, static and dynamic proxies are identical except for the way they are instantiated. For example:
// Static proxy: just call %New() on a pregenerated class set newFruit = ##class(Fruit).%New() // Dynamic proxy: create an Object instance and specify the class to introspect set newFruit = ##class(%Net.Remote.Object).%New(gateway,"Fruit")
Creating and Using a Dynamic Object Gateway
This section describes the complete procedure for creating and running an Object Gateway that uses dynamic proxies:
Start the Object Gateway server — dynamic proxies can’t be created without querying the server at runtime.
Get the server definition and test the server — the server definition is required for methods that monitor and control the server.
Define the location of your .NET classes and create a Gateway connection — connection information must include the path to each DLL you intend to use.
Create a proxy and call some methods — after they’ve been created, static and dynamic proxies work the same way.
Disconnect and shut down — the server will continue to run until you explicitly stop it.
Since dynamic proxies are created at runtime, you need access to a server that can introspect .NET classes and return the information needed to generate the proxies.
To start the gateway, open the Management Portal and go to the Object Gateways Page, System Administration > Configuration > Connectivity > Object Gateways. This page displays all ObjectGateway descriptions that have been persisted to the database, and provides a convenient way to control, monitor, and modify the servers they describe. The following example displays information on the NetGate server, and indicates that an instance of that server is currently running:

The examples in this chapter will assume that the server named NetGate has been defined (see “Creating the NetGate Server Definition” for details).
The Service class provides methods to start, stop, and test the server. Most of these methods require the server definition contained in an ObjectGateway object. In the following example, Service.OpenGateway() returns an ObjectGateway instance defining the server named NetGate. Service.IsGatewayRunning() uses the information to test the current state of the server, and Service.StartGateway() can start it if necessary.
try { // Get a ObjectGateway instance that defines the NetGate server set st = ##class(%Net.Remote.Service).OpenGateway("NetGate",.OG) write !, "Using "_OG.Name_" server definition" // Test to make sure the server is running. If not, start it if ('##class(%Net.Remote.Service).IsGatewayRunning(OG.Server,OG.Port,,.status)) { // Instantiate the server on the host machine set status = ##class(%Net.Remote.Service).StartGateway(OG.Name) } } catch ex { write !,$system.OBJ.DisplayError(ex.AsStatus()) }
Your ObjectScript code should always use try/catch for Object Gateway error handling. When the class running on the host machine encounters an exception, the dynamic proxy will throw %Net.Remote.Exception. In addition to standard status details, the Exception object contains call stack information, which is returned by calling method Exception.StackAsArray(.array). The information may be different between Java and .NET.
Each dynamic proxy will access the corresponding .NET object through a Gateway object connected to the server. The Gateway object needs a path to each DLL containing a class you want to use. In this example, the location of the DLL containing class Fruit is specified in the last argument to Gateway.%Connect().
// define the location of the file containing the Fruit class set fruitPath = ##class(%ListOfDataTypes).%New() do fruitPath.Insert("C:\Dev\Fruit.dll") // Connect a Gateway instance to server NetGate on the host machine set GW = ##class(%Net.Remote.Gateway).%New() set st = GW.%Connect(OG.Server, OG.Port, "USER",,fruitPath)
This example only adds one path, but you can insert a separate path for each DLL you want to use.
In this example, an new instance of Object is created, specifying Fruit as the .NET class to introspect. The resulting dynamic proxy will communicate with the .NET object through Gateway instance GW (created in the previous example). Once the dynamic proxy is instantiated, it works just like a static proxy.
// Use GW connection to create a proxy for the Fruit class set proxyFruit = ##class(%Net.Remote.Object).%New(GW,"Fruit") write !,proxyFruit.id() write !,"Current fruit preference: ",!," "_proxyFruit.getFruit() do proxyFruit.setFruit("Jujube") write !,"Fruit preference has been reset: ",!," "_proxyFruit.getFruit()
Prints:
This .NET class displays your favorite fruit. Current fruit preference: UglyFruit Fruit preference has been reset: Jujube
The connection remains open and the server continues to run until you explicitly disconnect and shut down. In this example, the Gateway.%Disconnect() method breaks the connection, but the server won’t release the port it uses until the call to Service.StopGatewayObject() stops it.
// Disconnect from the server set st = GW.%Disconnect() // Stop the server set st = ##class(%Net.Remote.Service).StopGatewayObject(OG)
Array Access
When a property is declared as an array, it is projected to the proxy as an instance of %Net.Remote.Object. To create a new array, call the Object.%New() method and use array class name syntax in place of the regular class name. Arguments are used as the initial values for the array. Here are some examples:
Array can be initialized by rank or elements:
// by rank set newArray = ##class(%Net.Remote.Object).%New(gateway,"String[2]") // by elements set newArray = ##class(%Net.Remote.Object).%New(gateway,"String[]","A","B","C")
When rank and elements are both specified, rank must match element count.
// both rank and elements specified set newArray = ##class(%Net.Remote.Object).%New(gateway,"int[3]",11,22,33)
.NET multidimensional arrays are supported. They can only be initialized by rank:
// Create a two dimensional array by rank set newArray = ##class(%Net.Remote.Object).%New(gateway,"String[2,3]")
Besides supporting all the normal properties and methods available on an array object, Dynamic Gateway supports two special methods for accessing array elements:
%get(index) — object.%get(index)is translated to object[index]
%set(index,value) — object.%set(index,value) is translated to object[index] = value.
For example:
// Create a two dimensional array and populate it set arr = ##class(%Net.Remote.Object).%New(gateway,"String[2,3]") for x=0:1:1 {for y=0:1:2 {set i=x*3+y do arr.%set(x,y,$CHAR(65+i)) }} write !,"Array length is" _arr.Length_" and rank is "_arr.Rank for x=0:1:1 {for y=0:1:2 {write "("_x_","_y_")="_arr.%get(x,y)_" " }}
Prints:
Array length is 6 and rank is 2 (0,0)=A (0,1)=B (0,2)=C (1,0)=D (1,1)=E (1,2)=F
Gateway Reentrancy
Reentrancy means that when InterSystems IRIS code sends a request to .NET, the .NET code can respond with a request of its own using the same physical connection and context as the original request. The following is a typical call sequence:
Starting from the InterSystems IRIS side, a connection is made to .NET using a %Net.Remote.Gateway object, allowing us to create proxy objects that can make calls to .NET methods.
From a proxy, the .NET ADO.Gateway.GatewayContext.getIRIS() method can be called to create an instance of ADO.IRIS. The IRIS object uses the same connection and context as the proxy, and provides access to all the methods for invoking Native API calls (see “Using the InterSystems Native API for .NET”).
The IRIS object can call back into InterSystems IRIS, where it can get and set global values or call classmethods and functions. Making one of these calls puts us back in InterSystems IRIS code again.
From here, we can make another inner call back to .NET using the same Gateway connection. Method ##class(%Net.Remote.Gateway).%GetContextGateway() returns the Gateway object for the current context.
Calls back and forth between InterSystems IRIS and .NET can go on indefinitely, always using the same connection and context.
The following example gets an ADO.IRIS object in .NET, and uses it to read a global value from the InterSystems database:
IRIS native = GatewayContext.getIRIS(); // Read the value of global ^GlobalName("value2") if ( native != null ) { String globalVal = native.GetString("GlobalName","value2"); }
If the code is not invoked inside a valid Gateway context, the getIRIS() method will return a null object.
This example gets a %Net.Remote.Gateway object for the current context, and uses it to create a proxy for a .NET object in the same context:
set gateway = ##class(%Net.Remote.Gateway).%GetContextGateway() if gateway'=$$$NULLOREF { set objFruit = ##class(%Net.Remote.Object).%New(gateway,"Fruit") }
If the context is not valid, the Gateway object will be $$$NULLOREF.
In general, reentrancy support is not compatible with prior versions of the Object Gateway .
Example Classes
This section provides detailed descriptions of the two sample classes used in this chapter:
The .NET Fruit Class is the trivial .NET class from which the proxy objects are projected.
Creating the NetGate Server Definition is an instance of ObjectGateway that defines the Object Gateway server named NetGate.
The .NET Fruit Class
This section lists the Fruit class used by most of the examples in this chapter. This trivial .NET class has all the features needed to demonstrate a simple proxy object: static class method id(), and property accessors getFruit() and setFruit().
using System; namespace demo { public class Fruit { public static String id() { return "This .NET class displays your favorite fruit."; } public String fruit; public Fruit () { fruit = "Uglyfruit"; } public void setFruit(String newFruit) { fruit = newFruit; } public String getFruit() { return "My favorite fruit is "+fruit; } public static void main(String []args) { System.out.println("\nGenerating output from .NET class Fruit: "); Fruit myFruit = new Fruit (); System.out.println(myFruit.getFruit()); } } }
Creating the NetGate Server Definition
This section demonstrates how to create the NetGate Object Gateway server definition used by most of the examples in this chapter.
The Management Portal provides convenient interactive tools for creating and controlling Object Gateway servers (as mentioned earlier in this chapter), but this example will demonstrate what those tools are actually doing in the background. You can create and run a server with a few lines of ObjectScript code, specifying exactly the same information you would enter in the Management Portal.
The properties of an Object Gateway server are defined in an instance of %Net.Remote.ObjectGateway. The following code specifies properties for a .NET-based server named NetGate, persists the server definition in the InterSystems IRIS database, and then starts an instance of the server on the host machine.
This server will run on the same machine as InterSystems IRIS (since the Server property specifies localhost as the host machine), but this is not a requirement. The host machine only needs a copy of the server executable.
set OG = ##class(%Net.Remote.ObjectGateway).%New() // Properties used in all server definitions set OG.Type = "2" // Defines a .NET server set OG.Name = "NetGate" set OG.Server = "127.0.0.1" set OG.Port = "55602" // port number must be unique // .NET-only properties set OG.AllowedIPAddresses = "127.0.0.1"" set OG.FilePath = "C:\InterSystems\IRIS\dev\dotnet\bin\v4.5\" // keep Exec64 default value set OG.DotNetVersion = "4.5"
The Object Gateway needs the FilePath and DotNetVersion paths to execute the command that will instantiate the server on the host machine. They are optional in this example (where localhost is the host machine), since they just specify the default paths to a version of the InterSystems.Data.Gateway64.exe executable (installed with InterSystems IRIS) and the corresponding version of .NET.
Calling ObjectGateway.%Save() adds this definition to the list stored in the InterSystems IRIS database. When the server definition is persisted in the database, it will be listed on the Object Gateway Page in the Management Portal.
write "Saving "_OG.Name set status = OG.%Save()
The definition can now be used to construct and run the command that will start an instance of the server on the host machine. You can start the server either by clicking Start on the Object Gateway Page, or by calling the %Net.Remote.Service.StartGateway() method as demonstrated below.
// Instantiate the server on the host machine set status = ##class(%Net.Remote.Service).StartGateway(OG.Name)