Writing Win32 Dynamic Link Libraries (DLLs) and Calling Them from LabVIEW

Publish Date: Jun 01, 2010 | 105 Ratings | 3.82 out of 5 |  PDF

Overview

LabVIEW for Windows XP and Windows Vista have a Call Library Function that you can use to access 32-bit DLLs. This document gives a brief introduction on how to create simple 32-bit DLLs and call them from LabVIEW under Windows XP and Vista. This document does not discuss details about C programming or all the different compilers you can use to create DLLs. Because a DLL uses a format that is standard among several development environments, you should be able to use almost any development environment to create a DLL that LabVIEW can call.

For introductory information on the Call Library Function in LabVIEW, refer to the related link How to Call Win32 Dynamic Link Libraries (DLLs) from LabVIEW. Currently LabVIEW does not have support for calling a 64 bit DLL as is explained in the related link Can You Call A 64 bit DLL from LabVIEW.

Table of Contents

  1. The Anatomy of a DLL
  2. Outline of the Source Code of a DLL
  3. Example - Writing a DLL with Microsoft Visual C++
  4. Advanced Topic Array and String Options
  5. Troubleshooting the Call Library Function and Your DLL
  6. Important Reminders and Quick Reference

1. The Anatomy of a DLL

Dynamic linking is a mechanism that links applications to libraries at run time. The libraries remain in their own files and are not copied into the executable files of the applications. DLLs link to an application when the application is executed, rather than when it is created. DLLs may also contain links to other DLLs.

Note Many times, DLLs are placed in files with different extensions such as .EXE, .DRV or .DLL.

Applications and DLLs can link to other DLLs automatically if the DLL linkage is specified in the IMPORTS section of the module definition file as part of the compile or you can explicitly load them using the Windows LoadLibrary function.

Back to Top

2. Outline of the Source Code of a DLL

The following example code illustrates the basic structure of a DLL:

BOOL WINAPI DllMain(HINSTANCE hinstDLL,
DWORD     fdwReason,
LPVOID    lpvReserved)
{
    switch (fdwReason)
    {
        case DLL_PROCESS_ATTACH:
            /* Init Code here */
            break;

        case DLL_THREAD_ATTACH:
            /* Thread-specific init code here */
            break;

        case DLL_THREAD_DETACH:
            /* Thread-specific cleanup code here.
            */
            break;

       case DLL_PROCESS_DETACH:
            /* Cleanup code here */
            break;
    }
    /* The return value is used for successful DLL_PROCESS_ATTACH */
        return TRUE;

}
/* One or more functions */
__declspec (dllexport) DWORD Function1(.... ){}
__declspec (dllexport) DWORD Function2(.... ){}

As shown in the example code, The DllMain function is called when a DLL is loaded or unloaded.  The DllMain function is also called when a new thread is being created in a process already attached to the DLL, or when a thread has exited cleanly. Finally, a DLL also contains functions that perform the activities the DLL expects to accomplish. These functions are exported by using the DllEntryPoint function.  The _declspec (dllexport) keyword is a Microsoft-specific extension to the C or C++ language. Alternatively, the EXPORTS section in module definition files can be used to export functions. For more information about module definition files, please refer to the next section, Example - Writing a DLL in Microsoft Visual C++.

Back to Top

3. Example - Writing a DLL with Microsoft Visual C++

In some cases, you may want to write your own DLL, for example, to communicate with your own custom-built hardware. In this section, you will find sample code that illustrates how to create a simple DLL.

To create a DLL, you will need the following four files:

  • A C Language source file (required)
  • A custom header file (optional – may be part of the source code)
  • A module definition file (may be required if using _stdcall calling convention – or functions can be exported by using the keyword _declspec (dllexport)
  • A make file, or set compiler options to generate a DLL (required – some development environments create and execute the make file)

C Language Source File
The following code is the C language source file for the DLL we will create in this example. It uses the C calling convention. Also, in this case functions are exported by using the _declspec (dllexport) keyword. Declaring the dllexport keyword eliminates the need for a module definition file. However if you would like to use the standard calling convention using the _stdcall keyword, you may have to export the functions in your dll using the module definition file. This may have to be done because _stdcall mangles the names of the functions. If you can find the mangled name, you can still call the function using that mangled name.

The example DLL defines three simple functions:

  • add_num adds two integers
  • avg_num finds the simple average of an array of numeric data
  • numIntegers counts the number of integers in a string

In this example, we will create a VI that calls only one of these functions. As a further exercise on your own, create VIs that use the Call Library Function to use the other functions in the DLL.

After the listing of the source code for the DLL, you will also find listing for the custom header file, ourdll.h.

/* OurDLL.c source code */

#include "stdafx.h"
#include <windows.h>
#include <string.h>
#include <ctype.h>
#include "ourdll.h"

BOOL WINAPI  DllMain (
            HANDLE    hModule,
            DWORD     dwFunction,
            LPVOID    lpNot)
{
    return TRUE;
}

/* Add two integers */
_declspec (dllexport) long add_num(long a, long b){
    return((long)(a+b));}

/* This function finds the average of an array of single precision numbers */
_declspec (dllexport)  long  avg_num(float  *a, long size, float  *avg)
{
    float sum=0;

    if(a != NULL)
    {
        for(int i=0;i < size; i++)
        sum = sum + a[i];
    }
    else
        return (1);
    *avg = sum / size;
    return (0);
}

// Counts the number of integer numbers appearing in a string. */
// Note that this function does not check for sign, decimal, or exponent
_declspec (dllexport) unsigned int numIntegers (char *inputString) {

    int      lastDigit = 0;
    int      numberOfNumbers = 0;
    int      stringSize;

    stringSize = strlen(inputString);
    for(int i = 0; i < stringSize; i++)
    {
        if (!lastDigit && isdigit(inputString[i]))
            numberOfNumbers++;
        lastDigit = isdigit(inputString[i]);
    }
    return numberOfNumbers;
}

Note: LabVIEW can call DLLs that use the stdcall calling convention as well as DLLs that use C calling conventions

The Header File
The following code lists the header file OurDLL.h. All of the functions we have created in the source code are available to other applications because they were exported using the _declspec (dllexport) keyword.

BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD,LPVOID);
_declspec (dllexport) long  add_num(long, long);
_declspec (dllexport) long  avg_num(float *, long, float *);
_declspec (dllexport) unsigned int  numIntegers (unsigned char *);

The Module Definition File
A DLL can have a module definition file(.def) associated with it. The .def file contains the statements for defining a DLL. e.g name of the DLL and the functions it exports. The LINK option in Project Settings of the Visual C++ compiler provides equivalent command-line options for most module-definition statements, and hence a typical program for Win32 does not usually require a .def file. The only mandatory entries in the .def files are the LIBRARY statement and the EXPORT statement. The LIBRARY statement must be the first statement in the file. The name specified in the LIBRARY statement identifies the library in the import library of the DLL. The EXPORTS statement lists the names of the functions exported by the DLL.

If you were using the _stdcall calling convention in your DLL, then you would have to use the module definition file to export the functions that the DLL exposes, as shown below. This has to be done because the compiler mangles the function names. Following is the listing of the module definition file.

LIBRARY OurDLL
EXPORTS
      avg_num
      add_num
      num_Integers

Building the Project in Microsoft Visual C++ 2005
Most Windows compilers offer an integrated development environment, and it is possible to choose the options for compiling a project as a DLL. First, you will need to create the project. The steps to create the example DLL are being described for the Microsoft Visual C++ 2005 compiler. From the Start menu, choose Programs->Microsoft Visual Studio 2005->Microsoft Visual Studio 2005. This launches Microsoft Visual C++ 2005. Next create a new project by selecting New->Project… from the File menu. Highlight Visual C++ under Project Types: and Win32 Project under Templates.  Name the project OurDLL and click on Ok.  Click Next on the Win32 Application Wizard, select DLL under Application Type and then click Finish. Replace the template code with the code listed above and then add a file to the Header Files folder and name it OurDLL.h.  Copy the header code into the header file and save the project.  To compile the code into a DLL, select Build Solution from the from the Build menu. (If you chose a different project name than OurDLL, then that name will appear in the Project menu.)

Calling the DLL
This simple DLL can now be called by using the LabVIEW Call Library Function node. We will create a VI called ArrayAvg.VI that calls the avg_num function in OurDLL.dll. Place a Call Library Function icon on the block diagram and configure it to call the avg_num function as shown in Figure 1.


a. Call Library Function Node – Function Tab


b. Call Library Function Node – Parameters Tab

Figure 1. Configuration for the avg_num Function in OurDLL.dll


As specified in the previous diagram, first specify the location of the DLL by typing in the path name or browsing to the dll. You can then find the name of the function in the Function Name dropdown menu.  In this case it is avg_num. The calling convention for this function in the DLL is C.

The return type is Signed 32-bit Integer. The parameters to the function are an Array Data Pointer to 4-byte Single precision floating point numbers, a 32-bit Signed Integer which contains the size of the array, and a pointer to a 4-byte Single precision floating point value, which will return the average of the elements in the array.

Create the front panel shown in Figure 2.a. Then, connect the appropriate controls and indicators to the Call Library Function icon. Figure 2.b shows the appropriate block diagram connections.


a. Front Panel


b. Block Diagram

Figure 2. ArrayAvg.VI


After making the appropriate connections, run the VI. Congratulations! You’ve written, compiled, and linked to your own DLL and called a function within it from LabVIEW. If your DLL returns incorrect results or crashes, verify the datatypes and wiring to see if you miswired the wrong type of information.

Back to Top

4. Advanced Topic Array and String Options

This section of the document reviews some important concepts you should be familiar with when using the Call Library Function to work with array and string data. Operations on arrays and strings of data make use of pointers, so this information will be useful in helping you implement the Call Library Function.

Arrays of Numeric Data
Arrays of numeric data can be of any integer type, or single (4-byte) or double (8-byte) precision floating point numbers. When you pass an array of data to a DLL function, you will see that you have the option to pass the data as an Array Data Pointer or a LabVIEW Array Handle.

When you pass an Array Data Pointer, you can also set the number of dimensions in the array, but you do not include information about the size of the array dimension(s). DLL functions either assume the data is of a specific size or expect the size to be passed as a separate input. Also, because the array pointer refers to LabVIEW data, never resize the array within the DLL. Doing this may cause your computer to crash. If you must return an array of data, allocate an array of sufficient size in LabVIEW, pass it to your function, and have it act as the buffer. If the data takes less space, you can return the correct size as a separate parameter and then on the calling diagram use array subset to extract the valid data. Alternatively, if you pass the array data as a LabVIEW Array Handle then you can use the LabVIEW CIN functions to resize the array within the DLL. In order to call LabVIEW CIN functions from your DLL while using the Visual C++ compiler you will have to include the library, labview.lib found in LabVIEW 8.5/cintools/Win32 directory. Also, if you would like to access these CIN functions using the Symantec compiler you will need to link to the library labview.sym.lib found in LabVIEW 8.5/cintools/win32 directory.

String Data
LabVIEW stores strings in a format that is different from C strings. The Call Library Function works with LabVIEW String Handles or C style string pointers. The difference between these formats is explained in the following paragraphs.

You can think of a string as an array of characters; assembling the characters in order forms a string. LabVIEW stores a string in a special format in which the first four bytes of the array of characters form a signed 32-bit integer that stores how many characters appear in the string. Thus, a string with n characters will require n + 4 bytes to store in memory. For example, the string text contains four characters. When LabVIEW stores the string, the first four bytes contain the value 4 as a signed 32-bit number, and each of the following four bytes contains a character of the string. The advantage of this type of string storage is that NULL characters are allowed in the string. Strings are virtually unlimited in length (up to 231 characters). This method of string storage is illustrated in Figure 3. If you pass a LabVIEW String Handle from the Call Library function to the DLL, then you can use the LabVIEW CIN functions like DSSetHandleSize to resize the LabVIEW String Handle. Also, you will have to add labview.lib to your project if you are using Visual C++ and labview.sym.lib if you are using the Symantec compiler from the LabVIEW 8.5/cintools/Win32 directory while building your DLL.


Figure 3. The LabVIEW String Format


C strings are probably the type of strings you will deal with most commonly. The similarities between the C-style string and normal numeric arrays in C becomes much more clear when one observes that C strings are declared as char *, where char is typically an unsigned byte. C strings do not contain any information that directly gives the length of the string, as the LabVIEW strings does. Instead, C strings use a special character, called the NULL character, to indicate the end of the string. NULL is defined to have a value of zero in the ASCII character set. Note that this is the number zero and not the character “0”. Thus, in C, a string containing n characters requires n + 1 bytes of memory to store: n bytes for the characters in the string, and one additional byte for the NULL termination character. The advantage of C-style strings is that they are limited in size only by available memory. However, if you are acquiring data from an instrument that returns numeric data as a binary string, as is common with serial or GPIB instruments, values of zero in the string are possible. For binary data where NULLs may be present, you should probably use an array of unsigned 8-bit integers. If you treat the string as a C-style string, your program will incorrectly assume that the end of the string has been reached, when in fact your instrument is returning a numeric value of zero. An illustration of how a C-style string is stored in memory is given in Figure 5.


Figure 5. The C String Format

When you pass string data to a DLL, you must follow the same guidelines as for arrays. Specifically, never resize a string, concatenate a string, or perform operations that may increase the length of string data passed from LabVIEW if you are using the C string pointers. If you must return data as a string, you should first allocate a string of the appropriate length in LabVIEW, and pass this string into the DLL to act as a buffer. But, if you pass a LabVIEW String Handle from the Call Library function to the DLL, then you can use the LabVIEW CIN functions like DSSetHandleSize to resize the LabVIEW string handle.

Note: To use the LabVIEW CIN function calls you must add labview.lib to your project if you are using the Visual C++ compiler, or add labview.sym.lib to your project if you are using the Symantec compiler.

Array and String Tips
If your DLL function must create an array, change its size, or resize a string of data without using LabVIEW handles, then break the function into two steps. In the first step, determine the number of elements needed in the array, or the length of the string to be returned. Have this function return the desired size to LabVIEW. In LabVIEW, initialize an array or string with default values, and pass this array to a second function in your DLL, which actually places the data into the array. If you are working with string-based instrument control, it may be easier to pass an array of 8-bit integers than C strings because of the possibility of NULL values in the string. Alternatively, if you are using the Visual C++ compiler or the Symantec compiler and pass a LabVIEW Array Handle or LabVIEW String Handle from the Call Library node to your DLL, then you can use the LabVIEW CIN functions to resize or create an array or string.

Back to Top

5. Troubleshooting the Call Library Function and Your DLL

If, after configuring the Call Library Function dialog, you still have a broken Run arrow in LabVIEW, check to make sure that the path to the DLL file is correct. If LabVIEW gives you the error message function not found in library, double-check the spelling of the name of the function you wish to call. Remember that function names are case sensitive. Also, recall that you need to declare the function with the _declspec (dllexport) keyword in the header file and the source code or define it in the exports section of the module definition file. Even if you have used the _declspec (dllexport) keyword and are using the _stdcall calling convention, then you have to declare the DLL function name in the EXPORTS section of the module definition file. If this is not done, the function will be exported with the mangled name, and the actual function name will be unavailable to applications that call the DLL. If the function has not been properly exported, you will need to recompile the DLL. Before recompiling, make sure to close all applications and VIs that may make use of the DLL. If the DLL is still in memory, the recompile will fail. Most compilers will warn you if the DLL is in use by an application.

If you’ve already double-checked the name of the function, and have properly exported the function, find out whether you have used the C or C++ compiler on the code. If you have used the C++ compiler, the names of the functions in the DLL have been altered by a process called “name mangling”. The easiest way to correct this is to enclose the declarations of the functions you wish to export in your header file with the extern “C” statement:

extern “C”
{
    /* your function prototypes here */
}

After properly configuring the Call Library Function, run the VI. If it does not run successfully, you might get errors or a General Protection Fault. If you get a General Protection Fault, there are several possible causes. First, make sure that you are passing exactly the parameters that the function in the DLL expects. For example, make sure that you are passing an int16 and not an int32 when the function expects int16. Also confirm that you are using the correct calling convention _stdcall or C.

Another troubleshooting option is to try to debug your DLL by using the source level debugger provided with your compiler. In Microsoft Visual C++ 2005, you can set in the Build»Settings»Debug section, Executable for Debug session as labview.exe to debug your DLL. See Using Visual Studio to Debug an Assembly in LabVIEW for detailed instructions about how to troubleshoot an assembly.  For even more information on debugging, please refer to the appropriate manual for your compiler.

Calling the DLL from another C program is also an excellent way to debug your DLL. By doing this, you have a means of testing your DLL independent of LabVIEW, thus helping you identify possible problems sooner.

Back to Top

6. Important Reminders and Quick Reference

Keep in mind the following tips when writing your DLL:

  • Make sure you use the proper calling convention (C or stdcall).
  • Know the correct order of the arguments passed to the function.
  • NEVER resize arrays or concatenate strings using the arguments passed directly to a function. Remember, the parameters you pass are LabVIEW data. Changing array or string sizes may result in a crash by overwriting other data stored in LabVIEW memory. You MAY resize arrays or concatenate strings if you pass a LabVIEW Array Handle or LabVIEW String Handle and are using the Visual C++ compiler or Symantec compiler to compile your DLL.
  • When passing strings to a function, remember to select the correct type of string to pass – C or LabVIEW string Handle.
  • Remember, C strings are NULL terminated. If your DLL function returns numeric data in a binary string format (for example, via GPIB or the serial port), it may return NULL values as part of the data string. In such cases, passing arrays of short (8-bit) integers is most reliable.
  • If you are working with arrays or strings of data, ALWAYS pass a buffer or array that is large enough to hold any results placed in the buffer by the function unless you are passing them as LabVIEW handles, in which case you can resize them using CIN functions under Visual C++ or Symantec compiler.
  • Remember to list DLL functions in the EXPORTS section of the module definition file if you are using _stdcall.
  • Remember to list DLL functions that other applications call in the module definition file EXPORTS section or to include the _declspec (dllexport) keyword in the function declaration.
  • If you use a C++ compiler, remember to export functions with the extern “C”{} statement in your header file in order to prevent name mangling.
  • If you are writing your own DLL, you should not recompile a DLL while the DLL is loaded into memory by another application (for example, your VI). Before recompiling a DLL, make sure that all applications making use of the DLL are unloaded from memory. This ensures that the DLL itself is not loaded into memory. You may fail to rebuild correctly if you forget this and your compiler does not warn you.
  • Test your DLLs with another program to ensure that the function (and the DLL) behave correctly. Testing it with the debugger of your compiler or a simple C program in which you can call a function in a DLL will help you identify whether possible difficulties are inherent to the DLL or LabVIEW related.

 

Back to Top

Bookmark & Share


Ratings

Rate this document

Answered Your Question?
Yes No

Submit