This step-by-step article describes how to pass arrays and
strings between Microsoft Visual Basic 6.0 applications and functions that are
written in C or in C++. This article discusses the following topics:
| • | SAFEARRAY data types |
| • | C-language arrays |
| • | BSTR data types |
| • | C-language strings |
The sample code in the "Download the sample code" section uses
the concepts that this article discusses. However, the sample code does not
describe how to work with SAFEARRAY data types or with BSTR data types in C or
in C++.
Microsoft provides programming examples for illustration only, without warranty either expressed or implied. This includes, but is not limited to, the implied warranties of merchantability or fitness for a particular purpose. This article assumes that you are familiar with the programming language that is being demonstrated and with the tools that are used to create and to debug procedures. Microsoft support engineers can help explain the functionality of a particular procedure, but they will not modify these examples to provide added functionality or construct procedures to meet your specific requirements.
Back to the top
Requirements
This
article assumes that you are familiar with the following topics:
| • | Microsoft Visual Basic 6.0 programming |
| • | Microsoft Visual C++ 6.0 programming |
| • | Microsoft Active Template Library (ATL)
programming |
The
following list outlines the recommended hardware, software, network
infrastructure, and service packs that you need:
| • | Microsoft Windows 2000 or Microsoft Windows XP |
| • | Microsoft Visual Basic 6.0 |
| • | Microsoft Visual C++ 6.0 |
Back to the top
Cross-language function calls
When you make cross-language function calls, you must know
how each language stores various types of parameters because different
programming languages store some data types differently. These parameter types
include non-primitive data types such as arrays, strings, and user-defined data
types.
To write functions in C or in C++ that are called from Visual
Basic, you must understand how these programming languages store the types of
parameters that you are using.
Back to the top
Create a DLL project
To pass arrays or strings between Visual Basic and C or between
Visual Basic and C++, you can use C, C++, or ATL to create a DLL. When you use
ATL, a type library is created that is used for function calls between Visual
Basic and the DLL. To use ATL, see the "
Create
an ATL DLL" section.
To use C or C++ to
create a DLL, follow these steps
| 1. | Start Microsoft Visual C++. |
| 2. | On the File menu, click
New. |
| 3. | Under Projects, click Win32
Dynamic-Link Library. |
| 4. | In the Project name box, type
StdDLL. |
| 5. | In the Location box, type
C:\, and then click OK. |
| 6. | Click A simple DLL project, and then click
Finish. |
| 7. | In the New Project Information dialog
box, click OK. |
| 8. | Include OLE data types in your project. To do this, follow
these steps:
| a. | Expand StdDLL files, and then expand
Header Files. | | b. | Right-click the StdAfx.h file, and
then click Open. | | c. | In the StdAfx.h file, locate the following line of
code: #include <windows.h> | | d. | Paste the following code before the code that you
located in step c: #define INC_OLE2 | | e. | On the File menu, click
New. | | f. | Under Files, click Text
File. | | g. | In the File name box, type
StdDLL.def, and then click
OK. | | h. | Add the following text to the StdDLL.def file: LIBRARY StdDLL
EXPORTS |
|
Back to the top
Create a Visual Basic project
To call functions in a DLL, you can use a Visual Basic
application. To create a Visual Basic application project, follow these steps:
| 1. | In Visual Basic 6.0, create a new Standard EXE project. By
default, a form that is named Form1 is created. |
| 2. | Add a CommandButton object to the Form1 form. By default, the Command1 object is created. |
| 3. | In the Project Explorer, right-click
Form1, and then click View Code. |
| 4. | Add the following code to Form1: Option Base 0
Option Explicit |
| 5. | In the Project Explorer, right-click
Project1, point to Add, and then click
Module. The Add Module dialog box
appears. |
| 6. | Under New, click Module,
and then click Open. By default, the Module1 module is created. |
Back to the top
Pass arrays without
using type libraries
In Visual Basic, an array is stored as a SAFEARRAY. A SAFEARRAY
is a structure that contains information about an array such as the number of
dimensions and the size of each element. In C or in C++, an array name is a
pointer to the memory location that contains the first element of the array.
Visual Basic permits only valid access to arrays. However, in C or in
C++, you are responsible for permitting only valid access to
arrays.
SAFEARRAYs
The definition of a SAFEARRAY varies, depending on the operating
system that you are using. The following SAFEARRAY structure is a typical,
generic definition of a SAFEARRAY:
typedef struct FARSTRUCT tagSAFEARRAY {
// Count of dimensions in this array.
unsigned short cDims;
// Flags that are used by the SafeArray routines that are documented later in the definition.
unsigned short fFeatures;
// Size of an element of the array. Does not include size of the data that is pointed to.
#if defined(WIN32) unsigned long cbElements;
// Number of times the array has been locked without corresponding unlock.
unsigned long cLocks;
#else unsigned short cbElements;
unsigned short cLocks;
// Used on Macintosh only.
unsigned long handle;
// Pointer to the data.
#endif void HUGEP* pvData;
// One bound for each dimension.
SAFEARRAYBOUND rgsabound[1];
} SAFEARRAY; Operating systems such as Microsoft Windows 2000 and Windows XP use the
whole Win32 API. Therefore, on a computer that is running Windows 2000 or
Windows XP, after you process conditional directives, you may define SAFEARRAY
in the Oaidl.h file as in the following sample code:
typedef struct tagSAFEARRAY {
USHORT cDims;
USHORT fFeatures;
ULONG cbElements;
ULONG cLocks;
PVOID pvData;
SAFEARRAYBOUND rgsabound[ 1 ];
} SAFEARRAY; In the Oaidl.h file, SAFEARRAYBOUND is a structure that is defined as
in the following sample code:
typedef struct tagSAFEARRAYBOUND {
unsigned long cElements;
long lLbound;
} SAFEARRAYBOUND; The Oaidl.h file also contains prototypes for functions that you can
use to access SAFEARRAYs.
Pass an array to a C
function or to a C++ function that expects a pointer
Typically, if a function was written in C or in C++, and the
function was not specifically designed to be called from Visual Basic, the
function expects a pointer to the first element of the array when you pass an
array to the function from Visual Basic.
In Visual Basic, you can
call a C function or a C++ function that expects a pointer to the first element
of the array by passing the first element of the array by reference. Because
the C function or the C++ function cannot determine the size of the array, this
type of function typically will accept a second parameter that contains the size
of the array.
To call a function that expects a pointer to the first
element of the array, follow these steps:
| 1. | Switch to Visual C++ 6.0. |
| 2. | In the File View, expand Source Files.
|
| 3. | Right-click StdDLL.cpp, and then click
Open. |
| 4. | Add the following code after the DllMain function: long __declspec (dllexport) __stdcall Func1(long *pnFirstElement, long lSize) {
return 1;
} |
| 5. | In the File View, right-click StdDLL.def,
and then click Open. |
| 6. | Append the following text to the existing text in the
StdDLL.def file: Func1 |
| 7. | Switch to Visual Basic 6.0. |
| 8. | Append the following code to the existing code in the Module1 module: Declare Function Func1 Lib"C:\StdDLL\Debug\StdDLL.dll" (FirstElement As Long, ByVal Size As Long) As Long |
| 9. | In the Project Explorer, right-click
Form1, and then click View Object.
|
| 10. | Double-click Command1 to view the Command1_Click event procedure. |
| 11. | Append the following code to the existing code in the Command1_Click event procedure: Dim Result As Long
Dim MyArrayOfLongs(3) As Long
MyArrayOfLongs(0) = 0
MyArrayOfLongs(1) = 1
MyArrayOfLongs(2) = 2
MyArrayOfLongs(2) = 3
Result = Func1(MyArrayOfLongs(0), 4)
MsgBox ("Func1 returned " & Result) |
Note You cannot use earlier steps to pass an array of Strings or to
pass an array of user-defined data types if one or more of the user-defined
data types contains Strings.
Pass an array to a C
function or to a C++ function that expects a SAFEARRAY
Some C functions and some C++ functions are written specifically
to be called from Visual Basic. If you want to pass an array between Visual
Basic and one of these functions, use the SAFEARRAY structure in the prototype
of the C function or in the prototype of the C++ function.
The
advantages of using the SAFEARRAY structure in the prototype of the C function
or in the prototype of the C++ function are the following:
| • | Because Visual Basic stores arrays as SAFEARRAYs, your
Visual Basic application can call the C function or the C++ function as if the
function were written in Visual Basic. |
| • | Because Visual Basic permits only valid access to arrays,
when you use the SAFEARRAY structure in the prototype of the C function or in
the prototype of the C++ function, your C function or your C++ function will
have valid access to information in the SAFEARRAY structure. |
| • | Because the SAFEARRAY structure contains the size of the
array, you do not have to pass an additional parameter to the C function or to
the C++ function. |
To call a C++ function that uses the SAFEARRAY structure,
follow these steps:
| 1. | Switch to Visual C++ 6.0. |
| 2. | In the File View, right-click StdDLL.cpp,
and then click Open. |
| 3. | Add the following code after the Func1 function: long __declspec (dllexport) __stdcall Func2(SAFEARRAY **ppsaMyArray) {
return 2;
} |
| 4. | In the File View, right-click StdDLL.def,
and then click Open. |
| 5. | Append the following text to the existing text in the
StdDLL.def file: Func2 |
| 6. | Switch to Visual Basic 6.0. |
| 7. | In the Project Explorer, right-click
Module1, and then click View Code.
|
| 8. | Append the following code to the existing code in the Module1 module:Declare Function Func2 Lib"C:\StdDLL\Debug\StdDLL.dll" (MyArray() As Long) As Long |
| 9. | In the Project Explorer, right-click
Form1, and then click View Code. |
| 10. | Append the following code to the existing code in the Command1_Click event procedure:Result = Func2(MyArrayOfLongs())
MsgBox ("Func2 returned" & Result) |
Note Because Visual Basic passes a SAFEARRAY as a pointer to a
pointer, you must use SAFEARRAY ** in the prototype of the
Func2 function.
Also, you must lock the array before you
access it, and then you must release the array after you are finished using
it.
Back to the top
Pass strings without
using type libraries
A string is a sequence of continuous characters. A BSTR is also
known as a basic string or as a binary string.
Strings
To pass strings between Visual Basic and C or between Visual
Basic and C++, it is a good idea to understand the different methods that these
programming languages use to represent characters. It is also a good idea to
understand how these programming languages store strings.
The
American National Standards Institute system (ANSI) and the Unicode system
(Unicode) are two systems to represent characters. ANSI uses one byte to store
characters. Unicode uses two bytes to store characters.
Although
Visual Basic uses Unicode internally, Visual Basic converts Unicode characters
to ANSI equivalents while passing strings to C or to C++. Visual Basic converts
ANSI characters to Unicode equivalents while accepting strings from C or from
C++.
However, if you use type libraries to pass strings between
Visual Basic and C or between Visual Basic and C++, Visual Basic does not
convert the characters because type libraries are based on OLE. OLE uses only
Unicode.
Visual Basic stores a string as a BSTR. Essentially, a BSTR
is a pointer to a Unicode string. However, this string is prefixed by the
length of the string and is followed by a NULL character.
C and C++
store a string as an array of characters. In C or in C++, the name of the
character array that is used to store a string is a pointer to a memory
location. This memory location contains the first character of the string.
Therefore, you can use a pointer to an ANSI string by using the following
declaration:
char *pcharMyANSIString;
You can also use a pointer to point to a Unicode string by using the
following declaration:
wchar_t *pwcharMyUNICODEString;
However, in C or in C++, you must know the size of the string or you
must use a delimiter to mark the end of the string. C and C++ use the NULL
character as a delimiter. The NULL character is the ASCII value of zero. ANSI
strings use one zero byte as a delimiter and Unicode strings use two zero bytes
as a delimiter.
BSTRs
The following line of code defines a BSTR:
typedef OLECHAR FAR* BSTR;
In this definition, if you replace the intermediate definitions from
Windows header files and from OLE header files, BSTR is defined the following:
typedef wchar_t* BSTR;
Therefore, a BSTR appears to be a pointer to a Unicode character.
A variable of type BSTR has three parts:
| • | The actual Unicode string |
| • | A 4-byte value that stores the length of the Unicode
string
Note This value prefixes the Unicode string. |
| • | A 2-byte NULL character that is a delimiter
Note The delimiter is stored immediately after the Unicode string. The
delimiter is not considered to be part of the string. The delimiter does not
contribute to the length of the string. |
A BSTR that has
n characters
occupies 2*
n + 6 bytes of storage:
| • | 4 bytes for the value that stores the length of the
string |
| • | 2*n bytes for the
string |
| • | 2 bytes for the delimiter |
Also, the 4-byte value that stores the length of the string is
2*
n.
Some important issues to consider
while working with BSTRs include the following:
| • | Unlike typical C strings or C++ strings, you may have NULL
characters as part of your string. Ultimately, the length of the string is
determined by the 4-byte value that is used to store the length of the string.
The length of the string is not determined by the occurrence of the first NULL
character. |
| • | You can directly access the string that is stored in a
BSTR. You can also directly access the 4-byte value that is used to store the
length of the string. However, you must be extremely careful if you try to
modify a BSTR. If you modify a BSTR incorrectly, your application may quit
unexpectedly (crash). |
| • | The Oleauto.h file contains prototypes for functions that
you can use to work with BSTRs. These functions include the SysAllocString function and the SysFreeString function. You can use the functions in the Oleauto.h file to
allocate, to destroy, or to reallocate memory for BSTRs. |
| • | A NULL BSTR is considered to be the equivalent of an empty
BSTR. Therefore, when you write C functions or C++ functions that use BSTRs, it
is a good idea to look for NULL pointers before you use BSTRs. |
Pass a string to a C
function or to a C++ function that expects a pointer to an ANSI
character
Frequently, you must pass a string from Visual Basic to a C
function or to a C++ function that was not specifically intended to be called
from Visual Basic. Because Visual Basic converts Unicode characters to ANSI
equivalents while passing strings to C or to C++, a pointer to an ANSI
character is equivalent to a pointer to a Unicode character before the
character has been converted.
A pointer to a Unicode character is
represented as the following:
wchar_t
Essentially, this is a BSTR. Therefore, you can call functions that
expect a pointer to an ANSI character from Visual Basic by passing a string by
value. However, you must always include a parameter in these functions that
contains the size of the string that you are passing.
If the C
function or the C++ function is going to modify the string, you must allocate
sufficient memory for the modified string before you pass the string to the
function. Also, a C function or a C++ function that modifies a string should
return a value that indicates the size of the modified string.
To
call a function that expects a pointer to an ANSI character, follow these
steps:
| 1. | Switch to Visual C++ 6.0. |
| 2. | In the File View, right-click StdDLL.cpp,
and then click Open. |
| 3. | Add the following code after the Func2 function: long __declspec (dllexport) __stdcall Func3(char *pcharFirstCharacter, long lSize){
return 3;
} |
| 4. | In the File View, right-click StdDLL.def,
and then click Open. |
| 5. | Append the following text to the existing text in the
StdDLL.def file: Func3 |
| 6. | Switch to Visual Basic 6.0. |
| 7. | In the Project Explorer, right-click
Module1, and then click View Code.
|
| 8. | Append the following code to the existing code in the Module1 module: Declare Function Func3 Lib"C:\StdDLL\Debug\StdDLL.dll" (ByVal MyString As String, ByVal Size As Long) As Long |
| 9. | In the Project Explorer, right-click
Form1, and then click View Code. |
| 10. | Append the following code to the existing code in the Command1_Click event procedure: Dim MyString As String
MyString = "Hello"
Result = Func3(MyString, 5)
MsgBox ("Func3 returned " & Result) |
Pass a string by
value
In Visual Basic, if you pass a parameter to a function and you
want to prevent the function from modifying the parameter, pass the parameter
by value. In the following Visual Basic code, you declare the
MyString BSTR as a string that is passed by value from Visual Basic to a C
function or to a C++ function that is named
MyFunc:
Declare Function MyFunc Lib"C:\MyDLL\Debug\MyDLL.dll" (ByVal MyString As String, ByVal Size As Long) As Long
Because
MyString is a BSTR that is passed by value, the C prototype or the C++
prototype of the
MyFunc function is the following:
long MyFunc(BSTR MyString, long lSize);
Because BSTR is a pointer to a wchar_t, you may want to write the prototype
of the
MyFunc function as the following:
long MyFunc(wchar_t *MyString, long lSize);
However, because Visual Basic converts Unicode characters to ANSI
equivalents while passing strings to C or to C++, write the
prototype of the
MyFunc function as the following:
long MyFunc(char *MyString, long lSize);
The
lSize parameter passes the size of the
MyString BSTR from Visual Basic to C or to C++. The implementation for the
MyFunc function is similar to the implementation of the
Func3 function in the "
Pass a string
to a C functions or to a C++ function that expects a pointer to an ANSI
character" section.
Pass a string by
reference
In Visual Basic, if you pass a parameter to a function, and you
want to permit the function to modify the parameter, pass the parameter by
reference. In the following Visual Basic code, you declare the
MyString BSTR as a string that is passed by reference from Visual Basic to
a C function or to a C++ function that is named
Func4:
Declare Function Func4 Lib"C:\StdDLL\Debug\StdDLL.dll" (MyString As String, ByVal Size As Long) As Long
Because
MyString is a BSTR that is passed by reference, if you want to modify the
passed string, you must make the
Func4 function accept a pointer to a BSTR. The prototype of the
Func4 is the following:
long Func4(BSTR *MyString, long lSize)
Because BSTR is a pointer to a wchar_t, you may want to write the prototype
of the
Func4 function as the following:
long Func4(wchar_t **MyString, long lSize)
However, because Visual Basic converts Unicode characters to ANSI
equivalents while passing strings to C or to C++, write the
prototype of the
Func4 function as the following:
long Func4(char **MyString, long lSize)
To call the
Func4 function, follow these steps:
| 1. | Switch to Visual C++ 6.0. |
| 2. | In the File View, right-click StdDLL.cpp,
and then click Open. |
| 3. | Add the following code after the Func3 function: long __declspec (dllexport) __stdcall Func4(char **ppcharFirstCharacter, long lSize){
return 4;
} |
| 4. | In the File View, right-click StdDLL.def,
and then click Open. |
| 5. | Append the following text to the existing text in the
StdDLL.def file: Func4 |
| 6. | Switch to Visual Basic 6.0. |
| 7. | In the Project Explorer, right-click
Module1, and then click View Code.
|
| 8. | Append the following code to the existing code in the Module1 module: Declare Function Func4 Lib"C:\StdDLL\Debug\StdDLL.dll" (MyString As String, ByVal Size As Long) As Long |
| 9. | In the Project Explorer, right-click
Form1, and then click View Code. |
| 10. | Append the following code to the existing code in the Command1_Click event procedure: Result = Func4(MyString, 5)
MsgBox ("Func4 returned" & Result) |
Note This type of function must include a parameter that contains the
size of the string that is being passed. This type of function returns a value
that indicates the size of the modified string.
Back to the top
Pass strings without Unicode-to-ANSI conversion
In Visual Basic, an array of strings is a SAFEARRAY, and every
element is a BSTR. If you do not use type libraries when you pass SAFEARRAYs
between Visual Basic and C or between Visual Basic and C++, you must use the
following methods to prevent Unicode-to-ANSI conversion.
Helper
functions
Typically, you can prevent Unicode-to-ANSI conversion by using
built-in helper functions that exist in Visual Basic.
The
VarPtr function returns the address of a variable. However, you cannot
use the
VarPtr function to return the address of an array.
The
StrPtr function returns the address of a Unicode string. The
StrPtr function is used to distinguish between an empty string (
"") and a NULL string (
vbNullString). The function
StrPtr("") returns the address of the memory location where the empty string
is stored. However, the
StrPtr(vbNullString) function returns zero.
The difference between the
VarPtr function and the
StrPtr function is especially important if the variable that is being
passed to these functions is a string. If the variable is a string, these
functions return the following:
| • | The StrPtr function returns the address of the Unicode string that is part
of the BSTR variable. |
| • | The VarPtr function returns the address of the BSTR variable. |
Therefore, the
StrPtr function returns a pointer to the Unicode string and the
VarPtr function returns a pointer to the pointer that the
StrPtr function returns.
There is no built-in Visual Basic
function that returns the address of an array of strings or the address of an
array of user-defined types that contains strings. However, you can do this by
using the
VarPtr function that is defined in the Msvbvm60.dll file.
To
use the
VarPtr function that is defined in the Msvbvm60.dll file, create a type
library that contains corresponding declarations. If you declare the
VarPtr function in a type library, Unicode-to-ANSI conversion does not
occur because the function call uses the type library.
To use the
VarPtr function to return the address of an array of strings or to
return the address of an array of user-defined types that contains strings,
follow these steps:
| 1. | In a text editor such as Notepad, paste the following
code: #define RTCALL _stdcall
[
uuid(C6799410-4431-11d2-A7F1-00A0C91110C3),
lcid (0), version(6.0), helpstring("VarPtrStringArray Support for Visual Basic")
]
library PtrLib
{
importlib ("stdole2.tlb");
[dllname("msvbvm60.dll")]
module ArrayPtr
{
[entry("VarPtr")]
long RTCALL VarPtrStringArray([in] SAFEARRAY (BSTR) *Ptr);
}
} |
| 2. | On the File menu, click
Save. The Save As dialog box
appears. |
| 3. | In the File name box, type
VBptrlib.odl. |
| 4. | In the Save as type list, select
All Files, and then click Save. |
| 5. | Open Command Prompt window. |
| 6. | Change the directory path to the location where you saved
the VBptrlib.odl file in step 4. |
| 7. | Create a type library. To do this, type the following
command at the command prompt, and then press ENTER: MIDL VBptrlib.odl |
| 8. | Switch to Visual Basic 6.0. |
| 9. | On the Project menu, click
References. |
| 10. | Locate and then click VarPtrStringArray Support
for Visual Basic. |
| 11. | Click OK to add a reference the type
library that you created in step 7. |
Pass a string to a C
function or to a C++ function that expects a pointer to a Unicode
character
Consider a C function or a C++ function that has the following
prototype:
long __declspec (dllexport) __stdcall Func5(wchar_t *pwcharFirstCharacter, long lSize);
In the equivalent Visual Basic declaration, you must pass the address
of a Unicode string by value. You can use the
StrPtr function to obtain this address.
To call the
Func5 function from Visual Basic, follow these steps:
| 1. | Switch to Visual C++ 6.0. |
| 2. | In the File View, right-click StdDLL.cpp,
and then click Open. |
| 3. | Add the following code after the Func4 function: long __declspec (dllexport) __stdcall Func5(wchar_t *pwcharFirstCharacter, long lSize){
return 5;
} |
| 4. | In the File View, right-click StdDLL.def,
and then click Open. |
| 5. | On a new line, append the following text to the existing
text in the StdDLL.def file: Func5 |
| 6. | Switch to Visual Basic 6.0. |
| 7. | In the Project Explorer, right-click
Module1, and then click View Code.
|
| 8. | Append the following code to the existing code in the Module1 module: Declare Function Func5 Lib"C:\StdDLL\Debug\StdDLL.dll" (ByVal AddressOfFirstCharacter As Long, ByVal Size As Long) As Long |
| 9. | In the Project Explorer, right-click
Form1, and then click View Code. |
| 10. | Append the following code to the existing code in the Command1_Click event procedure: Result = Func5(StrPtr(MyString), 5)
MsgBox ("Func5 returned" & Result) |
Pass a string to a C
function or to a C++ function that expects a BSTR
Consider a C function or a C++ function that has the following
prototype:
long __declspec (dllexport) __stdcall Func6(BSTR bstrMyString);
A BSTR is essentially a pointer to a
wchar_t type. Therefore, in the equivalent Visual Basic declaration, you
must pass the address of a Unicode string by value. You can use the
StrPtr function to obtain this address.
To call the
Func6 function from Visual Basic, follow these steps:
| 1. | Switch to Visual C++ 6.0. |
| 2. | In the File View, right-click StdDLL.cpp,
and then click Open. |
| 3. | Add the following code after the Func5 function: long __declspec (dllexport) __stdcall Func6(BSTR bstrMyString){
return 6;
} |
| 4. | In the File View, right-click StdDLL.def,
and then click Open. |
| 5. | Append the following text to the existing text in the
StdDLL.def file: Func6 |
| 6. | Switch to Visual Basic 6.0. |
| 7. | In the Project Explorer, right-click
Module1, and then click View Code. |
| 8. | Append the following code to the existing code in the Module1 module: Declare Function Func6 Lib"C:\StdDLL\Debug\StdDLL.dll" (ByVal AddressOfFirstCharacter As Long) As Long |
| 9. | In the Project Explorer, right-click
Form1, and then click View Code. |
| 10. | Append the following code to the existing code in the Command1_Click event procedure: Result = Func6(StrPtr(MyString))
MsgBox ("Func6 returned" & Result) |
Pass a string to a C
function or to a C++ function that expects a pointer to a
BSTR
Consider a C function or a C++ function that has the following
prototype:
long __declspec (dllexport) __stdcall Func7(BSTR *bstrMyString);
In the equivalent Visual Basic declaration, you must pass the address
of a BSTR variable by value. You can use the
VarPtr function to obtain this address.
To call the
Func7 function from Visual Basic, follow these steps:
| 1. | Switch to Visual C++ 6.0. |
| 2. | In the File View, right-click StdDLL.cpp,
and then click Open. |
| 3. | Add the following code after the Func6 function: long __declspec (dllexport) __stdcall Func7(BSTR *bstrMyString){
return 7;
} |
| 4. | In the File View, right-click StdDLL.def,
and then click Open. |
| 5. | On a new line, append the following text to the existing
text in the StdDLL.def file: Func7 |
| 6. | Switch to Visual Basic 6.0. |
| 7. | In the Project Explorer, right-click
Module1, and then click View Code. |
| 8. | Append the following code to the existing code in the Module1 module:Declare Function Func7 Lib"C:\StdDLL\Debug\StdDLL.dll" (ByVal AddressOfStringVariable As Long) As Long |
| 9. | In the Project Explorer, right-click
Form1, and then click View Code. |
| 10. | Append the following code to the existing code in the Command1_Click event procedure: Result = Func7(VarPtr(MyString))
MsgBox ("Func7 returned" & Result) |
Pass an array of
strings to a C function or to a C++ function that expects a
SAFEARRAY
Consider a C function or a C++ function that has the following
prototype:
long __declspec (dllexport) __stdcall Func8(SAFEARRAY **ppsaMyArray);
In the equivalent Visual Basic declaration, you must pass the address
of a SAFEARRAY by value. You can use the
VarPtrStringArray function to obtain this address.
To call the
Func8 function from Visual Basic, follow these steps:
| 1. | Switch to Visual C++ 6.0. |
| 2. | In the File View, right-click StdDLL.cpp,
and then click Open. |
| 3. | Add the following code after the Func7 function: long __declspec (dllexport) __stdcall Func8(SAFEARRAY **ppsaMyArray){
return 8;
} |
| 4. | In the File View, right-click StdDLL.def,
and then click Open. |
| 5. | Append the following text to the existing text in the
StdDLL.def file: Func8 |
| 6. | Switch to Visual Basic 6.0. |
| 7. | In the Project Explorer, right-click
Module1, and then click View Code. |
| 8. | Append the following code to the existing code in the Module1 module:Declare Function Func8 Lib"C:\StdDLL\Debug\StdDLL.dll" (ByVal AddressOfArrayOfStrings As Long) As Long |
| 9. | In the Project Explorer, right-click
Form1, and then click View Code. |
| 10. | Append the following code to the existing code in the Command1_Click event procedure: Dim MyArrayOfStrings(2) As String
MyArrayOfStrings(0) = "One"
MyArrayOfStrings(1) = "Two"
MyArrayOfStrings(2) = "Three"
Result = Func8(VarPtrStringArray(MyArrayOfStrings()))
MsgBox ("Func8 returned " & Result) |
Pass a user-defined
data type that contains strings to a C function or to a C++ function that expects
a pointer to a structure
Consider a C function or a C++ function that has the following
prototype:
long __declspec (dllexport) __stdcall Func9(MYSTRUCTURE *pstructMyStruct)
In this prototype, the
MYSTRUCTURE structure is defined as the following:
typedef struct{
long MyLong;
wchar_t MyUNICODEString[5];
}MYSTRUCTURE; In the equivalent Visual Basic declaration, you must pass the address
of a user-defined data type variable by value. You can use the
VarPtr function to obtain this address.
To call the
Func9 function from Visual Basic, follow these steps:
| 1. | Switch to Visual C++ 6.0. |
| 2. | In the File View, right-click StdDLL.cpp,
and then click Open. |
| 3. | Add the following code after the Func8 function: typedef struct{
long MyLong;
wchar_t MyUNICODEString[5];
}MYSTRUCTURE;
long __declspec (dllexport) __stdcall Func9(MYSTRUCTURE *pstructMyStruct){
return 9;
} |
| 4. | In the File View, right-click StdDLL.def,
and then click Open. |
| 5. | On a new line, append the following text to the existing
text in the StdDLL.def file: Func9 |
| 6. | Switch to Visual Basic 6.0. |
| 7. | In the Project Explorer, right-click
Module1, and then click View Code. |
| 8. | Append the following code to the existing code in the Module1 module:Type MYSTRUCTURE
MyLong As Long
MyString As String * 5
End Type
Declare Function Func9 Lib "C:\StdDLL\Debug\StdDLL.dll" (ByVal AddressOfStruct As Long) As Long |
| 9. | In the Project Explorer, right-click
Form1, and then click View Code. |
| 10. | Append the following code to the existing code in the Command1_Click event procedure: Dim MyStruct As MYSTRUCTURE
MyStruct.MyLong = 1
MyStruct.MyString = "Hi"
Result = Func9(VarPtr(MyStruct))
MsgBox ("Func9 returned " & Result) |
Pass an array of
user-defined data types that contains strings to a C function or to a C++ function
that expects a pointer to a structure
Consider a C function or a C++ function that has the following
prototype:
long __declspec (dllexport) __stdcall Func10(MYSTRUCTURE *pstructMyStruct, long lSize);
When the Visual Basic array contains strings, you cannot directly pass
the first user-defined data type element of the array by reference because
Unicode-to-ANSI conversion occurs. However, you can use the
VarPtr function to pass the address of the first user-defined data type
element of the array by value.
To call the
Func10 function from Visual Basic, follow these steps:
| 1. | Switch to Visual C++ 6.0. |
| 2. | In the File View, right-click StdDLL.cpp,
and then click Open. |
| 3. | Add the following code after the Func9 function: long __declspec (dllexport) __stdcall Func10(MYSTRUCTURE *pstructMyStruct, long lSize){
return 10;
} |
| 4. | In the File View, right-click StdDLL.def,
and then click Open. |
| 5. | Append the following text to the existing text in the
StdDLL.def file: Func10 |
| 6. | On the Build menu, click Build
StdDLL.dll. |
| 7. | On the File menu, click Close
Workspace.
Note If you receive a message to save files or to close document
windows, click Yes. |
| 8. | Switch to Visual Basic 6.0. |
| 9. | In the Project Explorer, right-click
Module1, and then click View Code. |
| 10. | Append the following code to the existing code in the Module1 module:Declare Function Func10 Lib"C:\StdDLL\Debug\StdDLL.dll" (ByVal AddressOfFirstStruct As Long, ByVal Size As Long) As Long |
| 11. | In the Project Explorer, right-click
Form1, and then click View Code. |
| 12. | Append the following code to the existing code in the Command1_Click event procedure: Dim MyArrayOfStructs(1) As MYSTRUCTURE
MyArrayOfStructs(0).MyLong = 0
MyArrayOfStructs(0).MyString = "Zero"
MyArrayOfStructs(1).MyLong = 1
MyArrayOfStructs(1).MyString = "One"
Result = Func10(VarPtr(MyArrayOfStructs(0)), 2)
MsgBox ("Func10 returned " & Result) |
Back to the top
Create an ATL DLL
DLLs that are created by using ATL contain a type library. When
you use type libraries, you do not have to declare references to C functions or
to C++ functions that you must call from Visual Basic. Visual Basic obtains all
the information that it requires from the type libraries. Also, when you use type
libraries, you can call C functions or C++ functions from Visual Basic as
though the functions were written in Visual Basic.
To create an ATL
DLL, follow these steps:
| 1. | Switch to Visual C++ 6.0. |
| 2. | On the File menu, click
New. |
| 3. | Under Projects, click ATL COM App
Wizard |
| 4. | In the Project name box, type
ATLDLL, and then click OK. The
ATL COM AppWizard dialog box appears. |
| 5. | Under Server Type, click Dynamic
Link Library, and then click Finish. |
| 6. | In the New Project Information dialog
box, click OK. |
| 7. | In the Class View, right-click ATLDLL
classes, and then click New ATL Object. |
| 8. | Under Category, click
Objects. |
| 9. | Under Objects, click Simple
Object, and then click Next. |
| 10. | In the Short Name box, type
Obj1, and then click OK. |
Pass an array by using
type libraries
When you write a C function or a C++ function specifically to be
called from Visual Basic, and you want to pass an array between Visual Basic
and C or between Visual Basic and C++, use a SAFEARRAY in the prototype of the
C function or in the prototype of the C++ function.
To add a C
function or a C++ function to an ATL DLL so that the function accepts an array
of long values and so that the function can be called from Visual Basic as
though the function were written in Visual Basic, follow these steps.
Note You can follow these steps to add functions to an ATL DLL if you do
not pass an array of strings.
| 1. | In the Class View, expand ATLDLL classes,
right-click IObj1, and then click Add
Method. |
| 2. | In the Method Name box, type
ATL_Func1. |
| 3. | In the Parameters box, type the following,
and then click OK: [in, out] SAFEARRAY **ppsaMyArray, [out, retval] long *plResult |
| 4. | In the Class View, right-click IObj1, and
then click Go to Definition. |
| 5. | In the definition, locate the following code:[id(1), helpstring("method ATL_Func1")] HRESULT ATL_Func1([in, out] SAFEARRAY **ppsaMyArray, [out, retval] long *plResult); |
| 6. | To make the ATL_Func1 function comply with the OLE specifications, replace the code
that you located in step 5 with the following code: [id(1), helpstring("method ATL_Func1")] HRESULT ATL_Func1([in, out] SAFEARRAY (long)*ppsaMyArray, [out, retval] long *plResult);Note In this code, (long) specifies an array of long values. |
| 7. | In the Class View, expand IObj1,
right-click ATL_Func1, and then click Go to
Definition. The contents of the Obj1.cpp file appears. |
| 8. | Locate the following code:// TODO: Add your implementation code here |
| 9. | Paste the following code after the code that you located in
step 8: *plResult = 11; |
| 10. | Switch to Visual Basic 6.0. |
| 11. | In the Project Explorer, right-click
Form1, and then click View Code.
|
| 12. | Append the following code to the existing code in the Command1_Click event procedure:Dim MyObj As New Obj1
Result = MyObj.ATL_Func1(MyArrayOfLongs())
MsgBox ("ATL_Func1 returned " & Result) |
Note Because Visual Basic passes a SAFEARRAY as a pointer to a
pointer, use SAFEARRAY ** in the prototype of the
Func2 function.
Pass a string by value
by using type libraries
When you write a C function or a C++ function specifically to be
called from Visual Basic, and you want to pass a string between Visual Basic
and C or between Visual Basic and C++, and you want to prevent the C function
or the C++ function from modifying the string, pass the string by value and use
BSTR in the prototype of the C function or in the prototype of the C++
function.
To add a function to an ATL DLL so that the function accepts
a BSTR and so that the function can be called from Visual Basic as though the
function were written in Visual Basic, follow these steps:
| 1. | Under ATLDLL classes in the Class View,
right-click IObj1, and then click Add
Method. |
| 2. | In the Method Name box, type
ATL_Func2. |
| 3. | In the Parameters box, type the following,
and then click OK: [in] BSTR bstrMyString, [out, retval] long *plResult |
| 4. | In the implementation of the ATL_Func2 function in the Obj1.cpp file, locate the following code:// TODO: Add your implementation code here |
| 5. | Paste the following code after the code that you located in
step 4: *plResult = 12; |
| 6. | Switch to Visual Basic 6.0. |
| 7. | In the Project Explorer, right-click
Form1, and then click View Code.
|
| 8. | Append the following code to the existing code in the Command1_Click event procedure: Result = MyObj.ATL_Func2(MyString)
MsgBox ("ATL_Func2 returned " & Result) |
Note f you add a project reference to the type library for your ATL DLL, the type library is used for all calls from Visual Basic to the functions
that are declared in the ATLDLL.dll file. Therefore, no Unicode-to-ANSI
conversion occurs.
Pass a string by
reference by using type libraries
When you write a C function or a C++ function specifically to be
called from Visual Basic, and you want to pass a string between Visual Basic
and C or between Visual Basic and C++, and you want to permit the C function or
the C++ function to modify the string, pass the string by reference and use
BSTR * in the prototype of the C function or in the prototype of the C++
function.
To add a function to an ATL DLL so that the function
accepts a pointer to a BSTR and so that the function can be called from Visual
Basic as though the function were written in Visual Basic, follow these steps:
| 1. | Under ATLDLL classes in the Class View,
right-click IObj1, and then click Add
Method. |
| 2. | In the Method Name box, type
ATL_Func3. |
| 3. | In the Parameters box, type the following,
and then click OK: [in, out] BSTR *pbstrMyString, [out, retval] long *plResult |
| 4. | In the implementation of the ATL_Func3 function in the Obj1.cpp file, locate the following code: // TODO: Add your implementation code here |
| 5. | Paste the following code after the code that you located in
step 4: *plResult = 13; |
| 6. | Switch to Visual Basic 6.0. |
| 7. | In the Project Explorer, right-click
Form1, and then click View Code.
|
| 8. | Append the following code to the existing code in the Command1_Click event procedure: Result = MyObj.ATL_Func3(MyString)
MsgBox ("ATL_Func3 returned " & Result) |
Pass an array of
strings by using type libraries
When you write a C function or a C++ function specifically to be
called from Visual Basic, and you want to pass an array of strings between
Visual Basic and C or between Visual Basic and C++, use a SAFEARRAY in the
prototype of the C function or in the prototype of the C++ function.
To add a function to an ATL DLL so that the function accepts an array
of strings and so that the function can be called from Visual Basic as though
the function were written in Visual Basic, follow these steps:
| 1. | Under ATLDLL classes in the Class View,
right-click IObj1, and then click Add
Method. |
| 2. | In the Method Name box, type
ATL_Func4. |
| 3. | In the Parameters box, type the
following, and then click OK: [in, out] SAFEARRAY **ppsaMyArray, [out, retval] long *plResult |
| 4. | In the implementation of the ATL_Func4 function in the Obj1.cpp file, locate the following code: // TODO: Add your implementation code here |
| 5. | Paste the following code after the code that you located in
step 4: *plResult = 14; |
| 6. | Under ATLDLL classes in the Class View,
right-click IObj1, and then click Go to
Definition. |
| 7. | Locate the following code: [id(4), helpstring("method ATL_Func4")] HRESULT ATL_Func4([in, out] SAFEARRAY **ppsaMyArray, [out, retval] long *plResult); |
| 8. | To make the ATL_Func4 function comply with the OLE specifications, replace the code
that you located in step 7 with the following code: [id(4), helpstring("method ATL_Func4")] HRESULT ATL_Func4([in, out] SAFEARRAY (BSTR)*ppsaMyArray, [out, retval] long *plResult);Note In this code, (BSTR) specifies an array of strings. |
| 9. | Switch to Visual Basic 6.0. |
| 10. | In the Project Explorer, right-click
Form1, and then click View Code.
|
| 11. | Append the following code to the existing code in the Command1_Click event procedure: Result = MyObj.ATL_Func4(MyArrayOfStrings())
MsgBox ("ATL_Func4 returned " & Result) |
Back to the top
Verify that your
project works
To verify that your Visual Basic project works, follow these
steps:
| 1. | Switch to Visual C++ 6.0. |
| 2. | On the Build menu, click Build
ATLDLL.dll. |
| 3. | Switch to Visual Basic 6.0. |
| 4. | On the Project menu, click
References. |
| 5. | Locate and then click ATLDLL 1.0 Type
Library, and then click OK. |
| 6. | On the Run menu, click
Start. By default, a form that is named Form1 is
created. |
| 7. | In the Form1 form, click Command1. You
may notice a series of messages that contain the return values of the C
functions or of the C++ functions. |
| 8. | Click OK to close each message box. When
you close each message box, the next message box appears. |
Back to the top
Download the sample code
Sample code that uses the concepts that are mentioned in this
article is available in the Microsoft Download Center.
The following file is available for
download from the Microsoft Download Center:
For
additional information about how to download Microsoft Support files, click the
following article number to view the article in the Microsoft Knowledge Base:
119591 (http://support.microsoft.com/kb/119591/) How to Obtain Microsoft Support Files from Online Services
Microsoft scanned this file for viruses. Microsoft used the most
current virus-detection software that was available on the date that the file
was posted. The file is stored on security-enhanced servers that help to
prevent any unauthorized changes to the file.
Download this file and unzip all the files.
This .zip file contains the following three folders:
| • | StdDLL - This folder contains a Visual C project to create
a standard C DLL or a standard C++ DLL. |
| • | ATLDLL - This folder contains a Visual C project to create
an ATL DLL. |
| • | VB - This folder contains a Visual Basic project to call
functions in the StdDLL.dll file and in the ATLDLL.dll file. This project file
is named Project1.vbp. |
To use these projects, follow these steps:
| 1. | Build the StdDLL.dll file and the ATLDLL.dll
file. |
| 2. | Change the declarations in the Module1.bas file of the
Project1.vbp project file, to point to the location of the StdDLL.dll
file. |
| 3. | In the Project1.vbp project file, add a reference to the
ATLDLL 1.0 type library that you built in step 1. |
You can now run the Visual Basic application.
Back to the top
Troubleshooting
The following list contains problems that you may experience when
you create the sample project that this article uses:
| • | Symptom When you try to build the StdDLL.dll file, you may receive an
error message that is similar to the following: error
C2065: 'SAFEARRAY' : undeclared identifier Cause This problem may occur if your project has not included the
Ole2.h header file.
Resolution To resolve this problem, locate the following code in the
StdAfx.h file: #include <windows.h> Paste the following code before the code that you located: #define INC_OLE2 |
| • | Symptom When you try to build the ATLDLL.dll file, you may receive
error messages that are similar to the following: error
MIDL2139 : type of the parameter cannot derive from void or void * : [ Type
'PVOID' ( Parameter 'ppsaMyArray' ) ] error MIDL2105 : pointee / array
does not derive any size : [ Field 'rgsabound' of Struct 'tagSAFEARRAY' (
Parameter 'ppsaMyArray' ) ] Cause This problem may occur if your function does not comply with
the OLE specifications.
Resolution To resolve this problem, locate the code that corresponds to
the following code for the function in the ATLDLL.dll file: [id(1), helpstring("method ATL_Func1")] HRESULT ATL_Func1([in, out] SAFEARRAY **ppsaMyArray, [out, retval] long *plResult); Replace the code that you located with code that corresponds to the
following code: [id(1), helpstring("method ATL_Func1")] HRESULT ATL_Func1([in, out] SAFEARRAY (long)*ppsaMyArray, [out, retval] long *plResult); |
| • | Symptom When you click Command1, you may receive an
error message that is similar to the following: Run-time
error '453': Can't find DLL entry point Func1 in
C:\StdDLL\Debug\StdDLL.dll Cause This problem may occur if you have not added a function name
to the StdDLL.def file.
Resolution To resolve this problem, add the missing function name to the
StdDLL.def file. |
Back to the top