📄 article4.htm
字号:
sTarget = vTarget;
sFind = vFind;
}
// Find the string.
return sTarget.Find(sFind, afCompare, iStart);
} catch(Long e) {
ErrorHandler(e);
return 0;
}
}
</FONT></PRE><P>Most of the code deals with optional arguments. Once you figure them out, it takes only one line to find the string. <P><h3>How Variants Work</h3><P>You don't need to understand how the <b>Variant</b> class works to use it, but you can never know too much. See if you can find the private members where variant data is stored in the <b>Variant</b> class: <P><PRE><FONT FACE="COURIER" SIZE="2">class Variant : VARIANT
{
public:
// Constructors
Variant();
Variant(const Variant& vSrc);
Variant(BYTE bSrc); // VT_UI1
.
.
.
private:
// Constructor helper
void VariantCreate(VARTYPE vt = VT_EMPTY);
// Destructor helper
void VariantDestroy();
Boolean IsConstructed();
void Constructed(Boolean f);
// Look, Ma! No data.
};
</FONT></PRE><P>If you looked in the private section at the end of the class (where any self-respecting class would store data), you'd be out of luck. Instead the data comes at the very start where the <b>Variant</b> class is inherited from OLE's VARIANT structure. The <b>Variant</b> class gets internal access to all the VARIANT members, but because VARIANT is inherited privately, users of Variant can't see them. That's why Visual Basic can pass you a VARIANT and you can receive it as a Variant. They're the same thing--with a different interface. <P><h4>Variant Construction and Destruction</H4><P>Let's check out a few constructors. <P><PRE><FONT FACE="COURIER" SIZE="2">inline Variant::Variant()
{
VariantCreate();
}
inline Variant::Variant(const Variant& v)
{
VariantCreate();
HRESULT hres = VariantCopy(this, (Variant *)&v);
if (hres) throw hres;
}
inline Variant::Variant(BYTE nSrc)
{
VariantCreate(VT_UI1);
bVal = nSrc;
}
</FONT></PRE><P>What is this <b>VariantCreate</b> that seems to be doing the real construction work? Let's take a brief look at the destructor before I reveal the answer. <P><PRE><FONT FACE="COURIER" SIZE="2">inline Variant::~Variant()
{
if (IsConstructed()) {
VariantDestroy();
}
}
</FONT></PRE>
<P><h4>Implementation Choices</H4>
<P>If the variable was received from the host as a parameter, the host owns it and it shouldn't be destroyed. If we created it with a constructor, we need to destroy it. But you can't see how any of this works because I hide the details in the <b>VariantCreate</b>, <b>IsConstructed</b>, and <b>VariantDestroy</b> helper functions.
<P>You're going to have to check the source code to unravel this mystery. I'm not going to reveal the implementation in print because I might have to change it. One way or another, the class needs a flag (just one bit) indicating whether a given <b>Variant</b> object should be deallocated or not. <b>VariantCreate</b> will set this flag, <b>IsConstructed</b> will test it, and <b>VariantDestroy</b> will clear it. But you can't store the flag as a separate member variable, because that would change the size of the <b>Variant</b> class and make it impossible to receive VARIANTs as Variants. I can think of three ways to implement Variant constructors and destructors:
<P><OL><LI> Use a bit in one of the <b>wReserved</b><i> </i>fields as a flag. You know better than to use reserved fields of system data. What if they change the VARIANT type? Remember that I mentioned earlier that there are rumors they'll add a new type that uses part of that reserved data. On the other hand, with six bytes of unused data, surely no one would notice if we used just one itty bitty bit. But which one?
<LI> Use an unused bit in the <b>vt</b><i> </i>field as a flag. Some of those bits are used for some <b>IDispatch</b> purposes that don't matter to our limited version of the VARIANT structure. On the other hand, Variant system functions might not like us to use those bits for unrelated things. We might have to save and restore this bit before calling certain system functions.
<LI> Keep track of each Variant object in a static array of data structures as suggested at the end of Article 3 for the <b>String</b> class. This is the safest way, but it will be a lot more work to implement and will have a performance cost. </OL>I opted for the easiest choice, method 1, even though it's risky. My code works fine for Visual Basic 4.0, but who can say about future versions? You can check the source file for details, and change the implementation of <b>VariantCreate</b>, <b>IsConstructed</b>, and <b>VariantDestroy</b> if you feel uncomfortable with my choice. <P><h4>Variant Operators</H4><P>Just to give you a feel for how operator overloading of Variants works, here's the prefix version of the <b>operator++</b> function:<P><PRE><FONT FACE="COURIER" SIZE="2">// Prefix
Variant & Variant::operator++()
{
switch (vt) {
case VT_UI1:
++bVal;
break;
case VT_I2:
++iVal;
break;
case VT_I4:
++lVal;
break;
case VT_R4:
++fltVal;
break;
case VT_R8:
++dblVal;
break;
case VT_CY:
++cyVal.int64;
break;
default:
throw DISP_E_TYPEMISMATCH;
}
return *this;
}
</FONT></PRE><P><h3>Exception Handling Revisited</h3><P>You've seen several examples of functions that throw exceptions. The <b>GetTempFile</b> function in Article 3 and the <b>SearchDirs</b> function in this article throw Win32 error constants as exceptions. But if you look at the <b>operator++</b> example above or at many of the other methods of the <b>String</b>, <b>Variant</b>, and <b>SafeArray</b> classes, you'll see that they throw OLE HRESULT constants. Win32 errors are always positive. HRESULTs can be negative or positive, and contain specific status bits. Mixing these two kinds of errors may seem random, but there's a method in the madness. <P>The <b>String</b>, <b>Variant</b>, and <b>SafeArray</b> classes are designed to be used with OLE objects. They happen to also work with the non-object DLLs described in this series. If we were dealing with OLE objects, the <b>ErrorHandler</b> function called in the catch blocks would be raising OLE exceptions and it would be much easier to do so if the errors raised were HRESULT values. The OLE exceptions would be seen in Visual Basic as normal errors, trappable with Basic's <b>On Error</b> statements. This is what you should be doing, and what I originally intended to explain before deadline realities set in. <P>Unfortunately, you can't generate Basic errors from non-object DLLs. You have to fall back on old-fashioned techniques such as returning error values. The API way of doing this is to use <b>SetLastError</b> to set an error code in the DLL. The DLL client can call <b>GetLastError</b> to get details (although, as mentioned in Article 1, this actually means checking the <b>LastDllError</b> property of the Basic <b>Err</b> object). That's what the <b>ErrorHandler</b> function does in VBUTIL. Here's the code: <P><PRE><FONT FACE="COURIER" SIZE="2">void ErrorHandler(Long e)
{
DWORD err = 0;
if (e >= 0) {
err = (DWORD)e;
} else {
err = HResultToErr(e);
}
SetLastError((DWORD)err);
}
</FONT></PRE>
<P>If the error is an HRESULT, <b>ErrorHandler</b> calls <b>HResultToErr</b> (which is nothing more than a big switch statement) to translate to a Win32 error. It then calls <b>SetLastError</b> to store the error value for any client that wants to check it. Because VBUTIL functions such as <b>SearchDirs</b> are local to this DLL, they won't be used by OLE objects and thus can throw Win32 errors directly to avoid the translation.
<P>
One other point. When you use the OleType static library in your own projects, don抰 forget about exceptions. The <b>String</b>, <b>Variant</b>, and <b>SafeArray</b> classes can throw exceptions, and you must be ready to catch them. You have two choices. First, you can make sure you never do anything with a <b>String</b>, <b>Variant</b>, or <b>SafeArray</b> that could throw an exception. This is tough, but you could probably manage it in some projects. Your second, better choice is to catch and handle exceptions. VBUTIL provides a model of one simple technique, but there抯 nothing sacred about its <b>ErrorHandler</b> function. You may want to design a better function for handling exceptions, especially if you use the library in OLE servers or controls.
<P>
<P><A HREF="intro.htm">Introduction</A>
<P><A HREF="article1.htm">Article 1. Stealing Code with Type Libraries</A>
<P><A HREF="article2.htm">Article 2. Libraries Made Too Easy</A>
<P><A HREF="article3.htm">Article 3. Strings the OLE Way</A>
<P><A HREF="article5.htm">Article 5. The Safe OLE Way of Handling Arrays</a><P>
<!--DocFooterEnd-->
</BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -