Working with External Languages
The $system.external interface allows you to generate ObjectScript proxy objects that control corresponding Java, .NET, or Python target objects. A proxy object has the same set of methods and properties as the target, and each call to the proxy is echoed by the target. Communications between the proxy and the target are managed by an ObjectScript Gateway object connected to a Java, .NET, or Python External Server.
-
The Gateway process runs in an InterSystems IRIS namespace, and manages the connection for the ObjectScript application.
-
The InterSystems External Server process runs in the external language environment (Java, .NET, or Python) and provides the interface to external language shared libraries. Individual server threads provide access to class methods and properties, and manage communications between proxy and target objects.
-
A bidirectional TCP/IP connection allows the gateway object and the external server to exchange messages using a port number that uniquely identifies the external server instance.
The following sections demonstrate how to establish connections, define targets, and use proxy objects:
Creating a Gateway and Using a Proxy Object
The whole process of starting a connection, creating a proxy object, and calling a method can be compressed into a single statement. The following example starts the Java External Server, creates a proxy for an instance of target class java.util.Date, and calls a method to display the date:
write $system.external.getJavaGateway().new("java.util.Date").toString()
Sun May 02 15:18:15 EDT 2021
This one line of code demonstrates the entire process of creating and using proxy objects. It establishes a Java gateway connection, creates an instance of class java.util.Date and a corresponding ObjectScript proxy, and writes the date returned by method toString().
Try running this call at the InterSystems Terminal. Here are equivalent commands for C# and Python:
write $system.external.getDotNetGateway.new("System.DateTime",0).Now
write $system.external.getPythonGateway.new("datetime.datetime",1,1,1).now().strftime(%c)
The external servers are set up automatically when you install InterSystems IRIS, and will probably work without further attention (if not, see “Troubleshooting External Server Definitions” — you may have to change the path setting that identifies your preferred language platform).
The previous example compressed everything into one line, but that line is doing three important things:
-
First, it creates an ObjectScript Gateway object that encapsulates the connection between ObjectScript and the external server. You can assign the gateway to a persistent variable:
set javaGate = $system.external.getJavaGateway()
-
Next it calls the gateway object’s new() method, which creates an instance of java.util.Date in an external server thread and a corresponding proxy object in the ObjectScript process. The proxy object can also be assigned to a persistent variable:
set dateJava = javaGate.new("java.util.Date")
-
Finally, it calls a proxy object method. The target object echoes the call and returns the result to the proxy:
write dateJava.toString()
The following examples demonstrate these steps for all three languages.
There are specific gateway creation methods for the Java, .NET, and Python External Servers: getJavaGateway(), getDotNetGateway(), and getPythonGateway(). Each of these methods starts its external server in the default configuration and returns a Gateway object to manage the connection:
set javaGate = $system.external.getJavaGateway()
set netGate = $system.external.getDotNetGateway()
set pyGate = $system.external.getPythonGateway()
There is also a generic getGateway() method for use with customized external server configurations (see “Customizing External Server Definitions” for details) but the defaults should be sufficient for most purposes.
Each gateway object has a new() method for creating target and proxy objects. Each call to new() specifies a class name and any required arguments. When the call is made, the external server creates a target instance of the class, and the gateway creates a corresponding proxy object that has the same set of methods and properties as the target. Each call to the proxy is echoed by the target, which returns the result to the proxy.
This example creates three proxy objects connected through three different external servers (since each language requires its own server):
set dateJava = javaGate.new("java.util.Date")
set dateNet = newGate.new("System.DateTime",0)
set datePy = pyGate.new("datetime.datetime",1,1,1).now()
Each proxy can be treated like any other ObjectScript object. All three can be used in the same ObjectScript application.
You can use method and property calls from all three proxy objects in the same statement:
write !," Java: "_dateJava.toString(),!," .NET: "_dateNet.Now,!, "Python: "_datePy.strftime("%c")
Java: Sun May 02 15:18:15 EDT 2021
.NET: 2021-05-02 16:38:36.9512565
Python: Sun May 02 15:23:55 2021
The Java and Python examples are method calls, and the .NET example uses a property.
So far the examples have only used system classes, which are easy to demonstrate because they’re available at all times. In other cases, you will have to specify the location of a class before it can be used. For details, see “Defining Paths to Target Software” later in this section.
Defining Paths to Target Software
All Gateway objects can store a list of paths to software libraries for a specific language. Java gateways accept .jar files, .NET gateways accept .dll assemblies, and Python gateways accept .py (module or class) files.
The addToPath() method allows you to add new paths to the list. The path argument can be a simple string containing a single path, or a dynamic array containing multiple paths. For example:
do javaGate.addToPath("/home/myhome/someclasses.jar")
do netGate.addToPath("C:\Dev\myApp\somedll.dll")
do pyGate.addToPath("/rootpath/person.py")
The Java and .NET gateways also accept paths to folders containing one or more shared libraries. See “Specifying Python Targets” for more advanced Python options
The %DynamicArrayOpens in a new tab class is an ObjectScript wrapper that provides a simple way to create a JSON array structure. Use the dynamic array %Push() method to add path strings to the array. The following example adds two paths to array pathlist and then passes it to the addToPath() method of a Java Gateway object:
set pathlist = []
do pathlist.%Push("/home/myhome/firstpath.jar")
do pathlist.%Push("/home/myhome/another.jar")
do javaGate.addToPath(pathlist)
For more information on dynamic arrays, see “Using %Push and %Pop with Dynamic Arrays” in Using JSON.
The path argument can also be specified as an instance of %Library.ListOfDataTypesOpens in a new tab containing multiple path strings, but dynamic arrays are recommended for ease of use.
The following examples demonstrate how to specify classes for each language:
For Java, the path can be a folder or a jar. The following example adds a path to someclasses.jar and then creates a proxy for an instance of someclasses.classname.
set javaGate = getJavaGateway()
do javaGate.addToPath("/home/myhome/someclasses.jar")
set someProxy = javaGate.new("someclasses.classname")
For .NET, the path can be a folder or an assembly. The following example adds a path to someassembly.dll and then creates a proxy for an instance of someassembly.classname
set netGate = getDotNetGateway()
do netGate.addToPath("C:\Dev\myApp\somedll.dll")
set someProxy = netGate.new("someassembly.classname")
For Python, the path can be a module or a package. When a module is part of a package, the module path must be specified with dot notation starting at the top level package directory. For example, the path to module Foo.py could be specified in either of two ways:
-
Standard path notation if treated as a module: C:\Dev\demo\Foo.py
-
Dot notation if treated as part of package demo: C:\Dev\demo.Foo.py
The following example uses a dynamic array to add paths for two different files, both in folder C:\Dev\demo\. File Foo.py contains unpackaged class personOne, and file Bar.py contains class personTwo, which is part of package demo. Calls to new() create proxies for both classes:
set pyPaths = []
do pyPaths.%Push("C:\Dev\demo\Foo.py")
do pyPaths.%Push("C:\Dev\demo.Bar.py")
set pyGate = getPythonGateway()
do pyGate.addToPath(pyPaths)
set fooProxy = pyGate.new("Foo.personOne")
set barProxy = pyGate.new("demo.Bar.personTwo")
For more information on dynamic arrays, see “Using %Push and %Pop with Dynamic Arrays” in Using JSON.
It is also possible to assign targets for modules or packages that do not have classes, as described in the following section.
Specifying Python Targets
The syntax for specifying a Python target differs depending on whether the target is in a package, in a class, both, or neither. The following examples show the path as it should be specified in addToPath() and the class name as specified in new().
In the examples with classes, person is the main class and company is a class that it imports. The imported class does not have to be specified even if it is in a separate file.
You can also assign targets for modules or packages that do not have classes, but they are effectively limited to static methods and properties (since you can`t have class instance methods without a class).
Modules with no package and no class use the file name. Note that the argument for new() is the filename followed by a period, indicating that there is no class:
do pyGate.addToPath("/rootpath/noclass.py")
set proxy = pyGate.new("noclass."))
Main class person and imported class company are both in file /rootpath/onefile.py.
do pyGate.addToPath(/rootpath/onefile.py")
set proxy = pyGate.new("onefile.person")
Main class person and imported class company are in separate files within /rootpath:
do pyGate.addToPath(/rootpath/person.py")
set proxy = pyGate.new("person.person")
Packages with no classes use the package name and file name. The actual path for the file in this example is /rootpath/demo/noclass.py. Note that the argument for new() ends with a period, indicating that there is no class:
do pyGate.addToPath(/rootpath/demo.noclass.py")
set proxy = pyGate.new("demo.noclass.")
Main class person and imported class company are both in file /rootpath/demo/onefile.py:
do pyGate.addToPath(/rootpath/demo.onefile.py")
set proxy = pyGate.new("demo.onefile.person")
Main class person and imported class company are in separate files in /rootpath/demo:
do pyGate.addToPath(/rootpath/demo.person.py")
set proxy = pyGate.new("demo.person.person")