Calling convention determines how arguments are passed onto the stack when a function is called and whether the caller or the called function cleans up the stack when the function returns. When using functions written in a different languages (C/C++, Fortran, etc...) with Visual Basic, it is important the dll functions are consistent with the Visual Basic convention. Visual Basic expects functions called from a dll to use the stdcall calling convention. For example in C/C++, you would specify that you want to use stdcall by specifying the identifier _stdcall in the function prototype and definition header. In Fortran, stdcall is the default.

In Visual Basic 5.0, if you pass an argument ByVal by either specifying ByVal in the Declare statement or when calling the procedure, the actual value is passed on the stack.(the exception to the rule being the passing of strings, that are handled differently as detailed in section 5). However, if you don't specify ByVal, a pointer to the value is passed on the stack.

DLLs actually get loaded into memory only when a function in the DLL is called for the first time.

1) Size of data types.
Traditionally, in prior 16 bit versions of Visual Basic, if you needed a byte sized data type, you could use string*1. In Visual Basic 5.0, it is better to use the byte data type since string*1 is a Unicode string that takes 2 bytes.

An integer in Visual Basic 5.0 still occupies 2 bytes. In contrast, an integer in a 32 bit DLL written in C/C++ occupies 4 bytes. If you need a 4 byte integer on the Visual Basic side, use a long data type. If you need a 2 byte integer on the C/C++ side use a short.

2) Calling functions from the Win32 API.
In Win32, DLL procedure names are case-sensitive; In Win16, they
are not. For example, this means that GetSystemMetrics and GETSYSTEMMETICS are distinct function names. However, in this case, only the former is correct as it exists in User32.dll. Now if the normal Declare Statement for this function:

This makes no difference on Win16 as function names are not case
sensitive, but on Win32, a run-time error will occur because
GETSYSTEMMETRICS does not exist in the DLL. To avoid this problem,
it is recommended to use the Alias Clause as follows:

By doing this, you make sure that the name used in the alias is not
affected by a conversion (if any); so regardless of what case is used
for the name in other parts of the code, the declaration will still
refer to the correct procedure name in the DLL.

3) ANSI/UNICODE issues
When converting a 16 bit DLL to a 32 bit DLL, perhaps one of the biggest concerns you will run into are the ANSI/UNICODE issues. When writing a DLL, you can consider an ANSI string as a one-byte-per-character string, and a UNICODE string as a two-byte-per-character string.

Prior versions of Visual Basic 16 bit use either ANSI or DBCS strings. Visual Basic 5.0 uses UNICODE Strings. Because of this, there are some issues you need to be aware of when writing DLLs for use with Visual Basic 5.0.

The 32-bit version of Visual Basic 5.0 maintains UNICODE strings internally. But whenever you pass a string to a DLL, Visual Basic will convert it to ANSI. If you do not want Visual Basic to convert your UNICODE string to ANSI, you should first place the string into a byte array, and then pass the address of the first element of the byte array. Note, however, that this is only true if you are using Declare statements for your DLL. If you create a type library, Visual Basic will use whatever type is indicated by the type library. Please refer to section 11 for more information on type libraries.

4) Exporting functions
Normally when compiling 32-bit DLLs using a Microsoft Visual C++ compiler the _declspec keyword is used with the dllexport modifier attribute to enable you to export functions, data, and objects from a DLL. This attribute explicitly defines the DLL's interface to its client, which can be an executable file or another DLL. Declaring functions as dllexport eliminates the need for a module-definition (.DEF) file, at least with respect to the specification of exported functions. dllexport replaces the _export keyword used earlier with
16-bit DLLs. However, when writing 32-bit DLLs that need to be called from Visual Basic 5.0, you need to export your function via the DEF file, by specifying all the function names with an EXPORTS statement. This is because the use of the stdcall calling convention mangles function names, which is the very nature of a C++ compiler, but VB does not understand mangled names. _declspec just puts it in the export table, but does not unmangle the function name. Even if you use a file with an extension of .C for your source code (so that the standard C compiler is used), you need to export functions using a DEF file.

The functions in this example demonstrate how to pass variables of common VB built-in types like Byte, Integer, Long, Boolean, Single and Double, both by value and by reference to a DLL, as well as how to return them. Each function, takes a variable of the type by value as its first parameter, and another variable of the same type by reference, as its second parameter. It assigns the byVal parameter to the byRef parameter (which is reflected back in VB), and then modifies the byVal parameter and returns it.

Visual Basic maintains variable-length strings internally as BSTRs. BSTRs are defined in the OLE header files as OLECHAR FAR*. An OLECHAR is a UNICODE character in 32-bit OLE and an ANSI character in 16-bit OLE. A BSTR can contain NULL values because a length is also maintained with the BSTR. BSTRs are also NULL terminated so they can be treated as an LPSTR. Currently this length is stored immediately prior to the string. This may change in the future, however, so you should use the OLE APIs to access the string length. A subtle point is if you use a declare statement as opposed to a typelib in VB, the string is NOT coerced into an ANSI string. It is left as a UNICODE string. If you use a declare statement, then the string passed is coerced into an ANSI string. In the below example, please pay special attention to the comments. Please refer to section 10 for more information.

You can pass a string from Visual Basic to a DLL in one of two ways. You can pass it "by value" (ByVal) or "by reference". When you pass a string ByVal, Visual Basic passes a pointer to the beginning of the string data (i.e. it passes a BSTR). When a string is passed by reference, Visual Basic passes a pointer to a pointer to the string data (i.e. it passes a BSTR*). Since VB coerces the UNICODE string to ANSI, it allocates temporary memory to store the ANSI string. VB recopies the UNICODE version of the string back to the passed in variable.

The following table lists what Visual Basic will pass to a DLL function when passing a string.

In Visual Basic 3.0, you could use the Visual Basic API routines to access and modify an HLSTR. In Visual Basic 5.0 you should use the OLE APIs to access a BSTR. The following table lists the Visual Basic 3.0 string-handling APIs, and the OLE equivalents.

NOTE: The BSTR is a pointer to the string, so you don't need to dereference it.

Example
-------

The first function in this example takes a Visual Basic string by reference, and returns an uppercase copy of the string. The second function takes a Visual Basic string by value and also returns an uppercase copy of the string. This is similar to the UCase function in Visual Basic. In both cases, the DLL function modifies the passed string, which is reflected back in VB. This happens even when the VB string is passed "ByVal" because what is passed to the DLL function is a BSTR which is a char far*, and thus, it is possible to directly access the memory buffer pointed to by the BSTR. The DEF file is not included.

//We are planning to return bstrUpperCase if we are using a declare
//statement, we need to use SysAllocStringByteLen instead of SysAllocStringLen
//because of the coercion of ansi back to a unicode string on return of the
//function. See Note #1 below.
bstrUpperCase = SysAllocStringByteLen(NULL, cbOriginalLen);

strSrcByRef = (LPSTR)*pbstrOriginal;
strDst = (LPSTR)bstrUpperCase;

for(i=0; i<=cbOriginalLen; i++)
*strDst++ = toupper(*strSrcByRef++);

SysReAllocString (pbstrOriginal, (BSTR)"Good Bye");

//On return of this function, bstrUpperCase will be coerced back to
//a Unicode string if we are using a declare statement instead of a
//typelib. See Note #1 below.
return bstrUpperCase;
}

//We are planning to return bstrUpperCase if we are using a declare
//statement, we need to use SysAllocStringByteLen instead of SysAllocStringLen
//because of the coercion of ansi back to a unicode string on return of the
//function. See Note #1 below.
bstrUpperCase = SysAllocStringByteLen(NULL, cbOriginalLen);

strSrcByVal = (LPSTR)bstrOriginal;
strDst = (LPSTR)bstrUpperCase;

for(i=0; i<=cbOriginalLen; i++)
*strDst++ = toupper(*strSrcByVal++);

SysReAllocString (&bstrOriginal, (BSTR)"Good Bye");
//On return of this function, bstrUpperCase will be coerced back to
//a Unicode string if we are using a declare statement instead of a
//typelib. See Note #1 below
return bstrUpperCase;
}

The following Visual Basic code calls the above two UpperCase
functions:

Note #1: As an example, if we pass in the string "abs" and we used SysAllocStringLen, then it would allocate a string of length 6. On return of the function, it will coerce the string into Unicode also changing the prefixed length of the BSTR to 12 which is double the amount, therefore we would get a larger string and potential garbage characters. By using SysAllocStringByteLen, it allocates a string of length 3. On return, it coerces the ANSI string back to Unicode string of the proper length 6.

With Visual Basic 5.0, you can pass a user-defined type (UDT) by reference to a function. In addition to this, Visual Basic 5.0 also allows functions to return a user-defined type. You still cannot pass a UDT by value to a DLL function. A user-defined type is returned in the same way it would be with any other function, as demonstrated in code example below.

It is important to make sure that the UDT as defined in Visual Basic is the same size as the C structure defined in the DLL by enforcing a strict one-to-one alignment of corresponding members in both UDTs. This can be a problem because VB5 uses dword (4-byte) alignment; while the DLL uses the "Struct Member Alignment" Compiler Option as specified by the program developer. Thus, an odd-sized string in VB may cause this problem. In a 32-bit Application, it is efficient\optimal to use a 4-byte Struct Member Alignment, which is what VB uses. In the 32 bit dll, please make sure that you select the 4-byte struct member alignment compiler setting.

If you need to call a DLL function that takes a C struct by reference from VB, you should keep in mind a simple rule while designing its counterpart UDT in VB. The rule is that if the size of the struct member is less than the "Struct Member Alignment" size and if the next member can fit in the same alignment size then they will be packed together. Thus, the order in which the members are declared inside a struct is also important. For instance, in the example below, the "byt" member is packed together with the "bln" member because of the above rule and it is followed by a 1 byte padding because the next member which is a VARIANT structure is 16 bytes, which is greater than the 4 byte struct alignment size being used. However, if the location of the "vnt" member and the "strg" member were swapped, then there would be no padding and the 11 character of "strg" would occupy consequent memory locations immediately after the "byt" member. This is because the size of a char is 1 byte which is less than the alignment size of 4. Please refer to section 14 on other references that talk about structure padding.

As already mentioned in section 3, another thing to keep in mind is that an integer in VB is always 2 bytes, whereas an "int" in C is 4 bytes on Win32, but 2 bytes on Win16. So, on Win32, this might pose a problem when using an array of integers in VB. The corresponding array of "int"s in C will take up 2 extra bytes and a huge amount of padding may be required in the VB UDT. Hence a short is used, which is 2 bytes (on both win16 and win32).

If the C type is int then the VB type will have to change from an integer to a long when going from 16 to 32 bits. Otherwise, if short and long are used in C, then the corresponding integer and long types can be used in VB, regardless of the platform.

It is usually preferable to place all odd-sized fields at the end of the user-defined type. This way even-sized fields will be on at least a WORD (2-byte) boundary, and are thus more efficiently accessed.

Example
-------

The function in this example, shows how to pass and return a user-defined type (UDT) containing all possible data types that can be members, from a DLL function called from Visual Basic. It takes a UDT by reference and returns a copy of the UDT to Visual Basic. The DLL function modifies the members of the passed UDT which is reflected back in VB.

NOTE: The Size of the UDT passed to the DLL will be 76 bytes (after all padding. However, the size of the UDT actually stored by VB is 86 bytes, the extra 12 bytes accounting for the fact that the fixed length string member "strg" is stored as a Unicode string, and so the extra 11 bytes for the string and 1 byte for "Unicode padding".

//Remember to set compiler struct member alignment to 4 bytes.
#include <windows.h>

NOTE: In the Visual Basic UDT, the first member is an integer which is 2 bytes in size. The corresponding member in the C struct is a short, whose size is also 2 bytes. Hence there is a padding of 2 bytes following it, as the next member is a long which is 4 bytes and cannot fit in the same "alignment slot". The "bln" and "byt" member are packed together and there is a 1 byte padding after it. Finally, the "strg" member in the VB UDT is a fixed string of size 22 bytes as stored in VB and 11 bytes when passed to the DLL. The next member is an array of integers. Each member of this array which is of size 2 bytes is actually stored inside the UDT. So, there is no padding after the fixed length string as stored in VB as it can be packed with the last Unicode character of the fixed length string according to the rule above; but there is a padding of 1 byte in the passed UDT (because only 1 byte can be accommodated in the current "alignment slot" and the next member is an array element of size 2 bytes).

Private Const MAXSIZE = 11

Private Type UDT
intgr As Integer
lng As Long
sng As Single
dbl As Double
cur As Currency
dtm As Date
bln As Boolean
byt As Byte
vnt As Variant
vstrg As String
strg As String * MAXSIZE
arr(0, 0, 1) As Integer
End Type

You can pass and return objects from a function in Visual Basic. What Visual Basic is actually passing is a pointer to an OLE interface. All OLE interfaces support QueryInterface, so you can use this interface to get to other interfaces you might need. To return an object to Visual Basic, you simply return an interface pointer.

For example, the following ClearObject function will try to execute the Clear method for the object, if it has one. The function will then simply return a pointer to the interface passed in.

When an array is passed to a DLL function, Visual Basic actually passes a pointer to a pointer to a SAFEARRAY structure or an LPSAFEARRAY FAR*. SafeArrays are structures that are used in OLE 2.0. Visual Basic 5.0 uses SafeArrays internally to store arrays.

SafeArrays contain information about the number of dimensions and their bounds. The data referred by an array descriptor is stored in column-major order (i.e. the leftmost dimension changes first), which is the same scheme used by Visual Basic, but different than that used by PASCAL or C. The subscripts for SafeArrays are zero-based.

The OLE 2.0 APIs can be used to access and manipulate the array. For those familiar with the methods VB 3.0 used, the following table lists the Visual Basic APIs used to reference arrays and the OLE 2.0 equivalents.

The OLE API function SafeArrayGetDim returns the number of dimensions in the array, and the functions SafeArrayGetLBound and SafeArrayGetUBound return the lower and upper bounds for a given array dimension. All of these functions require a parameter of type LPSAFEARRAY, in order to describe the target array.

The first function in this example shows how to get the dimension and bound information of a Visual Basic array (of strings) that is passed to a DLL. It also creates a new array and copies an element of the passed in array into the corresponding index location in the new array. It then modifies the string element at this same index location in the original passed in array (which is reflected back in VB). Finally it stores the new array in a Variant and returns the same to VB.

The second function is a shorter version of the first one. It is slightly different in that all the elements from the passed-in array are copied into the new array automatically. In both cases, however, for string arrays, you should first convert the string to Unicode. This is required because VB5 uses Unicode to store strings internally, however it converts them to ANSI on the way in to a DLL. It will normally convert it back to Unicode on the way out of the DLL; but since the string is being copied into an array that has been *NEWLY CREATED INSIDE THE DLL*, VB will not know enough to do the conversion.

This example demonstrates passing and returning arrays of strings. But it can easily be modified to work for arrays of any permitted datatype. The only modifications that have to be made are changing the Declare statements and the VT_XXXX flags to match the appropriate type. Of course, you don't have to worry about Unicode conversions when dealing with non-string data-types.

Example
-------

#include <windows.h>
#include <stdio.h>

#define CCONV _stdcall

// hold the SAFEARRAY pointer to be returned in a Variant
LPSAFEARRAY lpsa1;
LPSAFEARRAY lpsa2;

// Get the value of the string element at (0,1,2). This will
// be an ANSI string.
SafeArrayGetElement (*ppsa, rgIndices, &element);

// Convert this to Unicode, as VB5 will not do
// so for you, as the string is inside an array *NEWLY
// CREATED* inside the DLL!
unsigned int length = SysStringByteLen(element);
BSTR wcElement = NULL;

// Put this Unicode string into the corresponding
// location in the array to be returned in a variant
lpsa1->fFeatures ^= FADF_BSTR;
SafeArrayPutElement (lpsa1, rgIndices, &wcElement);
lpsa1->fFeatures |= FADF_BSTR;

// copy the passed-in array to the array to be returned in
// variant
SafeArrayCopy (*ppsa, &lpsa2);

// Get the value of the string element at (0,1,2). This will
// be an ANSI string.
SafeArrayGetElement (lpsa2, rgIndices, &element);

// Convert this to Unicode, as VB5 will not do
// so for you, as the string is inside an array *NEWLY
// CREATED* inside the DLL!
unsigned int length = SysStringByteLen(element);
BSTR wcElement = NULL;

Visual Basic 2.0 introduced a new data type known as a Variant. Developers were able to use the Visual Basic APIs in a DLL to access and manipulate these Variant types. Visual Basic 5.0 also has a Variant data type, but it uses the OLE 2.0 Variant data type. Therefore you must use the OLE 2.0 API functions to access and manipulate Variants.

The following table lists the Visual Basic 3.0 APIs used to access
Variants, and the OLE 2.0 API equivalents.

Field A contains the Variant type. Fields B, C, and D are reserved for future use, and field E is an 8-byte union that contains any intrinsic type from an Integer to a double-precision floating point number. It can also contain BSTRs, interface pointers, or pointers to one of the intrinsic types.

Example
-------

The first function in this example demonstrates how to pass a variant by reference to a DLL, makes a copy of it, changes the type of the copy to a string, and then returns the copy. The second function does the same thing, but passes the variant by value.

UNICODE is a 16-bit character set capable of encoding all known characters and used as a world-wide character encoding standard. A Unicode string is a two-byte-per character string while an ANSI string is a one-byte-per character string.

Win32 API functions that have string parameters are generally implemented in one of three formats:

- A generic version that can be compiled for either ANSI or UNICODE
- An ANSI version
- A UNICODE version

The generic function prototype consists of the standard function name implemented as a macro, that maps to the appropriate specific version of the function. The letters A (for ANSI version) and W (for wide-character or UNICODE version) are added to the end of the function names in the specific function prototypes.

Windows 95 uses ANSI internally. Windows NT uses Unicode exclusively at the system level. Visual Basic 5.0 maintains Unicode strings internally. Whenever Visual Basic passes a string to a DLL function, it always converts it to ANSI on the way out and back to UNICODE on the way in. You can always call the ANSI version of the APIs from VB on either platform. The only way for VB to call the UNICODE version of the APIs from either platform is to create a type library resource for the DLL, register it in the system registry and then add a reference to it from VB. This will allow you to call the UNICODE version of the API functions without an explicit Declare Statement because Visual Basic will then use whatever type is indicated by the type library. Note that Windows 95 currently does not support the UNICODE version of the APIs. It just has "placeholder" entry points for the UNICODE (W) API functions.

Visual Basic 5.0 will coerce every string passed to a DLL to ANSI on the way out of Visual Basic and back to UNICODE on the way in. In order to force Visual Basic 5.0 to always pass Unicode strings, you can create a type library for the DLL and then add a reference to the typelib from Visual Basic 5.0. This is one good reason to use type libraries instead of declare statements. We can now work with VB's internal data types avoiding the coercion of types. Unfortunately, there is a weakness of a type library. We won't be able to work with fixed length strings as we do with variable length strings. Right now, one workaround is to work with a byte array. Refer to section 13 for more information.

Type libraries are built using a language known as the Object Description Language (ODL). An ODL file is similar to a C file, but contains additional OLE 2.0 specific additions. The ODL file needs to be compiled to a type library (.TLB) file using the MKTYPLIB utility that comes with Microsoft Visual C++.

With Visual Basic, you can make calls into a DLL by declaring the DLL function inside your Visual Basic code. However, it is also possible to make a type library resource for your DLL. The advantage of this technique is that if users register your type library, then they can add a reference to your DLL from within Visual Basic, and call your DLL routines without an explicit Declare statement.

The following code is a sample ODL file you can use to build a 32-bit type library to replace the following Declare statement. Note that the line numbers aren't really part of the ODL file, and are included for reference purposes only.

Line 1: Defines a universally unique identifier (uuid) that will
uniquely define this library on any system. (You need to use
the GUIDGEN utility that ships with the OLE SDK to create
this number.)

Line 2: Allows you to specify a name for your library.

Line 3: Specifies the name of the DLL that contains the functions in
question. The recommended practice,here is *not* to hard-code
a path to the location of the dll. When a function is called
from this DLL, it will be loaded by searching for it in the
standard directories that the LoadLibrary() API uses.

NOTE: While debugging, you can enter a hard-coded name but
each backslash (\) should be prefixed with another backslash:

[dllname("c:\\projects\\oletest.dll")]

Line 4: Allows you to specify the name for the Module. (This will be
the name that shows up in the Object Browser in Visual Basic)

Line 5: You will want to add a line similar to this for each of the
functions you are exporting from the DLL.

NOTE#1: You could also specify an ordinal entry point.
(e.g.: [entry(1)] ...). This gives better performance than
named entry points. However, ordinals should not be used for
Win API calls because the ordinals are different on different
Operating Systems/OS versions.

For complete instructions on creating an ODL file and using the MKTYPLIB utility, see Chapter 7, "Object Description Language," of the OLE 2 Programmer's Guide, Volume 2.

The following example consists of two parts. The first part is to create a ODL file for KERNEL32.DLL in Visual C++ and compile it into a type library. The second part is to register and reference this type library from Visual Basic and then call the Unicode version of the function in the DLL. The API function used for this purpose is GetPrivateProfileStringW (Note the W denoting the UNICODE version).

Note that will work only on Windows NT and not on Windows 95, because Windows 95 does not support the UNICODE version of this API.

Creating The ODL File and compiling the TypeLib
-----------------------------------------------

1. Start Visual C++, Version 2.0 or higher.

2. Choose New from the File menu, select Code\Text from the "New"
dialog box and click the OK button. A code window titled "Text1"
will be created.

5. From the DOS prompt (or the File Manager) run the following
command:

MKTYPLIB /I C:\MSVC20\INCLUDE /win32 /tlb WIDEAPI.TLB WIDEAPI.ODL

Make sure that the MKTYPLIB.EXE utility that you are using is the
32-bit version. You will find it in the C:\MSVC20\BIN\ directory.
If WIDEAPI.ODL is not in the current directory, make sure that you
specify the complete pathname in the above command.

6. The type library WIDEAPI.TLB will be created in the current
directory.

2. Choose References from the Project menu and register the type
library WIDEAPI.TLB by browsing for the file and then clicking the
OK button. You should see "WIDE Windows API Type Library" selected
in the Available References list. Click the OK button.

3. Add the following code to the Form_Click event of Form1:

Dim i As Long
Dim sRet As String
Dim sSection As String
Dim sEntry As String
Dim sDefault As String
Dim sFileName As String

Press F5 to run the program. Click on Form1. A message box showing
the number of characters read from the vb.ini file will be displayed.
Subsequently, another message box will display the value of the
requested key from the vb.ini file.

The above examples were written only from the stand point of creating
ANSI versions of a DLL. The following example demonstrates how you
can modify the above code so that you have a common code base for
compiling either the UNICODE or ANSI version of a 32-bit DLL. Note
that only 4 functions (UpperCaseByRef, UpperCaseByVal, ProcessArray
and CopyArray) need to be changed. The rest are also included anyway,
unchanged from their ANSI version. The code for calling these
functions from VB is also the same as before. Only you will have to
comment out all the DECLARE statements.

// comment out the following 2 lines if *NOT* using a TYPELIB, but if
// using DECLARE statements for the DLL functions (i.e if compiling
// the ANSI version of a 32-bit DLL)

// Get the value of the string element at (0,1,2). this will be
// an ANSI string.
SafeArrayGetElement (*ppsa, rgIndices, &element);

#if !defined(UNICODE)
// Convert this to Unicode, as VB5 will not do so
// for you, as the string is inside an array *NEWLY CREATED*
// inside the DLL!
unsigned int length = SysStringByteLen(element);
BSTR wcElement = NULL;

// Put this Unicode string into the corresponding location in
// the array to be returned in a variant
lpsa1->fFeatures ^= FADF_BSTR;
SafeArrayPutElement (lpsa1, rgIndices, &wcElement);
lpsa1->fFeatures |= FADF_BSTR;
#else
// Put the (ANSI) string back into the corresponding location
// in the array to be returned
SafeArrayPutElement (lpsa1, rgIndices, element);
#endif

// copy the passed-in array to the array to be returned in
// variant
SafeArrayCopy (*ppsa, &lpsa2);

// Get the value of the string element at (0,1,2). this will be
// an ANSI string.
SafeArrayGetElement (lpsa2, rgIndices, &element);

#if !defined(UNICODE)
// Convert this to Unicode, as VB5 will not do so
// for you, as the string is inside an array *NEWLY CREATED*
// inside the DLL!
unsigned int length = SysStringByteLen(element);
BSTR wcElement = NULL;

=====================================================================
13: Creating a TypeLib For All The Example Functions In This Document
=====================================================================

You can do away with the requirement for DECLARE statements, if you create a TypeLib for each of the functions exported from the DLL. However, it is important to realize that VB5 will not automatically convert strings from Unicode to ANSI and back to Unicode when not using DECLARE statements. For functions involving strings, you must compile it as a 32-bit Unicode DLL.

This section is a perfect opportunity to introduce an enhancement to VB when calling DLL functions. Previous versions did not allow UDTs as parameter types to DLL functions when using type libraries instead of declare statements. Visual Basic 5.0 now allows a UDT as a valid parameter type. We can also use void* as a valid parameter type in a typelib. Void* is interpreted "as any" on the Visual Basic side. The CopyUDT function declaration below is an example of how to pass a UDT to a DLL function using type libraries. As noted earlier, one drawback with using type libraries currently is we will not be able to avoid the conversion from Unicode to ANSI if we are passing fixed length strings in a UDT to our DLL function. If the UDT contains a fixed length string, then in the type library, one workaround is to use an array of unsigned chars. An array of unsigned chars is interpreted as a byte array on the VB side. Since there is not a great way to copy a string to a byte array, the API function lstrcpy is used to copy strings to byte arrays. The VB function strconv is used to convert the byte array to a string.

The following example demonstrates how to build an ODL file for each
of the above functions: