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

📄 article4.htm

📁 The code for this article was written for version 1.0 of the Active Template Library (ATL). The cu
💻 HTM
📖 第 1 页 / 共 3 页
字号:
                      sExt.NullIfEmpty(), ctchTempMax, 
                      Buffer(sRet), &ptchFilePart);
    ASSERT(ctch <= ctchTempMax);
    Long iDirPart = 0, iFilePart = 0, iExtPart = 0;
    if (ctch == 0) {
        sRet.Nullify();
        // Not finding a file returns zero, but isn't an error.
        Long err = (Long)GetLastError();
        if (err) throw err;
    } else {
        // Calculate the file part offsets.
        iFilePart = ptchFilePart - (LPCTSTR)sRet + 1;
        GetDirExt(sRet, &iDirPart, &iExtPart);
        // Resize must be after calculation because it may move ANSI buffer.
        sRet.Resize(ctch);
    }
    // Cram into variants regardless of missing optional arguments.
    *pvDirPart = iDirPart;
    *pvFilePart = iFilePart;
    *pvExtPart = iExtPart;
    return sRet;
  } catch(Long e) {
    ErrorHandler(e);
    return BNULL;
  }
}
</FONT></PRE><P>Visual Basic passes in VARIANTs (as shown in the ODL declaration), but C++ receives Variants. The first two Variants in the parameter list are supposed to contain strings, but it's possible that the user might omit the parameter or pass an empty Variant (using Visual Basic's <b>Empty</b> keyword). If so, we'll just use a null String for the parameter (<i>sExt</i> or <i>sPath</i>). If the user passed a valid BSTR in the Variant, we'll assign it to our String variable. This works because the Variant type has an operator BSTR member that converts a Variant to a BSTR, and the String type has a constructor that takes a BSTR argument. There's a lot going on behind the scenes, as you can tell if you step through this code with a debugger. Finally, if someone passes the floating point number 3.1416 as the file extension, we'll throw the bozo out with an &quot;Invalid Parameter&quot; error. <P>There's not much to the output Variant code. You simply calculate the correct values in integers (such as <i>iFilePart</i>) and then cram them into the Variants for return. There's no reason to test whether optional arguments were actually passed. If an argument was omitted, Basic will simply create a temporary Variant with an error setting. You can fill this variable with whatever you like because it's going to be tossed anyway. <P><h3>Debugging Variants</h3><P>Before you start developing your own Variant functions (or stepping through mine), users of Microsoft Developer Studio will probably want to make one more change in AUTOEXP.DAT in the \MSDEV\BIN directory. See Article 3 for a review of how we edited this file to handle output of BSTRs and Strings in Unicode format. Handling Variants is a little different. Here's the setting I use: <P><PRE><FONT FACE="COURIER" SIZE="2">Variant =vt=&lt;vt,x&gt; short=&lt;iVal&gt; long=&lt;lVal&gt; dbl=&lt;dblVal,g&gt; str=&lt;bstrVal,su&gt;
</FONT></PRE><P>The result isn't exactly is pretty, but it's the best I could do. The debugger shows five of the most common fields of the VARIANT structure and union. You have to look at the value of the <b>vt</b><i> </i>field (after memorizing the VT constant values) to figure out which of the other fields (if any) is meaningful. The alternative is to expand every Variant as it appears in the locals or watch the window--a task that gets old in a hurry. <P>In a language such as Visual Basic that supports Variant as an intrinsic type, the debugger can look at the the <b>vt</b><i> </i>field and display the appropriate data in the appropriate format automatically. Will Visual C++ ever get an intrinsic __variant type, and will Microsoft Developer Studio ever display formatted __variant values? It could happen, but don't hold your breath. <P><h3>A Variant Workout</h3><P>The goal of the C++ Variant type is to work the same as the Visual Basic Variant type. Because Visual Basic invented Variants, whatever it does is the definition of Variant. Of course, C++ is a different language, and some things won't work quite the same. <P>The following tests are located in the <b>TestVariant</b> function in Test.Cpp. The event procedure of the Variant button in the sample program calls this function. It works a lot like the <b>TestString</b> function discussed in Article 3--writing test output to a string that is returned to the Visual Basic program for display.<P><h4>Variant Construction</H4><P>Consider these variable initializations: 
<P><PRE><FONT FACE="COURIER" SIZE="2">Variant vInteger = (Integer)6;
Variant vLong = 9L;
Variant vSingle = 7.87f;
Variant vDouble = -89.2;
Variant vBoolean(True, VT_BOOL);
Variant vString = _W(&quot;String&quot;);  
Variant vError((Long)DISP_E_EXCEPTION, VT_ERROR);
Variant vCurrency = (Currency)78965;
Variant vDate(2.5, VT_DATE);    // Noon, January 1, 1900.
</FONT></PRE><P>This isn't the same as Visual Basic because you can't initialize variables in the declarations in Visual Basic. In addition, you'll notice quite a few casts and extra arguments.<P>When initializing <i>vInteger</i>, the constant 6 must be cast to Integer (short) so that it will go through the right constructor. The variable <i>vBoolean </i>must have an argument specifying its type because an OLE Boolean is actually the same as a short and so they must share the same constructor. The <i>vError </i>and <i>vDate </i>variables must also have type arguments because they share the long and double types respectively. C++ is a finicky langauge, and you'll have to do a lot of casting to make it understand what you want. <P><h4>Variant Assignment</H4>
<P>Assignments work the same as initialization except that you can't use a type argument with operator=(). Therefore, the <b>Variant</b> class doesn't support direct assignment to the Error, Boolean, and Date types with the assignment operator. Instead, you can use the <b>SetError</b>,  <b>SetBoolean</b>, and <b>SetDate</b> methods:
<P><PRE><FONT FACE="COURIER" SIZE="2">// Assign to types that have no operator=.
vBoolean.SetBoolean(False);
vError.SetError((Long)E_ACCESSDENIED);
vDate.SetDate(3333.125);
</FONT></PRE><P>It's your responsibility to remember to use the method for these types. Alternatively, you can assign to some other type and change with the <b>Type</b> method: 
<P><PRE><FONT FACE="COURIER" SIZE="2">// Assign to nearest type and then change to desired type.
vDate = 3333.125;
vDate.Type(VT_DATE);
</FONT></PRE><P><h4>Variant Type Conversion</H4><P>Variants also have conversion operators so that you can assign a variant to a native type and the conversion will take place automatically:<P><PRE><FONT FACE="COURIER" SIZE="2">// Assign double to long (throwing away remainder).
Long i = vDouble;
// Assign a date to a string (appears in date string format).
String s = vDate;
// Assign numeric string a numeric variable.
vString = _W(&quot;3.1416&quot;);
vSingle = vString.CopyAs(VT_R4);
</FONT></PRE><P>String conversions are particularly convenient because OLE takes care of converting to and from numeric and string arguments. Date variables, for example, come out as strings in the standard date format. The iostream insertion operator (&lt;&lt;) for Variants takes advantage of string conversion to output Variants to an output streams. Here's an example of how the insertion operator displays different Variant types:<P><PRE><FONT FACE="COURIER" SIZE="2">vInteger==6
vLong==9
vSingle==7.87
vDouble==-89.2
vBoolean==-1
vString==&quot;String&quot;
vError==Facility:2, Severity:1, Code:9
vCurrency==7.8965
vDate==1/1/00 12:00:00 PM
</FONT></PRE><P>Notice that although the currency type is stored as a 64-bit integer, OLE translates the string output to a fixed-point number with four decimal places. The date type is stored as a double, but OLE translates it to a formatted date and time string. The error type is stored as a long, and OLE doesn't do anything with it. It's my insertion code in the operator&lt;&lt; function that treats it as a special case and breaks it into the standard parts of an OLE HRESULT. OLE translates Booleans to 0 or -1, but you could easily update the insertion code to output them as &quot;True&quot; and &quot;False.&quot;<P><h4>Variant Arithmetic</H4><P>Of course, any self-respecting Variant class must handle normal arithmetic operations. Here are some addition statements and their output. <P><PRE><FONT FACE="COURIER" SIZE="2">vTmp = vInteger + vLong; // 6 + 9 == 15
vTmp += vDouble;         // 15 += -89.2 == -74.2
float flt = 3.25;
flt += (float)vTmp;      // 3.25 += -74.2 == -70.95
vTmp += (short)77;       // -74.2 += 77 == 2.8
vTmp += vInteger++;      // 2.8 += 6 == 8.8
vTmp += ++vInteger;      // 8.8 += 8 == 16.8
vTmp = vString + vLong;  // Stuff9 + 9 == Stuff9
vTmp += vDouble;         // Stuff9 += -89.2 == Stuff9-89.2
</FONT></PRE><P>I'll leave you to check similar statements (in the <b>TestVariant</b> function) using subtraction, multiplication, division, and even modulus. I confess that I didn't implement the C++ bitwise operators (&amp;,|, and ~), but you could add them in a few minutes by copying the existing operator code. Of course, bitwise operators (like the modulus operator) only make sense for integer types. Or maybe not. If you can figure out a reasonable meaning for bitwise operators on strings or dates, C++ won't stop you from implementing it. <P><h4>Variant Comparison</H4><P>The Variant type also has logical operators. <P><PRE><FONT FACE="COURIER" SIZE="2">f = (vDouble == vLong);  // (9 == 9) == 1
f = (vInteger == vLong); // (6 == 9) == 0
f = (vInteger != vLong); // (6 != 9) == 1
f = (vInteger &lt;= vLong); // (6 &lt;= 9) == 1
f = (vInteger &lt; vLong);  // (6 &lt; 9) == 1
f = (vInteger &gt; vLong);  // (6 &gt; 9) == 0
f = (vInteger &gt;= vLong); // (6 &gt;= 9) == 0
f = (vTmp == vDate);     // (2/14/09 3:00:00 AM == 2/14/09 3:00:00 AM) == 1
f = (vDate == vTmp);     // (2/14/09 3:00:00 AM == 2/14/09 3:00:00 AM) == 0
f = (vTmp == vString);   // (2/14/09 3:00:00 AM == Stuff9) == 0
f = (vTmp != vString);   // (2/14/09 3:00:00 AM != Stuff9) == 1
</FONT></PRE><P>This looks pretty good except for one minor problem--<i>vTmp </i>equals <i>vDate</i>, but <i>vDate </i>doesn't equal <i>vTmp</i>. How can that be? Well, it's kind of a peculiarity of the way OLE works when <i>vTmp </i>is a string variant and <i>vDate </i>is a date. If <i>vTmp </i>comes first, the operator==() code asks OLE to convert <i>vDate </i>to a string and then compares the two strings. Match! If <i>vDate </i>comes first, the code tries to convert <i>vTmp </i>to a date, but unfortunately OLE doesn't know how to convert a date format string to a numeric date. No match! I'm sure you could fix this problem with enough special case code. <P>You may notice that some arithmetic and logical expressions require typecasts where you might prefer not to see them. You'd need a lot of operator functions to support every possible combination. Consider addition. I provide this function: <P><PRE><FONT FACE="COURIER" SIZE="2">friend Variant operator+(Variant&amp; v1, Variant&amp; v2);
</FONT></PRE><P>To cover all your bases, you'd need these: <P><PRE><FONT FACE="COURIER" SIZE="2">friend Variant operator+(Variant&amp; v1, BYTE b2);
friend Variant operator+(BYTE b1, Variant&amp; v2);
friend Variant operator+(Variant&amp; v1, short i2);
friend Variant operator+(short i1, Variant&amp; v2);
</FONT></PRE><P>And so on. Be my guest if you want to add these. Most of them could be implemented as simple inline functions. <P><blockquote>Note     The <b>COleVariant</b> class provided by MFC has some nice features (support for <b>COleDateTime</b> and <b>COleCurrency</b>). These were apparently required to meet its primary goal, which I'm told was to fit in with the MFC database classes. I'm not qualified to judge how it fits that goal, but it certainly doesn't meet my goal, which was to match the Visual Basic <b>Variant</b> type. <b>COleVariant</b> has no arithmetic operators and only one logical operator--equality. Unfortunately, its equality operator will indicate that a variant double containing zero is not equal to a variant long containing zero. That's quite a change from Visual Basic. </blockquote><P><h3>One More Variant Example</h3><P>Visual Basic provides the <b>InStr</b> function for searching strings, but it always assumes you want to start at the front of the string and search back. There's no <b>InStrR</b> for searching backward. I needed a reverse string search function in my book <i>Hardcore Visual Basic</i>, so I wrote a crude <b>InStrR</b> in Basic, but noted in a comment that somebody ought to write an efficient version in C++. <P>Well, here it is. Check out the code in Tools.Cpp. The String Find button in the sample program tests it. The syntax of <b>InStr</b> is a little unusual, and <b>InStrR</b> has to jump through hoops to work the same way except better. The first and last parameters are optional, as shown in this syntax: <P><PRE><FONT FACE="COURIER" SIZE="2">position = InStr([start,] target, search[, compare])
</FONT></PRE><P>But alas, <b>InStr</b> behavior fails to match this syntax. You can't leave off the first argument if you supply the last argument. The following gives you a syntax error: <P><PRE><FONT FACE="COURIER" SIZE="2">i = InStr(sTarget, sFind, 1)
</FONT></PRE><P>Instead, you have to give the default first parameter (1) explicitly. <P><PRE><FONT FACE="COURIER" SIZE="2">i = InStr(1, sTarget, sFind, 1)
</FONT></PRE><P>I can't guess how Visual Basic implements this function so that the first argument isn't really optional. It's optional in my <b>InStrR</b>. All the parameters have to be Variants in order to make the optional arguments work. Here's how I do it: <P><PRE><FONT FACE="COURIER" SIZE="2">Long DLLAPI InStrR(Variant vStart, Variant vTarget, 
                   Variant vFind, Variant vCompare) 
{
  try {

    Long iStart = -1, afCompare = ffReverse;
    String sTarget, sFind;

    // Assign strings depending on whether vStart is given.
    if (vStart.Type() == VT_BSTR) {
        if (!vFind.IsMissing()) {
            afCompare |= ((Long)vFind ? ffIgnoreCase : 0);
        }
        sTarget = vStart;
        sFind = vTarget;

    } else {
        iStart = vStart;
        if (iStart &lt; 1) throw ERROR_INVALID_PARAMETER;
        if (!vCompare.IsMissing()) {
            afCompare |= ((Long)vCompare ? ffIgnoreCase : 0);
        }

⌨️ 快捷键说明

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