External Code in LabVIEW, Part2: Comparison between shared libraries and CINs

This is an old post that has appeared on expressionflow.com on May 19, 2007

Not looking at ActiveX and .Net, since they both are Windows technologies only and are not really suited to integrate generic external code into LabVIEW, there remain two similar possibilities nowadays to integrate external code into LabVIEW. These are CINs and shared libraries through use of the Call Library Node. While CINs used to have certain advantages over shared libraries in the past, this is since about LabVIEW 6 basically not true anymore and with LabVIEW 8.20 the Call Library Node got additional features that remove the last (esoteric) advantages CINs had over shared libraries. On the other hand however have shared libraries quite a few advantages.

In this article I will try to compare the most important differences between these two technologies and try to make a point, why anyone should nowadays go for the Call Library Node with shared libraries, instead of using CINs.

Let’s start with two features where CINs still had a slight advantage over shared libraries until the advent of LabVIEW 8.20. The previously mentioned “callback” functions introduced in LabVIEW 8.20 actually make the Call Library Node go on par with CINs in these aspects. I will investigate into the specifics of those callback functions in a later article in this series.

Advantages of CINs before LabVIEW 8.20

Instance specific data storage

One advantage of CINs used to be the possibility of instance specific global data storage. If you don’t know what this could be and why you could use it, you most probably never will need it and you can skip this section.

Using the functions GetDSStorage() and SetDSStorage() one can use a single 4 byte location which LabVIEW manages on a per instance base. This means that unlike a global variable declared outside any function body in the CIN code, each instance of a CIN in a diagram using that particular CIN code resource gets its own 4 byte value, managed by LabVIEW. You could see this similar to an uninitialized shift register in a LabVIEW VI, which has been set to be reentrant. While this feature could be powerful in some esoteric situations it is almost never used and its understanding is made even more complex by the fact that such a CIN located in a reentrant VI itself, will actually maintain multiple instance specific data storages for each instance of that reentrant VI multiplied by the number of those CIN code resources located inside that VI. Once you continue this acrobatic brain exercise to include multiple levels of reentrant VIs, everything gets very soon very complicated for anybody not used to think in parallel realities in n-dimensional systems.

More detailed control over initializing and unintializing of the code resource

While shared libraries only support initializing and unintializing of code on loading and unloading of the library through OS provided mechanisms such as DLLMain() in Windows or init() and fini()  see footnote 1) under ELF shared libraries, LabVIEW CINs separate these actions into load, init, uninit, and unload together with a save operation. The load and unload is called once for each CIN code resource and could be used to initialize and deallocate global data while the init and uninit is called once for each code resource instance and can be used to initialize and deallocate above mentioned instance specific data storage. The save routine doesn’t really make much sense for someone outside of NI as one needs to know about some undocumented parts in LabVIEW to properly make use of this.

Both these features are very rarely used and can be achieved through other means such as maintaining a pointer in the calling VI inside a shift register and passing this pointer to the function on every call, or by using the platform specific shared library initialization and deinitialization methods in a smart way.

But shared libraries have a number of advantages, which should make it easy for anybody to choose for them instead of trying to dig into the proprietary CIN technology.

Advantages of the use of the Call Library Node over CINs

Parameter type support

With the new Adapt to Type parameter configuration since LabVIEW 5.1, a Call Library Node can pass any LabVIEW data type directly to a shared library without any translation or fiddling around. In this aspect the parameter support is now superior to CINs, since the Call Library Function supports some additional configuration options as well, such as passing LabVIEW handles by reference whereas a CIN will always pass them by value. And the Call Library Node supports also other data types such as C string pointers or C arrays among others and LabVIEW will take care to pass the correct part of its own data type to the shared library so that the library can work on them with the standard C runtime routines. While it is not necessary to use these types for your own libraries, it is an additional bonus which can sometimes be very handy.

– Accessing most shared libraries directly

With CINs you always have to write some intermediate C code to access already existing shared libraries. The Call Library Node can however interface with many shared libraries directly, making the creation of an intermediate wrapper DLL in many cases an optional step. This does not always work that way, since LabVIEW arrays and strings are really dynamically resizeable while most standard C datatypes do not allow for easy dynamic resizable pointers. In a later article I will go into more depths about when you would need to create a wrapper for use with the Call Library Node, but in general many shared libraries can be interfaced directly without an intermediate wrapper shared library.

Multiplattform support

With the current LabVIEW platforms, there is no reason, why maintaining a shared library source code for multiple platforms would be more difficult, than doing the same for a CIN. On the other side if you take care to follow some recommendations in respect to calling and naming convention, mentioned in the next article, there is no reason why you would need one set of VIs for each platform you want to support. For CINs this is mandatory and can only be sort of circumvented with a technique invented by Christophe Salzmann, which he called FatCIN. With this technique you create a VI for each platform you want to support and then you create another wrapper VI, which uses dynamic calls through VI server to load and execute the appropriate VI for the current platform. But this is a rather cumbersome technique, as it will still require one VI for each platform for every CIN and an additional wrapper VI. Also the maintenance of the CIN VI itself will be cumbersome too, since whenever you make a change to the CIN code no matter how small it is, you have to manually touch each platform VI to reload the new code resource.

Multifunction support

If you happen to have a number of functions to support, you can implement them all in a single shared library and create one VI for each function in that shared library. If you are using CINs instead, you either have to incorporate all the functions into a single CIN and create a CIN which takes a selector parameter for the function to execute. This VI also will require a more or less involved parameter list, to support the most extended list of parameters of all the functions. Or you can create a CIN for each of those functions and wonder why a single change to some common parameter type will have you compile each of the source codes separately and then require you to go into each CIN (and in case of multiplatform support into each of those platform VIs too) and reload the code resource into the CIN. In the case of separate CINs for each function you also don’t have any means to store some global information inside the CIN to allow sharing that information among multiple functions.

For shared libraries you almost have to create a VI for each shared library function. But even when using the single CIN approach you still will also usually want to create one additional wrapper VI for every function selector value. This is because direct use of the single selector based CIN VI is not very user friendly. Imagine the many VI parameters on such a selector based CIN that will not have any effect for most of the selected functions.

Compiler tools support

Shared libraries can be created in virtually any development environment which is able to create them. There is no need to use a specific C compiler unless you want to link with the labview.lib library to make use of the LabVIEW manager functions documented in the External Code Reference Manual. This limitation is because object libraries can come in a number of file formats and each compiler has its own preferred object file format and often no support for object file formats from other compilers. LabVIEW for Windows 32-bit comes with a labview.lib file in COFF format, which is the format used by Microsoft Visual C and a labview.sym.lib file in the OMF format used by the now discontinued Symantec C compiler. Borland C also uses some form of OMF format, but it is not clear to me if the Symantec C provided library is compatible with any version of Borland C, because object file formats can and will certainly come in various flavors.

As long as you do not want to link to labview.lib however, there is really no limitation in what development environment you use to create a shared library, provided this development environment can create shared libraries usable by other development environments such as Visual Basic, Delphi or similar.

CINs on the other hand always will have to link to at least cin(.sym).lib and lvsb(.sym).lib and in most cases labview(.sym).lib too and therefore will always be limited to the LabVIEW supported compiler tool chains.

In fact Open source tool chains such as the GNU C Compiler can be used on all current platforms to create shared libraries callable by the Call Library Node, although for Windows I would recommend the use of some customized GNU tool chain such as the MingW compiler tool chain http://www.mingw.org, possibly in connection with a development environment such as Dev C++  http://www.bloodshed.net, or Code::Blocks http://www.codeblocks.org

Conclusions

CINs should be considered legacy technology and should absolutely not be used for new developments. CINs have basically not one single advantage anymore over the use of the Call Library Node but quite a few disadvantages, especially in terms of code maintenance both for the C code as well as for the LabVIEW part! This is already true since about LabVIEW 6.

To see where the future of CINs most probably will go you only have to investigate a current LabVIEW installation and try to find stock VIs, which still make use of CINs. They are virtually non-existent. National Instruments has ported all their LabVIEW interfaces such as NI-DAQ, NI-IMAQ, NI-CAN, etc and add-on tools like the Advanced Analysis library or IMAQ Vision to use shared libraries instead of CINs.

Note from March 2, 2017: Recent versions of LabVIEW have no support to create CINs anymore. While some of them can still execute CINs created for earlier versions, it’s cumbersome at best to create new CINs, as one would need to get the tools from an old LabVIEW installation to do so. And all new LabVIEW platforms such as the Windows and Mac OS X 64-bit versions, or the NI Linux Realtime versions, never received support for creating CINs at all. So CINs are definitely a legacy technology, which is only supported on a fraction of the currently available LabVIEW platforms.

  1. init() and fini() are considered obsolete in modern shared libraries. Instead the GCC attributes __attribute__((constructor)) and __attribute__((destructor)) should be used.

Leave a Reply

Your email address will not be published. Required fields are marked *