LAST PAGE  BACK TO INDEX  NEXT PAGE

[22.0] Using & Running DLLs from VEE For Windows

[22.0] Using & Running DLLs from VEE For Windows

v5.0 / 22 of 23 / 01 sep 99 / gvg

* HP VEE on the PC can access external dynamic-link library (DLL) routines. This chapter describes how to use DLL routines with VEE and how to create DLLs for use with VEE.

The focus is on DLLs under Win95 and WinNT, written by the Microsoft dialect of C. This chapter does not pretend to be a comprehensive guide to the creation of the DLLs themselves. That is a complicated subject that is covered in commercially-available books, and it must be assumed that the reader has familiarity with such matters.

Additional comments on DLLs under Win3, Borland compilers, C++, and so on, are covered in the last section of this chapter.


[22.1] CALLING DLLS FROM VEE
[22.2] DLL PARAMETER PASSING
[22.3] BUILDING DLLS FOR VEE
[22.4] FINAL COMMENTS

 BACK TO INDEX

[22.1] CALLING DLLS FROM VEE

* Calling external DLL routines from VEE is conceptually much the same as loading and running a VEE UserLibrary. For an example, consider using a PEEKPOKE DLL from a VEE program.

Several different PEEKPOKE DLLs have been designed for VEE on different Windows environments; this example will use the syntax for one of the earlier versions, since it was one of the simplest implementations. The principles are the same for later implementations (or, for that matter, any other DLL).

This version of PEEKPOKE is implemented in two files:

The definition file defines the syntax of the library routines. In this case, it contains:


   long writeByte( long portAddr, long dataByte );
   long writeWord( long portAddr, long dataWord );
   long readByte( long portAddr );
   long readWord( long portAddr );
 
The four functions in this library should be self-explanatory. The "portAddr" parameter gives the desired port address, and the "dataByte" and "dataWord" parameters give the data to be transferred. All the parameters and the return values are defined as "long", or 32-bit integer.

The following VEE program shows how to use the PEEKPOKE library:


   +----------------+
   | Import Library | allows connection to PEEKPOKE library
   +-------+--------+
           |                   invokes "writeByte"
  +--------+---------+       +--------------------+   +-------------------+
  |     Integer      |  +--->|portAddr            |   |   Alphanumeric    |
  +------------------+  |    |      Call Function |   +-------------------+
  | 25               +--|--->|dataByte            +-->|                   |
  +------------------+  |    +---------+----------+   +-------------------+
    data to be input    |              |              displays return value
                        |              |
  +------------------+  |    +---------+----------+   +-------------------+
  |     Integer      |  |    |                    |   |   Alphanumeric    |
  +------------------+  |    |      Call Function |   +-------------------+
  | 1016             +--+--->|portAddr            +-->|                   |
  +------------------+       +--------------------+   +-------------------+
    I/O port address           invokes "readByte"     displays return value 
 
This program imports the PEEKPOKE library, then writes a byte to a port and brings it back. The three important elements in it are the "Import Library" object, and the two "Call Function" objects.

The "Import Library" object allows access to the library:


   +------------------------------------------+
   |             Import Library               |
   +------------------------------------------+
   | Library Type    [    User Function     ] |
   | Library Name    [      myLibrary       ] |
   | File Name       [       myfile         ] |
   +------------------------------------------+
 
However, in this case, we don't want a "User Function", so click on the "User Function" field, then select "Compiled Function" from the dialog that comes up:

   +------------------------------------------+
   |             Import Library               |
   +------------------------------------------+
   | Library Type    [   Compiled Function  ] |
   | Library Name    [      myLibrary       ] |
   | File Name       [       myfile         ] |
   | Definition File [      myfile.h        ] |
   +------------------------------------------+
 
The "Library Name" field just gives an arbitrary name that the rest of the program uses to access the routines in the library. We'll call it, say, "pp". The "File Name" is of course the name of the library -- PEEKPOKE.DLL -- and the "Definition File" is the name of the definition file -- PEEKPOKE.H. (You can get a "browser" to find the files by clicking on these fields.) Anyway, once all this is done you have:

   +------------------------------------------+
   |             Import Library               |
   +------------------------------------------+
   | Library Type    [   Compiled Function  ] |
   | Library Name    [          pp          ] |
   | File Name       [  C:\VW\PEEKPOKE.DLL  ] |
   | Definition File [   C:\VW\PEEKPOKE.H   ] |
   +------------------------------------------+
 
This done, we can then use the "Call Function" object:

   +------------------------------+
   |        Call Function         |
   +------------------------------+
   | Function Name [ myFunction ] |
   +------------------------------+
 
To modify this to perform a "writeByte", just put "pp.writeByte" (note the use of the "pp" prefix, defined by "Import Library", to identify the library) in the "Function Name" field, and hit Enter. The pinouts on the Call Function box will be set up automatically as long as the library has been preloaded. You end up with:

   +-------------------------------------------------------+
   |                     Call Function                     |
   +----------+--------------------------------+-----------+
   | portAddr |                                |           |
   |  Int32   |                                |           |
   |          |                                |           |
   |          | Function Name [ pp.writeByte ] | Ret Value |
   |          |                                |           |
   | dataByte |                                |           |
   |  Int32   |                                |           |
   +----------+--------------------------------+-----------+
 
Note that the "Ret(urn) Value" is simply the value written -- no error code is returned. Note also that if you set up the input pins by hand, instead of using the "Configure Pinouts" menu entry, that the names of the pins are not important. The function only cares about the order of the pins and their values. Reverse the order of "portAddr" and "dataByte" and you'll have trouble.

Similarly, for "readByte" you get:


   +-------------------------------------------------------+
   |                     Call Function                     |
   +----------+--------------------------------+-----------+
   |          |                                |           |
   |          |                                |           |
   | portAddr | Function Name [ pp.readByte  ] | Ret Value |
   |  Int32   |                                |           |
   |          |                                |           |
   +----------+--------------------------------+-----------+
 
-- where the "Ret(urn) Value" is of course the value returned from the I/O port.

Similar comments apply to the use of "writeWord" and "readWord".

When you are done using a library it is usually good practice to delete it with the Delete Library object:


   +---------------------+
   |   Delete Library    |
   +---------------------+
   | Library Name [ pp ] |
   +---------------------+
 
One important thing to remember is that DLLs cannot allocate memory in VEE. If you want to get information back in a string, for instance, you have to allocate the string in VEE (just use a Formula box containing, say, a string of spaces, like " "). Forget this assumption and you'll likely get a crash.

 TOP OF PAGE

[22.2] DLL PARAMETER PASSING

* The key trick in the discussion above is creating the definition file to define the DLL routines to VEE. The definition file provides descriptions of DLL functions in the following format:


   <return type> <function name> (type parm1, type *parm2, ...) 
 
Under Win3, there was an optional qualifier needed to specify the calling convention, but this is not needed on Win32. Win32 supports the CDECL, STDCALL, and FASTCALL calling conventions. Most system DLLs use STDCALL. HP VEE will automatically recognize CDECL and STDCALL without any declaration. However, it will not support FASTCALL.

Valid return and parameter types include character strings, short integers, long integers, and double-precision real numbers. These correspond to the VEE data types string, INT16, INT32, and REAL respectively. The function name must start with an alpha character, followed by alphanumeric characters.

More formally:


   <return type>:  [ int | long | double | char* | void ]
   <type>:  [ int | long | double | char* | int* | long* | double* | char** ]
   <name>:  [ alpha char ][ zero or more alpha or num chars ] 
 
Note that "unsigned" types are not supported, since VEE doesn't have them. Structures aren't supported, either, since VEE doesn't understand C structures.

Parameters can be defined to be passed by value or by reference. A value parameter simply accepts a value that the function cannot change and return to VEE, while a reference parameter accepts an address pointer and so can alter the values of data VEE sends to it -- note that you must pass an array by reference, you can't pass an array by value. You specify whether a parameter passed by value or reference with by including or excluding an indirection symbol, "*", preceding the parameter name.

VEE uses a maximum of 144 bytes to pass parameters. This means that up to 36 32-bit pointers can be passed, or 36 32-bit integers, or up to 18 64-bit floats.

 TOP OF PAGE

[22.3] BUILDING DLLS FOR VEE

* The major issue with building DLLs with VEE is interfacing them to VEE. After all, once that issue is resolved, the only issues left become matters of C and Windows programming that have nothing in particular to do with VEE. The interfacing issues fall into three categories:

This section and those following examine these issues by creating a DLL using Microsoft Visual C++ with 5 functions to test common parameter-passing schemes:

* The first function is as follows:


   include <string.h>                  // Used for routine number 5.
   include <stdlib.h>                  // Handy to have around.

   // Test function 1 -- just increments an integer value by 1.  This
   // is the simplest case; it accepts a value and returns a value.

   _declspec(dllexport) t1_char[] = "Increments integer by 1.";
   
   _declspec(dllexport) long t1( long a )
   {
      return( ++a );
   } 
 
Nothing to it. Note the declaration "_declspec(dllexport)" for the function. A DLL will have routines that are to be called externally. It may also have "hidden" routines that cannot be called externally and are called by other routines in the DLL. The external routines must be declared as such, and that's what that declaration does (for current versions of Microsoft C, in any case).

The parameter "intparm" is declared as "long". VEE integers are 32-bit "long" integers. VEE reals are 64-bit "double" reals.

Note also the declaration:


   _declspec(dllexport) t1_char[] = "Increments integer by 1.";
 
This sets up an exported variable containing a string that the VEE Function & Object Browser uses as a description for the function. That is, if you import the DLL, its functions will be listed in the Function & Object Browser, and if you select one of these functions the browser will display the associated string.

The general format for setting up such strings is as follows:


   _declspec(dllexport) <function_name>_char[] = "<string>";
 
* The second function is a simple variation on the first:

   // Test function 2 -- gives negative value of number.  This accepts
   // a pointer to a variable and returns the value in the same way.  The
   // function itself always returns 1.
   
   _declspec(dllexport) t2_char[] = "Returns negative value of real.";

   _declspec(dllexport) long t2( long *a )
   {
      *a = -*a;
      return( 1 );              // Return value doesn't matter.
   } 
 
* The third function accepts a pointer to an array, which is really the only reasonable way to handle arrays:

   // Test function 3 -- Sums array of doubles and passes sum back via
   // function return value.  Note how you have to give it the array size.
   // This is because there is no way in a C function to tell how big
   // an array is.

   _declspec(dllexport) t3_char[] = "Sums array of doubles.";

   _declspec(dllexport) double t3( double *a, long l )
   {
     long n;
     double asum = 0;
     for( n = 0; n < l; n++ )
     {
       asum += a[n];
     }
     return( asum );
   } 
 
* The fourth function handles multiple arrays:

   // Test function 4 -- accepts three arrays; adds the first two on an 
   // element-by-element basis and then returns the sums in the third.
   // Again, you have to give it the array size.

   _declspec(dllexport) t4_char[] = "Adds two arrays.";

   _declspec(dllexport) long t4( double *a, double *b, double *c, long l )
   {
     long n;
     for( n = 0; n < l; n++ )
     {
       c[n] = a[n] + b[n];
     }
     return( 1 );
   } 
 
* The fifth function accepts a pointer to an array of char (a string) and returns a string through it.

   // Test function 5 -- Return string through array of char.  This
   // assumes that the string has been allocated and initialized
   // ahead of time (with a null termination character).

   _declspec(dllexport) t5_char[] = "Returns 'Hello, World!' string.";

   _declspec(dllexport) long t5( unsigned char *str )
   {
     long s;
     char *p;
     p = "Hello, World!";
     s = strlen(str);
     if (s >= strlen(p))
     {
       strcpy(str,p);
     }
     return( 1 );
   }
 
* These routines are consolidated in a file named TESTLIB.C and then compiled into a file named TESTLIB.DLL. Under Windows 3 and earlier versions of Microsoft C, this was incredibly troublesome to do. Now it's extremely simple:

Having completed the DLL, the next thing required is a VEE definition file for it. TESTLIB.H includes the text:


   long t1( long a );
   long t2( long *a );
   double t3( double *a, long l );
   long t4( double *a, double *b, double *c, long l );
   long t5( char *str );
 
* The first thing to do in running the DLL functions under VEE is to load the DLL using the Import Library object. The following objects perform this loading, and well as the logic to drive tests for the DLL functions and gracefully exit the program:

   +------------------------------------------+
   |             Import Library               |
   +------------------------------------------+
   | Library Type    [   Compiled Function  ] |
   | Library Name    [          t           ] |
   | File Name       [ C:\TEST\TESTLIB.DLL  ] |
   | Definition File [  C:\TEST\TESTLIB.H   ] |
   +--------------------+---------------------+
                        |
                     +--+--+
                     | Do  +--> test function 1
                     +--+--+
                        |
                     +--+--+
                     | Do  +--> test function 2
                     +--+--+
                        |
                     +--+--+
                     | Do  +--> test function 3
                     +--+--+
                        |
                     +--+--+
                     | Do  +--> test function 4
                     +--+--+
                        |
                     +--+--+
                     | Do  +--> test function 5
                     +--+--+
                        |
                     +--+--+
                     | OK  +--+
                     +-----+  |
                              |
                        +-----+
                        |
               +--------+-------+
               | Delete Library |
               +----------------+
 
Creating each of the test blocks for the five DLL functions was simple. The test block for t1() provides an integer and displays one on return:

   -----+     
        |        +-------------------------------+
   +----+----+   |         Call Function         |   +--------------+
   | Integer |   +---+---------------+-----------+   |  t1_results  |
   +---------+   |   | Function Name |           |   +--------------+
   | 100     +-->| a |[    t.t1     ]| Ret value +-->| 101          |
   +---------+   |   |               |           |   +--------------+
                 +---+---------------+-----------+
 
The test block for t2() was effectively the same:

   -----+
        |        +-------------------------------+
   +----+----+   |         Call Function         |   +--------------+
   | Integer |   +---+---------------+-----------+   |  t2_results  |
   +---------+   |   | Function Name | Ret value |   +--------------+
   | 42      +-->| a |[    t.t2     ]|     a     +-->| -42          |
   +---------+   |   |               |           |   +--------------+
                 +---+---------------+-----------+
 
The only difference is that the return value is through the "A" output pin, as fits a call by reference.

The test for t3() -- which accepts an array and returns the sum of that array -- is a little more complicated:


   -------------------------+
                            |
     +----------------------+--------------------+ 
     | randomize( ramp( 15,1,1 ), -1000, +1000 ) +--+
     +-------------------------------------------+  |
                                                    |
   +------------------------------------------------+
   |              
   |                 +-------------------------------+
   |                 |        Call Function          |   +--------------+
   |                 +---+---------------+-----------+   |  t3_results  |
   +---------------->| a | Function Name |           |   +--------------+
   |   +---------+   |   |[     t.t3    ]| Ret Value +-->| 6834.95      |
   +-->| totSize +-->| l |               |     a     |   +--------------+
   |   +---------+   +---+---------------+-----------+           
   |                                   
   |                 +--------------+
   |                 |   t3_check   |  
   |   +-----+       +--------------+  
   +-->| sum +------>| 6834.95      |
       +-----+       +--------------+
 
The "randomize" function returns an array of 25 random numbers that are passed to the DLL function. Note how I use the VEE "sum()" function in parallel to check my work.

* The t4() function -- which accepts two real arrays and returns the sums of the elements in a third -- is tested as follows:


   ------------------------------+
                                 |
                       +---------+--------+ 
                   +-->| ramp(A,1,A)      +--------+
     +---------+   |   +---------+--------+        |
     | Integer |   |             |                 |
     +---------+   |   +---------+--------+        |
     | 25      +---+-->| ramp(A,100,99+A) +-----+  |
     +---------+   |   +---------+--------+     |  |
                   |             |              |  |
                   |   +---------+--------+     |  |
                   +-->| ramp(A,0,0)      +--+  |  |
                   |   +------------------+  |  |  |
        +----------+                         |  |  |
        |  +---------------------------------+  |  |
        |  |  +---------------------------------+  |
        |  |  |  +---------------------------------+ 
        |  |  |  |
        |  |  |  |   +-------------------------------+
        |  |  |  |   |        Call Function          |
        |  |  |  |   +---+---------------+-----------+
        |  |  |  +-->| a |               | Ret Value |   +--------------+
        |  |  +----->| b | Function Name | a         |   |  t4_results  |
        |  +-------->| c |               | b         |   +--------------+
        |            |   |[   t.t4      ]| c         +-->| 100          |
        +----------->| l |               |           |   | 101          | 
                     +---+---------------+-----------+   | ...          |
                                                         +--------------+
 
Note how you have to define the return-value array ("c") to pass to the function so it can pass one back.

* The t5() function is tested simply as follows:


   --------+
           |           +-------------------------------+
   +-------+-------+   |         Call Function         |   +--------------+
   |    Formula    |   +---+---------------+-----------+   | AlphaNumeric |
   +---------------+   |   | Function Name | Ret.value |   +--------------+
   | "           " +-->| a |[    t.t5     ]|     a     +-->| Hello,World  |
   +---------------+   |   |               |           |   +--------------+
                       +---+---------------+-----------+
 
A kit of the files needed to get this example to work -- TESTLIB.C, TESTLIB.DLL, TESTLIB.H, and TESTLIB.VEE -- is available in the archive TESTLIB.EXE.

 TOP OF PAGE

[22.4] FINAL COMMENTS

* Debugging DLL operation can be a tricky issue because the problems can lie in either VEE or the DLL. The only real problem with VEE itself relative to DLLs, however, is parameter passing. Get this wrong and VEE will often crash.

If you are having troubles with a DLL, one way to determine if it's a parameter passing problem is to write a dummy routine that merely accepts the parameters given to it and returns the proper values. If this works OK and the actual DLL routines have the same parameter definitions, then the problem likely lies inside the DLL code.

Similarly, it may be useful to write a C program to call a troublesome DLL routine to separate problems with parameter passing from actual DLL problems.

If the problem is isolated to the DLL itself, then it is not really a VEE problem any longer. Standard C debugging tools should be used to trace the operation of the DLL to isolate the problem. How this is done is beyond the scope of this chapter (and really a C programming issue).

* VEE cannot handle DLLs written in C++. The parameter passing conventions are different from C. I believe there are ways to allow C parameter passing to C++ routines, but in general it is just simpler to write the routines in C.

We have no information on writing DLLs in other languages and interfacing them to VEE.

* This chapter only covers writing DLLs from scratch and interfacing them to VEE. It may be possible to interface pre-written DLLs to VEE if their parameter schemes are understood well enough and they don't require structures, but if not an intermediate DLL can be written to convert the parameters required by VEE to those required by the original DLL.

 TOP OF PAGE


 LAST PAGE  BACK TO INDEX  NEXT PAGE