| | |
Safe Array
![]() |
Using and Returning SafeArrays
Nothing seems to cause more confusion than the concept of SafeArrays. First, documentation is very sparse. Second, SafeArrays can contain pointers to objects on the system heap (BSTRs) making resource allocation/deallocation critical. You can look at a SafeArray as a Visual Basic Array, which contains information about the array. Alternatively, you can look at a SafeArray as a wrapper class providing high level protected access to an array. In the context of ASP COM programming, the SafeArray is a way to package data for consumption by an ASP script. It is also useful within a COM component that must pass data to an ASP Response object. this article represents my best understanding of SafeArrays and resource allocation, after struggling through the available documentation and source code.
Constructing a SafeArray using SafeArrayPutElement
Returning a SafeArray of VARIANT BSTR
Returning a Two Dimensional SafeArrray
Low Level Construction of a SafeArray of BYTES
Constructing a SafeArray using SafeArrayPutElements
You can put elements in a SafeArray using SafeArrayPutElements. This simplifies data access, but adds overhead to the array construction. A common requirement is to construct a SafeArray of VARIANTs of TYPE VT_BSTR. Such a SafeArray can then be returned to an ASP script as a VARIANT of TYPE VT_ARRAY | VT_VARIANT. This complex structure is required for compatibility with various scripting languages such as Visual Basic Script. Such a construct can also be used by JScript, but with more difficulty.
In the following code sample, I construct a SafeArray of ten elements. The sample converts the element index to an ANSI string using ltoa(). It then uses the ATL conversion macro A2W to convert the ANSI string to a Unicode "wide character" string. Finally, the Unicode string is "converted" to a BSTR by allocating memory for a length prefixed string on the system heap, returning a pointer to the string. This could also be accomplished using A2BSTR, but this macro hides the memory allocation that I am trying to demonstrate.
The important point here is that the BSTR exist in memory independent of the COM component. When returned as an [out, retval] it is the responsibility of the calling script engine to release the system memory holding the actual string data. You can look at a BSTR as calling new() in the COM component and calling delete() in the ASP script. Here is the verbose first pass at the code to create a SafeArray of BSTR VARIANTs:
The key point here, at least according to my reading of the docs, is that SafeArrayPutElement "correctly" copies the VARIANT into the SafeArray, including presumably a deep copy of the BSTR. Any existing element in the SafeArray is released "correctly". To avoid a memory leak, you must call VariantClear() on the source VARIANT to reclaim the source BSTR memory after each PUT.
The class _variant_t is a wrapper class that automates memory management much like CComVariant. Using _variant_t greatly simplifies the above code since the overloaded assignment operator converts a wide character string to a BSTR.
To use the _variant_t class you must add the #include <comdef.h> to the header file. I also eliminated the array of longs in this simplified version. I modeled the following code after Shelley Powers "Developing ASP Components Second Edition".
Returning a SafeArray of VARIANT BSTRs
The following code demonstrates how to return the SafeArray of VARIANTs of type VT_BSTR constructed in the preceding section "Constructing A SafeArray Using SafeArrayPutElements", The following code takes a long as an input parameter and creates a SafeArray of VARIANTS of the corresponding length. The IDL looks like this:
It is important to note the the out VARIANT *pVal is not initialized on entry to the method. If you call a method such as CComVariant.Detach(pVal), the out VARIANT will be cleared. Since the out VARIANT is not yet initialized, it contains random data. Clearing a VARIANT of random data is not recommended! The following code sample also calls the MACROS V_VT and V_ARRAY. These are poorly documented, but available for review in the header file oleauto.h.
Again, I modeled the C++ and ASP code after Shelley Powers "Developing ASP Components Second Edition".
[code]
// ASSERT newVal > 0
STDMETHODIMP CArray::GetArray(int newVal, VARIANT *pVal)
{
// TODO: Add your implementation code here
USES_CONVERSION;
char buffer[20];
HRESULT hr= S_OK;
if (newVal < 1) {
newVal= 1;
}
// Create SafeArray of VARIANT BSTRs
SAFEARRAY *pSA;
SAFEARRAYBOUND aDim[1];
aDim[0].lLbound= 0;
aDim[0].cElements= newVal;
pSA= SafeArrayCreate(VT_VARIANT,1,aDim);
if (pSA != NULL) {
_variant_t vOut;
for (long l= aDim[0].lLbound; l< (aDim[0].cElements + aDim[0].lLbound); l++) {
ltoa(l,buffer,10);
vOut= A2W(buffer);
if (hr= SafeArrayPutElement(pSA, &l, &vOut)) {
SafeArrayDestroy(pSA); // does a deep destroy
return hr;
}
}
}
// return SafeArray as VARIANT
//VariantInit(pVal); // WARNING You must initialize *pVal before calling Detach
V_VT(pVal)= VT_ARRAY | VT_VARIANT; //pVal->vt= VT_ARRAY | VT_VARIANT; // oleauto.h
V_ARRAY(pVal)= pSA; // (pSA may be null) //pVal->parray= pSA; // oleauto.h
return S_OK;
}
You can call the code from VBScript like this:
<%@ LANGUAGE="VBScript" %>
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" content="text/html; charset=iso-8859-1">
<TITLE>Test Array DLL</TITLE>
</HEAD>
<BODY TopMargin="0" Leftmargin="0">
<%
Set objArray= Server.CreateObject("JALArray.Array")
Dim arrayBSTR
Dim x
arrayBSTR= objArray.GetArray(10)
for x= LBound(arrayBSTR) to UBound(arrayBSTR)
Response.write "<BR>"&x&": "&arrayBSTR(x)
next
Set objArray= nothing
%>
</BODY>
</HTML>
Calling the code from JScript is more complicated:
<%@ LANGUAGE="JSCRIPT" %>
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" content="text/html; charset=iso-8859-1">
<TITLE>Test Array DLL</TITLE>
</HEAD>
<BODY TopMargin="0" Leftmargin="0">
<%
var objArray= Server.CreateObject("JALArray.Array") ;
var arrayVariants= new VBArray(objArray.GetArray(10));
var arrayBSTR= arrayVariants.toArray();
for (i= arrayVariants.lbound();i<= arrayVariants.ubound(); i++) {
Response.write("<BR>"+i+": "+arrayBSTR[i]+"");
}
objArray= null;
%>
</BODY>
</HTML>
The preceding JScript code first creates a VB array from the SafeArray. The call toArray() then returns a JScript array from the VB array.
Returning a Two Dimensional Array
Returning a rectangular two dimensional array is not that much different than the preceding code. Here is the two dimensioned version:
// ASSERT newVal > 0
STDMETHODIMP CArray::GetArray(int newVal, VARIANT *pVal)
{
// TODO: Add your implementation code here
USES_CONVERSION;
char buffer[20];
HRESULT hr= S_OK;
if (newVal < 1) {
newVal= 1;
}
// Create SafeArray of VARIANT BSTRs
SAFEARRAY *pSA;
SAFEARRAYBOUND aDim[2]; // two dimensional array
aDim[0].lLbound= 0;
aDim[0].cElements= newVal;
aDim[1].lLbound= 0;
aDim[1].cElements= newVal; // rectangular array
pSA= SafeArrayCreate(VT_VARIANT,2,aDim); // again, 2 dimensions
long aLong[2];
long lSum;
if (pSA != NULL) {
_variant_t vOut;
for (long x= aDim[0].lLbound; x< (aDim[0].cElements + aDim[0].lLbound); x++) {
aLong[0]= x; // set x index
for (long y= aDim[1].lLbound; y< (aDim[1].cElements + aDim[1].lLbound); y++) {
aLong[1]= y; // set y index
lSum= x+y;
ltoa(lSum,buffer,10);
vOut= A2W(buffer);
if (hr= SafeArrayPutElement(pSA, aLong, &vOut)) {
SafeArrayDestroy(pSA); // does a deep destroy
return hr;
}
}
}
}
// return SafeArray as VARIANT
//VariantInit(pVal); // WARNING You must initialize *pVal before calling Detach
V_VT(pVal)= VT_ARRAY | VT_VARIANT; //pVal->vt= VT_ARRAY | VT_VARIANT;
V_ARRAY(pVal)= pSA; // (pSA may be null) //pVal->parray= pSA; // UNION oleauto.h
return S_OK;
}
Looping through a two dimensional array in the client script is a bit more complicated. Here is a VBScript sample that can process either a one or two dimensional array modified from "Programming MS Visual Basic 6.0" by Francesco Balena (MS Press).
Low Level Construction of a SafeArray of BYTES
While SafeArrayPutElement simplifies array access, it is not as efficient as directly accessing the underlying array. The following code demonstrates how to fill a SafeArray with BYTES of data using low level access and then write out the SafeArray using the Response object. This requires locking/unlocking of the SafeArray by calling SafeArrayAccessData and SafeArrayUnaccessData. Locking the SafeArray prevents the destruction of the SafeArray during manipulation. Calling SafeArrayDelete() on a locked SafeArray returns an error. In this sample, the raw data is stored in a array of char of size m_nChunkSize.
Have fun,
Nothing seems to cause more confusion than the concept of SafeArrays. First, documentation is very sparse. Second, SafeArrays can contain pointers to objects on the system heap (BSTRs) making resource allocation/deallocation critical. You can look at a SafeArray as a Visual Basic Array, which contains information about the array. Alternatively, you can look at a SafeArray as a wrapper class providing high level protected access to an array. In the context of ASP COM programming, the SafeArray is a way to package data for consumption by an ASP script. It is also useful within a COM component that must pass data to an ASP Response object. this article represents my best understanding of SafeArrays and resource allocation, after struggling through the available documentation and source code.
Constructing a SafeArray using SafeArrayPutElement
Returning a SafeArray of VARIANT BSTR
Returning a Two Dimensional SafeArrray
Low Level Construction of a SafeArray of BYTES
Constructing a SafeArray using SafeArrayPutElements
You can put elements in a SafeArray using SafeArrayPutElements. This simplifies data access, but adds overhead to the array construction. A common requirement is to construct a SafeArray of VARIANTs of TYPE VT_BSTR. Such a SafeArray can then be returned to an ASP script as a VARIANT of TYPE VT_ARRAY | VT_VARIANT. This complex structure is required for compatibility with various scripting languages such as Visual Basic Script. Such a construct can also be used by JScript, but with more difficulty.
In the following code sample, I construct a SafeArray of ten elements. The sample converts the element index to an ANSI string using ltoa(). It then uses the ATL conversion macro A2W to convert the ANSI string to a Unicode "wide character" string. Finally, the Unicode string is "converted" to a BSTR by allocating memory for a length prefixed string on the system heap, returning a pointer to the string. This could also be accomplished using A2BSTR, but this macro hides the memory allocation that I am trying to demonstrate.
The important point here is that the BSTR exist in memory independent of the COM component. When returned as an [out, retval] it is the responsibility of the calling script engine to release the system memory holding the actual string data. You can look at a BSTR as calling new() in the COM component and calling delete() in the ASP script. Here is the verbose first pass at the code to create a SafeArray of BSTR VARIANTs:
C++ Syntax (Toggle Plain Text)
USES_CONVERSION; // enables use of ATL conversion macro A2W char buffer[20]; // used to store ANSI string HRESULT hr= S_OK; // Create SafeArray of VARIANT BSTRs SAFEARRAY *pSA; SAFEARRAYBOUND aDim[1]; // a one dimensional array aDim[0].lLbound= 0; // Visual Basic arrays start with index 0 aDim[0].cElements= 10; pSA= SafeArrayCreate(VT_VARIANT,1,aDim); // create a 1D SafeArray of VARIANTS if (pSA != NULL) { long aLong[1]; // iterate over array adding VARIANTs of type VT_BSTR for (long l= aDim[0].lLbound; l< (aDim[0].cElements + aDim[0].lLbound); l++) { VARIANT vOut; VariantInit(&vOut); vOut.vt= VT_BSTR; // set type ltoa(l,buffer,10); // convert long to ANSI string value vOut.bstrVal= ::SysAllocString(A2W(buffer)); // system wide "new" aLong[0]= l; // set index value if (hr= SafeArrayPutElement(pSA, aLong, &vOut)) { // "correctly" copies VARIANT VariantClear(&vOut); // release BSTR from memory on error SafeArrayDestroy(pSA); // does a deep destroy on error return hr; } VariantClear(&vOut); // does a deep destroy of source VARIANT } // end iteration } // clean up here only if you do not return SafeArray as an [out, retval] SafeArrayDestroy(pSA); // again does a deep destroy
The class _variant_t is a wrapper class that automates memory management much like CComVariant. Using _variant_t greatly simplifies the above code since the overloaded assignment operator converts a wide character string to a BSTR.
C++ Syntax (Toggle Plain Text)
_variant_t& operator=(const wchar_t* pSrc) throw(_com_error); // Assign a VT_BSTR
C++ Syntax (Toggle Plain Text)
USES_CONVERSION; char buffer[20]; HRESULT hr= S_OK; // Create SafeArray of VARIANT BSTRs SAFEARRAY *pSA; SAFEARRAYBOUND aDim[1]; aDim[0].lLbound= 0; aDim[0].cElements= 10; pSA= SafeArrayCreate(VT_VARIANT,1,aDim); if (pSA != NULL) { _variant_t vOut; for (long l= aDim[0].lLbound; l< (aDim[0].cElements + aDim[0].lLbound); l++) { ltoa(l,buffer,10); vOut= A2W(buffer); // assignment operator automates memory allocation for BSTR if (hr= SafeArrayPutElement(pSA, &l, &vOut)) { SafeArrayDestroy(pSA); // does a deep destroy on error return hr; } } } // clean up here only if you do not return SafeArray as an [out, retval] SafeArrayDestroy(pSA); // again does a deep destroy
Returning a SafeArray of VARIANT BSTRs
The following code demonstrates how to return the SafeArray of VARIANTs of type VT_BSTR constructed in the preceding section "Constructing A SafeArray Using SafeArrayPutElements", The following code takes a long as an input parameter and creates a SafeArray of VARIANTS of the corresponding length. The IDL looks like this:
C++ Syntax (Toggle Plain Text)
[id(1), helpstring("method GetArray")] HRESULT GetArray([in] int newVal, [out, retval] VARIANT *pVal);
Again, I modeled the C++ and ASP code after Shelley Powers "Developing ASP Components Second Edition".
[code]
// ASSERT newVal > 0
STDMETHODIMP CArray::GetArray(int newVal, VARIANT *pVal)
{
// TODO: Add your implementation code here
USES_CONVERSION;
char buffer[20];
HRESULT hr= S_OK;
if (newVal < 1) {
newVal= 1;
}
// Create SafeArray of VARIANT BSTRs
SAFEARRAY *pSA;
SAFEARRAYBOUND aDim[1];
aDim[0].lLbound= 0;
aDim[0].cElements= newVal;
pSA= SafeArrayCreate(VT_VARIANT,1,aDim);
if (pSA != NULL) {
_variant_t vOut;
for (long l= aDim[0].lLbound; l< (aDim[0].cElements + aDim[0].lLbound); l++) {
ltoa(l,buffer,10);
vOut= A2W(buffer);
if (hr= SafeArrayPutElement(pSA, &l, &vOut)) {
SafeArrayDestroy(pSA); // does a deep destroy
return hr;
}
}
}
// return SafeArray as VARIANT
//VariantInit(pVal); // WARNING You must initialize *pVal before calling Detach
V_VT(pVal)= VT_ARRAY | VT_VARIANT; //pVal->vt= VT_ARRAY | VT_VARIANT; // oleauto.h
V_ARRAY(pVal)= pSA; // (pSA may be null) //pVal->parray= pSA; // oleauto.h
return S_OK;
}
You can call the code from VBScript like this:
<%@ LANGUAGE="VBScript" %>
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" content="text/html; charset=iso-8859-1">
<TITLE>Test Array DLL</TITLE>
</HEAD>
<BODY TopMargin="0" Leftmargin="0">
<%
Set objArray= Server.CreateObject("JALArray.Array")
Dim arrayBSTR
Dim x
arrayBSTR= objArray.GetArray(10)
for x= LBound(arrayBSTR) to UBound(arrayBSTR)
Response.write "<BR>"&x&": "&arrayBSTR(x)
next
Set objArray= nothing
%>
</BODY>
</HTML>
Calling the code from JScript is more complicated:
<%@ LANGUAGE="JSCRIPT" %>
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" content="text/html; charset=iso-8859-1">
<TITLE>Test Array DLL</TITLE>
</HEAD>
<BODY TopMargin="0" Leftmargin="0">
<%
var objArray= Server.CreateObject("JALArray.Array") ;
var arrayVariants= new VBArray(objArray.GetArray(10));
var arrayBSTR= arrayVariants.toArray();
for (i= arrayVariants.lbound();i<= arrayVariants.ubound(); i++) {
Response.write("<BR>"+i+": "+arrayBSTR[i]+"");
}
objArray= null;
%>
</BODY>
</HTML>
The preceding JScript code first creates a VB array from the SafeArray. The call toArray() then returns a JScript array from the VB array.
Returning a Two Dimensional Array
Returning a rectangular two dimensional array is not that much different than the preceding code. Here is the two dimensioned version:
// ASSERT newVal > 0
STDMETHODIMP CArray::GetArray(int newVal, VARIANT *pVal)
{
// TODO: Add your implementation code here
USES_CONVERSION;
char buffer[20];
HRESULT hr= S_OK;
if (newVal < 1) {
newVal= 1;
}
// Create SafeArray of VARIANT BSTRs
SAFEARRAY *pSA;
SAFEARRAYBOUND aDim[2]; // two dimensional array
aDim[0].lLbound= 0;
aDim[0].cElements= newVal;
aDim[1].lLbound= 0;
aDim[1].cElements= newVal; // rectangular array
pSA= SafeArrayCreate(VT_VARIANT,2,aDim); // again, 2 dimensions
long aLong[2];
long lSum;
if (pSA != NULL) {
_variant_t vOut;
for (long x= aDim[0].lLbound; x< (aDim[0].cElements + aDim[0].lLbound); x++) {
aLong[0]= x; // set x index
for (long y= aDim[1].lLbound; y< (aDim[1].cElements + aDim[1].lLbound); y++) {
aLong[1]= y; // set y index
lSum= x+y;
ltoa(lSum,buffer,10);
vOut= A2W(buffer);
if (hr= SafeArrayPutElement(pSA, aLong, &vOut)) {
SafeArrayDestroy(pSA); // does a deep destroy
return hr;
}
}
}
}
// return SafeArray as VARIANT
//VariantInit(pVal); // WARNING You must initialize *pVal before calling Detach
V_VT(pVal)= VT_ARRAY | VT_VARIANT; //pVal->vt= VT_ARRAY | VT_VARIANT;
V_ARRAY(pVal)= pSA; // (pSA may be null) //pVal->parray= pSA; // UNION oleauto.h
return S_OK;
}
Looping through a two dimensional array in the client script is a bit more complicated. Here is a VBScript sample that can process either a one or two dimensional array modified from "Programming MS Visual Basic 6.0" by Francesco Balena (MS Press).
C++ Syntax (Toggle Plain Text)
<%@ LANGUAGE="VBScript" %> <HTML> <HEAD> <META HTTP-EQUIV="Content-Type" content="text/html; charset=iso-8859-1"> <TITLE>Test MultiDimensionalArray DLL</TITLE> </HEAD> <BODY TopMargin="0" Leftmargin="0"> <% Set objArray= Server.CreateObject("JALArray.Array") Dim arrayBSTR Dim x arrayBSTR= objArray.GetArray(10) Select Case NumberOfDims(arrayBSTR) ' Function defined below Case 1 'one dimensional array as table Response.write("<TABLE BORDER=1><TR>") for x= LBound(arrayBSTR) to UBound(arrayBSTR) Response.write "<TD>"&arrayBSTR(x)&"</TD>" next Response.write("</TR></TABLE>") Case 2 'two dimensional array as table Response.write("<TABLE BORDER=1>") for x= LBound(arrayBSTR) to UBound(arrayBSTR) Response.write("<TR>") for y= LBound(arrayBSTR,2) to UBound(arrayBSTR,2) Response.write "<TD>"&arrayBSTR(x,y)&"</TD>" next Response.write("</TR>") next Response.write("</TABLE>") Case Else Err.Raise 1001 ' more than two dimensions ? not an array End Select Set objArray= nothing %> <SCRIPT LANGUAGE="VBScript" RUNAT="Server"> 'Modified From Programming MS Visual Basic 6.0 Balena Function NumberOfDims(array) Dim temp On Error Resume Next Do temp= UBound(array, NumberOfDims +1) If Err Then Exit Do NumberOfDims= NumberOfDims+1 Loop End Function </SCRIPT> </BODY> </HTML>
While SafeArrayPutElement simplifies array access, it is not as efficient as directly accessing the underlying array. The following code demonstrates how to fill a SafeArray with BYTES of data using low level access and then write out the SafeArray using the Response object. This requires locking/unlocking of the SafeArray by calling SafeArrayAccessData and SafeArrayUnaccessData. Locking the SafeArray prevents the destruction of the SafeArray during manipulation. Calling SafeArrayDelete() on a locked SafeArray returns an error. In this sample, the raw data is stored in a array of char of size m_nChunkSize.
C++ Syntax (Toggle Plain Text)
// Create char Array char* out= new (nothrow) char[m_nChunkSize]; // Dynamic Allocation! if(out == NULL) { return E_OUTOFMEMORY; } ... code to fill char array removed for clarity VARIANT vOut; VariantInit(&vOut); // extra call SAFEARRAY *pSA; SAFEARRAYBOUND aDim[1]; // one dimensional array aDim[0].lLbound= 0; aDim[0].cElements= m_nChunkSize; char * pActualData= NULL; HRESULT hres= S_OK; // Create SafeArray pSA= SafeArrayCreate(VT_UI1,1,aDim); if (pSA == NULL) { delete [] out; return E_OUTOFMEMORY; } try { VariantInit(&vOut); // may not be necessary vOut.vt= (VT_ARRAY|VT_UI1); // V_VT(&vOut)= (VT_ARRAY|VT_UI1); vOut.parray= pSA; // V_ARRAY(&vOut)= pSA; if (hres=SafeArrayAccessData(pSA,(void **)&pActualData)) throw hres; for (int i=0; i<m_nChunkSize; i++) { pActualData[i]= out[i]; } if (hres=SafeArrayUnaccessData(pSA)) throw hres; m_piResponse->BinaryWrite(vOut); catch(...) { } // Clean Up delete [] out; VariantClear(&vOut); // calls SafeArrayDestroy! return hres; }
Have fun,
Real Eyes Realize Real Lies
My Resume
My Resume
![]() |
Similar Threads
- Safe Array within a Safe Array Implementation (C++)
- Passing Variants from VB to VC++ (Visual Basic 4 / 5 / 6)
- "Safe array" assignment (C++)
Other Threads in the C++ Forum
- Previous Thread: hi plz help me...
- Next Thread: Function pointer suddenly causes segmentation fault
| Thread Tools | Search this Thread |
api array based binary bitmap business c++ c/c++ char class classes code codesamplerunwhilecommands coding commentinghelp compile console conversion count decide delete deploy desktop developer directshow dll download dynamic dynamiccharacterarray email encryption error faq file forms fstream function functions game givemetehcodez graph guess gui hash homeworkhelp homeworkhelper iamthwee ifpug ifstream incrementoperators infinite input int integer java lib linkedlist linker listing loop looping loops map math matrix memory multiple news node output pointer port problem proficiency program programming project python random read recursion reference rpg string strings temperature template test text text-file tree url variable vector video win32 windows winsock wordfrequency wxwidgets






