Skip to main content

Working with External Languages

The $system.external interface allows you to use external language shared library files (.jar, .dll, .py) from embedded language code (ObjectScript or Embedded Python). Gateway objects provide connections to InterSystems External Servers running in Java, .NET, or Python environments. Shared library class methods and properties can be called directly from the $system.external interface, and you can generate an ObjectScript proxy object to access all methods and properties of a corresponding Java, .NET, or Python target object.

Connection between a Gateway object and an InterSystems External Server
Connection diagram: proxy object <-> Gateway object <-> TCP/IP <-> External server <-> target object
  • The Gateway process runs in an InterSystems IRIS namespace, and manages the connection for the ObjectScript application. Each gateway can connect to one or more external shared libraries, providing access to all available classes in the libraries.

  • 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. Each external server process is paired with its own gateway process, using a port number that uniquely identifies the connection.

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().

Tip:

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 normally 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 supported languages.

Creating gateway objects

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.

Creating proxy objects

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.

Calling proxy object methods and properties

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.

Tip:
Controlling ObjectScript Objects with External Language Proxies

It is also possible to create inverse proxy objects that allow your external language applications to control ObjectScript objects. For details see the following sections:

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:

Adding a single path
  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 Package and Class Paths” for more advanced Python options

Adding paths to a dynamic array

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 (see Using %Push and %Pop with Dynamic Arrays in Using JSON). 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)
Note:

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 (see “Using %Push and %Pop with Dynamic Arrays” in Using JSON).

The following examples demonstrate how to specify classes for each language.

Defining paths to Java classes

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")
Defining paths to .NET classes

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")
Defining paths to Python classes

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")

It is also possible to assign targets for modules or packages that do not have classes, as described in Specifying Python Package and Class Paths.

Specifying Python Package and Class Paths

The addToPath() method allows you to create a list of paths to Python modules or classes (see Defining Paths to Target Software for an overview). 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).

no package, no 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."))

no package, two classes in one file

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")
no package, two classes in separate files

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")
package, no class

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.")
package, two classes in one file

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")
package, two classes in separate files

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")

Upgrading Object Gateway Code

External servers use an enhanced and simplified form of the older Dynamic Object Gateway technology. All Object Gateway features are still available, so upgrading your code will mainly involve replacing certain class and method references. The following code demonstrates old and new ways to perform some common activities:

Starting the server and getting a Gateway object

The process of getting a Gateway object with an active server connection involved several calls to methods of two different ObjectScript classes:

  set status = ##class(%Net.Remote.Service).OpenGateway("JavaGate",.GatewayInfo)
  set name = GatewayInfo.Name
  set port = GatewayInfo.Port
  set server = GatewayInfo.Server
  if ('##class(%Net.Remote.Service).IsGatewayRunning(server,port,,.status)) {
    set status = ##class(%Net.Remote.Service).StartGateway(name)
  }
set gateway = ##class(%Net.Remote.Gateway).%New()
set status = gateway.%Connect(server, port, "USER")

With external servers, it’s much simpler:

set gateway = $system.external.getJavaGateway()

One call creates a Gateway object with all the appropriate settings, and automatically starts the connection to the external server. You can still access the external server manually (see “Starting and Stopping External Servers”), but this is unnecessary for most applications.

Specifying a class path and creating a proxy object

Object Gateway code required references to several different ObjectScript classes and a significant amount of server-specific information before a proxy could be created:

set path = ##class(%ListOfDataTypes).%New()
do path.Insert("C:\Dev\SomeClasses.jar")
do ##class(%Net.Remote.Service).OpenGateway("JavaGate",.GatewayInfo)
set gateway = ##class(%Net.Remote.Gateway).%New()
do gateway.%Connect(GatewayInfo.Server, GatewayInfo.Port, "USER",,path)
set proxy = ##class(%Net.Remote.Object).%New(gateway,"SomeClasses.ClassOne")

Once again, external server code is considerably simpler:

set gateway = getJavaGateway()
do gateway.addToPath("C:\Dev\SomeClasses.jar")
set proxy = gateway.new("SomeClasses.ClassOne")

The addToPath() method also provides a simpler way to add multiple class paths.

Calling a class method

Instead of the old %ClassMethod(), external servers use the new Gateway invoke() method.

// Object Gateway
set num = ##class(%Net.Remote.Object).%ClassMethod(gateway,"Demo.ReverseGateway","factorial",num-1)

// external server
set num = gateway.invoke("Demo.ReverseGateway","factorial",num-1)
FeedbackOpens in a new tab