Working with External Languages
The $system.internal 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 object runs in an InterSystems IRIS namespace, and manages the connection for one or more ObjectScript proxy objects.
The InterSystems External Server process runs in the external language environment (Java, .NET, or Python) and manages the connection for one or more target objects. Each target object runs in a separate thread spawned by the external server process.
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:
Sun May 02 15:18:15 EDT 2021
This one line of code demonstrates the entire process of creating and using proxy objects.
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. Normally, you would assign the gateway to a variable:
set javaGate = $system.external.getJavaGateway()
Next it uses the gateway object’s new() method to create a proxy object for a specified class. Again, the object would normally be assigned to a variable:
set dateJava = javaGate.new("java.util.Date")
Finally, it calls a proxy object method:
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 proxy objects:
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 call to new() specifies a class name and any required arguments. This example now has three proxy objects connected through three different external servers. Each one can be treated like any other ObjectScript object. All three can be used within the same ObjectScript application.
Now methods or properties from all three objects can be used in the same write 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 most cases, you will have to specify the location of a class before it can be used. The following section describes how to get a proxy for any class.
Defining Paths to Target Software
All Gateway objects can store a list of paths to software libraries (jar files, assemblies, and modules). The addToPath() method allows you to add new paths to the list. The method signature for addToPath() is:
addToPath(path As %RawString)
The path argument can be a simple string containing a single path, or a dynamic array containing multiple paths. For example:
The %DynamicArray class is an ObjectScript wrapper that provides a simple way to create a JSON array structure. Use the dynamic array %Set() 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.%Set("/home/myhome/firstpath.jar") do pathlist.%Set("/home/myhome/another.jar") do javaGate.addToPath(pathlist)
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.%Set("C:\Dev\demo\Foo.py") do pyPaths.%Set("C:\Dev\demo.Bar.py") set pyGate = getPythonGateway() do netGate.addToPath(pyPaths) set fooProxy = pyGate.new("Foo.personOne") set barProxy = pyGate.new("demo.Bar.personTwo")
It is also possible to assign targets for modules or packages that do not have classes. See “Specifying Python Targets” at the end of this section for more information.
The path argument can also be specified as an instance of %Library.ListOfDataTypes containing multiple path strings, but dynamic arrays are recommended for ease of use.
Specifying Python Targets
The syntax for specifying a Python target differs depending on whether or not 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 gw.addToPath(/rootpath/noclass.py") set proxy = gw.new("noclass."))
Main class person and imported class company are both in file /rootpath/onefile.py.
do gw.addToPath(/rootpath/onefile.py") set proxy = gw.new("onefile.person")
Main class person and imported class company are in separate files within /rootpath:
do gw.addToPath(/rootpath/person.py") set proxy = gw.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 gw.addToPath(/rootpath/demo.noclass.py") set proxy = gw.new("demo.noclass.")
Main class person and imported class company are both in file /rootpath/demo/onefile.py:
do gw.addToPath(/rootpath/demo.onefile.py") set proxy = gw.new("demo.onefile.person")
Main class person and imported class company are in separate files in /rootpath/demo:
do gw.addToPath(/rootpath/demo.person.py") set proxy = gw.new("demo.person.person")