📄 article5.htm
字号:
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<HTML><HEAD><TITLE>Article 5. The Safe OLE Way of Handling Arrays</TITLE></HEAD>
<!--DocHeaderStart-->
<BODY bgcolor="#ffffff" text="#000000" leftmargin=0 topmargin=0 link="#0080C0" vlink="gray">
<BR>
<table width=100% border=0 cellspacing=0 cellpadding=0>
<TR>
<TD WIDTH="10" VALIGN=TOP> </TD>
<TD VALIGN="TOP">
<!--DocHeaderEnd-->
<!-- This is a PANDA Generated HTML file. The source is a WinWord Document. -->
<P><h1>Article 5. The Safe OLE Way of Handling Arrays</h1><P>Bruce McKinney<P>April 18, 1996 <P><P>
Although Microsoft® Visual C++® supports arrays intrinsically, it does so with no index protection, no size limit, no initialization--just a pointer to random-value memory. Even C++ programmers are reluctant to use raw arrays. Many of them write protected wrapper classes with names like <b>Array</b> or <b>Vector</b>. You can make such classes look and act like an array--but one that is protected by armor. <P>If you're going to make arrays available across process, machine, and operating system boundaries, your clients will expect more protection than a raw C++ array can provide. The OLE way of doing arrays (which is exactly the same as the Visual Basic® way) is through a protected data structure called a SAFEARRAY.<P><h2>What Is a SAFEARRAY?</h2><P>Because the OLE header files are implemented for both C and C++ (although using C is cumbersome, so this article will examine only the C++ case), they provide the protected standard array as a SAFEARRAY structure with a group of system functions that work on it. <P><h3>The SAFEARRAY Structure </h3><P>When converted to C++ and trimmed of excess typedefs and conditionals, the SAFEARRAY structure looks something like this: <P><PRE><FONT FACE="COURIER" SIZE="2">struct SAFEARRAY {
WORD cDims;
WORD fFeatures;
DWORD cbElements;
DWORD cLocks;
void * pvData;
SAFEARRAYBOUND rgsabound[1];
};
</FONT></PRE>
<P><UL><LI> The <b>cDims</b><i> </i>field contains the number of dimensions of the array.
<LI> The <b>fFeatures</b><i> </i>field is a bitfield indicating attributes of a particular array. (More on that later.)
<LI> The <b>cbElements</b><i> </i>field defines the size of each element in the array.
<LI> The <b>cLocks</b><i> </i>field is a reference count that indicates how many times the array has been locked. When there is no lock, you're not supposed to access the array data, which is located in <b>pvData</b>. </UL>The last field is an array of boundary structures. By default, there's only one of these, but if you define multiple dimensions, the appropriate system function will reallocate the array to give you as many array elements as you need. The dimension array is the last member of the array so that it can expand. A SAFEARRAYBOUND structure looks like this: <P><PRE><FONT FACE="COURIER" SIZE="2">struct SAFEARRAYBOUND {
DWORD cElements;
LONG lLbound;
};
</FONT></PRE><P>The <b>cElements</b><i> </i>field has the number of elements in the dimension, and the <b>lLBound</b><i> </i>field has the lower boundary. In theory, you could define a range either by giving the first and last element, or by giving the first element and the number of elements. OLE chose the second format, but we'll fix that shortly.<P><h3>The SAFEARRAY System Functions</h3><P>Many of the system functions tend to work together in groups, so we'll talk about related functions in logical order. My descriptions of these functions are sometimes different from (and sometimes more complete than) the descriptions in OLE documentation. Because I personally tested them to determine some behavior that was sparsely documented, I am confident that my descriptions are valid and will work for you.<P>As long as you're working with single-dimension arrays, the SAFEARRAY functions are simple and straightforward. Things get more complicated with multi-dimensional arrays, partly because Visual Basic and Visual C++ have a different ideas of how to arrange the data in different dimensions. Also C++ wants to access zero-based arrays, but the SAFEARRAY type can have index boundaries based on any signed number. <P>The <b>TestSA</b> function in Test.Cpp gives the <b>SafeArray</b> system functions a workout. The examples for the functions described below are taken from this function, and the event handler for the SAFEARRAY button in the Cpp4VB sample program calls this function. I'm not going to go through the code in detail, but I will say that the more you study the raw functions, the more you'll appreciate the <b>SafeArray</b> class shown later. <P>One note on terminology: OLE documentation calls a pointer to an allocated SAFEARRAY structure an <i>array descriptor</i>. Note that an array descriptor isn't necessarily the same as a pointer to a SAFEARRAY structure. The structure has space for only one dimension (SAFEARRAYBOUND structure). A descriptor is expanded to provide additional memory for each dimension. <P><h4>SAFEARRAY * SafeArrayCreate(VARTYPE vt, UINT cDims, SAFEARRAYBOUND * aDims);<BR>HRESULT SafeArrayDestroy(SAFEARRAY * psa);</H4><P>You create an array by calling <b>SafeArrayCreate</b>, passing it the type of the array in the <i>vt </i>parameter, the number of dimensions in the <i>cDims </i>parameter, and the size of each dimension in the <i>adims </i>parameter (an array of SAFEARRYBOUND structures). <b>SafeArrayCreate</b> creates a new array, allocates and initializes the data for the array, and returns a pointer to the SAFEARRAY structure. When you're done with the array, call <b>SafeArrayDestroy</b>. The advantage of these functions is that they're simple. The disadvantage is that they can only handle the OLE variant subtypes (excluding VT_ARRAY, VT_BYREF, VT_EMPTY, or VT_NULL). That's not really a disadvantage when you're dealing with Visual Basic or most other OLE clients.<P>When you destroy an array of BSTRs, VARIANTs, or objects with <b>SafeArrayDestroy</b>, BSTRs and VARIANTs are freed and objects are released. <P><b>Example:</b>
<P><PRE><FONT FACE="COURIER" SIZE="2">// Create a new 1-D array of Integers.
SAFEARRAY * psaiNew;
SAFEARRAYBOUND aDim[1];
aDim[0].lLbound = 1;
aDim[0].cElements = 8;
// Equivalent to: Dim aiNew(1 To 8) As Integer.
psaiNew = SafeArrayCreate(VT_I2, 1, aDim);
if (psaiNew == NULL) throw ERROR_NOT_ENOUGH_MEMORY;
.
. // Use array.
.
if (hres = SafeArrayDestroy(psaiNew)) throw hres;
</FONT></PRE><P><h4>HRESULT SafeArrayAllocDescriptor(UINT cDims, SAFEARRAY ** ppsaOut);<BR>HRESULT SafeArrayAllocData(SAFEARRAY * psa);<BR>HRESULT SafeArrayDestroyData(SAFEARRAY * psa);<BR>HRESULT SafeArrayDestroyDescriptor(SAFEARRAY * psa);</H4><P>These functions provide more complicated and flexible alternative to <b>SafeArrayCreate</b> and <b>SafeArrayDestroy</b>. You can put non-OLE types into the array, but you must manage the data yourself. In this series of articles, we have no reason to put nonstandard data types into arrays. Visual Basic wouldn't know what to do with them anyway.<P><h4>HRESULT SafeArrayGetElement(SAFEARRAY * psa, long * aiIndex, void * pvElem);<BR>HRESULT SafeArrayPutElement(SAFEARRAY * psa, long * aiIndex, void * pvElem);</H4><P>These functions insert or extract a single array element. You pass one of these functions an array pointer and an array of indexes for the element you want to access. It returns a pointer to a single element through the <i>pvElem </i>parameter. You also need to know the number of dimensions and supply an index array of the right size. The rightmost (least significant) dimension should be <i>aiIndex[0] </i>and the leftmost dimension should be <i>aiIndex[psa->cDims-1]</i>. These functions automatically call <b>SafeArrayLock</b> and <b>SafeArrayUnlock</b> before and after accessing the element. If the data element is a BSTR, VARIANT, or object, it is copied correctly with the appropriate reference counting or allocation. During an assignment, if the existing element is a BSTR, VARIANT, or object, it is cleared correctly, with the appropriate release or free before the new element is inserted. You can have multiple locks on an array, so it's OK to use these functions while the array is locked by other operations.<P><b>Example:</b><P><PRE><FONT FACE="COURIER" SIZE="2">// Modify 2-D array with SafeArrayGetElement and SafeArrayGetElement.
long ai[2];
Integer iVal;
xMin = aDims[0].lLbound;
xMax = xMin + (int)aDims[0].cElements - 1;
yMin = aDims[1].lLbound;
yMax = yMin + (int)aDims[1].cElements - 1;
for (x = xMin; x <= xMax; x++) {
ai[0] = x;
for (y = yMin; y <= yMax; y++) {
ai[1] = y;
if (hres = SafeArrayGetElement(psaiInOut, ai, &iVal)) throw hres;
// Equivalent to: aiInOut(x, y) = aiInOut(x, y) + 1.
iVal++;
if (hres = SafeArrayPutElement(psaiInOut, ai, &iVal)) throw hres;
}
}
</FONT></PRE><P><h4>HRESULT SafeArrayLock(SAFEARRAY * psa);<BR>HRESULT SafeArrayUnlock(SAFEARRAY * psa);</H4><P>These functions increment or decrement the lock count of an array. The data becomes accessible through the <b>pvData</b><i> </i>field of the array descriptor. The pointer in the array descriptor is valid until <b>SafeArrayUnlock</b> is called. Note that the <b>pvData</b> field, like all C++ arrays, is zero-indexed. If you need to keep track of the Basic index, initialize it from the <b>lLbound</b><i> </i>field of the SAFEARRAYBOUND structure. <P>When processing data in a loop, it is more efficient to lock the array, process the data, and then unlock it, rather than making multiple calls to <b>SafeArrayGetElement</b> and <b>SafeArrayPutElement</b>. You can nest equal pairs of calls to <b>SafeArrayLock</b> and <b>SafeArrayUnlock</b>, so it's possible to lock and use an array while another operation also has a lock on the array. An array can't be deleted while it is locked.<P><b>Example:</b><P><PRE><FONT FACE="COURIER" SIZE="2">// Initialize Integer array to squares of index.
if (hres = SafeArrayLock(psaiNew)) throw hres;
int iCur = aDim[0].lLbound;
// Keep separate C++ index (i) and Basic index (iCur).
for (i = 0; i < (int)aDim[0].cElements; i++, iCur++) {
// Equivalent to: ai(iCur) = iCur * iCur.
((Integer*)psaiNew->pvData)[i] = iCur * iCur;
}
if (hres = SafeArrayUnlock(psaiNew)) throw hres;
</FONT></PRE><P>The example above illustrates accessing a simple 1-D array. As a bonus, here's an example of accessing a 2-D array without any help from <b>SafeArrayPtrOfIndex</b>. I had to consult a pack of C++ language lawyers (special credit to Paul Johns) for help untangling a type cast that looks sort of like a spilled can of night crawlers.<P><PRE><FONT FACE="COURIER" SIZE="2">// Set up dimension array and pointer to receive value.
if (hres = SafeArrayLock(psaiInOut)) throw hres;
Integer (*aiInOut)[4] = (Integer(*)[4])psaiInOut->pvData;
for (x = 0; x < (int)aDims[0].cElements; x++) {
for (y = 0; y < (int)aDims[1].cElements; y++) {
// Equivalent to: aiInOut(x, y) = aiInOut(x, y) + 1.
// Switch x and y order for Visual Basic storage order.
aiInOut[y][x]++;
}
}
if (hres = SafeArrayUnlock(psaiInOut)) throw hres;
</FONT></PRE><P><h4>HRESULT SafeArrayPtrOfIndex(SAFEARRAY * psa, long * aiIndex, void ** ppv);</H4><P>This function returns a pointer to an array element. You pass it an array of index values that identify an element of the array; it returns a pointer to the element. The array should be locked before <b>SafeArrayPtrOfIndex</b> is called. Use this function with multi-dimension arrays when using <b>SafeArrayLock</b>. For single-dimension arrays, it's usually easier to just index into the array directly without this function. <P><b>Example:</b>
<P><PRE><FONT FACE="COURIER" SIZE="2">// Lock 2-D array and modify.
xMin = aDims[0].lLbound;
xMax = xMin + (int)aDims[0].cElements - 1;
yMin = aDims[1].lLbound;
yMax = yMin + (int)aDims[1].cElements - 1;
// Set up dimension array and pointer to receive value.
Integer * piInOut;
if (hres = SafeArrayLock(psaiInOut)) throw hres;
for (x = xMin; x <= xMax; x++) {
ai[0] = x;
for (y = yMin; y <= yMax; y++) {
ai[1] = y;
hres = SafeArrayPtrOfIndex(psaiInOut, ai, (void **)&piInOut);
if (hres) throw hres;
// Equivalent to: aiInOut(x, y) = aiInOut(x, y) + 1.
(*piInOut)++;
}
}
if (hres = SafeArrayUnlock(psaiInOut)) throw hres;
</FONT></PRE><P><h4>HRESULT SafeArrayAccessData (SAFEARRAY * psa, void ** ppvData);<BR>HRESULT SafeArrayUnaccessData(SAFEARRAY * psa);</H4><P>You pass <b>SafeArrayAccessData</b> a SAFEARRAY pointer and a variable to receive the address of the array data; it locks the array and returns a pointer to the data. When you're done, you call <b>SafeArrayUnaccessData</b>. This is the verbose equivalent of locking the data and using the <b>pvData</b><i> </i>member out of the SAFEARRAY structure. It provides no way to calculate the index of multi-dimension arrays. I can't think of any reason to use these functions, so if you do, you're on your own.<P><h4>HRESULT SafeArrayCopy(SAFEARRAY * psaIn, SAFEARRAY ** ppsaOut);</H4><P>This function creates a copy of an existing safe array: You pass it the descriptor of the array to copy and the address of a SAFEARRAY pointer that will receive the copy; it copies the source data to the destination. If the source array contains BSTR or VARIANT types, <b>SafeArrayCopy</b> calls the appropriate system functions to create the copies. If the source array contains object references, <b>SafeArrayCopy</b> increments their reference counts. You end up with two identical copies of the array. <P><b>Example:</b><P><PRE><FONT FACE="COURIER" SIZE="2">// Copy from psaiNew to psaiRet.
SAFEARRAY * psaiRet;
if (hres = SafeArrayCopy(psaiNew, &psaiRet)) throw hres;
</FONT></PRE><P><h4>UINT SafeArrayGetDim(SAFEARRAY * psa);<BR>UINT SafeArrayGetElemsize(SAFEARRAY * psa);</H4><P>These functions return the number of dimensions in the array or the size in bytes of an element. They are equivalent to getting the corresponding elements out of the descriptor.<P><b>Example:</b><P><PRE><FONT FACE="COURIER" SIZE="2">long cDim = SafeArrayGetDim(psaiInOut);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -