Microsoft KB Archive/118643

From BetaArchive Wiki

Article ID: 118643

Article Last Modified on 1/9/2003



APPLIES TO

  • Microsoft Visual Basic 3.0 Professional Edition



This article was previously published under Q118643


SUMMARY

This article demonstrates techniques to perform the following tasks between Visual Basic and a C DLL:

  • Pass a string to a C DLL.
  • Pass (and modify) an array of strings to a C DLL.
  • Return a string from a function within a C DLL.

Each of these techniques demonstrates slightly different way of handling strings within a DLL, and the last two require the Control Development Kit, which comes with Visual Basic version 3.0, Professional Edition.

MORE INFORMATION

There are two parts to each technique demonstrated: the declaration within your Visual Basic code for the C function, and the internals of the C function that process the data being passed, modified, or sent back.

Pass a String to a C DLL

The declaration for the C function must use ByVal for C to receive a string. C functions typically expect strings to end in a "null character" (binary zero). When you declare the function argument using ByVal, this tells Visual Basic to pass the string as a null-terminated string. Thus, the declaration looks like this:

   Declare Sub passOneString Lib "mydll.dll" (ByVal lpszBuf As String)

The actual C function looks like this:

   void __far __pascal __export passOneString(char __far *lpszBuf)

The actual internals of the C function are straightforward while it receives the argument as a standard C string.

NOTE: Both "far" and "pascal" are compiler-specific keywords that are allowed by the ANSI standard. They should have the appropriate number of underscores to put them in the appropriate name space. The latest compilers strictly follow the standard and say that you need two underscores (but will allow fewer for backward compatibility). Some previous Microsoft compilers only allowed 0 or 1 underscores.

Pass (and Modify) an Array of Strings to a C DLL

The declaration for the function demonstrating this technique does not use ByVal. Because an array is being passed, the declaration needs to pass the array of strings to the function using the internal Visual Basic array descriptor (HAD). Thus, the declaration looks like this:

   Declare Sub passStrings Lib "mydll.dll" (theArray() As String,
                   ByVal nIndex% )

The actual C function looks like this:

   void __far __pascal __export passStrings(HAD had, int nIndex)

The function VBArrayElement() is used to extract a Visual Basic string handle (HLSTR) from the array (using the index into the array specified by nIndex). Then VBGetHlstr() converts that string handle into a standard C string. If you want, and if the buffer used to receive the string is large enough for the changes, then you can modify that C string as you like. However, the modified string needs to be stored in the original Visual Basic string handle. This is done through the VBSetHlstr() function.

Return a String from a Function Within a C DLL

For a Visual Basic routine to receive a string from a function, that function must be declared to return a string and, within the C side, to return a variable of type HLSTR. Thus, the declaration looks like this:

   Declare Function returnAString Lib "mydll.dll" () As String

The actual C function looks like this:

   hlstr __far __pascal __export returnAString()

This time, the VBCreateTempHlstr is used to transform a given standard C string into a Visual Basic string handle. Then the function simply returns that handle, which Visual Basic receives as a string.

Additionally, the VBRuntimeError function is used to generate a Visual Basic run-time error. If an error condition exists after calling VBCreateTempHlstr, VBRuntimeError sets a Visual Basic error code and then exits the function. For this reason, this function should only be used in a routine that is called directly by Visual Basic, not in the control procedure of a custom control or other location where Windows itself invokes the code.

Step-by-Step Example to Create the DLL

Here are the steps necessary to build a DLL using Visual C++:

  1. Start Visual C++.
  2. Create a new project by choosing New from the Project menu. Select the following options:

    1. Set the Project Type to "Windows dynamic-link library (.DLL)".
    2. Clear the "Use Microsoft Foundation Classes" check box.
  3. Create a new file, add the following text and save it as "MYDLL.DEF":

       LIBRARY        MYDLL
       DESCRIPTION    "String Manipulation DLL"
       EXETYPE        WINDOWS 3.1
       CODE           PRELOAD MOVEABLE DISCARDABLE
       DATA           PRELOAD MOVEABLE SINGLE
       HEAPSIZE       4096
       EXPORTS
                passOneString  @1
                passStrings    @2
                returnAString  @3
  4. Create a new file, add the following code, and save it as "MYDLL.C":

       #include <windows.h>
       #include <string.h>
       #include <stdio.h>
       #include "vbapi.h"
    
       #define USHORT unsigned short
    
       //-------------------------------------------------------------------
       // Global Variables
       //-------------------------------------------------------------------
       HANDLE hmodDLL;
    
       //-------------------------------------------------------------------
       // Receive a single string in functions argument list:
       // display string to user
       //-------------------------------------------------------------------
       void __far __pascal __export passOneString(LPSTR lpszBuf)
       {
          // Display old string to user.
          MessageBox( NULL, lpszBuf, "Inside DLL...  (unmodified)", 0 );
       }
    
       //-------------------------------------------------------------------
       // Receive/modify array of strings in functions argument list:
       // - Retrieve a specified element in an array of strings.
       // - Transform that element into a standard C string.
       // - Modify that standard C string.
       // - Save the modified string back into the original element of the
       //   array.
       //-------------------------------------------------------------------
       void __far __pascal __export passStrings(HAD had, int nIndex)
       {
          HLSTR       hlstr;               // Reference to VB string
          int         idx[1];              // Array in one dimension
          char        lpszBuf[255];        // "Std C" string buffer
          USHORT      cbCount;             // # of characters in string
    
          // Retrieve a specified element (nIndex) in an array of strings.
          idx[0] = nIndex;
          hlstr = VBArrayElement(had, 1, (LPINT)idx);
    
          // Transform that element into a standard C string.
          cbCount = VBGetHlstr( hlstr, lpszBuf, sizeof( lpszBuf ) - 1 );
    
          // Display old string to user.
          MessageBox( NULL, lpszBuf, "Inside DLL...  (unmodified)", 0 );
    
          // Modify that standard C string.
          wsprintf(lpszBuf, "%s <-- size = %d", lpszBuf, lstrlen(lpszBuf));
    
          // Save the modified string in the original element of the array.
          VBSetHlstr( &hlstr, lpszBuf, lstrlen( lpszBuf ) );
       }
    
       //-------------------------------------------------------------------
       // Return a string from a function back to a Visual Basic program:
       // - Create Hlstr from standard C string.
       // - Report error (if any).
       // - Return Hlstr to calling VB function.
       //-------------------------------------------------------------------
       HLSTR __far __pascal __export returnAString()
       {
           HLSTR temp;
           char *buff = {"This function returns a string from a DLL."};
    
          // Creates Hlstr from standard C string.
           temp = VBCreateTempHlstr(buff, lstrlen(buff));
    
          // Report error (if any).
          if (HIWORD(temp) == -1)
             VBRuntimeError(LOWORD(temp));
    
          // Return Hlstr to calling VB function.
          return temp;
       }
    
       //-------------------------------------------------------------------
       // Initialize library.
       // This routine is called from the DLL entry point in LIBINIT.ASM,
       // which is called when the first client loads the DLL.
       //-------------------------------------------------------------------
       BOOL FAR PASCAL LibMain(HANDLE hmod, HANDLE segDS, USHORT cbHeapSize)
       {
           // Avoid warnings on unused (but required) formal parameters.
           cbHeapSize = cbHeapSize;
           segDS = segDS;
    
           hmodDLL = hmod;
    
           // Leave the DS unlocked when not running.
           // Required only under Windows version 3.1
           // Win32 does not require or support UnlockData()
           UnlockData( 0 );
    
           return TRUE;
       }
    
       //-------------------------------------------------------------------
       // Handle exit notification from Windows.
       // This routine is called by Windows when the library is freed
       // by its last client.
       //-------------------------------------------------------------------
       VOID __far __pascal __export WEP (BOOL fSystemExit)
       {
           // Avoid warnings on unused (but required) formal parameters.
           fSystemExit = fSystemExit;
       }
  5. From the Project menu, choose the Build MYDLL.DLL option.
  6. Add the MYDLL.DEF, MYDLL.C, and \VB\CDK\VBAPI.LIB files to the project. This assumes you have installed Visual Basic in the \VB directory. Otherwise, modify this last entry to the correct path.


Step-by-Step Example to Create Visual Basic Application to Use DLL

  1. Start a new project in Visual Basic. Form1 is created by default.
  2. Place three command buttons (Command1, Command2, and Command3) on Form1.
  3. Using the following table as a guide, set the properties of the controls added in step 2.

       Control name   Property   New value
       ---------------------------------------------
       Command1       Caption    &Pass a String
       Command2       Caption    Pass &And Modify Array
       Command3       Caption    &Receive a String
  4. Add the following code to the (general) (declarations) section of Form1:

       ' Enter each of the following Declare statements as one, single line:
    
       Declare Sub passOneString Lib "mydll.dll" (ByVal lpszBuf As String)
       Declare Sub passStrings Lib "mydll.dll" (theArray() As String,
          ByVal nIndex%)
       Declare Function returnAString Lib "mydll.dll" () As String
  5. Place the following code in the Command1 Click event procedure:

       Sub Command1_Click ()
          Dim MyStr As String * 80
          Dim My2ndStr$
    
          MyStr = "Hello World (First String)"
          My2ndStr$ = "Hello World Again (Second String)"
    
          Call passOneString(MyStr)
          Call passOneString(My2ndStr$)
       End Sub

    NOTE: This demonstrates that either method of defining a string works equally well.

  6. Place the following code in the Command2 Click event procedure:

       Sub Command2_Click ()
          Dim i%
          ReDim array(0 To 2) As String
    
          array(0) = "Short"
          array(1) = "Medium Medium"
          array(2) = "Long Longer Longest"
    
          For i% = 0 To 2
             Call passStrings(array(), i%)
             MsgBox array(i%), 0, "Inside VB...  (modified)"
          Next i%
       End Sub
  7. Place the following code in the Command3 Click event procedure:

       Sub Command3_Click ()
          Dim MyStr$
    
          MyStr$ = returnAString()
    
          MsgBox MyStr$, 0, "Returned from DLL..."
       End Sub
  8. Choose Start from the Run menu to run the program.


REFERENCES

  • Visual Basic for Windows, version 3.0 "Programmer's Guide," pp. 563-566.
  • Visual Basic for Windows, version 3.0 "Professional Features Book 1: Control Development Guide," pp. 69-71.
  • Knowledge Base article 106553, "How to Write C DLLs and Call Them from Visual Basic."



Additional query words: 3.00 DLL Strings HLSTR

Keywords: KB118643