Multithreaded communication for GPIB/Visa/Serial interfaces



This project originated from my efforts to obtain efficient GPIB control in an environment where some devices are slow to respond and usually create a bottleneck in the data flow. The solution proposed in this software consists in using as much as possible the asynchronous operations where a different thread for each device is used so that the write/read operation sequences for different devices can be interleaved. This works quite well as I could easily obtain relatively high reading rates (e.g. an average of 2-3 reading/second/device for 10 devices i.e. a total of 20-30 operations/second) even in configurations where a few devices are very slow (e.g. 2-3 seconds response time). Asynchronous operations are queued which makes programming very simple. However standard synchronous (blocking) operations are allowed too, and the code can safely and transparently handle concurrent use of both synchronous and asynchronous commands.

As the software has been made sufficiently general to adapt it to various Gpib libraries it could eventually also be adapted to other interfaces such as the NI’s Visa library and the serial port. Visa is especially interesting as it enables controlling devices via USB (for devices compliant with USB-TMC protocol) or TCP/IP (LXI standard) without need to develop specific drivers. Of course it would be great if someone could develop an implementation for these protocols not relying on Visa.

The software needs third-party drivers (except for the serial port) to be installed to operate.

Following the object-oriented philosophy, all interfaces are represented as classes derived from an abstract class representing a generic device, and the low-level interface-dependent operations are implemented as virtual methods so that the interfaces can be used “polymorphically” : except when instances of various devices are created, the code does not need to know which interface is being used for each of them. This approach also makes easy to add new interfaces or to tweak the provided implementations by creating child classes overriding methods that need to be modified.

All files and projects are written for WindowsForms applications and come in two versions: C# and VB.NET

All projects were created under VS2008 (also have been tested under SharpDevelop v4.3)

Projects and files included

The projects IODevices and IODevices_withNINET construct a library (NET assembly “IODevices.dll“) which defines a generic (abstract) class “IODevice” and its implementations (inherited classes) for various interfaces (see section “IODevices assembly and implementations” for more details on each of these classes and the required drivers). The interfaces are:

  • GPIBDevice_NINET – for NI boards
  • GPIBDevice_ADLink – for ADLink boards 
  • GPIBDevice_gpib488 – for Keithley,MCC and older NI boards
  • VisaDevice – generic interface (GPIB,USB etc.) via Ni Visa
  • SerialDevice – for serial ports

The library also defines two forms to display status and error messages: DevicesForm, IOmsg

You have to add a reference to this assembly in your project to use it, see test projects. (Alternatively, you may just copy the necessary files directly to your project – but then the application will get access to all “internal” fields and methods – not very safe in principle!).

The class GPIBDevice_NINET refers to the National Instruments’ .NET assemblies to access the Gpib driver (see details in the class description, these are not included here because of copyright) and any project including this file will not compile without them, therefore I made two projects:

  • project IODevices: without GPIBDevice_NINET
  • project IODevices_withNINET: including GPIBDevice_NINET

(The name of the resulting NET assembly is “IODevices.dll” for both, so it should be recompiled when switching from one version to the other)

Other implementations use Windows “dll”s only (“plain C” dll files provided by various board vendors) so that the project IODevices will always compile whether or not the interfaces are installed on the system, if a necessary dll is not present an error will only occur if a corresponding class is instanciated. Note that Gpib boards from NI can also be accessed via Visa interface which usually is also installed with them, so the GPIBDevice_NINET interface class may not be needed.

The projects testIODevice and testIODevice-withNINET are demos showing how to use the basic functions of the library. You may choose two devices (eg. one fast and one slow) and see how it works sending commands to both at the same time.

The test project contains a reference to the assembly “IODevices.dll” therefore both projects are interdependent (it is practical to put them in the same solution if you want to examine how the code in the IODevices assembly works).

All these projects are also contained in two “solutions”:

  • testIODevice,
  • testIODevice-withNINET

each containing both a library and a test project.

Background: Gpib communication issues

NI references:


Let us examine a simple approach where Gpib devices are addressed sequentially:

  • send command to device 1
  • wait for response from device 1
  • send command to device 2
  • wait for response from device 2
  • send command to device 3
  • wait for response from device 3

This scheme has several potential shortcomings:

  1. if waiting for a response from device is achieved via low-level gpib functions then:
    • the application freezes during wait if low-level gpib functions (“receive”) are called on the main (GUI) thread. This problem can be solved using a different thread for gpib operations (asynchronous operations)
    • the GPIB bus is locked during low-level interface calls therefore even using threading the sequence is not efficient because other devices have to wait too, unless we shorten as much as possible the “waiting for response” time inside “receive” functions, which are waiting for response with the gpib bus locked. This can be achieved either using polling or setting very short timeout values and repeating reading on timeout, so that the bus is not locked most of the time.
  2. The sequential querying will take time which causes problem if we need to periodically scan many devices. The time needed to get the response is not only function of the size of data to transfer, usually the slowest response is expected for commands that trigger a measurement, such as the commonly used “Read” command of DMM’s. For DMM’s, the delay depends on the resolution of the measurement and getting a response can easily take a couple of seconds which is often the main source of bottleneck on the gpib. Of course there are software solutions using device specific configurations (trigger commands or auto-trigger mode if available etc.) however such an approach requires more programming effort and is more difficult to be made generic (typically an application has to handle a number of similar devices selected from a device pool each time a new experiment is configured). Therefore it is better if a generic yet efficient approach may be found.

Both the delays and the time intervals when gpib bus is unavailable can be minimized using (a) interleaving of command/response sequences and (b) polling. Command interleaving can be obtained automatically if each device has a dedicated thread for asynchronous operations, as explained below.

GPIB is a bus where the controller (PC) decides when each device is allowed to send data therefore the GPIB “write” and “read” operations can be interleaved, leading effectively to a parallel query of several devices at a time:

  • send command to device 1
  • send command to device 2
  • send command to device 3
  • wait for response from device 1
  • wait for response from device 2
  • wait for response from device 3

Then at the time we receive a response from device 1, the devices 2 and 3 might be ready too to send data therefore there is no additional performance penalty due to devices 2 and 3.

The interleaving of the write/read sequences can be achieved automatically in a way totally transparent to the calling program if each device uses a different thread to perform gpib operations. The scheme implementing this will be:

  • thread1: send command to device 1; wait for response from device 1
  • thread2: send command to device 2; wait for response from device 2
  • thread3: send command to device 3; wait for response from device 3

Here each thread will proceed as soon as the gpib bus is available. There is the remaining problem of blocking the bus while waiting for a device to respond. This is most efficiently solved using the “poll” feature of GPIB: test if a device is ready to send data before actually allow it to talk. So finally the most efficient scheme is:

  • thread1: send command to device 1; periodically poll it for “data ready” status; get response from device 1
  • thread2: send command to device 2; periodically poll it for “data ready” status; get response from device 2

If a device does not support polling then it may be good to replace it by a constant delay (during which the respective thread is sent to sleep) between the write and read operations so as to shorten the “wait for response” time during which the bus is unavailable to other devices. Also, it is good to set a short timeout at the interface level and repeat reading after a delay if timeout occurs.

The purpose of this library implementing this scheme is to provide an out-of-the-box solution for creating such efficient code with minimum programming effort, all thread manipulating is transparent to the user code. Actually my own purpose was to easily adapt some existing applications that were using classical sequential approach with as few changes in the code as possible.

The IODevice class is intended to be adapted to any low level GPIB interface and therefore all low-level operations are defined as abstract (VB: MustInherit) methods. The project provides implementations (derived classes) of this abstract class for several GPIB interfaces (see file list above and reference below). Also, the abstract methods are sufficiently general to allow constructing implementations for other interfaces such as NI Visa and serial port (hence the initial name “GpibDevice” eventually became “IODevice”). It should be quite easy to create implementations for other hardware.

Note that the standard GPIB libraries also provide a sort of asynchronous operations, however somewhat limited, for example it is said in the NI reference manual :


“The asynchronous I/O calls (BeginRead and BeginWrite) are designed so that applications can perform other non-GPIB operations while the I/O is in progress. Once the asynchronous I/O has begun, further NI-488.2 calls are strictly limited. Any calls that interfere with the I/O in progress are not allowed and return an exception.”

This means that no queuing is performed (and probably also that asynchronous operations on various devices are all executed on the same thread), anyway such limitation is not compatible with the parallel querying scheme described above (it also adds a lot of programming overhead compared to simple synchronous calls) and makes these features not very helpful for our purpose. Note that, on the other hand, the GPIB “Notify” callback feature does not suffer from such limitations and can be implemented resulting in a more powerful and more flexible asynchronous reading scheme, as explained later.

The code proposed here provides a somewhat higher abstraction level since asynchronous tasks are queued (see below) so that the calling program does not have to care about the moment a command is allowed to be sent: here all asynchronous calls are allowed at all times (but we can decide if a call is necessary inspecting the queue content with “PendingTasks” methods).

Note that queuing messages resulting from asynchronous write/read operations is possible in NI Visa (cf. viWriteAsync, viReadAsync functions). However, here the philosophy is different because the whole queries (write/read sequences, including commands) are queued for each device, so that the program does not have to wait until an asynchronous read operation completes before sending other commands to the same device*. This makes the asynchronous programming extremely simple and allows an automatic “retry on error” feature. If the “retry” flag is set then, in case of error, the function will clear the device and repeat the whole write/read sequence until success or abort by user.

*Actually I don’t have enough experience with Visa to tell if it can handle a query queue (but in all examples the program waits for write event before proceeding with read) so correct me if I am wrong. On the other some lower-level protocols like HiSLIP  implement query queuing.


quoted from: :

“Serial polling is a method of obtaining specific information from GPIB devices when they request service. When you conduct a serial poll, the Controller queries each device looking for the one that asserted SRQ. The device responds to the poll by returning the value of its Status Byte. Device-dependent conditions, such as the presence of available data or an error condition, determine this value. ANSI/IEEE Standard 488.1-1987 specifies only one bit in the Status Byte, Bit 6, which is TRUE if the device requests service. The other bits in the Status Byte are left to the instrument manufacturer to define. IEEE 488.1-compatible instruments have bits that determine if an instrument error has occurred or if the device is conducting a self-test. These bit definitions are not consistent among instrument vendors and the method for determining the cause of a service request varies with each device.

ANSI/IEEE Standard 488.2-1987 solves this problem by defining certain service request conditions so that one model describes the Status Byte for all compliant devices. Bit 6, the device Request Service (RQS) bit, maintains the IEEE 488.1 definition. If Bit 6 is set, then the device requested service. The IEEE 488.2 standard defines Bits 4 and 5; instrument manufacturers define the remaining bits (0 through 3 and 7). Bit 4 is the Message Available (MAV) bit. This bit is set if the device has been previously queried for data and the device has a pending data message to send.

The polling option is enabled setting “enablepoll” field to true (default). It should be enabled if the device is compatible with the 488.2 standard. Then the serial poll is used to see if a device is ready to send data by examining its Status Byte, in this way the gpib bus is not locked most of the time when waiting for a device to respond. This is especially important when the query command also acts as a software trigger of a new measurement (standard behavior for DMMs).

This library only uses the MAV (Message Available) bit of the status byte defined in the standard as explained above.

Most devices comply to 488.2, but not all eg. some Lakeshore temperature controllers define their own meaning for all the status byte bits. If you see a “poll timeout” error appearing then it is probably the case and polling should be disabled. Alternatively, it is possible to write a derived class overriding the virtual method “pollMAV” : this method also returns the whole status byte so that it is easy to write a modified implementation where the status byte is interpreted differently (see example in the section about implementations).  If polling is not available then, as said above, we should set a short timeout at the interface level (the reading will anyway be repeated automatically on timeout) so to not to block the bus for long periods of time.

Asynchronous interface callbacks

In a scheme based solely on polling the effective response time of a device has a minimum granularity defined by the polling frequency.  For time-critical applications we can increase this frequency but this will also increase inefficient traffic on the bus (and may even cause errors if delays between subsequent polls are very short).  On the other hand, a low-level driver can have access to hardware interrupts and thus knows immediately about all signals appearing on the bus.  Various interfaces provide means of asynchronous signalling of events which can make polling more efficient.   In GPIB,  we can configure selected devices to pull the Service Request (SRQ) GPIB line when they are ready to send data and configure the driver to fire an asynchronous callback each time SRQ is detected (  The protocols USBTMC, VXI11 and HiSLIP also implement asynchronous service requests, using out-of-band signalling.  Likewise, the serial port can be configured to raise an event each time new data arrives.

The IODevice class provides a very simple (optional) feature that can be used with asynchronous callbacks from the driver and which seamlessly integrates with the polling scheme described above:  the waiting for read after write or for next polling/reading trial can be asynchronously interrupted by another thread calling the device’s method  “WakeUp". Two delays are concerned: delay between write and read (delayread) and the delay between subsequent read/poll trials (delayrereadontimeout).  This method can be called from any thread and is intended to be used in a callback function called by a low-level driver (i.e. unsynchronized callbacks can be used).

 In the current project version this technique is implemented in three classes: the class GPIBDevice_NINET and VisaDevice offer an optional possibility to set up the GPIB “Notify” callback or its equivalent in other protocols supported by Visa,  and  the class SerialDevice uses it by default, implementing a handler of the DataReceived event of the SerialPort class (see class descriptions below for details).

Using the code : IODevice class reference

I/O functions

There are two types of I/O functions:

  1. Send” functions are intended for commands where no response is expected from device
  2. Query” functions write a command and read response

Both use the same code and architecture built around the “IOQuery” class, in the code the term “query” is used for both (the “type” field in the IOquery class distinguishing between the two versions).

NB. There is no separate “read” operation as this would be incompatible with the “retry” feature (note that VISA defines three sorts of functions: write, query and read).

The “read” operation alone would only be required in the case of “talk only” devices, I don’t know any but if such operation is needed, if a query method is called with command set to empty string then it will not call the “send” operation, it is therefore equivalent to a “read” alone.

The I/O functions don’t throw any exceptions. External exceptions in the library functions or in the user callback functions can be catched or not (see “catchinterfaceexceptions” and “catchcallbackexceptions” flags). Note however that the class constructors can throw exceptions (this is to avoid creating ill-defined objects, catching constructor exceptions should be done outside the constructor), see descriptions of different classes below.

Each type of command is provided in two versions (see reference below for the syntax of each command):

  1. blocking commands: SendBlocking, QueryBlocking are immediately executed on the calling thread (usually GUI thread), the method waits until it gets response from the interface.

    Here “blocking” means that the call will not return until the response is received, however the bus is not blocked during the whole query therefore other queries can be conducted in parallel, exactly as for “async” commands. Of course for each device only one blocking command is allowed at a time.

  2. asynchronous commands: SendAsync, QueryAsync : here queries are queued and the queue is processed on a different thread (producer-consumer model). The call appends the query to the queue and returns immediately. When the query is completed the user “callback” function is called.

Each device has its own queue and runs its own dedicated thread processing queries from the queue. In this manner the asynchronous commands to a device are processed sequentially but commands to different devices can be processed in parallel, as explained above.

The idea of using both types of queries is that often the mainstream sequence (eg. needing complex sequence for device configuration, arming, acquiring etc., where the commands to sent may sometimes depend on the data received so it is simpler and natural to use synchronous calls) runs in parallel with some annex tasks repeated at constant intervals to update the status of the experiment (e.g. reading temperature every second). For these tasks we usually use timers, and such tasks are much more efficient with asynchronous queries, using available bus bandwidth and time slots.

One can mix blocking and asynchronous commands even on the same device, then a blocking command should in principle be processed as soon as the current asynchronous operation (if any) completes or when it is waiting for retry (however there is no guarantee as for the exact timing which is decided by the OS task scheduler).

The method “WaitAsync” can be used for synchronization between the asynchronous command queue and the main thread (see class description below): this method waits until queries initiated before the call are completed (NB. not until the queue is empty – this may never happen if asynchronous queries are issued in timers).

Common arguments:

  • string cmd: command string
  • bool retry: if set to true the whole query will be repeated on error (until success or abort by user)
  • bool cbwait: if set to true the async thread will wait until the callback function completes before processing remaining queries in the queue (this is the default behavior for short versions where this argument is absent). May be set to false eg. if a callback function starts a long processing and there is no need to wait, however in this case the callback function has either to block event processing or to be reentrant (can be called again before it returns). Note also that with this setting the class’ “catching callback exceptions” feature (“catchcallbackexceptions” flag set to true) will not work, if you want to understand why I recommend this excellent series of articles:
  • int tag: an additional field passed on to the query variable, may be used to distinguish between queries if the same callback function is used to process different queries.

Blocking commands

1) SendBlocking


public int SendBlocking(string cmd, bool retry)


Public Function SendBlocking(ByVal cmd As String, ByVal retry As Boolean) As Integer

2) QueryBlocking


public int QueryBlocking(string cmd, out IOQuery q, bool retry) public int QueryBlocking(string cmd, out string resp, bool retry) public int QueryBlocking(string cmd, out byte[] resparr, bool retry)


Public Function QueryBlocking(ByVal cmd As String, ByRef q As IOQuery, ByVal retry As Boolean) As Integer Public Function QueryBlocking(ByVal cmd As String, ByRef resp As String, ByVal retry As Boolean) As Integer Public Function QueryBlocking(ByVal cmd As String, ByRef resparr As Byte(), ByVal retry As Boolean) As Integer

In the first syntax the variable q contains the full information on the query (status,data, timings), the other two are simpler versions giving directly the result either as a string (resp) or a byte array (resparr). In case of error (return value different from 0) q will contain the error code and message, however the data fields (ResponseAsString, ResponseAsByteArray) will be null references (VB: Nothing), as well as the corresponding variables resp and resparr in the two other versions.

Return value: same as q.status (0 if ok or error code), otherwise -1 if blocking call on this device is already in progress (may happen if events allowed), -2 if device is disposing.

Asynchronous commands: SendAsync and QueryAsync

Most of these methods use an argument of type IOCallback to define the callback function. The signature of this function is given by the declaration:

VB : Public Delegate Sub IOCallback(ByVal q As IOQuery) C#: public delegate void IOCallback(IOQuery q);

Here the variable q will contain the status and data relative to the operation, the rules are the same as for the blocking calls i.e. the data fields are null references if an error occurred.

Note that, even though the callback is initiated from a different thread, the callback function will be executed on the main (GUI) thread (this is to allow updating GUI components within the callback function), in other words the asynchronous thread sends a message to the main thread to call the function. Therefore the callback function will not be called until processing messages by the GUI thread is allowed.

The return value for all versions of SendAsync and QueryAsync methods is: 0 if ok, -1 if the queue is full (for each device the maximum queue length is defined by the field maxtasks, default is 50), -2 if device is disposing.



public int SendAsync(string cmd, bool retry) public int SendAsync(string cmd, IOCallback callback, bool retry, bool cbwait, int tag)


Public Function SendAsync(ByVal cmd As String, ByVal retry As Boolean) Public Function SendAsync(ByVal cmd As String, ByVal callback As IOCallback, ByVal retry As Boolean, ByVal cbwait As Boolean, ByVal tag As Integer) As Integer

In the complete version (probably rarely needed) the callback function will be called to signal the status of the operation (however there will be no valid data in the IOquery variable passed to it). This version enables however the calling program to break the “retry” loop calling the “AbortRetry” method of the IOQuery variable.

QueryAsync with callback

When the function completes (or when there is an error) it will call the callback function passing the result in a IOQuery variable. The callback function has to check the status field of the received IOQuery variable to check if there is a valid data there (if status is not 0 then ResponseAsString and ResponseAsByteArray will be null references).


 public int QueryAsync(string cmd, IOCallback callback, bool retry) public int QueryAsync(string cmd, IOCallback callback, bool retry, bool cbwait, int tag)


 Public Function QueryAsync(ByVal cmd As String, ByVal callback As IOCallback, ByVal retry As Boolean) As Integer Public Function QueryAsync(ByVal cmd As String, ByVal callback As IOCallback, ByVal retry As Boolean, ByVal cbwait As Boolean, ByVal tag As Integer) As Integer
QueryAsync with TextBox

This is a “light” version where, instead of calling a callback function, the result is put (if no error occurs) in a TextBox variable (its Text property). This version does not return any error information (except in the message window, if enabled).


 public int QueryAsync(string cmd, TextBox text, bool retry) public int QueryAsync(string cmd, TextBox text, bool retry, int tag)


 Public Function QueryAsync(ByVal cmd As String, ByVal text As TextBox, ByVal retry As Boolean) As Integer Public Function QueryAsync(ByVal cmd As String, ByVal text As TextBox, ByVal retry As Boolean, ByVal tag As Integer) As Integer

Other methods of IODevice class

Instance public methods:


public bool IsBlocking(); public int PendingTasks(); public int PendingTasks(string cmd); public void WaitAsync(); public void AbortAllTasks(); public void Dispose();

VB: (see C# code comments above)

Public Function IsBlocking() As Boolean Public Function PendingTasks() As Integer Public Function PendingTasks(ByVal cmd As String) As Integer Public Sub WaitAsync() public void AbortAllTasks() Public Sub AbortAllTasks() Public Sub Dispose()

Static (VB shared) methods


public static void ShowDevices() public static IODevice DeviceByName(string name) public static void DisposeAll() 


Public Shared Sub ShowDevices() Public Shared Function DeviceByName(ByVal name As String) As IODevice Public Shared Sub DisposeAll()

Public fields :

public int maxtasks; public string devname, devaddr; public static string statusmsg; public int delayread; public int delayrereadontimeout; public int delayop; public int readtimeout; public int delayretry; public bool checkEOI; public bool enablepoll; public bool stripcrlf; public bool eventsallowed; public bool showmessages; public volatile IOQuery lastasyncquery; public bool catchinterfaceexceptions; public bool catchcallbackexceptions; public bool callbackonretry; 

Protected methods and properties to use in implementations:

C# :

protected void AddToList(); protected void WakeUp(); protected IOQuery currentactivequery 


Protected Sub WakeUp() Protected Sub AddToList() Protected ReadOnly Property currentactivequery() As IOQuery

Devices list window

opens when calling the static method IODevices.ShowDevices(), looks like this example:

By default ShowDevices(), will be called at startup (in the static constructor of IODevices), if you find it annoying you can set the constant showdevicesonstartup to false. 

Error message window

When “showmessages” field is set to true (default) this forms opens when an error occurs. The form is not modal and is for information only: the program continues the same way whether the form is displayed or not. However it allows you to easily abort a “retry”. When an error is corrected after a successful retry then the form will close itself. In the example above it signals that a device is not connected and that the query will be repeated, once it is connected the message will disappear. If you close the window and the error persists then the message will pupup again, minimize the window instead of closing it if you find it annoying. 

IOQuery class public members


public string cmd; public int tag; public string ResponseAsString; public byte[] ResponseAsByteArray; public int status; public int errcode; public string errmsg; public DateTime timecall; public DateTime timestart; public DateTime timeend; public int type; public IODevice device ; public void AbortRetry(); public void AbortAll();

VB (see C# code comments above)

Public cmd As String Public tag As Integer Public ReadOnly Property ResponseAsString() As String Public ReadOnly Property ResponseAsByteArray() As Byte() Public status As Integer Public errcode As Integer Public errmsg As String Public timecall As DateTime Public timestart As DateTime Public timeend As DateTime Public type As Integer Public ReadOnly Property device() As IODevice Public Sub AbortRetry()

IODevices assembly and implementations

The assembly defines two namespaces: IODevices and IODeviceForms. The latter is used internally to access forms displaying the device list and error messages and does not need to be imported in the application.

The IODevices namespace which should be imported to the application defines the following classes:

class IOQuery

as explained above

class IODevice

Is an abstract (VB: MustInherit) class from which various kinds of real devices are derived as child classes. Contains the main code and public methods but refers to four abstract methods to address the low level interface (see explanation in the code under “abstract methods” comment if you want to implement other interfaces).

Following the object-oriented philosophy every device derives from IODevice which contains all public methods, whereas the low level interface is accessed via private virtual methods, in this way the child classes can be used polymorphically: except when the instance of a device is created the code does not need to know which interface is used (see sample code in the “testIODevice” project).

The implementations of this abstract class given here provide a basic configuration (but succesfully tested with various devices). Again following the object-oriented philosophy, it is easier to write a derived class for each specific configuration than to make a class which would take into account all available options. For example, all GPIB classes use the standard “EOI” signal to detect the end of message. If your device cannot set EOI but instead uses a specific caracter to terminate messages (e.g.”\n”) you can write a child class which redefines the constructor to set the device options accordingly (alternatively, it may also override the “ReceiveByteArray” method, see section about writing new implementations).

All implementations configure the interface with a relatively short timeout (300ms for GPIB, few ms for serial) because anyway the reading will be repeated until the “cumulative” timeout period “readtimeout” elapses, in this way the bus will not be blocked for a long time even if polling is disabled.  

The constructor of each of these classes will throw an exception if the device initialization fails. This is to prevent creating ill-defined objects, catching constructor exceptions should be done outside the constructor.

For all GPIB classes, if “buffersize” is not specified it will be set to 32k.

The current version provides the following implementations :

class GPIBDevice_NINET

uses the native .NET library for NI Gpib , uses as reference the following .NET assemblies from NI: NationalInstrumentsCommon, NationalInstruments.NI4882 (these are installed with the NI GPIB driver). It was intensively tested with the GPIB-USB-HS+ board from NI. The assembly will not compile if these files are not found (and were not included here because of copyright), therefore a version of the project where this class is not included is also provided. Note that in many cases we won’t necessary need this library: usually the NI software for GPIB will also install Visa.

class constructors:


​​​​​public GPIBDevice_NINET(string name, string addr) public GPIBDevice_NINET(string name, string addr, int buffersize)


Sub New(ByVal name As String, ByVal addr As String) Sub New(ByVal name As String, ByVal addr As String, ByVal buffersize As Integer)

name is the name given to the device and displayed in the DevicesForm.

addr is the GPIB address and can have the following forms:

"n" device n at board n°0 e.g. "1" "b:n" device n at board n° b e.g. "0:1" "GPIBb::n::INSTR" (Visa format) device n at board n° b e.g. "GPIB0::1::INSTR"

The version of April 2017 adds a boolean property EnableNotify (default=false).   Setting this property to true will enable the board’s “Notify” callback to be activated on SRQ and subscribe to the notify event for the device (the class keeps a list of all devices for which EnableNotify was set).  Each callback on SRQ will then call the device’s WakeUp() method as explained above.  

The NI library provides two versions of the callback: at board level and at device level.  For the latter, as there is only a single SRQ line, the NI driver will automatically poll all selected devices to find the source of SRQ.

This library uses only the board-level callback, since calling Wakeup will proceed to poll anyway (if polling is enabled, however note that you can use this feature even with polling disabled: then calling Wakeup will result in trying to read data immediately).   As only one callback function can be defined for a board, each SRQ request will call WakeUp methods of all devices having subscribed therefore if used for many devices on the same board it may become less efficient and even cause driver errors.  Typically, it should be used for selected devices for which it is essential to get data as soon as it is ready.  

In the previous release I was complaining that the Notify feature was sometimes not working properly, depending on the hardware configuration.  I found since that the problems were related to the NI driver’s “automatic polling” feature which is activated by default (and apparently tries to poll all devices whether or not they subscribed to device-level callbacks). In this version the autopolling has been disabled (in the class constructor).  

To enable setting SRQ when the MAV bit is set you need to enable the appropriate bit in the Service Request Enable Register of your instrument. Usually it is the bit 4 and it will be set sending the command “*SRE 16” to the device (note that you can also add other flags to wakeup on, eg. OPC,  error etc.). There is an example of function doing all these configuration steps in the test Form:

public void setnotify(GPIBDevice_NINET dev) { dev.delayread = 1000; dev.delayrereadontimeout = 1000; dev.SendBlocking("*SRE 16", false); dev.EnableNotify = true; }
class GPIBDevice_gpib488

uses the Windows dll library “gpib488.dll” provided with Measurement&Computing or Keithley boards and also some older NI boards. This dll is usually provided in both 32bit and 64bit versions (with the same name but in different Windows directories). The class has been tested with the KUSB-488A board from Keithley. I don’t have a MCC board but the signatures of all the gpib488.dll functions are exactly the same for both (let me know if there are problems). For older NI boards there is an equivalent library named “NI4882.dll” that I have tested too, it works in principle but seems a bit flaky with some devices, using Visa is apparently more reliable. The name of the dll is defined by the string constant “_GPIBDll” so it is easy to change it in the code. However, for NI I noticed that many provided GPIB examples for C/C++ programming use rather Visa interface instead of these older dlls so this is probably the way to go if you don’t want to use the NINET interface.

The constructor parameters are the same as for GPIBDevice_NINET.

class GPIBDevice_ADLink

uses the Windows dll  “gpib-32.dll” provided with ADLink boards. Was intensively tested with the USB-GPIB3488A board from ADLink.

NB. this dll has the same name as the one installed by NI software or older MCC software. It may be wiser to rename this dll before installing it in the Windows directory (Windows/SysWow64 on 64 bit systems) and then change the string constant “_GPIBDll” in the code.

The constructor parameters are the same as for GPIBDevice_NINET.

Note: In the previous version the class was using the driver calls provided in the ADLink library import module for C#. These are not all compatible with the “standard” calls found in other “gpib-32” dlls, however all standard routines exist there as can be found using a dll browser.  In this version I made some modifications (transparent to the class users though) so that only standard calls are used.  Therefore this class should also be compatible with NI drivers  (so the class could rather be called “GPIBDevice_gpib-32” but I did not want to change the name), note however that error messages are less complete here than those returned by the NI NET library.   Also, the code of the class is now almost identical to the one of GPIBDevice_gpib488  except for some tiny differences in the driver functions signatures.  

class VisaDevice

Uses Windows dll “Visa32.dll”. It is provided in both 32bit and 64bit versions (with the same name, to force using the 64 bit version “Visa64.dll” you may change the constant “_VisaDll” in the code).

NB. In 32bit Windows dlls are in Windows/system32 but in 64bit Windows, 32bit dlls are in Windows/SysWow64 and 64bit dlls are in Windows/system32.

National Instruments’ Visa library (that may be downloaded from NI website) provides a generic interface that can be used to access various physical interfaces (Gpib, serial, USB, TCP/IP etc.). Visa implements various protocols developed for instrumentation like  USBTMC (over USB),  VXI-11 and HiSLIP (over TCP/IP).  These protocols mimic the behavior of GPIB in many aspects as they were intended to replace it. We find there the equivalents of polling the status register, out-of-band signalling for service request messages etc.   The VXI-11 and HiSLIP protocols are part of the LXI standard (Lan eXtension for Instrumentation, which defines protocols for controlling instruments via TCP/IP). 

In principle the basic read/write functions are the same for all interfaces handled by Visa however each of them may need a specific configuration (options – called “attributes” in Visa, error handling etc.). The VisaDevice class provides a basic Visa configuration, you may need to create derived classes to tweak it for a specific configuration. It was successfully tested with GPIB (using GPIB-USB-HS+ board from NI),  USB and TCP/IP.  For GPIB, a small advantage of VisaDevice over the GPIBDevice_NINET class is that it does not require any external assemblies from NI (the needed version of these may depend on the compiler, .NET version etc.).

The “Notify” feature is now available for Visa.  The class’ property EnableNotify works exactly like in the GPIBDevice_NINET class.

VisaDevice can be used for GPIB with NI drivers (then you don’t need to install the NI Net assemblies), then there are small differences compared to GPIBDevice_NINET :

  •  NINET allows to create a device even if it is not connected to the system while in Visa this will cause an error (actually the error occurs when trying to clear the device on startup, this may be disabled in the code setting the constant clearonstartup in VisaDevice to false)
  • in VisaDevice only a few common errors give full-text messages, for other errors only the hexadecimal error code is reported (I was just too lazy to code all possible messages)
  • Visa only provides device-level callbacks, for NI GPIB these rely on “autopolling”, therefore for some hardware configuration EnableNotify may encounter problems that I previously had with  GPIBDevice_NINET (see the GPIBDevice_NINET class description above).

For USB, the device has to be compatible with the “Test and Measurement Class” (USB-TMC) protocol. When after connecting it the device is detected as a “Test and Measurement Device” then it can be used immediately. If the driver is not present then see for instructions to create one.

For VXI-11 and HiSLIP it may be convenient to use NI MAX utility to setup the LAN parameters.

The constructor parameters are the same as for GPIBDevice_NINET. However, here the format for the address parameter must be compatible with Visa specifications:

for Gpib: GPIB[board]::address::INSTR for USB: USB[board]::manufacturer ID::model code::serial number::INSTR for TCPIP: TCPIP[board]::IP address[::LAN device name]::INSTR

This class also adds a method to set Visa attributes:

C# : 

public uint SetAttribute(int attribute, int attrState)

VB :

​​​​​​​Public Function SetAttribute(ByVal attribute As Integer, ByVal attrState As Integer) As UInteger
class SerialDevice

Although Visa can be used to access serial ports it was simple (also more useful and efficient since it does not need Visa resources) to write an implementation using the standard SerialPort class provided in NET.



public SerialDevice(string name, string addr) public SerialDevice(string name, string addr, string termstr, int buffersize)


Sub New(ByVal name As String, ByVal addr As String) Sub New(ByVal name As String, ByVal addr As String, ByVal termstr As String, ByVal buffersize As Integer)


termstr is the “NewLine” string

address format:

COMport,baud,parity,databits,stopbits [,newline]

newline can be CR (“\r”),LF (“\n”) or CRLF (“\r\n”) (for other values use the second version of the constructor).

for example:


if “termstr” is defined in the constructor then it will take over the value set in “addr”.

There is no polling feature for serial port, therefore for this interface 1) the polling function sets the MAV status to true as soon as the input buffer is not empty; 2) then the serial port timeout is set to very short value (few ms) so that blocking commands do not freeze GUI when waiting for a line to be completed (here reading will almost always be repeated more than once).

Like for the classes GPIBDevice_NINET and VisaDevice, the current version uses an asynchronous signalling, here the property controlling it is EnableDataReceivedEvent and is set to true by default in the class constructor.   When enabled it defines a handler for the SerialPort class’ DataReceivedEvent, the handler calls WakeUp() to immediately interrupt any waiting delay.  Unlike for GPIB, each serial port has its own handler so there is no problem of making lots of unnecessary polls.  With this feature the SerialDevice class  will provide response as soon as it is completed, in both blocking and asynchronous queries.   In principle there is no reason to disable this default behavior – maybe except when you are expecting an extremely long response and want to avoid events to be fired every few incoming characters or so.  

Note that unlike protocols like GPIB, USBTMC or LXI which are defined to make things uniform, the serial port does not have any standard protocol for messages therefore it is not possible to make a plug-and-play class (like Visa) working for all serial connections.  The SerialDevice class implements a simple line-oriented protocol: each message terminates with the same special end-of-line character and there is one response message for each query. However if your device uses something different  –  such as either fixed length messages with no termination or some weird syntax where both terminated and fixed-length messages are mixed or where responses can use different termination characters depending on command – then it will be necessary to make a derived class overriding the method “ReadByteArray”.  In case this method needs to know what the command sent was to determine how to format the response, I have added a property currentactivequery which may be used to access this information.

Writing implementations

It is easy to create derived classes to create new implementations or to tweak existing implementations for a specific configuration. There are four abstract methods to define (override):


protected abstract int ClearDevice(ref int errcode, ref string errmsg);
protected abstract int Send(string cmd, ref int errcode, ref string errmsg);
protected abstract int PollMAV(ref bool mav, ref byte statusbyte, ref int errcode, ref string errmsg);
protected abstract int ReceiveByteArray(ref byte[] arr, ref bool EOI, ref int errcode, ref string errmsg); protected abstract void DisposeDevice();


Protected MustOverride Function ClearDevice(ByRef errcode As Integer, ByRef errmsg As String) As Integer
Protected MustOverride Function Send(ByVal cmd As String, ByRef errcode As Integer, ByRef errmsg As String) As Integer
Protected MustOverride Function PollMAV(ByRef mav As Boolean, ByRef statusbyte As Byte, ByRef errcode As Integer, ByRef errmsg As String) As Integer Protected MustOverride Function ReceiveByteArray(ByRef arr As Byte(), ByRef EOI As Boolean, ByRef errcode As Integer, ByRef errmsg As String) As Integer Protected MustOverride Sub DisposeDevice()

See explanations in the code of IODevice class under the comment “interface abstract methods that have to be defined” for the meaning of parameters and return values.

Example: if your device does not comply with the 488.2 standard on the meaning of th status byte bits, you may need to re-interpret this byte to get the “message available” status, something like this:

protected override int PollMAV(ref bool mav, ref byte statusbyte, ref int errcode, ref string errmsg){ int pollresult = base.PollMAV(ref mav, ref statusbyte, ref errcode, ref errmsg); if (pollresult == 0) { mav = (statusbyte & newmask)!=0;} return pollresult; }

Some technical details: query sequence and lock levels

There are three lock levels: bus, device, queue.

The query sequence is the same for blocking/async commands, the only difference is that 1) they are executed on different threads 2) for blocking commands executed on the main thread, processing application events is allowed in waiting loops to not to freeze the GUI (this may be disabled setting “eventsallowed” to false)

In order to prevent deadlocks, all locks are released at the time the user callback is invoked (therefore in principle there are no restrictions on what you can do inside a callback function).

Note that locking the GPIB bus is not necessary for modern Gpib libraries that are said to be thread-safe (e.g. NI NET and Visa library, I did not check for the others). However there might be an advantage of adding this additional lock level: here both bus and device locking is made via “Monitor.TryEnter” repeated in a loop, this to prevent freezing GUI in blocking calls when the bus or device is not available immediately.

In order for the bus locking to be flexible, the respective lock object is selected from an array whose index is in the variable

protected int interfacelockid;

This variable has to be set by the child class constructor. The idea is that if two different interfaces that the can be used concurently are present they will use different lock objects so that locking will not degrade performance. In this code “interfacelockid” is set to 0 for NI-NET/NIVisa, 1 for ADLink and 2 for gpib488. If interfacelockid is set to a negative value then no bus locking is performed. This is the case for the serial port implementation (there is no common bus or common driver and the “SerialPort” .NET class is thread-safe).

Query Sequence

lock device

check if a minimum delay (“delayop”) elapsed since last operation, otherwise wait until the condition is true (delay cannot be interrupted)

send command (bus locked during send)

wait a delay “delayread” (can be interrupted from another thread calling WakeUp() )

if send ok and response expected:

  • if polling enabled: poll status byte periodically until MAV bit is set, quit if timeout or abort (waiting between subsequent polling trials can be interrupted by any other thread calling WakeUp() )
  • try to read periodically (bus locked during each read) , quit if “readtimeout” elapsed or abort (waiting between subsequent reading trials can be interrupted by any other thread calling WakeUp() )
  • if “checkEOI” enabled: repeat reading until EOI set, appending new data to receive buffer  

if any gpib function returned error: clear device, if “showmessages” flag set then show message

unlock device

for async threads, if user callback function defined: send message to GUI thread to call it (if “cbwait” flag set then wait until the callback returns)

  • if error and retry enabled: wait a delay “delayretry” (cannot be interrupted), then repeat the whole query process unless aborted by user

Here the bus is locked during each I/O operation but not between write and read, however the device is locked during the whole query. Therefore when a thread is waiting for response from a device other async threads can send commands to their respective devices. Interleaving is obtained automatically.

Also, for the same reason interleaving is possible for blocking calls if “eventsallowed” field is set (default): this flag enables processing application events during delay and wait loops in blocking calls: if there are blocking calls in timers then timer events can be processed between write and read (this is disabled if “eventsallowed” is set to false, then however the GUI may freeze during blocking calls!).

It must be precised here that the device lock, which is based on interthread synchronisation, does not prevent a simultaneous querying of the same device by different processes (e.g. if a C# application runs in parallel with a Labview program). For such situations some GPIB drivers provide their own lock instructions ensuring an interprocess synchronization, these however are not used in the current version.


see “test” projects included


1st version submitted on Jan 26, 2017

13 Feb 2017 :

  • C# version: corrected an address format problem in VisaDevice.cs (by mistake I had included a version which tried to format it for GPIB like the other classes, now the formatting has been removed so it will work correctly for all Visa address formats like USB etc.)
  • minor updates in other files

10 Apr 2017 :

  • added a possibility of asynchronous callback from the driver, for time-critical applications.  See section Asynchronous interface callbacks, the query sequence and the description of modified implementations: GPIBDevice_NINET , SerialDevice

  • DevicesForm update method improved
  • minor modification in GPIBDevice_ADLink, see class description
  • minor updates in test forms

24 May 2017

  • asynchronous callbacks on device service request are now implemented for VisaDevice, see class description for details 
  • class GPIBDevice_NINET :  driver configuration modified (autopolling disabled) so that asynchronous callbacks are more reliable (see class description)
  • minor updates in the main class IODevices (slight changes in error messages; rearming for asynchronous callback when command sent;  in C# version some parameters changed from “ref” to “out”)  
  • test forms adapted to changes

contact: pawel.wzietek (at)