DLL/shared library

From LabVIEW Wiki
Jump to: navigation, search

General

The first question would be if you really need to do this. While it is a powerful tool it is also tedious work to have to interface to external code through DLLs/shared libraries.

For the rest of this text I will refer to shared libraries but this should be always read to mean DLLs too, which is the term Windows uses for shared libraries.

With the Import Library Wizard in LabVIEW 8.2 a lot of the actual work of interfacing has been automated but this is just an automatic tool and it can not do everything for you. Especially for more complex shared library interfaces it simply has to fail for particular functions and those are also the ones that are likely to take up hours and hours of fiddling, testing and learning about how a C compiler actually puts variables into memory.

The shared library interface in all OSes so far was designed for and around what C needs and can provide. Therefore one needs to know some basic principles about C and for more complex interfaces also relatively intimate knowledge about how a C compiler actually puts up things in memory and such. This is a very steep learning curve for someone not already familiar with C.

An extra difficulty is that the shared library interface has none of the more modern features like automatic type checking and parameter validation like what ActiveX and .Net provide for instance. This means that one single error in the configuration of the Call Library Node or a bug in the shared library itself can and usually will have fatal consequences sooner or later. This can in the best case show as a Protection fault error dialog at runtime of that function, but can also cause so subtle damage that LabVIEW will only crash at some later point when trying to access information that was destroyed earlier on. One possibility is for instance that it will often crash when you try to close LabVIEW since it tries to deallocate resources that have been destroyed by a misbehaving shared library or configuration thereof earlier on.


C++

Calling C++ classes from LabVIEW can sometimes be useful. For instance, when you would like to use a special mathematical/scientific library that is written in C++. However, since LabVIEW only can call DLLs written in plain C, a wrapper must be made that "translates" the C++ classes to ordinary C function calls. This means that you have to write some C code that LabVIEW understands, to indirectly call the C++ classes that LabVIEW doesn't understand. Since calling C++ classes from C is a rather odd thing to do, there is almost no references to this on the internet (you would rather normally use C++ to call C++ classes),.

Fortunately the wrappers are strightforward to code. There is no need to touch any of the C++ code either, by making the C++ classes opaque. What you end up with is objects that behave exactly like GOOP objects, only they are made in C++.

The wrappers must be compiled with a C++ compiler, since an ordinary C compiler will not do the trick making the classes opaque. In C++ a struct IS a class, so making the classes opaque is simply a matter of typedef'ing the class to a struct with the same name. Making the function calls C calls instead of C++ calls is done by the extern "C" directive.

Below is a very simple example  that shows the principle. It can also be downloaded with source and a LabVIEW vi here: forums.lavag.org/C-classes-from-LV-t12905.html. In the real world, the most difficult part may be to "translate" LabVIEW types to C types and/or the other way around, as is the main problem with ordinary DLLs. Any such translation must also be handled by the wrapper and/or the vi.


The header file for the C++ class base_class.h and the classes themselves in base_class.cpp:

#ifndef _BASE_CLASS_H_
#define _BASE_CLASS_H_

class base
{
   public:
      base(double x_in);
      virtual ~base(void);
      void set_x(double x_in);
      void get_x(double* x_out);
      void sqr_x(void);

   private:
      double x;
};

#endif /* _BASE_CLASS_H_ */

The base_class.cpp (the classes)

#include "base_class.h"
#include <windows.h>

base::base(double x_in)
   {
      x = x_in;
   }

base::~base ()
   {
      /* clean up */
   }

void base::set_x(double x_in)
   {
      x = x_in;
   }

void base::get_x(double* x_out)
   {
      *x_out = x;
   }

void base::sqr_x(void)
   {
      x*=x;
   }

The header file for the DLL wrapper, c_dll_wrapper.h

#ifndef _C_DLL_WRAPPER_H_
#define _C_DLL_WRAPPER_H_

/* building a DLL */
#define DLLIMPORT __declspec (dllexport)

#ifdef __cplusplus
   extern "C" { /* using a C++ compiler */
#endif

   typedef struct base base; /* make the class opaque to the wrapper */

   DLLIMPORT base* create_base(double x_in);
   DLLIMPORT void set_x(base* LV_ref, double x_in);
   DLLIMPORT void get_x(base* LV_ref, double* x_out);
   DLLIMPORT void destroy_base(base* LV_ref);
   DLLIMPORT void sqr_x(base* LV_ref);

#ifdef __cplusplus
   }
#endif


#endif /* _C_DLL_WRAPPER_H_ */

And the wrapper itself, c_dll_wrapper.cpp

#include "c_dll_wrapper.h"
#include "base_class.h"
#include <windows.h>

   DLLIMPORT base* create_base(double x_in)
   {
      return new base(x_in);
   }

   DLLIMPORT void set_x(base* LV_ref, double x_in)
   {
      LV_ref->set_x(x_in);
   }

   DLLIMPORT void get_x(base* LV_ref, double* x_out)
   {
      LV_ref->get_x(x_out);
   }

   DLLIMPORT void destroy_base(base* LV_ref)
   {
      delete LV_ref;
   }

   DLLIMPORT void sqr_x(base* LV_ref)
   {
      LV_ref->sqr_x();
   }


/* The rest must be included in any DLL, and is not part of the wrapper */

BOOL APIENTRY DllMain (HINSTANCE hInst /* Library instance handle. */ ,
                       DWORD reason /* Reason this function is being called. */ ,
                       LPVOID reserved /* Not used. */ )
{
   switch (reason)
   {
      case DLL_PROCESS_ATTACH:
         break;
      case DLL_PROCESS_DETACH:
         break;
       case DLL_THREAD_ATTACH:
         break;
      case DLL_THREAD_DETACH:
         break;
   }


/* Returns TRUE on success, FALSE on failure */
   return TRUE;
}