================================ 7: Passing and Returning Arrays ================================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 4.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. The following table lists the Visual Basic APIs used to reference arrays, and the OLE 2.0 equivalents.Visual Basic API OLE API ------------------------------------------------------------- VBArrayBounds SafeArrayGetLBound/SafeArrayGetUBound VBArrayElement SafeArrayGetElement VBArrayElemSize SafeArrayGetElemsize VBArrayFirstElem N/A VBArrayIndexCount SafeArrayGetDimThe 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, 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 in the 32-bit version of VB4, you should first convert the string to Unicode. This is required because, VB4 (32-bit) 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 is not required when compiling the DLL for 16-bits because, the 16-bit version of VB4 uses ANSI strings internally.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. And of course, you don't have to worry about Unicode conversions when dealing with non-string data-types. 16\32-Bit Example -----------------#include <windows.h> #include <ole2.h> #include <stdio.h>#ifdef _WIN32 #define CCONV _stdcall #define NOMANGLE #else #define CCONV FAR PASCAL _export #define NOMANGLE EXTERN_C #include <stdlib.h> #include <compobj.h> #include <dispatch.h> #include <variant.h> #include <olenls.h> #endif// hold the SAFEARRAY pointer to be returned in a Variant LPSAFEARRAY lpsa1; LPSAFEARRAY lpsa2;NOMANGLE VARIANT CCONV ProcessArray(LPSAFEARRAY FAR *ppsa) { VARIANT vnt; unsigned int i, cdims; char buff[40]; long rgIndices[] = {0,1,2}; BSTR element = NULL; cdims = SafeArrayGetDim(*ppsa); // Must initialize variant first VariantInit(&vnt); // Create an array descriptor if (SafeArrayAllocDescriptor (cdims, &lpsa1) != S_OK) { MessageBox (NULL, "Can't create array descriptor. \n Will return an empty variant", "Error!", MB_OK); lpsa1 = NULL; } // Specify the size and type of array elements if (lpsa1) { lpsa1->cbElements = sizeof(BSTR); lpsa1->fFeatures = FADF_BSTR; } // Get the bound info for passed in array, display it and // store the same in the array to be returned in a variant for (i=1; i <= cdims; i++) { long Lbound, Ubound; SafeArrayGetLBound (*ppsa, i, &Lbound); SafeArrayGetUBound (*ppsa, i, &Ubound);
if (lpsa1) { lpsa1->rgsabound[cdims-i].cElements= Ubound-Lbound+1; lpsa1->rgsabound[cdims-i].lLbound = Lbound; } sprintf (buff, "Index %d: Lbound = %li, Ubound = %li\n", i, Lbound, Ubound); MessageBox (NULL, buff, "SafeArrayInfo from DLL", MB_OK); }
if (!lpsa1) return vnt; // Allocate space for the actual array elements if (SafeArrayAllocData (lpsa1) != S_OK) { MessageBox (NULL, "can't create array elements","Error!", MB_OK); return vnt; } // Get the value of the string element at (0,1,2). This will // be an ANSI string. SafeArrayGetElement (*ppsa, rgIndices, &element); #ifdef _WIN32 // Convert this to Unicode, as VB4 (32-bit) 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;
wcElement = SysAllocStringLen(NULL, length*2); MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,(LPCSTR)element , -1, (LPWSTR)wcElement, length*2); // 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 SysFreeString (element); element = SysAllocString((BSTR)"Good Bye"); // Modify the same element (0,1,2) of the passed-in array SafeArrayPutElement (*ppsa, rgIndices, element); SysFreeString (element); // store the array to be returned in a variant vnt.vt = VT_ARRAY|VT_BYREF|VT_BSTR; vnt.pparray = &lpsa1; return vnt; }NOMANGLE VARIANT CCONV CopyArray(LPSAFEARRAY FAR *ppsa) { VARIANT vnt; BSTR element = NULL; long rgIndices[] = {0,1,2}; // Must initialize variant first VariantInit(&vnt); // 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); #ifdef _WIN32 // Convert this to Unicode, as VB4 (32-bit) 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;
wcElement = SysAllocStringLen(NULL, length*2); MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,(LPCSTR)element , -1, (LPWSTR)wcElement, length*2); // Put this Unicode string back into the corresponding // location in the array to be returned SafeArrayPutElement (lpsa2, rgIndices, wcElement); SysFreeString (wcElement); #else // Put the (ANSI) string back into the corresponding // location in the array to be returned SafeArrayPutElement (lpsa2, rgIndices, element); #endif SysFreeString (element); element = SysAllocString((BSTR)"Hello Again!"); // Modify the same element (0,1,2) of the passed-in array SafeArrayPutElement (*ppsa, rgIndices, element); SysFreeString (element); // store the array to be returned in a variant vnt.vt = VT_ARRAY|VT_BYREF|VT_BSTR; vnt.pparray = &lpsa2; return vnt; } The following Visual Basic code calls the above two array functions: #If Win32 Then Private Declare Function ProcessArray Lib "vb4dll32.dll"(a() As _ String) As Variant Private Declare Function CopyArray Lib "vb4dll32.dll" (a() As _ String) As Variant#Else Private Declare Function ProcessArray Lib "vb4dll16.dll"(a() As _ String) As Variant Private Declare Function CopyArray Lib "vb4dll16.dll" (a() As _ String) As Variant#End If Private Sub ArrayTest() Dim a(4, 5, 6) As String Dim v1 As Variant Dim v2 As Variant a(0, 1, 2) = "Hello!" Print VarType(v1) v1 = ProcessArray(a()) Print VarType(v1) MsgBox v1(0, 1, 2), vbInformation, "Element Value of Array _ Returned In Variant - 1" MsgBox a(0, 1, 2), vbInformation, "Modified Element Value of _ Passed-in Array - 1" v2 = CopyArray(a()) Print VarType(v2) MsgBox v2(0, 1, 2), vbInformation, "Element Value of Array _ Returned In Variant - 2" MsgBox a(0, 1, 2), vbInformation, "Modified Element Value of _ Passed-in Array - 2 "End Sub
to glcs:有没有具体方法可参考?
http://www.freevbcode.com/code/APIByName.zip
7: Passing and Returning Arrays
================================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 4.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. The
following table lists the Visual Basic APIs used to reference arrays,
and the OLE 2.0 equivalents.Visual Basic API OLE API
-------------------------------------------------------------
VBArrayBounds SafeArrayGetLBound/SafeArrayGetUBound
VBArrayElement SafeArrayGetElement
VBArrayElemSize SafeArrayGetElemsize
VBArrayFirstElem N/A
VBArrayIndexCount SafeArrayGetDimThe 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, 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 in the 32-bit version of VB4, you should first
convert the string to Unicode. This is required because, VB4 (32-bit)
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 is not required when
compiling the DLL for 16-bits because, the 16-bit version of VB4 uses
ANSI strings internally.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. And of course, you don't have to worry about Unicode
conversions when dealing with non-string data-types.
16\32-Bit Example
-----------------#include <windows.h>
#include <ole2.h>
#include <stdio.h>#ifdef _WIN32
#define CCONV _stdcall
#define NOMANGLE
#else
#define CCONV FAR PASCAL _export
#define NOMANGLE EXTERN_C
#include <stdlib.h>
#include <compobj.h>
#include <dispatch.h>
#include <variant.h>
#include <olenls.h>
#endif// hold the SAFEARRAY pointer to be returned in a Variant
LPSAFEARRAY lpsa1;
LPSAFEARRAY lpsa2;NOMANGLE VARIANT CCONV ProcessArray(LPSAFEARRAY FAR *ppsa)
{
VARIANT vnt;
unsigned int i, cdims;
char buff[40];
long rgIndices[] = {0,1,2};
BSTR element = NULL; cdims = SafeArrayGetDim(*ppsa); // Must initialize variant first
VariantInit(&vnt); // Create an array descriptor
if (SafeArrayAllocDescriptor (cdims, &lpsa1) != S_OK)
{
MessageBox (NULL, "Can't create array descriptor. \n
Will return an empty variant", "Error!", MB_OK);
lpsa1 = NULL;
} // Specify the size and type of array elements
if (lpsa1)
{
lpsa1->cbElements = sizeof(BSTR);
lpsa1->fFeatures = FADF_BSTR;
} // Get the bound info for passed in array, display it and
// store the same in the array to be returned in a variant
for (i=1; i <= cdims; i++)
{
long Lbound, Ubound; SafeArrayGetLBound (*ppsa, i, &Lbound);
SafeArrayGetUBound (*ppsa, i, &Ubound);
if (lpsa1)
{
lpsa1->rgsabound[cdims-i].cElements= Ubound-Lbound+1;
lpsa1->rgsabound[cdims-i].lLbound = Lbound;
} sprintf (buff, "Index %d: Lbound = %li, Ubound = %li\n",
i, Lbound, Ubound);
MessageBox (NULL, buff, "SafeArrayInfo from DLL", MB_OK);
}
if (!lpsa1)
return vnt; // Allocate space for the actual array elements
if (SafeArrayAllocData (lpsa1) != S_OK)
{
MessageBox (NULL, "can't create array elements","Error!",
MB_OK);
return vnt;
} // Get the value of the string element at (0,1,2). This will
// be an ANSI string.
SafeArrayGetElement (*ppsa, rgIndices, &element); #ifdef _WIN32
// Convert this to Unicode, as VB4 (32-bit) 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;
wcElement = SysAllocStringLen(NULL, length*2);
MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,(LPCSTR)element
, -1, (LPWSTR)wcElement, length*2); // 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 SysFreeString (element);
element = SysAllocString((BSTR)"Good Bye"); // Modify the same element (0,1,2) of the passed-in array
SafeArrayPutElement (*ppsa, rgIndices, element);
SysFreeString (element); // store the array to be returned in a variant
vnt.vt = VT_ARRAY|VT_BYREF|VT_BSTR;
vnt.pparray = &lpsa1; return vnt;
}NOMANGLE VARIANT CCONV CopyArray(LPSAFEARRAY FAR *ppsa)
{
VARIANT vnt;
BSTR element = NULL;
long rgIndices[] = {0,1,2}; // Must initialize variant first
VariantInit(&vnt); // 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); #ifdef _WIN32
// Convert this to Unicode, as VB4 (32-bit) 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;
wcElement = SysAllocStringLen(NULL, length*2);
MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,(LPCSTR)element
, -1, (LPWSTR)wcElement, length*2); // Put this Unicode string back into the corresponding
// location in the array to be returned
SafeArrayPutElement (lpsa2, rgIndices, wcElement);
SysFreeString (wcElement);
#else
// Put the (ANSI) string back into the corresponding
// location in the array to be returned
SafeArrayPutElement (lpsa2, rgIndices, element);
#endif SysFreeString (element);
element = SysAllocString((BSTR)"Hello Again!"); // Modify the same element (0,1,2) of the passed-in array
SafeArrayPutElement (*ppsa, rgIndices, element);
SysFreeString (element); // store the array to be returned in a variant
vnt.vt = VT_ARRAY|VT_BYREF|VT_BSTR;
vnt.pparray = &lpsa2; return vnt;
}
The following Visual Basic code calls the above two array functions:
#If Win32 Then Private Declare Function ProcessArray Lib "vb4dll32.dll"(a() As _
String) As Variant
Private Declare Function CopyArray Lib "vb4dll32.dll" (a() As _
String) As Variant#Else Private Declare Function ProcessArray Lib "vb4dll16.dll"(a() As _
String) As Variant
Private Declare Function CopyArray Lib "vb4dll16.dll" (a() As _
String) As Variant#End If
Private Sub ArrayTest() Dim a(4, 5, 6) As String
Dim v1 As Variant
Dim v2 As Variant a(0, 1, 2) = "Hello!" Print VarType(v1)
v1 = ProcessArray(a())
Print VarType(v1) MsgBox v1(0, 1, 2), vbInformation, "Element Value of Array _
Returned In Variant - 1"
MsgBox a(0, 1, 2), vbInformation, "Modified Element Value of _
Passed-in Array - 1" v2 = CopyArray(a())
Print VarType(v2) MsgBox v2(0, 1, 2), vbInformation, "Element Value of Array _
Returned In Variant - 2"
MsgBox a(0, 1, 2), vbInformation, "Modified Element Value of _
Passed-in Array - 2 "End Sub