Getting Started

Calling external DLL functions is a very basic and established way to communicate with third party software such as drivers. Igor Pro does not offer this feature natively. The usual way to include functionality from external libraries is writing an XOP. The XOP makes the external functions of a DLL available through internal Igor Pro operations or functions.

The CallFunction XOP closes this gap and allows to call external DLL functions directly from within Igor Pro.

Working Principle

How a DLL library function has to be called must be provided by the manufacturer of the software package. For hardware devices typically a software development kit for languages like C is delivered containing .h header files that describe in text form how a library function has to be called. The DLL library at hand and the information from the header file is enough to call a DLL function correctly. The CallFunction XOP uses a string in JSON format for the information from the header file.

Parameter Definition

It is strongly recommended to study the section Input Parameters. A discrepancy between the setup of input parameters and the actual declaration of the function easily results in hard crashes of Igor Pro.

Input Parameters

The parameters and return parameter for a function call with FCALL_CallFunction are defined by a string in JSON notation. The generic definition for parameterIn looks like:

{
  "Parameter": [
    {
      "type": "<type>",
      "value": <value>
    },
    {
      "type": "<type>",
      "value": <value>
    }
  ],
  "result": {
    "type": "<type>"
  },
  "version": 1
}

The main object has three members Parameter, result and version.

version stores the current version of the API of the XOP. It must be 1 for the current release.

result stores information about the expected return type. In the simple case it contains only a type member where the expected return type is given.

Parameter is an array where each element stores information about a single parameter. The order of the elements in the array translates to the order of function parameters. An element has a type and value member that defines the parameter.

Known types for Parameter elements are:

Type

Value description

C type declaration example(s)

Value example

UINT8

Unsigned 8 bit integer

unsigned char, uint8_t

255

UINT16

Unsigned 16 bit integer

unsigned short, uint16_t

65535

UINT32

Unsigned 32 bit integer

unsigned int, uint32_t

4294967295

UINT64

Unsigned 64 bit integer

unsigned long long int, uint64_t

4294967296

INT8

Signed 8 bit integer

char, int8_t

-100

INT16

Signed 16 bit integer

short, int16_t

-1000

INT32

Signed 32 bit integer

int, int32_t

-123456

INT64

Signed 64 bit integer

long long int, int64_t

-4294967296

PTR

Generic pointer (as signed 64 bit)

int*, void*, void**, someObject*

12345678912

FLOAT

Single Precision floating point number

float

-1.23

DOUBLE

Double Precision floating point number

double

-1.23e+300

STRING

a string, see String

char*

“some text”

WAVEREF

Path to a Igor Pro wave as string, see WAVEREF

int*, double*, float*

“root:myWave”

The content of the value field must fit to the set type in the type field. For floating point types it is allowed to specify value as string if it can be parsed to a floating point number. This enables to specify specialized values such as “NaN” and “Inf”. Escaping for values of type STRING is supported. That means that e.g. path like “C:\” must be specified in their escaped form “C:\\”.

STRING

The STRING type is treated as a pointer to a character array. On function call the character array contains the given string. For the case that the called function writes data back to the string the input string must have a sufficient size for the data the called function writes.

WAVEREF

The value for the WAVEREF type is a string containing the path to an Igor Pro wave. The waves data is treated as a memory array where the effective parameter is a pointer to the waves data. The wave must be located in a data folder and can not be a free wave. The called function can change the data in the wave. The FCALL_CallFunction operation does not check whether wave data was changed or not. Thus Igor Pro is not notified if the wave data was changed. If elements in Igor Pro have to be updated due to changed wave data a wave modified notification can be forced with a write to the wave that does not change its content:

myWave[0] = myWave[0]

Inline Arrays

Instead of primitive types for the “value” fields of the Parameter array also arrays can be used e.g. “value” : [1, 2, 3].

This is allowed for the following types:

  • INT8

  • INT16

  • INT32

  • INT64

  • UINT8

  • UINT16

  • UINT32

  • UINT64

  • PTR

  • FLOAT

  • DOUBLE

  • STRING

The “type” field specifies the type of the elements of the inline array. The corresponding C declaration is a pointer of that type.

C declaration
// setBuffer expects as first parameter a pointer to memory with 3 int elements
// C declaration
void setBuffer(int* buffer);
Inline Array for typed C pointer
string parameterIn, parameterOut

parameterIn = "{\"Parameter\": [{\"type\":\"INT32\",\"value\":[1, 2, 3]}],\"result\": {\"type\": "INT64"},\"version\": 1}"
FCALL_CallFunction handle, "setBuffer", parameterIn, parameterOut

An inline array of the type STRING is treated as one consecutive character array. All strings in the array are copied to a single memory area and the function parameter is a pointer to that memory. The copied parameter in parameterOut is a single JSON string.

Known types for result are:

Type

Result Value description

C type declaration example(s)

Value example

UINT8

Unsigned 8 bit integer

unsigned char, uint8_t

255

UINT16

Unsigned 16 bit integer

unsigned short, uint16_t

65535

UINT32

Unsigned 32 bit integer

unsigned int, uint32_t

4294967295

UINT64

Unsigned 64 bit integer

unsigned long long int, uint64_t

4294967296

INT8

Signed 8 bit integer

char, int8_t

-100

INT16

Signed 16 bit integer

short, int16_t

-1000

INT32

Signed 32 bit integer

int, int32_t

-123456

INT64

Signed 64 bit integer

long long int, int64_t

-4294967296

PTR

Generic pointer (as signed 64 bit)

int*, void*, void**, someObject*

12345678912

POINTER

Typed pointer, see POINTER

int*, char*, int**, float*

12345678912

FLOAT

Single Precision floating point number

float

-1.23

DOUBLE

Double Precision floating point number

double

-1.23e+300

STRING

a string, see String

char*

“returned”

WAVEREF

Path to a Igor Pro wave as string, see WAVEREF

int*, double*, float*

“root:myWave”

If a C function declaration specifies void as return value a primitive type such as “INT64” should be used as result type. The “value” returned in parameterOut is undefined.

STRING

The type STRING expects that the called function returns a pointer to a character array. After the call characters are read until a zero byte is encountered (C style stings). The read characters are returned in the value member as string.

WAVEREF

The WAVEREF type requires an additional member “value” in the result object with a string containing the path to an Igor Pro wave. The wave must be located in a data folder and can not be a free wave. It is expected that the called function returns a pointer to memory of the size of the wave data and elements of the type of the wave element. After the called function returns memory of the size of the wave is copied from the returned pointers location to the wave. The FCALL_CallFunction operation does not notify Igor Pro that the wave data was changed. If elements in Igor Pro have to be updated due to changed wave data a wave modified notification can be forced with a write to the wave that does not change its content:

C declaration
// getBuffer returns a pointer to memory with 1000 int elements
// C declaration
int* getBuffer();
WAVEREF as result type
string parameterIn, parameterOut

Make/I/N=(1000) myWave // note that the wave is created with type INT32

parameterIn = "{\"Parameter\": [],\"result\": {\"type\": "WAVEREF",\"value\": \"myWave\"},\"version\": 1}"
FCALL_CallFunction handle, "getBuffer", parameterIn, parameterOut
// Now the wave myWave has a copy of the memory buffer getBuffer returned.

In the JSON notation of the output string parameterOut the member pointer is added with the value of the returned memory address of the called function as signed 64-bit integer. The memory address value can be reused if further calls expect it as e.g. “PTR” typed parameter.

POINTER

The type POINTER allows to return data as an inline array in the parameterOut JSON string. It is expected that the called function returns a pointer to memory. Two additional members in the result object must be specified.

  • “pointee-type”: type of the elements to expect at the memory location the called function returns

  • “element-count”: number of elements to expect at the memory location the called function returns

pointee-type can be one of the following primitive types:

  • “INT8”: Signed 8 bit integer

  • “INT16”: Signed 16 bit integer

  • “INT32”: Signed 32 bit integer

  • “INT64”: Signed 64 bit integer

  • “UINT8”: Unsigned 8 bit integer

  • “UINT16”: Unsigned 16 bit integer

  • “UINT32”: Unsigned 32 bit integer

  • “UINT64”: Unsigned 64 bit integer

  • “FP32”: Single precision float

  • “FP64”: Double precision float

  • “CHAR”: C style string with terminating zero byte, returned as JSON string.

For all pointee-types except “CHAR” the “value” member in the result object of parameterOut will have the form [x, x, x, …].

In the JSON notation of the output string parameterOut the member pointer is added with the value of the returned memory address of the called function as signed 64-bit integer. The memory address value can be reused if further calls expect it as e.g. “PTR” typed parameter.

C declaration
// getBuffer returns a pointer to memory with 1000 int elements
// C declaration
int* getBuffer();
POINTER as result type
string parameterIn, parameterOut

parameterIn = "{\"Parameter\": [],\"result\": {\"type\": \"POINTER\",\"pointee-type\": \"INT32\",\"element-count\": \"1000\"},\"version\": 1}"
FCALL_CallFunction handle, "getBuffer", parameterIn, parameterOut
// Now parameterOut contains in the "value" member an inline array in the form [x, x, x, ...] with 1000 integer values.

Output Parameters

The parameterOut string uses also JSON notation. A simple output has this form:

{
  "Parameter": [
    {
      "type": "<type>",
      "value": <value>
    },
    {
      "type": "<type>",
      "value": <value>
    }
  ],
  "errorCode": {
    "value": 0
  },
  "result": {
    "value": <value>
  },
  "version": 1
}

Compared to parameterIn most notable is that an additional member errorCode is added. The error code value should always be checked when setting up a function call as it indicates problems with the setup of parameterIn, e.g. if a specified Igor Pro wave could not be found.

If no error occurred the “value” field reads 0. If an error occurred the function was not called. A member “msg” is added with an error description as string.

After the called function returns the parameter data is copied back to the Parameter block of the JSON string. Also data input through inline arrays is read back and put back to the Parameter block. Primitive result types are returned in the results “value” field. For more special result types see POINTER, STRING and WAVEREF.

Error Code

Function was called

Description

0

Yes

No Error

1

n/a

reserved

2

No

The CallFunction core encountered an unknown data type code

3

No

One or more required objects were not found in the JSON input string. This error occurs if one of the fields version, Parameter or result is missing.

4

No

The version field indicates an unsupported version. Currently supported is version 1.

5

No

No type field was found in the result block.

6

No

The type specified in the result block is unknown or a complementary field or property (e.g. for type “POINTER”, “WAVEREF”) is invalid.

7

No

No type field was found for a parameter in the Parameter block.

8

No

No value field was found for a parameter in the Parameter block.

9

No

The type specified in a parameter in the Parameter block is unknown.

10

No

The type specified for an inline array in the Parameter block is unknown.

11

No

A value found in an inline array does not fit to the specified type in the type field.

12

No

A value for a parameter is invalid. This can have multiple causes. The value does not fit to the specified type in the type field or a secondary property is invalid. e.g. for type “WAVEREF” the value specifies a path to an Igor Pro object which is not a wave.

PTR vs. WAVEREF

For a C function with a pointer type parameter the CallFunction XOP offers PTR and WAVEREF as viable types. Given a declaration like useBuffer:

C declaration
// C declaration
void useBuffer(int* buffer);

For buffer two scenarios exist: One is, that the external library allocates the memory for the buffer and returns a pointer to its own buffer memory. The second scenario is that the external library wants to have a pointer to memory that the caller, that is Igor Pro here, provides.

For the first case the PTR parameter type is the correct choice, where the returned pointer value is just stored. The value can be used on further calls to the library, e.g. when another function requests a buffer. It does not matter here that the declared pointer has the type int, as for the pure function call there is no type specific difference. Note that you can not access data that is stored in memory at the location where PTR points to.

For the second case a WAVEREF is required. As the buffer memory has to be provided by Igor Pro a wave with the correct size has to be allocated. Of course it would be convenient to use the same type for the wave as the declaration suggests. If the library expects a buffer of 1024 integers the fitting wave would be Make/I/N=1024 buffer.

Please refer to the documentation of the functions you call, if the first or seconds scenario is present.

Multithreading

The CallFunction XOPs operation are marked as threadsafe. The user has to take care if the called functions are actually threadsafe.

JSONXOP as Helper

Igor Pro has no convenient native support for parsing strings in JSON format. byte physics offers a free utility XOP that allows to easily parse, create and modify JSON objects and convert them to their respective string form. The JSONXOP comes included with the CallFunction XOP. Due to the ease of use it is recommended to employ the power of the JSONXOP when creating and parsing string for the CallFunction XOP.

As a general approach it is efficient to create a JSON object with the function call types and reuse it on each call. For a call only the values of the input parameters are set. Such reusable JSON object can be created with FCALL_SetupParameterIn.