Microsoft KB Archive/118643

= How to Pass a String or String Arrays Between VB and a C DLL =

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++:

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

 Set the Project Type to "Windows dynamic-link library (.DLL)". Clear the "Use Microsoft Foundation Classes" check box.   Create a new file, add the following text and save it as "MYDLL.DEF": <pre class="fixed_text">  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 </li>  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; } </li> From the Project menu, choose the Build MYDLL.DLL option.</li> 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.</li></ol>

Step-by-Step Example to Create Visual Basic Application to Use DLL
 Start a new project in Visual Basic. Form1 is created by default.</li> Place three command buttons (Command1, Command2, and Command3) on Form1.</li>  Using the following table as a guide, set the properties of the controls added in step 2. <pre class="fixed_text">  Control name   Property   New value -  Command1       Caption    &Pass a String Command2      Caption    Pass &And Modify Array Command3      Caption    &Receive a String </li>  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 </li>  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. </li>  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 </li>  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 </li> Choose Start from the Run menu to run the program.</li></ol>

<div class="references_section">