Please note that this story is over 18 months old. It may be outdated.

One of the API’s provided by the U2 range of products within the UniDK  is InterCall. This package provides access to U2 data and the ability to execute commands, including UniBasic programs in a non-SQL manner.

It is also possible to use InterCall within Python.

Python includes a library called ctypes, which provides a selection of C compatible data types and the ability to call DLL’s in MS-Windows and shared libraries within Unix.

There are a number of other products and/or methods to access C based libraries within Python; including SWIG. But, ctypes is lite and simple.

First up, install python. I am using version 2.6.5. Then you will need access to the InterCall library.

InterCall is part of the UniDK for MS-Windows; I will be using UniVerse on MS-Windows Server 2003 for these examples. So, start by installing the UniDK on MS-Windows; where InterCall is installed within the C:IBMUniDKlib directory (folder) as Uvic32.lib. According to the InterCall documentation, you can distribute the three client DLL library files with your resultant application.

To keep things simple and tidy, I copied the three DLL files: unirpc.dll, UvcInt32.dll, and Uvic32.dll,  file to the Python DLLs directory.

The next step is to access the InterCall library within Python. The ctypes module provides a number of mechanisms to load a shared library. There are two methods within the total of four which interest us; CDLL and WinDLL. The difference between them is the call conventions used by the C library. In our case, the InterCall library uses the stdcall calling convention, so we need to load using WinDLL.

Within Python, just import the ctypes module and then attach the InterCall DLL:

from ctypes import *
uviclib = WinDLL("Uvic32.dll")

As the DLL’s are in the Python DLLs directory, a full path name is not required. We now have the InterCall library attached to uviclib. You can change the name to what suits you better or more relevant.

Starting from the beginning, to read a record from a UniVerse file, we need to open a session to UniVerse, open the file and then read the record. To finish, the file should be closed and then the session closed.

From the InterCall documentation, we need to use ic_opensession, ic_open, ic_read, ic_close, and ic_quitall respectively, in that order. ctypes permits us to access the library functions by a simple dot method.

ic_opensession = uviclib.ic_opensession

Referring to the documentation again, ic_opensession has five input variables and two output variables:

session_id  = ic_opensession(server_name, user_name, password, account, status, subkey)

All the variable names make sense, except for subkey, which is for multi-tier connections. Just leave it as a empty string. The variables all have various types: long, long*, and char*. This is where the fun begins.

It is ironical that both UniVerse and Python are weakly typed languages, yet to connect the two we must use strongly typed variables! This is due to the underlining C language used for both environments. So be it.

There are four variable types used within the InterCall library. All data within Python must be converted to the appropriate type before using the library function. They are:

  • c_long
  • POINTER(c_long)
  • c_char_p
  • ic_string

The asterisk within C used with variable definitions indicates a pointer. All variables passed to the InterCall library are pointers to variables. This is the same as passing by reference within UniBasic. The only times when data is transferred by value is when data is returned by the functions ic_opensession, ic_universe_session, and ic_unidata_session. By a strange coincidence these routines are the only true functions within the library; all the other routines are subroutines, returning data via the calling variables; hence all the pointers. When no value is returned, as in a subroutine, the result type (restype) is None.

ctypes permits the definition of the argument types using argtypes and the result type using restype:

ic_opensession = uviclib.ic_opensession
ic_opensession.argtypes = [c_char_p, c_char_p, c_char_p, c_char_p, POINTER(c_long), c_char_p]
ic_opensession.restype = c_long

argtypes requires a list of the argument types in their correct order and restype is a single  type. The c_char_p is the ctypes character array pointer, c_long is the long type (for numbers) and a pointer is defined using POINTER. Note that c_char_p is already a pointer type!

To call the routine, each of the variables has to be defined and match the variable type within the routine parameter list. ctypes provides the c_char_p() and c_long() (and many more within the documentation) for this purpose. For example, for ic_opensession:

server = c_char_p("")
name = c_char_p("tester")
password = c_char_p("tester")
account = c_char_p("uv")
subkey = c_char_p("")

status = c_long(0)
session_id = c_long(0)

session_id = ic_opensession(server, name, password, account, byref(status), subkey)

The status parameter is transferred as a pointer by the byref functionality. All parameters defined with POINTER must also be used with a corresponding byref.

The return values are in the session_id and status variables.

That’s it.

Well, almost. The only other complication is the transfer of large data, as in a record read. A buffer within memory must be allocated before the read. Therefore, maximum size of the data that will be read must be known beforehand otherwise the read will fail. It will fail with an identifiable error code – 22002 or IE_BTS . ctypes has a function called create_string_buffer to facilitate this memory allocation.

Below is a simple top-down linear routine to open a session and account to UniVerse, open a file and read a record. The account is the uv, the file is BP and the record is CLE. I choose CLE as I wanted a not too small, not too big record size. Python people will scream with the style of this routine, but it is simple and similar in style to a simple UniBasic routine. This article is about transition.

There are lots of print statements included so the process can be viewed along the way:

#uvic Example
#    "InterCall Example
from ctypes import *

# stdcall format
uviclib = WinDLL("Uvic32.dll")

# session_id = ic_opensession(server_name, user_name, password, account, status, subkey)
ic_opensession = uviclib.ic_opensession
ic_opensession.argtypes = [c_char_p, c_char_p, c_char_p, c_char_p, POINTER(c_long), c_char_p]
ic_opensession.restype = c_long

# ic_session_info(key, data, max_data_len, data_len, code)
ic_session_info = uviclib.ic_session_info
ic_session_info.argtypes = [POINTER(c_long), c_char_p, POINTER(c_long), POINTER(c_long), POINTER(c_long)]
ic_session_info.restype = None

# ic_open(file_id, dict_flag, filename, file_len, status_func, code)
ic_open = uviclib.ic_open
ic_open.argtypes = [POINTER(c_long), POINTER(c_long), c_char_p, POINTER(c_long), POINTER(c_long), POINTER(c_long)]
ic_open.restype = None

# ic_read(file_id, lock, record_id, id_len, record, max_rec_size, record_len, status_func, code)
ic_read = uviclib.ic_read
ic_read.argtypes = [POINTER(c_long), POINTER(c_long), c_char_p, POINTER(c_long), c_char_p, POINTER(c_long), POINTER(c_long), POINTER(c_long), POINTER(c_long)]
ic_read.restype = None

# ic_close(file_id, code)
ic_close = uviclib.ic_close
ic_close.argtypes = [POINTER(c_long), POINTER(c_long)]
ic_close.restype = None

# ic_quitall(code)
ic_quitall = uviclib.ic_quitall
ic_quitall.argtypes = [POINTER(c_long)]
ic_quitall.restype = None

# Info Keys
IK_HOSTNAME    = c_long(1) # Hostname of conection
IK_HOSTTYPE    = c_long(2) # Host type, either UNIX or NT
IK_TRANSPORT = c_long(3) # Transport used by session
IK_USERNAME    = c_long(4) # Username used by session
IK_STATUS = c_long(5) # Status of connection
IK_NLS = c_long(6) # Status of NLS

# File level
IK_DATA = c_long(0) # Data level of file
IK_DICT = c_long(1) # Dict level of file

# Read locks
IK_READ = c_long(0)
IK_READU = c_long(2)
IK_READUW = c_long(3)
IK_READL = c_long(4)
IK_READLW = c_long(5)

# Open Connection

server = c_char_p("")
name = c_char_p(raw_input("User Name - "))
password = c_char_p(raw_input("Password - "))

account = c_char_p("uv")
subkey = c_char_p("")

status = c_long(0)
session_id = c_long(0)

print "ic_opensession"
session_id = ic_opensession(server, name, password, account, byref(status), subkey)
print "opensession id - ", session_id
print "opensession status - ", status
print "-"

if (status.value  != 0):
   print "not open"
   print "--------"
   finish = ""
   finish = raw_input("Return to exit")

info_key = IK_HOSTNAME
max_data_len = c_long(128)
session_data = c_char_p("")
data_len = c_long(0)
code = c_long(0)

print "ic_session_info"
ic_session_info(byref(info_key), session_data, byref(max_data_len), byref(data_len), byref(code))
print "session data - ", string_at(session_data, data_len)
print "data length - " , data_len
print "session code - ", code
print "-"

file_id = c_long(0)
dict_flag = IK_DATA
filename = c_char_p("BP")
file_len = c_long(len(filename.value))
status_func = c_long(0)
code = c_long(0)

print "ic_open"
ic_open(byref(file_id), byref(dict_flag), filename, byref(file_len), byref(status_func), byref(code))
print "file id - ", file_id
print "status func - ", status_func
print "code - ", code
print "-"

read_lock = IK_READ
record_id = c_char_p("CLE")
id_len = c_long(len(record_id.value))
max_rec_size = c_long(8192)
mvrecord = create_string_buffer(max_rec_size.value)
record_len = c_long(0)
status_func = c_long(0)
code = c_long(0)

print "ic_read"
ic_read(byref(file_id), byref(read_lock), record_id, byref(id_len), mvrecord, byref(max_rec_size), byref(record_len), byref(status_func), byref(code))
print "status func - ", status_func
print "code - ", code
print "record length - ", record_len
print "mvrecord - ", mvrecord.value
print "-"

code = c_long(0)

print "ic_close"
ic_close(byref(file_id), byref(code))
print "file id - ", file_id
print "code - ", code
print "-"

code = c_long(0)

print "ic_quitall"
print "quitall code - ", code
print "-"

finish = ""
finish = raw_input("Return to exit")

This routine will prompt for the user name and password. It is assumed that it is run on the same machine as the UniVerse database is. You can change the server variable to the server’s IP or prompt for it, if you wish.

The read record is printed to the screen. Depending how you run this routine (Python command line or IDLE) you will see the UniVerse markers as either hex codes or blocks. They are there!

So, now what?

There is the ability to run or execute s subroutine on the UniVerse server via ic_subcall. There is a special data structure (ic_string) used to pass parameters etc. It is the same as above, but more so…

Well, if you want to stay in the function/subroutine world, all of the InterCall routines can be defined using ctypes and used in a similar style as here. Or a series of classes can be defined around the InterCall ctype routines, which makes more sense in the long run. ctypes also offers prototypes for libraries. This does force you to use an error/result routine to extract the returned data. Using this method, all the routines become true functions, returning tuples.

Either way, the major issue is how to map the multivalue item structures to a python structure. Python does have lists; which can also be lists within lists etc. There are also sets which may work well with select-lists. Much thought and discussion here. Be wary though, InterCall is not a SQL based library. The U2 UniDK toolkit has UCI (Uni Call Interface) for that.