AnsweredAssumed Answered

VRF-Compiled_Functions

Question asked by VRFuser on May 5, 1998



[VEE] Using & Running DLLs from VEE For Windows

* HP VEE on the PC can access external dynamic-link library (DLL) routines.
This document 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 document 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.

[%%]


[1.0] 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:

% PEEKPOKE.DLL:  

   The actual Windows Dynamic Link Library itself.  All you have to do with
   this file is put it in some directory where VEE can access it.

% PEEKPOKE.H:    

   The VEE definition file.  This contains a description of the DLL routines
   so that VEE can access the routines.

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:VWPEEKPOKE.DLL  ] |
   | Definition File [   C:VWPEEKPOKE.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 the
thing 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.

[%%]


[2.0] 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.

[%%]


[3.0] 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:

% Defining the parameters for the DLL calls.
% Specifying the proper options and processes for creating the DLL.
% Properly using the DLL once it has been created.

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:

% t1() accepts a scalar as a value, and returns a scalar value through the
   function return call.

% t2() accepts a pointer to a scalar variable, and returns a scalar value
   using that pointer.

% t3() accepts a pointer to an array and a scalar as a value, and returns a
   scalar value through the function return call.

% t4() accepts pointers to three arrays, plus a scalar as a value, and
   returns information in one of the arrays.

% t5() accepts a pointer to an array of char (a string) and returns a string
   through that array of char.

* 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) long t1( long a )
   {
      return( ++a );
   }

Nothing to it.  Note the declaration "_declspec(dllexport)".  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.  (Note for later that VEE reals are 64-bit "double" reals.)

* 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) 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) 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) 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) 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:

% Create a project named TESTLIB.
% Add the TESTLIB.C file to the project.
% Compile it as a Win32 DLL.

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:TESTTESTLIB.DLL  ] |
   | Definition File [  C:TESTTESTLIB.H   ] |
   +--------------------+---------------------+
                        |
                     +--+--+
                     | Do  +--> test function 1
                     +--+--+
                        |
                     +--+--+
                     | Do  +--> test function 2
                     +--+--+
                        |
                     +--+--+
                     | Do  +--> test function 3
                     +--+--+
                        |
                     +--+--+
                     | Do  +--> test function 4
                     +--+--+
                        |
                     +--+--+
                     | Do  +--> test function 5
                     +--+--+
                        |
                     +--+--+
                     | OK  +--+
                     +-----+  |
                              |
                        +-----+
                        |
               +--------+-------+
               | Delete Library |
               +----------------+ g

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:
G
   -----+    
        |        +-------------------------------+
   +----+----+   |         Call Function         |   +--------------+
   | Integer |   +---+---------------+-----------+   |  t1_results  |
   +---------+   |   | Function Name |           |   +--------------+
   | 100     +-->| a |[    t.t1     ]| Ret value +-->| 101          |
   +---------+   |   |               |           |   +--------------+
                 +---+---------------+-----------+ g

The test block for t2() was effectively the same:
G
   -----+
        |        +-------------------------------+
   +----+----+   |         Call Function         |   +--------------+
   | Integer |   +---+---------------+-----------+   |  t2_results  |
   +---------+   |   | Function Name | Ret value |   +--------------+
   | 42      +-->| a |[    t.t2     ]|     a     +-->| -42          |
   +---------+   |   |               |           |   +--------------+
                 +---+---------------+-----------+  g
                    
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:
G
   -------------------------+
                            |
     +----------------------+--------------------+
     | 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      |
       +-----+       +--------------+ g

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:
G
   ------------------------------+
                                 |
                       +---------+--------+
                   +-->| 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          |
                     +---+---------------+-----------+   | ...          |
                                                         +--------------+ g

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:
G
   --------+
           |           +-------------------------------+
   +-------+-------+   |         Call Function         |   +--------------+
   |    Formula    |   +---+---------------+-----------+   | AlphaNumeric |
   +---------------+   |   | Function Name | Ret.value |   +--------------+
   | "           " +-->| a |[    t.t5     ]|     a     +-->| Hello,World  |
   +---------------+   |   |               |           |   +--------------+
                       +---+---------------+-----------+  g

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.

[%%]


H[4.0] FINAL COMMENTSh

* 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 document (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 document 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.

[<>]

Outcomes