⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 article5.htm

📁 The code for this article was written for version 1.0 of the Active Template Library (ATL). The cu
💻 HTM
📖 第 1 页 / 共 3 页
字号:
long cbElem = SafeArrayGetElemsize(psaiInOut); 
</FONT></PRE><P><h4>HRESULT SafeArrayGetLBound(SAFEARRAY * psa, UINT cDim, long * piLo);<BR>HRESULT SafeArrayGetUBound(SAFEARRAY * psa, UINT cDim, long * piUp);</H4><P>These functions return the lower or upper boundary for any dimension of a safe array.<P><b>Example</b>:<P><PRE><FONT FACE="COURIER" SIZE="2">SAFEARRAYBOUND * aDims = new SAFEARRAYBOUND[cDim];
long iT;
for (i = 0; i &lt; cDim; i++) {
    hres = SafeArrayGetLBound(psaiInOut, i + 1, &amp;aDims[i].lLbound);
    if (hres) throw hres; 
    if (hres = SafeArrayGetUBound(psaiInOut, i + 1, &amp;iT)) throw hres;
    // Calculate elements from upper and lower boundaries.
    aDims[i].cElements = iT - aDims[i].lLbound + 1;
}
</FONT></PRE><P><h4>HRESULT SafeArrayRedim(SAFEARRAY * psa, SAFEARRAYBOUND * pdimNew);</H4><P>This function changes the least significant (rightmost) bound of a safe array. You pass an array pointer to <b>SafeArrayRedim</b> and a pointer to a SAFEARRAYBOUND variable containing the desired dimensions. If you reduce the size of the array, <b>SafeArrayRedim</b> deallocates the array elements outside the new array boundary. If you increase the size, <b>SafeArrayRedim</b> allocates and initializes the new array elements. The data is preserved for elements that exist in both the old and the new array. To redimension an array passed from Visual Basic, the array must be a non-static array:<P><PRE><FONT FACE="COURIER" SIZE="2">' Use SafeArrayRedim on this one.
Dim aiModify () As Integer
ReDim Preserve aiModify(1 To 8, 1 To 8) As Integer
' Don't use SafeArrayRedim.
Dim aiFixed(1 To 8, 1 To 8) As Integer
</FONT></PRE><P>You can identify a fixed-length array passed from Visual Basic by the <b>fFeatures</b><i> </i>field of the SAFEARRAY structure. Basic sized arrays declared with <b>Dim</b>, <b>Private</b>, or <b>Public</b> will have the FADF_STATIC and FADF_FIXEDSIZE flags. Arrays sized with Basic's <b>ReDim</b> statement (and thus usable with <b>SafeArrayRedim</b>) will not have these flags. <P><b>Example</b>:
<P><PRE><FONT FACE="COURIER" SIZE="2">// Double the size of the last dimension.
i = cDim - 1;
aDims[i].cElements *= 2;
if (hres = SafeArrayRedim(psaiInOut, &amp;aDims[i])) throw hres;
</FONT></PRE><P><h2>The SafeArray Class</h2><P>You're probably not used to thinking of an array as a type--it's more of a container for other types. But in C++ just about anything can be a type. Until recently, if you wanted to define one type that held objects of another type, you had to decide at design time what type you wanted to contain. You could design an ArrayLong type, but then you'd have to copy all your code and do a lot of searching and replacing to add an ArrayString type. <P>C++ templates now let you define an array type that can contain any kind of OLE type. You can declare an array of strings like this:<P><PRE><FONT FACE="COURIER" SIZE="2">SafeArray&lt;BSTR, VT_BSTR&gt; as = Dim(1, 8); // Dim as(1 To 8) As String
</FONT></PRE><P>You specify by what you put in the angle brackets exactly what type you want to be contained in the array. It gets a little tiresome, not to mention redundant, to specify both the type and the OLE type constant for every array, but typedefs make it easy to define the standard OLE types: <P><PRE><FONT FACE="COURIER" SIZE="2">typedef SafeArray&lt;BSTR, VT_BSTR&gt;            ArrayString;
</FONT></PRE><P>Now you can define the string more naturally. <P><PRE><FONT FACE="COURIER" SIZE="2">ArrayString as = Dim(1, 8);             // Dim as(1 To 8) As String
</FONT></PRE><P>The other OLE types have Basic-style names: ArrayByte, ArrayInteger, ArrayLong, ArraySingle, ArrayDouble, ArrayVariant, ArrayCurrency, ArrayDate, ArrayBoolean, and ArrayObject. The trick is that we don't have to define a separate class for each type. There's just one class with 11 predefined variations, and the ability to define more, although the predefined ones are all you need for a Visual Basic client. <P>You can see some of these array definitions in action in the <b>TestSafeArray</b> function in Test.Cpp. This function is tested by the event handler of the SafeArray button in the Cpp4VB sample program.<P>But before we look at things you might want to do with SafeArray types, what the heck is that <b>Dim</b> object being assigned to the array? <P><h3>The Dim Type</h3><P>The SAFEARRAYBOUND type is the official way of specifying a dimension for a SAFEARRAY, but it's not my idea of how a dimension ought to be specified. It certainly doesn't look like a Visual Basic dimension, so I wrote a simpler, more friendly class called <b>Dim</b>. <b>Dim</b> is inherited from SAFEARRAYBOUND, so it has the same data members, but it also has friendly constructors and methods that make it easier to use. All methods of the <b>Dim</b> class are inline, so there's no penalty for using it. <P>Here's the entire class: <P><PRE><FONT FACE="COURIER" SIZE="2">class Dim : public SAFEARRAYBOUND
{
public:
    Dim(const long iLo, const long iHi)
    { cElements = abs(iHi - iLo) + 1; lLbound = iLo; }
    Dim(const long c)
    { cElements = c; lLbound = 0; }
    const Dim &amp; operator=(const Dim &amp; dim) 
    { cElements = dim.cElements; lLbound = dim.lLbound; return *this; }
    const Dim &amp; operator=(const long c) 
    { cElements = c; lLbound = 0; return *this; }
    ~Dim() {}
    long Elements() { return cElements; }
    long LBound() { return lLbound; }
    long UBound() { return lLbound + cElements - 1; }
};
</FONT></PRE><P>Notice first that the <b>Dim</b> object is inherited publicly from SAFEARRAYBOUND. This means that the <b>Dim</b> is a SAFEARRAYBOUND and you can use its data members--<b>lLbound</b><i> </i>and <b>cElements</b>. These have to be public so that you can pass a <b>Dim</b> to system functions such as <b>SafeArrayCreate</b>. You can use a <b>Dim</b> the hard way:<P><PRE><FONT FACE="COURIER" SIZE="2">Dim dim;
dim.lLBound = 4;
dim.cElements = 9;
</FONT></PRE><P>but why bother, when you can do the following: <P><PRE><FONT FACE="COURIER" SIZE="2">Dim dim(4, 12);
</FONT></PRE><P>That's more like Visual Basic. But you can also just specify the number of elements and assume zero as the starting point: <P><PRE><FONT FACE="COURIER" SIZE="2">Dim dim2(8);            // Same as Dim dim2(0, 7)
</FONT></PRE><P>Normally, you don't need a separate variable for <b>Dim</b>. Just create a temporary one in the assignment statement: <P><PRE><FONT FACE="COURIER" SIZE="2">ArrayString as = Dim(1, 8);             // Dim as(1 To 8) As String
</FONT></PRE><P>So why didn't I just skip the <b>Dim</b> class and give arrays a constructor taking two arguments? Then you could define the array like this: <P><PRE><FONT FACE="COURIER" SIZE="2">ArrayString as(1, 8);                   // Dim as(1 To 8) As String
</FONT></PRE><P>True, this would have been easy, and you should feel free to add it if you want. But what happens with multidimensional arrays? Which makes more sense? This:<P><PRE><FONT FACE="COURIER" SIZE="2">ArrayString2 as(Dim(1, 8), Dim(3, 9)); // Dim as(1 To 8, 3 To 9) As String
</FONT></PRE><P>Or this: <P><PRE><FONT FACE="COURIER" SIZE="2">ArrayString2 as(1, 8, 3, 9);           // Dim as(1 To 8, 3 To 9) As String
</FONT></PRE><P>It's arguable, but I find the separate <b>Dim</b> class easier to read. It's also more efficient internally.<P>Notice that I show a separate class, <b>ArrayString2</b>, for two-dimensional arrays. I tried to write a single <b>SafeArray</b> class that could handle multiple dimensions, but function overloading issues made it hard to implement cleanly. In this article, we'll only be using one-dimensional arrays, but you should have no problem cloning the <b>SafeArray</b> class as <b>SafeArray2</b>, <b>SafeArray3</b>, and so on. You won't be able to redimension a one-dimensional class to a two-dimensional class at run time. <P><h3>A SafeArray Warm-up</h3><P>The <b>TestSafeArray</b> function seems simple enough. It takes two parameters. One is an input array that the function will modify; the other is an empty output array that the function will create. The function would like to return another output array through the return value, but Visual Basic doesn't support direct return of arrays. They can only be returned in Variants (check out Visual Basic's <b>GetAllSettings</b> function), so that's what we'll do. <P>The function prototype looks simple enough:<P><PRE><FONT FACE="COURIER" SIZE="2">Variant DLLAPI TestSafeArray(ArrayInteger &amp; aiInOut, ArrayString &amp; asOut);
</FONT></PRE><P>Of course what you're really doing behind the typedefs is using templates: <P><PRE><FONT FACE="COURIER" SIZE="2">Variant DLLAPI TestSafeArray(SafeArray&lt;short, VT_I2&gt; &amp; aiInOut, 
                             SafeArray&lt;BSTR, VT_BSTR&gt; &amp; asOut);
</FONT></PRE><P>SafeArray parameters must always be passed as pointers, but we use references to make the array objects look normal. You'll see how clean this looks in the implementation shortly. <P>Unfortunately, MKTYPLIB doesn't know anything about references, much less about templates. Here's how you're supposed to define the SAFEARRAY structures in a type library: <P><PRE><FONT FACE="COURIER" SIZE="2">Variant WINAPI TestSafeArray([in, out] SAFEARRAY(short) * ai, 
                             [out] SAFEARRAY(BSTR) * as);
</FONT></PRE><P>You can see what's going on, but it looks almost as ugly as the template prototype. We can do better: <P><PRE><FONT FACE="COURIER" SIZE="2">Variant WINAPI TestSafeArray([in, out] ArrayInteger ai, 
                             [out] ArrayString as);
</FONT></PRE><P>This simple definition is made possible by <b>#define</b> statements in the OLETYPE.ODL standard include file. For example:<P><PRE><FONT FACE="COURIER" SIZE="2">#define ArrayLong     SAFEARRAY(long) *
</FONT></PRE><P>OLETYPE.ODL should be included in any type libraries that require SafeArrays. It includes WINTYPE.ODL, so you don't need to include both. You should put this file in a standard location known to your compiler (such as \MSDEV\INCLUDE for Microsoft Visual C++). <P><h3>A SafeArray Workout</h3><P>What would you like to do with an array? Well, if you had an input array you might want to examine elements. If the array were an in/out array, you might want to modify the contents. Here's one way of doing that:<P><PRE><FONT FACE="COURIER" SIZE="2">iMid = aiInOut.LBound() + (aiInOut.Elements() / 2);
// Get middle value of array.
iVal = aiInOut.Get(iMid);
// Double it
iVal *= 2; 
// Put modified version back.
aiInOut.Set(iVal, iMid);
</FONT></PRE><P>There's nothing really wrong with this, and it may be the most efficient way to access a single array element. But how often do you access a single element? Every time you call the <b>Get</b> or <b>Set</b> method, the class in turn calls the <b>SafeArrayGet</b> or <b>SafeArrayPut</b> API function, which in turn calls the <b>SafeArrayLock</b> or <b>SafeArrayUnlock</b> API function. You don't need the extra function calls or the hassle.<P><h4>SafeArray Looping in C++ or Basic Style</H4><P>Here's how you modify the entire array in a loop:<P><PRE><FONT FACE="COURIER" SIZE="2">// Square each value, C++ style.
aiInOut.Lock();
for (i = 0; i &lt; aiInOut.Elements(); i++) {
    aiInOut[i] *= aiInOut[i];
}
aiInOut.Unlock();
</FONT></PRE><P>You have to lock the array before accessing it, and unlock it when you're done. There's something strange here. This looks like a C++ array, starting at 0 and continuing as long as the index is less than the total number of elements. But what about the lower and upper boundaries the SafeArray is supposed to provide? Shouldn't you be looping from LBound() to UBound()? Well, it's a matter of taste. You can indeed do it that way if you prefer:<P><PRE><FONT FACE="COURIER" SIZE="2">// Divide each by two, Visual Basic style.
aiInOut.Lock();
for (i = aiInOut.LBound(); i &lt;= aiInOut.UBound(); i++) {
    aiInOut(i) /= 2;
}
aiInOut.Unlock();
</FONT></PRE><P>Notice that in this case you index with parentheses instead of brackets in the Visual Basic style. It makes sense, but how can C++ have Visual Basic indexing? Through the magic of operator overloading. The <b>SafeArray</b> class overloads the subscript operator (square brackets) for zero-based indexing. It overloads the function operator (parentheses) for boundary-based indexing. It may not be what C++ designer Bjarne Stroustrup had in mind, but it works. <P><h4>SafeArray Copying and Resizing</H4><P>You can copy an array in two ways--through initialization: <P><PRE><FONT FACE="COURIER" SIZE="2">// Copy an array.
ArrayInteger aiCopy = aiInOut;
</FONT></PRE><P>Or through assignment:
<P><PRE><FONT FACE="COURIER" SIZE="2">aiCopy = aiInOut;
</FONT></PRE><P>Either way you end up with a completely new array containing the same data. It's a deep copy--if the arrays contain BSTRs, each will have its own separate but identical string for each element. SafeArray doesn't support shallow copies in which two array pointers point to the same array data. SafeArray does allow you to redimension arrays--provided that they're sizable:<P><PRE><FONT FACE="COURIER" SIZE="2">// Redimension to throw away last element.
if (aiInOut.IsSizable()) {
    aiInOut.ReDim(Dim(aiInOut.LBound(), aiInOut.UBound() - 1)); 
}
</FONT></PRE><P>For this to work on an array passed from Visual Basic, the array must be created with ReDim rather than Dim. <P><PRE><FONT FACE="COURIER" SIZE="2">' Use SafeArrayRedim on this one.
Dim aiModify() As Integer
Redim Preserve aiModify(1 To 8) As Integer
</FONT></PRE><P><h4>Returning SafeArrays</H4><P>Often you'll want to create an array in a function and return it to Visual Basic through an out parameter. Here's an example: <P><PRE><FONT FACE="COURIER" SIZE="2">// Create array of strings.
ArrayString as = Dim(4, 9);
String s = _W(&quot;Fan&quot;);
for (i = as.LBound(); i &lt;= as.UBound(); i++) {
    s[0] = L'F' + (WCHAR)i;
    as(i) = s;
}
// Return it through out parameter.
asOut = as;
</FONT></PRE><P>And here's how to return through a Variant return value: <P><PRE><FONT FACE="COURIER" SIZE="2">// Create array of doubles.
ArrayDouble adbl = Dim(-5, 5);
for (i = adbl.LBound(); i &lt;= adbl.UBound(); i++) {
    adbl(i) = i * 3.1416;
}
// Return through Variant return value.
Variant vRet = (Variant)adbl;
return vRet;
</FONT></PRE><P>SafeArray has a Variant type conversion operator that makes the assignment to the Variant return value possible. <P><h3>A ParamArray Workout</h3><P>Visual Basic supports a passing a varying number of arguments to a procedure. To the caller of the function, it may look like you're calling a list of unrelated arguments, but the Visual Basic implementer knows that there is actually only one parameter--the last one--called a <i>ParamArray</i>. The C++ implementer knows that this final parameter is actually a variable-length array of Variants in which some elements may be missing. <P>The Visual Basic parser reads the caller's argument list and creates a Variant array of the same size. It fills the array with the arguments, setting any missing arguments to an error value (DISP_E_PARAMNOTFOUND). <P>The calls might looks like this on the Basic side:<P><PRE><FONT FACE="COURIER" SIZE="2">Debug.Print AddEmUp(7, 9.4, &quot;4&quot;)    ' 40.4

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -