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

📄 article4.htm

📁 The code for this article was written for version 1.0 of the Active Template Library (ATL). The cu
💻 HTM
📖 第 1 页 / 共 3 页
字号:
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<HTML><HEAD><TITLE>Article 4. The Ultimate Data Type</TITLE></HEAD>
<!--DocHeaderStart-->
<BODY bgcolor="#ffffff" text="#000000" leftmargin=0 topmargin=0 link="#0080C0" vlink="gray">

<!-- This is a PANDA Generated HTML file. The source is a WinWord Document. -->
  
<P><h1>Article 4. The Ultimate Data Type</h1>
<P>Bruce McKinney
<P><!--DATE-->April 18, 1996
<P><!--/DATE-->
<P>Microsoft&#174; Visual Basic&#174; changed the programming world when it introduced the Variant type. The idea that all types can be contained within a single type is a trade of performance, size, and implementation simplicity for user simplicity. A single type that automatically converts between strings, integers, real numbers, and objects is a compelling feature for macro languages. In fact, variants will be the only type in Microsoft's new Internet macro language, Visual Basic, Scripting Edition (VBScript). Variants are less compelling for full-featured languages that aspire to high performance. Still, they offer many features that you can't easily get through intrinsic types--named arguments, optional arguments, parameter arrays, and collections.
<P>Variants have been transformed from a proprietary feature of Visual Basic to a standard OLE type. Delphi&#174; 2.0 from Borland&#174; has an intrinsic Variant type, and the Microsoft Foundation Class Library (MFC) has a <b>COleVariant</b> type that encapsulates some of the Variant functionality.
<P><h2>What Is a VARIANT?</h2>
<P>We're going to be talking about three different things in this article: variants, VARIANTs, and Variants.
<P><UL><LI> Lowercase variant is the <i>name of the concept</i> of cramming different subtypes into one supertype.
<LI> To a C programmer, a VARIANT is something much more specific: It's a <i>structure containing a union</i>. You have to understand how it works to use it.
<LI> To a Visual Basic programmer, a Variant is a<i> type that can contain another type</i> (you don't have to understand how it works). To a C++ programmer, a Variant is like everything else in C++--it's whatever you want it to be. We want to make it into something as close to an intrinsic Variant type as C++ allows, but to make that happen, we'll have to take an inside look at the VARIANT structure.
</UL><h3>VARIANT Insides</h3><P>There's no getting around it. We'll have to look at the VARIANT structure before we can talk about what to do with it. <P><PRE><FONT FACE="COURIER" SIZE="2">struct tagVARIANT {
    VARTYPE vt;
    WORD wReserved1;
    WORD wReserved2;
    WORD wReserved3;
    union {
    //  C++ Type      Union Name   Type Tag                Basic Type
    //  --------      ----------   --------                ----------
        long          lVal;        // VT_I4                ByVal Long
        unsigned char bVal;        // VT_UI1               ByVal Byte
        short         iVal;        // VT_I2                ByVal Integer
        float         fltVal;      // VT_R4                ByVal Single
        double        dblVal;      // VT_R8                ByVal Double
        VARIANT_BOOL  boolVal;     // VT_BOOL              ByVal Boolean
        SCODE         scode;       // VT_ERROR
        CY            cyVal;       // VT_CY                ByVal Currency 
        DATE          date;        // VT_DATE              ByVal Date
        BSTR          bstrVal;     // VT_BSTR              ByVal String
        IUnknown      *punkVal;    // VT_UNKNOWN 
        IDispatch     *pdispVal;   // VT_DISPATCH          ByVal Object
        SAFEARRAY     *parray;     // VT_ARRAY|*           ByVal array
        // A bunch of other types that don't matter here...
        VARIANT       *pvarVal;    // VT_BYREF|VT_VARIANT  ByRef Variant
        void          * byref;     // Generic ByRef        
    };
};
</FONT></PRE><P>The idea is that the union part can contain any one of the supported types. The <b>VARTYPE vt</b><i> </i>field should contain a type tag (and possibly a tag modifier) indicating what the union contains. Technically, that's all you really need to know. Every time you put a variable into a VARIANT, you also insert its type tag. Every time you want extract a variable, you first check its type so that you know what to do with it. Actually, OLE provides a group of system functions that help manage these tasks. We'll get to them soon. <P>How big is a VARIANT? The first field is a <b>VARTYPE</b>, which is actually a typedef for an unsigned short. The next three fields are <b>WORD</b>, another typedef for an unsigned short. So VARIANT is 8 bytes plus the size of the union. A union contains enough storage space for its largest member, which in this case is 8 bytes (for a double, currency, or date). So a VARIANT is 16 bytes. That's a healthy chunk of storage if you have an array of variants containing 1 byte each. But of course, you won't be doing that very often. <P>The most common size of data is 4 bytes (for a long, an error, or any kind of pointer). In this case, 11 of those 16 bytes are just padding. It's rumored that VARIANTS may get a new internal data type that will use up some of those reserved fields in some future version of OLE and Visual Basic. <P>If you look in OLE documentation, you'll sometimes see parameters that look like VARIANTs, but actually have the type VARIANTARG. In fact, VARIANTARG is just a typedef for VARIANT, but just as a BSTR is more than its typedef, a VARIANTARG is more than a VARIANT. To be more exact, a VARIANTARG includes all of a VARIANT, but a VARIANT doesn't include all of a VARIANTARG. Or, to be less confusing, a VARIANTARG is a VARIANT used as an <b>IDispatch</b> argument and it can contain anything in the VARIANT's union, including by-reference arguments. A VARIANT in other contexts isn't supposed to use all those union members marked VT_BYREF in the second part of the union. <P>The <b>IDispatch</b> interface is a fine thing, but we won't have get around to it in this series of articles. You can forget about <b>VARIANTARGS</b> and all the by-reference fields of the VARIANT union. We'll only be talking about the standard OLE types introduced in Article 2, and some of them will get short shrift. <P><blockquote><b>Note: </b>   The original name for the <b>boolVal</b> field of the VARIANT union was <b>bool</b>, but <b>bool</b> is a future C++ keyword and Microsoft Visual C++&#174; 4.1 gives a warning message if you use it. Presumably, a future version will actually implement the new bool type. In the meantime, there is a slight discrepancy: Readers on the Visual C++ subscription program who have version 4.1 will see <i>boolVal </i>in their VARIANTS, but those who have version 4.0 or earlier will see <i>bool</i>. I solved this for Visual C++ by changing my code to use <i>boolVal </i>and providing a macro that defines <i>boolVal </i>to <i>bool </i>for earlier versions. You may have to hack further if you have a different compiler.</blockquote><P><h3>The VARIANT System Functions</h3><P>I find the documentation for the VARIANT system functions to be sparse, and somewhat confusing. So here's my version, with a few editorial comments on the side. My function declarations are cleaned up a little from the macro-ized portable versions in the include file. <P>But before we get into the individual functions, let's talk for a minute about the HRESULT type. It's actually a typedef to long, but not just any old long. An <b>HRESULT</b> is a bit field in which some bits specify status or severity and other bits contain an error number. For now, zero means no error (it is usually represented by the macro NO_ERROR or S_OK) and anything else means some kind of failure. Technically an HRESULT uses positive numbers to indicate different kinds of success and negative numbers to indicate different kinds of failure, but the VARIANT system functions return only one kind of success--represented by zero. <P><h4>void VariantInit(VARIANT * pv);</H4><P>This statement creates a new VARIANT. The <b>vt</b><i> </i>field of the VARIANT structure is set to VT_EMPTY. The OLE documentation states that the <b>wReserved</b><i> </i>field is set to zero. If you step into <b>VariantInit</b> in a debugger, you'll see that it sets the <b>vt</b><i> </i>field to zero and leaves the <b>wReserved1</b>, <b>wReserved2</b>, and <b>wReserved3</b><i> </i>fields untouched.<P><b>Example:</b><P><PRE><FONT FACE="COURIER" SIZE="2">// Create a VARIANT and initialize it to a string.
VARIANT varSrc;
VariantInit(&amp;varSrc); 
varSrc.bstrVal = SysAllocString(L&quot;The String To End All Strings&quot;)
</FONT></PRE><P><h4>HRESULT VariantClear(VARIANT * pv);</H4><P>This function destroys a VARIANT. It does the same thing as <b>VariantInit</b> and more. If the variant contains allocated data (a BSTR, SAFEARRAY, or <b>IDispatch</b> pointer), it calls the proper functions to free that data. Technically you could call <b>VariantInit</b> instead if you knew your VARIANT didn't contain allocatable data, but this is bad practice and you'll rarely be in a situation where you know the type at destruction time. <P><b>Example:</b><P><PRE><FONT FACE="COURIER" SIZE="2">// Destroy an existing VARIANT.
hres = VariantClear(&amp;varSrc); 
</FONT></PRE><P><h4>HRESULT VariantCopy(VARIANT * pvDst, VARIANT * pvSrc);</H4><P>This function copies one VARIANT to another. It frees any allocatable contents of the destination before the copy. It also allocates copies of any allocatable data in the source and copies the data to the other; the result is two identical VARIANTs. If the source contains a BSTR or SAFEARRAY, the destination will have a separate, identical copy. If the source contains an object (<b>IDispatch</b> or <b>IUnknown</b> pointer), the reference count will be incremented to indicate that both VARIANTs have access to the object. If this doesn't make sense, don't worry about it yet.<P><b>Example:</b><P><PRE><FONT FACE="COURIER" SIZE="2">// Create a new VARIANT by copying from an existing variant.
VARIANT varDst;
VariantInit(&amp;varDst);
hres = VariantCopy(&amp;varDst, &amp;varSrc); 
</FONT></PRE><P><h4>HRESULT VariantCopyInd(VARIANT * pvDst, VARIANT * pvSrc);</H4><P>This function copies one VARIANT to another, in the same manner as <b>VariantCopy</b>, but makes sure the destination contains a by-value copy of the data even if the source contained by-reference data. Because we won't be dealing with by-reference data, we won't need this function. <P><h4>HRESULT VariantChangeType(VARIANT * pvDst, VARIANT * pvSrc, WORD wFlags, VARTYPE vt);</H4><P>This function changes the type of a VARIANT without changing its value (if possible). To change a variable in place, make the destination the same as the source. To copy a variable while changing its type, make the source the VARIANT containing the desired value and the destination the VARIANT to receive the copy. Put the desired type into the <i>vt </i>parameter. The only flag you can pass to the <i>wFlags </i>parameter is VARIANT_NOVALUEPROP, but you should only do that for objects, and the reasons why you might want to are beyond the scope of this article. Just pass zero. Be sure to check the return value, because some type conversions don't make sense and are bound to fail.<P><b>Example:</b><P><PRE><FONT FACE="COURIER" SIZE="2">varSrc.dblVal = 3.1416
// Copy varSrc to varDst while changing its type to Long (value 3).
hres = VariantChangeType(&amp;varDst, &amp;varSrc, 0, VT_I4)
if (hres) throw hres;
// Change varSrc to a string (value &quot;3.1416&quot;).
hres = VariantChangeType(&amp;varSrc, &amp;varSrc, 0, VT_BSTR)
if (hres) throw hres;
</FONT></PRE><P><h4>HRESULT  VariantChangeTypeEx(VARIANT * pvDest, VARIANT * pvSrc, LCID lcid, WORD wFlags, VARTYPE vt); </H4><P>This function (and the OLE documentation for it) is exactly the same as <b>VariantChangeType</b> except that it has an <i>lcid </i>parameter where you can pass a language ID. Apparently passing a different language ID might result in some sort of language translation for strings, dates, and objects, but you won't be able to figure out, from the OLE documentation or from this article, how such a change works. We won't be using this function.<P><h4>And Lots More</H4><P>OLE provides 81 additional functions for converting between the various OLE types. If you've seen one, you've seen them all, but as a bonus, I'll show two. <P><h5>HRESULT VarI2FromI4(long lIn, short * psOut);<BR>HRESULT VarI4FromI2(short sIn, long * plOut);</h5><P>You pass the first function a long and it returns the converted short through a reference parameter. The second function will convert that short back to a long. This may look simple, but there are overflow problems that OLE wants to handle itself rather than leaving you to guess the approved way. Besides, some conversions--Boolean from BSTR or dispatch from date--are more difficult. The <b>VariantChangeType</b> function uses a lot of these functions internally, but you can use them directly if you want. <P>Example:<P><PRE><FONT FACE="COURIER" SIZE="2">short i2;
varSrc.lVal = 1234;
// Extract a short from a VARIANT containing a long.
hres = VarI2FromI4(varSrc.lVal, &amp;i2);
// Assign a short to a VARIANT, but make it long.
hres = VarI4FromI2(i2, &amp;varDst.lVal);
</FONT></PRE><P><h2>The Variant Class</h2><P>The Variant class attempts to hide VARIANT details in the same way that the String class hides BSTR details. In many ways, it does a much better job. Because VARIANT is a structure, you can simply inherit a Variant from it. Internally, a Variant has no private members of its own, just the members of VARIANT. But Variant has one thing that VARIANT doesn't have: member functions including contructors, a destructor, and lots of operator functions.<P>We'll look briefly at the implementation of Variant later. The important thing here is that Visual Basic can pass a VARIANT to your DLL, but you can receive it as a Variant. Let's take a look.<P><h3>A Variant API Example</h3><P>In my book <i>Hardcore Visual Basic </i>I provided a Basic-friendly wrapper function for the <b>SearchPath</b> application programming interface (API) function. My <b>SearchDirs</b> function expected three Long arguments and looked like this: <P><PRE><FONT FACE="COURIER" SIZE="2">sFullName = SearchDirs(sEmpty, &quot;vb.exe&quot;, sEmpty, iDir, iBase, iExt)
</FONT></PRE><P>The function would return the full path in the return value and the indexes of the directory, base file, and extension through reference variables. The first and third arguments were for the path to search and the extension, but in most cases you would just pass an empty string (the <i>sEmpty </i>constant from my type library). You could use the returned indexes with the <b>Mid$</b> function to extract the parts of the full path. This was nice, but you always had to declare and pass those index variables whether you wanted them or not, and you always had to pass empty variables for the path and extension. <P>The version of <b>SearchDirs</b> shown in this article takes optional Variants instead of Longs for the index variables, and optional Variants for the path and extension arguments. You can find the code for it in WIN32.CPP. The event procedure of the Win32 button in the sample program contains code that excercises <b>SearchDirs</b>. The <b>GetFullPath</b> function in the same location is similar: it expands a filename to a full path specification.<P>The original <b>SearchDirs</b> tried to maintain a parameter order similar to that of <b>SearchPath</b>. This version orders the arguments logically in order to make it easier to leave off ones that usually aren't needed. Any of the following Basic statements are OK:<P><PRE><FONT FACE="COURIER" SIZE="2">sFullName = SearchDirs(&quot;vb&quot;, &quot;.exe&quot;, &quot;.&quot;, vBase, vExt, vDir)
sFullName = SearchDirs(&quot;vb.exe&quot;, , , vBase, vExt)
sFullName = SearchDirs(&quot;vb&quot;, &quot;.exe&quot;, , vBase)
sFullName = SearchDirs(&quot;vb.exe&quot;)
</FONT></PRE><P>Notice that the directory parameter has been moved to the end because it is the least likely to be used. The filename parameter has been moved to the start of the list because it is required and is often the only argument needed. You could argue about the handiest order for the path and extension parameters. I chose to make the the extension the second parameter and the path the third parameter. <P>You can find this sample in the Win32.Cpp module. It is used by the event handler of the Win32 button in the Cpp4VB sample program. <P>Let's start by examining the ODL declarations: <P><PRE><FONT FACE="COURIER" SIZE="2">[
entry(&quot;SearchDirs&quot;),
usesgetlasterror,
helpstring(&quot;Searches sPath for sFile with sExt...&quot;),
]
BSTR WINAPI SearchDirs([in] BSTR bsFile,
                       [in, optional] VARIANT vExt,
                       [in, optional] VARIANT vPath,
                       [in, out, optional] VARIANT * pvFilePart,
                       [in, out, optional] VARIANT * pvExtPart,
                       [in, out, optional] VARIANT * pvDirPart);
</FONT></PRE><P>Notice that <b>SearchDirs</b> has two kinds of VARIANT parameters--input parameters that will pass in strings and output Variants that will receive integer indexes. The output Variants are pointers so that they can receive return values. Although the index parameters are only used for output, I chose to make them in/out parameters and overwrite any input. This makes it easier for clients to reuse the same variables for multiple calls.<P>Let's take a look at the C++ code that makes this possible. Actually, <b>SearchDirs</b> looks a lot like the <b>GetTempFile</b> function described in Article 3, except that it's a little more complicated and includes optional arguments. I'm going to ignore the String part (interesting though it may be) and concentrate on the Variant features: <P><PRE><FONT FACE="COURIER" SIZE="2">BSTR DLLAPI SearchDirs(
    BSTR bsFileName,
    Variant vExt,
    Variant vPath,
    Variant * pvFilePart,
    Variant * pvExtPart,
    Variant * pvDirPart
    )
{
  try {
    LPTSTR ptchFilePart;
    Long ctch;
    String sFileName = bsFileName;
    if (sFileName.IsEmpty()) throw ERROR_INVALID_PARAMETER;

    // Handle missing or invalid extension or path.
    String sExt;        // Default initialize to empty
    if (!vExt.IsMissing() &amp;&amp; vExt.Type() != VT_EMPTY) {
        if (vExt.Type() != VT_BSTR) throw ERROR_INVALID_PARAMETER;
        sExt = vExt;
    }
    String sPath;       // Default initialize to empty.
    if (!vPath.IsMissing() &amp;&amp; vPath.Type() != VT_EMPTY) {
        if (vPath.Type() != VT_BSTR) throw ERROR_INVALID_PARAMETER;
        sPath = vPath;
    }
    
    // Get the file (treating empty strings as NULL pointers).
    String sRet(ctchTempMax);
    ctch = SearchPath(sPath.NullIfEmpty(), sFileName, 

⌨️ 快捷键说明

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