Agilent VEE and C: The Piped DLL Connection

Meeting deadlines on any engineering task isn’t easy. This is especially true for test engineers who not only wrestle with the growing complexity of the device under test, but also with the test equipment itself. As if that weren’t enough, test engineers often feel pressured to restore the momentum of floundering projects.

Given these realities, it’s imperative that test-engineering survivalists use every tool at their disposal to meet deadlines. In the realm of automated test-code development, that means making sensible reuse of existing code. While it’s easy to reuse well-designed Agilent VEE functions, sometimes additional bridges are needed to fill the gap between Agilent VEE and non-Agilent VEE applications.

One interesting feature of interprocess communications (IPC) can be used for passing data between applications: Pipes. By using a Pipe-based Client/Server architecture, you can vastly expand your programming horizons.

A Real-World Problem

Just as I was putting the finishing touches on a recent ATE project, the project manager dumped a new set of requirements on my desk. I had a bad feeling about these 11th-hour minor additions, but he dismissed my worries and pushed a small cardboard box toward me. “You can use this board to help implement the new tests. It won’t be difficult—it’s off the shelf.”

Unfortunately, off-the-shelf hardware rarely comes with matching software, especially where specialized tests are involved. While the board could be installed in 15 minutes, I was not thrilled at the prospect of weeding through a 3²-thick programming tome to master yet another vendor-supplied application programming interface (API) library. It would take days—or more likely weeks—of programming and debugging time.

A Solution Lurking in the Shadows

The vendor was acutely aware of the API library’s learning curve and had cleverly developed an excellent, simple demo program to exercise every function on the board. Here was a quick and easy way for the hardware support technician on the other end of the toll-free line to say to a frustrated developer, “It works fine with our software, so it’s not a hardware problem. Your software is messed up.”

If I could just automate sending keystrokes to their demo program, my job would be done. Subsequent debugging would be limited to the new keystroke interface since the core demo program worked fine.

A Client/Server Solution

The software vendor had provided the source code for its demo program. So to facilitate IPC, I only added a few well-placed lines of Server code, applying the tried-and-true method of using Pipes. Now, instead of looking for keystrokes arriving from the keyboard, the modified demo program would look for ASCII characters coming from a Client via a Pipe.

In return, the Server would send responses indicating whether the requested operation was successful. The Client consisted of a few more lines of code, compiled as a dynamic linked library (DLL), to provide the bridge between the ATE-based test executive written in Agilent VEE and the stand-alone demo program written in C.

Application Modifications

Figure 1 is a software flow diagram for the modified application. New IPC code (shown in blue) was grafted into the existing C-based source listing. Here is a description of each new block:

  • Initialize IPC Constants and Variables—The source code for this block is shown in Listing 1. Placing the preprocessor directive #include with the other include statements at the beginning of the program provides access to the IPC function library.

    Listing 1

    #include // required for “printf()”, etc.
    #include // required for _getch() declaration
    #include // required for Pipes
    HANDLE hPipe; // Required for IPC
    SECURITY_ATTRIBUTES attributes; // required by IPC
    SECURITY_DESCRIPTOR descriptor; // required by IPC
    struct veeMessage {
    int subCode;
    char buffer[1024];
    }; // Structure for exchanging data w/ client
    struct veeMessage VEEmessage;
    unsigned long retSize;
    int retCode, running = 1;
    char ichar;
    char array[1024];
    int subcommand;


    The veeMessage structure combines distinct data elements together as a single unit composed of two parts: a 1,024-character array (buffer) that passes keystrokes from Agilent VEE to the Server and returns status messages back and an integer (subCode) containing data.

  • Initialize Server Pipe—Listing 2 presents code for initializing the Server Pipe. Again, this code should be implemented exactly as shown. The only thing you may opt to change is the name of the Pipe (DEMO). However, if you change the name here, you’ll also have to change it in the Client DLL so that both the Client and the Server can talk over the same Pipe. The printf statements are optional but may come in handy for troubleshooting purposes.

    Listing 2

    if (argc > 1)

    printf(“nCommand-line switch detected … Opening Server Pipe.n”);
    * Initialize Server Pipe 
    InitializeSecurityDescriptor(&descriptor, SECURITY_DESCRIPTOR_REVISION);
    SetSecurityDescriptorDacl(&descriptor, TRUE, NULL, FALSE);
    attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
    attributes.bInheritHandle = FALSE;
    attributes.lpSecurityDescriptor = &descriptor;
    hPipe = CreateNamedPipe(“\\.\PIPE\DEMO”,
    PIPE_ACCESS_DUPLEX,
    PIPE_WAIT|PIPE_READMODE_MESSAGE|
    PIPE_TYPE_MESSAGE,
    1, sizeof(VEEmessage), sizeof(VEEmessage), 0, 
    &attributes);
    if (hPipe==NULL)
    {
    printf(“Cannot create RFSU pipe.n”);
    exit(1);
    }
    }
    else
    printf(“No Command-line switch detected … Accepting input from keyboard.n”);


  • The Use Pipe? decision block preceding the Initialize Server Pipe block determines whether the program will look for input from the keyboard or the Pipe. This option is determined at run time using a command-line argument.
    For example, if you type demo.exe at a command-line prompt, the program will look for commands from the keyboard. But if you send demo.exe -x where x is any character, the program looks for commands from the Pipe. 
    In Listing 2, this is accomplished by examining integer constant argc that holds the number of command-line items. If I wanted to know what those arguments were, I could examine argv[ ]. But in this application, just knowing there is a command-line argument is sufficient.
  • Get CMD From Pipe—The ReadFile( ) function is used to retrieve a command from the pipe, as shown in Listing 3. Here, hPipe is the handle of the file to read, &VEEmessage is a pointer to the buffer that will receive the data, and sizeof(VEEmessage) holds the number of bytes to be read—in this case, all bytes in the VEEmessage structure. 
    Variable &retSize provides a pointer to the number of bytes read, and NULL is a data structure pointer. The character variable ichar accepts the ASCII character contained in the first element of a 1,024-element character array VEEmessage.buffer while integer variable subcommand takes on the value of VEEmessage.subCode.


    Listing 3

    * Main loop — keep putting up menu until user decides to quit while (running)
    {
    if (argc > 1)
    {
    retCode = ConnectNamedPipe(hPipe, NULL);
    retCode = ReadFile(hPipe, &VEEmessage, 
    sizeof(VEEmessage), &retSize, NULL);
    ichar = VEEmessage.buffer[0];
    subcommand = VEEmessage.subCode;
    printf(“character received from Client: %cn”,ichar);
    }
    else 
    {
    printf(“nEnter command: “);
    ichar = _getch();
    printf(“%cn”,ichar); // line feed
    }

    ichar = toupper(ichar); // convert to UPPERCASE


  • Return Status to Pipe and Close Pipe—In Listing 4, the strcpy( ) function copies the command status word, contained in array, back to VEEmessage.buffer. Similarly, the integer value of ichar is assigned to VEEmessage.subCode. WriteFile( ) sends this information back to the Client via the pipe and uses the same arguments as ReadFile( ). Finally, DisconnectNamedPipe( ) terminates the communications session.

    Listing 4

    if (argc > 1)
    {
    strcpy(VEEmessage.buffer, array);
    VEEmessage.subCode = ichar;
    retCode = WriteFile(hPipe, &VEEmessage, sizeof(VEEmessage), &retSize, NULL);
    DisconnectNamedPipe(hPipe); 
    }


  • Client DLL Code

    The Client-side code is considerably simpler. The entire DLL is presented in Listing 5. If you’ve had experience with C, you should notice several differences between a conventional .exe application and this DLL. For one thing, there’s no Main( ) function. Instead, there’s one or more functions preceded by DLLEXPORT. The first function, DLL_Client_desc[ ], is optional and simply provides a textual description of the function you can view from Agilent VEE.


    Listing 5

    FUNCTION: Provides IPC communication between Agilent VEE and GBdemoServer.exe
    #include // required for “sprintf()”, etc.
    #include // required for Pipes
    #ifdef WIN32
    # define DLLEXPORT __declspec(dllexport)
    #else
    # define DLLEXPORT
    #endif
    DLLEXPORT char DLL_Client_desc[] = “This function accepts an 
    array fromAgilent VEE and passes three arguments (long arraySize, 
    char *array, longsubCode) to the Server. It then receives status back via the 
    RETURNpin and the passed-back character array *array.”;
    DLLEXPORT long DLL_Client(long arraySize, char *array, long subCode)
    {
    struct veeMessage {
    int subCode;
    char buffer[1024];
    }; // Structure for exchanging data w/ Server
    struct veeMessage VEEmessage;
    HANDLE hPipe;
    unsigned long retSize;
    BOOL retCode;
    int retVal = 0;
    /* OPEN PIPE */
    hPipe = CreateFile(“\\.\PIPE\DEMO”,
    GENERIC_READ|GENERIC_WRITE,
    0,NULL, OPEN_EXISTING, 0, 0);
    if (hPipe==INVALID_HANDLE_VALUE)
    {
    sprintf(array, “Cannot open pipe connection to Server”);
    return -9999;
    }
    VEEmessage.subCode = (int) subCode;
    *VEEmessage.buffer = *array;
    retCode = WriteFile(hPipe, &VEEmessage, 
    sizeof(VEEmessage), &retSize, NULL);
    retCode = ReadFile(hPipe, &VEEmessage, 
    sizeof(VEEmessage), &retSize, NULL);
    strcpy(array,VEEmessage.buffer);
    retCode = VEEmessage.subCode;
    CloseHandle(hPipe);
    return (retCode);
    }


    The second DLLEXPORT function is associated with DLL_Client and offers a mechanism for accepting data from Agilent VEE and passing it to the stand-alone C-based Server program. It accepts three inputs from Agilent VEE: a pointer to a character array (char *array), an integer containing the length of the character array (long arraySize), and an integer (long subCode) containing numerical data such as desired voltage or clock rate. While knowing the size of the character array is not terribly important in this application (the Server expects a single argument), it is very important in applications using arrays of integer or real numbers since there is no end-of-array indicator.

    The Client first opens the Pipe to transfer the data to the Server. Make special note of the Client and Server Open Pipe command arguments—they are not identical.

    Then the Client combines the character array and subCode into a single, tidy data structure (VEEmessage) and ships it off to the Server using the WriteFile( ) function. It immediately executes ReadFile( ), which waits for a response from the Server.

    When this is received, it updates the character array with the contents of VEEmessage.buffer and promptly closes the pipe using CloseHandle( ). Finally, the DLL function exits, returning program control to Agilent VEE. Command status becomes available to Agilent VEE via the pass-through text array, and the DLL return code provides the integer value of the original keyboard command (converted to uppercase).

    To actually compile this code as a DLL under Microsoft Visual C++ (Version 5.0 or 6.0), first you must create a workspace. Next, you must choose the appropriate project type. In this case, select Win32 Dynamic-Link Library. Finally, add the file containing the code in Listing 5 to the DLL project by highlighting the project, right-clicking on it, and selecting Add files to Project…

    A browser window will pop up, allowing you to select the file containing your source code. Note: maintain a file extension of .c. A .cpp file will compile, but the resulting DLL will not run under Agilent VEE. Finally, select Build from the main menu and Rebuild All… from the pull-down menu. This will produce a file with a .dll extension.

    Agilent VEE requires a header file to use with the newly created DLL. A header file is a simple ASCII text file with an .h extension that contains the name of one or more of the DLL functions you want to access. If it’s not in the header file, Agilent VEE doesn’t know it exists.

    Listing 6 contains the header file. Except for the missing keyword DLLEXPORT, the header description is identical to the DLL function definition.


    Listing 6

    FUNCTION: Header file for GBdemoClient.dll

    long DLL_Client(long arraySize, char *array, long subCode)


    The Agilent VEE Interface

    Before the DLL can be used by the Agilent VEE application, it must be imported into Agilent VEE as a Compiled Function library. This is accomplished by the Import Library block located under Device->Import Library on the Agilent VEE main menu. The DLL has been assigned myLib.

    Next, the Server side of the path is opened by launching the target application using the Execute Program (PC) block with a command-line argument. Be sure Wait for prog exit is set to No or the VEE routine will appear to hang while waiting for the application to end.

    Commands and subcodes may be entered in the blocks and are sent to the function myLib.DLL_Client. Command status is returned via pass-through variable array. If RetValue = 90 (corresponding to an uppercase z), the Until Break loop is broken, and the imported DLL library is unloaded. The Z command also signals the Server to quit.

    Conclusion

    DLLs and Pipes open up exciting new territory for the ATE programmer to explore. By mastering and applying these techniques, you can greatly improve your odds of survival—and success—on future ATE expeditions.

    About the Author

    Greg Bonaguide is an RF engineer in the Microwave Remote Sensing Group at Raytheon Data Systems. He received a B.S.E.E. from the University of Hartford and master’s degrees in engineering management in 1989 and microwave engineering in 1996 from the University of South Florida. Raytheon Data Systems, 1501 72nd St. N, St. Petersburg, FL 33710, 727-302-3367, e-mail: [email protected].

    Return to EE Home Page

    Published by EE-Evaluation Engineering
    All contents © 2000 Nelson Publishing Inc.
    No reprint, distribution, or reuse in any medium is permitted
    without the express written consent of the publisher.

    August 2000

Sponsored Recommendations

Comments

To join the conversation, and become an exclusive member of Electronic Design, create an account today!