March 30, 1999
See also: "Dr. GUI and COM Automation, Part 1"
Dr. GUI's Bits and Bytes
Where We've Been; Where We're Going
COM Data Types
Give It a Shot!
Where We've Been; Where We're Going
As you probably know by now, MSDN Online and Site Builder Network have merged into one program to serve all developers—whether Web developers or more "traditional" developers. To paraphrase the fella in the Wendy's ad years ago, "Developers is developers."
And that's certainly more true today than it was a couple of years ago. Microsoft Visual Basic® developers are doing ASP pages. C++ developers are doing server components. HTML developers are doing C++. And everyone's using databases. (Okay, not quite everybody—yet.) More and more, folks are doing a little of everything, it seems—and that trend looks like it'll continue.
It's no longer true that there's a big gap between Web developers, desktop developers, and server developers. We've been noticing this at MSDN: The percentage of MSDN Online members who are also Site Builder members is well over 60 percent. And you've been telling us that having two sites for more or less the same audience is confusing.
So, we can take a hint: It's time to merge the sites, and the teams that create them. The benefit for you: "one-stop shopping." Enjoy!
By the way, Dr. GUI will be appearing as a monthly column in the "Voices" section of MSDN Online, along with other columnists.
Dr. GUI had the pleasure of hearing Roger Sessions, author of COM and DCOM, published by Wiley Computer Pub., 1998, and head of ObjectWatch (http://www.objectwatch.com/), speak on COM, DCOM, MTS, and COM+ recently at the MSDN Professional Developers' Summit. The summit, featuring Roger and other speakers, was an excellent one-day seminar put on by Microsoft's Pacific Northwest district office. (They actually held the event twice—once in Seattle and once in Portland.) Dr. GUI tips his stethoscope to the folks who put it together. It was excellent!
In addition to great speakers, the event's admission included a beta copy of Microsoft Windows® 2000 and a copy of Roger Sessions' book.
The good doctor nearly missed this event. He found out about it because a friend mentioned it. Don't you miss these events in the future: Plan your events for the next few months by visiting http://events.microsoft.com/isapi/events/default.asp and http://msdn.microsoft.com/events/. And you can sign up for e-mail bulletins from Microsoft (there are scores available) at http://www.microsoft.com/insider/subscribe/default.htm?mscomtb=icp_free%20newsletter. Oh—and most of these events are FREE!
In any case, I'll be writing more about Roger Sessions later on. For now, just let me say that he was heavily involved with CORBA in the past but now recommends COM to folks who need to build enterprise systems. Why? Read his book and find out. It's well written and easy to understand, and it'll give you a good grip on this technology, even if you're not primarily a programmer.
Dr. GUI's been using Internet Explorer 5 so long that he's almost forgotten about it. Frankly, it's easy to forget about because it almost never crashes. The reliability is far better than the version 4 browsers from either major company. It's worth getting for that reason alone.
There are a slew of productivity features. Dr. GUI's favorite is that Internet Explorer 5 remembers what you typed in form fields previous times and offers those as choices the next time you come to that form. Coolness! (And, yes, you can shut the feature off for passwords...)
For developers, there are three opportunities. First, the enhanced stability means that deploying apps that use Internet Explorer 5 makes more sense than ever. Second, there are a number of cool new features, including DHTML behaviors and XML support you may want to use. Third, there's an opportunity to write your own Web Accessories (http://www.microsoft.com/windows/ie/WebAccess/).
Check it out at http://www.microsoft.com/windows/ie/.
Walt Mossberg of The Wall Street Journal reviewed some calendar applications that are available on the Web—and didn't really like any of them. Why? Two reasons: First, "the language in which they're written, HTML, is hopelessly crude and clumsy for doing applications." Second, "For 95% of users, who use dial-up modems, the Web is far too slow and unreliable for an application program, especially when compared with the fast response times people have become accustomed to getting from programs running from a PC's hard disk." (Check out the entire article at http://ptech.wsj.com/archive/ptech-19990304.html.)
Before you commit to an all-Web approach, make sure you'll get the performance you'll need. It's amazing what a well-placed Microsoft ActiveX® control can do for performance in crucial areas—or maybe it really is better to do the app in Visual Basic. Often the Web is the best choice, but don't assume that's always the case. Compare Hotmail with Outlook Express, for instance. What's cool is that the new Outlook Express can read the mail from your Hotmail account for you. So you get both the speed of a PC app and the ability to access your e-mail from a Web browser anywhere in the world.
You mean to say that after all Dr. GUI's written about Windows CE the last couple of years, you STILL haven't developed your first app? The good doctor is disappointed. But wait—there's hope: Check out Douglas Boling's article "Programming for Windows CE Devices" in PC Magazine Online. He covers getting started writing apps for H/PC target platforms and a little bit on other platforms, including custom embedded platforms. Check it out at http://www.zdnet.com/pcmag/pctech/content/18/06/tf1806.001.html.
The fun thing about having a practice in computers is that there's always something new and fun. To follow are a few things Dr. GUI has noticed recently.
Working on a system that needs an embedded OS, but Windows CE isn't quite enough? Check out embedded Windows NT, now in beta release. (The press release is at http://www.microsoft.com/presspass/press/1999/Feb99/BeEmPr.htm.)
Dr. GUI got a chance to see machines running ClearType, the new Microsoft technology for making color LCD screens much easier to read.
It's amazing.
The contrast is reduced a little, but the clarity of the text is amazing. No more little dots—the characters are well formed. Seeing it makes it hard to go back to regular text. There's more on this at http://www.microsoft.com/OpenType/cleartype/.
Did you hear about the company that'll give you a free PC—and Internet access—if you agree to have ads displayed on the monitor? (See http://www.free-pc.com/.) Amazing. (They say they've had a million applicants for their initial run of 10,000 machines.)
Frankly, when Dr. GUI paid $3,000 for an original IBM PC with 48 KB (that's KB, not MB) of RAM, a 160 KB floppy disk drive (double-sided floppy drives weren't available, let alone hard disks), and a cheap color monitor, he never even dreamed of machines this cheap. And he suspects we ain't seen nothin' yet.
Office 2000 Developer will offer a tremendous amount more power than previous editions. While this is mostly intended for folks who want to write applications that use the full power of Office 2000, you'll even be able to write your own Office Assistants. Think you don't like the concept? What if Scott Adams did a set of Office Assistants based on the characters in Dilbert? The doctor would pay good money for something like this—so there may be some money to be made by you. There are a bunch of stops left on the Developer Technology Tour 99—probably at least one near you! Check it out at http://msdn.microsoft.com/community/usergroup/. Admission is free.
This is the second of three parts about COM Automation. (The good doctor was planning on, uh, dispatching COM data types in one column, but it was just too much to do.)
You think three parts is a lot? Well, just remember that Brockschmidt spent more than 100 pages on the topic, so be thankful. (And he didn't discuss a lot of what we'll discuss.) This time, we'll talk about variants. Next time, we'll talk about the other special data types COM provides—BSTR, DATE, CURRENCY, DECIMAL, and SAFEARRAY. After that, we'll switch gears and start talking about events in COM.
Last time around, the good doctor almost totally avoided talking about COM data types, including our friend the variant. In fact, about all Dr. GUI had to say last time was this:
"Variants are stored in 16 bytes. The first 2 bytes are a tag that contains a number representing the type of the variant, the next 6 bytes are padding, and the final 8 bytes are the value of the variant. The format of the value depends on the value of the tag. In C/C++, we represent the value of the variant with a union. Variants can hold most of the C++ data types plus pointers, arrays, strings, dates, and currency objects. We'll do the full treatment on COM data types, including variants, next time."
Well, last time's "next time" is this time, so here's your full discussion of variants.
As the good doctor was attending medical school classes about COM data types, he noticed an interesting thing: they are used everywhere! You can use them in C and C++. You can use most of them in Microsoft Visual J++®. You can use them all in Visual Basic. They show up in Microsoft SQL Server™, Access, Visual FoxPro®, and Excel. And, of course, they show up everywhere scripting languages such as Visual Basic for Applications shows up—and that's in a lot of apps, both Microsoft and non-Microsoft.
In other words, if you use COM data types you'll be able to move your data from application to language to database across the network somewhere very easily.
But wait—there's more: COM data types are powerful. There's a string type, two different currency types, a date type, and even a multidimensional array type. These can be handy for any programming, but they're especially handy when you're talking to COM components.
So even if you're not programming COM in C or C++, take a read through this article to learn about the data types you're using, whether you're writing Visual Basic for Applications macros in Word or stored procedures using SQL Server.
If you're a C or C++ programmer, you're going to drool over some of the data types we discuss here, especially those having to do with exact representation of decimal numbers (Currency and DECIMAL) and dates. (If you program in Visual Basic, Visual FoxPro, or SQL Server, you use these types all the time anyway. They're built in.)
You can certainly use them from programs that implement or use COM components, but you can also use them from "non-COM" programs that are running under an OS that supports COM.
For most of the data types, all you have to do is include the appropriate header. However, several data types rely on dynamic memory allocation. For those, you'll have to be sure to call CoInitialize or CoInitializeEx at the beginning of your program. Just make the appropriate call if you're going to use COM data types at all and save yourself trouble later.
The very simple example included with this article shows how to do this from a "Hello, world!" console application that doesn't otherwise use COM or Windows at all.
If you use Visual J++, note that there are classes in the com.ms.com.*
package that wrap variants and safe arrays, but not the others.
The quickest way to start to understand what you can put in a variant is to look at the definition (as of Microsoft Visual C++ version 6.0, currently in oaidl.idl, which MIDL outputs as oaidl.h, which is included by oleauto.h). Click to see the full definition.
We'll go through the definition section by section now. Here's the first line:
typedef [wire_marshal(wireVARIANT)] struct tagVARIANT VARIANT;
This forward declaration defines the VARIANT
type. The wire_marshal
attribute says that when we need to marshal a VARIANT
, we'll use the IDL type definition for wireVARIANT
. If you look up wireVARIANT
, you'll see the format COM uses to marshal variants, including the case attributes used to send only the correct member of the union. Note that our variant type, VARIANT
, is defined as a structure (that's defined next).
Next we have the beginning of the variant itself:
struct tagVARIANT {
union {
struct __tagVARIANT {
// some stuff deleted...
union {
// much stuff deleted...
struct tagBRECORD {
If you've read most COM references, the declaration we're about to see will look a little strange to you. Why is there a struct
nested inside a union
nested inside a struct
nested inside a union
nested inside a struct
? Oh, my!
We'll answer that question at the end of the discussion. The extra (outer) union
is for handling the DECIMAL type.
For now, let's ignore the outer struct and union and concentrate on the code starting with struct __tagVARIANT
. This code will look familiar if you've studied COM Automation before, although there is a recent addition (at the end).
struct __tagVARIANT {
VARTYPE vt;
WORD wReserved1;
WORD wReserved2;
WORD wReserved3;
First, we've got a 2-byte integer tag vt
that stores the type code for the variant. The codes are represented by constants that begin with VT
, such as you see in the comments to follow.
Following the tag are 6 bytes of padding in three words. The padding is so that the data that follows will begin on an 8-byte boundary.
Next comes the union that holds the data of the variant. We're breaking this up into several sections. The first section shows the basic data types:
union {
LONG lVal; /* VT_I4 */
BYTE bVal; /* VT_UI1 */
SHORT iVal; /* VT_I2 */
FLOAT fltVal; /* VT_R4 */
DOUBLE dblVal; /* VT_R8 */
VARIANT_BOOL boolVal; /* VT_BOOL */
_VARIANT_BOOL bool; /* (obsolete) */
This section comprises 1-, 2-, and 4-byte integers; 4- and 8-byte binary floating point (real) numbers, and Visual Basic-style Boolean (true/false) values. "I" and "R" refer to the type of the variable: integer or real. "U" indicates unsigned, and the number is the size of the type in bytes.
There will be 8-byte (64-bit) integer types (VT_I8
, VT_UI8
) in a future version of COM, but they're not currently implemented.
In Visual Basic and in VT_BOOL
, false is zero and true is –1. You have to be careful of this if you're using C, because C defines true as 1, not –1. The second BOOL
is obsolete—ignore it.
Next come some COM-specific data types:
SCODE scode; /* VT_ERROR */
CY cyVal; /* VT_CY */
DATE date; /* VT_DATE */
BSTR bstrVal; /* VT_BSTR */
IUnknown * punkVal; /* VT_UNKNOWN */
IDispatch * pdispVal; /* VT_DISPATCH */
SAFEARRAY * parray; /* VT_ARRAY */
SCODE
is an error code that holds an HRESULT
error code.
In 16-bit Windows, SCODE
and HRESULT
were different types. In Win32, they're the same type: a 32-bit signed integer.
DATE
is the Visual Basic and Excel date format: an 8-byte floating point number containing the number of days since December 30, 1899. We'll talk about this more next time.
CY
stands for "currency"—it corresponds to the Visual Basic currency type. We'll describe it next time.
BSTR
is the Visual Basic string format, also to be described next time.
The IUnknown
and IDispatch
pointers are what you'd expect: COM interface pointers. This makes variants very powerful: They can hold interface pointers to COM objects.
The IDispatch
pointer is the more powerful of the two, because any program that knows how to make Automation calls can use this pointer to access properties and call methods on the object. The IUnknown
pointer type can point to any COM object, whether or not it's an Automation object. Scripting languages can't call it, although they can pass it to another component that can use it.
Finally, a SAFEARRAY
is a fairly complex data structure that corresponds with how Visual Basic stores arrays. We'll talk about this in-depth next time.
The next section contains the data types just described (plus a couple more) passed by address rather than passed by value.
BYTE * pbVal; /* VT_BYREF|VT_UI1 */
SHORT * piVal; /* VT_BYREF|VT_I2 */
LONG * plVal; /* VT_BYREF|VT_I4 */
FLOAT * pfltVal; /* VT_BYREF|VT_R4 */
DOUBLE * pdblVal; /* VT_BYREF|VT_R8 */
VARIANT_BOOL *pboolVal; /* VT_BYREF|VT_BOOL */
_VARIANT_BOOL *pbool; /* (obsolete) */
SCODE * pscode; /* VT_BYREF|VT_ERROR */
CY * pcyVal; /* VT_BYREF|VT_CY */
DATE * pdate; /* VT_BYREF|VT_DATE */
BSTR * pbstrVal; /* VT_BYREF|VT_BSTR */
IUnknown ** ppunkVal; /* VT_BYREF|VT_UNKNOWN */
IDispatch ** ppdispVal; /* VT_BYREF|VT_DISPATCH */
SAFEARRAY ** pparray; /* VT_BYREF|VT_ARRAY */
VARIANT * pvarVal; /* VT_BYREF|VT_VARIANT */
PVOID byref; /* Generic ByRef */
You see that they all have the VT_BYREF
value logically or'ed with their type, and that the data type changes from Foo
to Foo *
. Also note that there's a pointer to variant type, as well as a generic void
pointer for C++ programmers to use.
The next set shows variations on the integer theme. The INT and UINT types are different sizes on 16-bit and 32-bit OS's, but because most folks are running 32-bit operating systems, that doesn't matter so much any more.
CHAR cVal; /* VT_I1 */
USHORT uiVal; /* VT_UI2 */
ULONG ulVal; /* VT_UI4 */
INT intVal; /* VT_INT */
UINT uintVal; /* VT_UINT */
The next set shows pointers to the preceding set, and something special: a pointer to a special decimal type that we'll talk about next time. The reason that we've not seen the nonreference version of this is that the DECIMAL type is larger than 8 bytes. We'll see how it fits into a variant:
DECIMAL * pdecVal; /* VT_BYREF|VT_DECIMAL */
CHAR * pcVal; /* VT_BYREF|VT_I1 */
USHORT * puiVal; /* VT_BYREF|VT_UI2 */
ULONG * pulVal; /* VT_BYREF|VT_UI4 */
INT * pintVal; /* VT_BYREF|VT_INT */
UINT * puintVal; /* VT_BYREF|VT_UINT */
We're almost done. If you've studied COM Automation before, you've probably not seen the next two data types. They were added relatively recently.
The first of these is a type to handle records, aka user-defined data types (UDTs).
struct __tagBRECORD {
PVOID pvRecord;
IRecordInfo * pRecInfo;
} __VARIANT_NAME_4; /* VT_RECORD */
} __VARIANT_NAME_3; // union
} __VARIANT_NAME_2; // struct __tagVARIANT—note underscores
Note that the last two lines are the closing braces for the union and for the whole variant structure. You can ignore VARIANT_NAME_1
through VARIANT_NAME_4
—they're defined as nothing, or as dummy names if your compiler can't handle unnamed unions and structs nested in other structs and unions.
The data for a UDT (or record) includes a pointer to the data and a pointer to the UDT's IRecordInfo
interface—in essence, a miniature type library for the members of the record. (You don't need to worry much about IrecordInfo
. It's mainly used if you're implementing language support for UDTs.) If you're using C/C++, you can access the data through the data pointer cast to the appropriate struct
type. MIDL will generate the struct
declaration for you. Otherwise, you'll access the record through whatever mechanism your language implements, such as the Visual Basic's UDT types.
VT_RECORD
is useful for passing Visual Basic UDTs to other Visual Basic components and for passing structures between C++ and Visual Basic; it's not currently useful for scripting languages. And the feature is only implemented in the most recent version of Visual Basic. It also requires a very recent version of COM (Windows 98, Windows 95 with DCOM95 1.2, or Window NT 4.0 with Service Pack 4), so you won't be able to use this feature unless you know your users have both the correct OS and the correct Visual Basic run time.
I'm going to skip talking more about this for now because it's not yet documented—although it will be documented within a few months.
You'll recall that the whole struct tagVARIANT
is actually a struct that contains a union. The first part of the union is what we just did—the struct __tagVARIANT
. The second is simple:
DECIMAL decVal;
When we talk about the DECIMAL type, you'll see that it's the only type that actually uses the 6 reserved bytes in the VARIANT. In fact, it uses 14 bytes for data. Like all variants, the first 2 bytes are used for the tag, in this case containing the value VT_DECIMAL.
So, the whole thing looks like this:
struct tagVARIANT {
union {
struct __tagVARIANT { // old variant, note underscores
// tag and padding--8 bytes
union {
// a huge number of VT_ data types—8 bytes
struct __tagBRECORD {
// record pointers
};
};
};
DECIMAL decVal; // new decimal type, with tag
};
};
In other words, a tagVARIANT is either a _tagVARIANT (which could be a record) or a DECIMAL.
Finally, there are some typedefs for other related types:
typedef VARIANT * LPVARIANT;
typedef VARIANT VARIANTARG;
typedef VARIANT * LPVARIANTARG;
VARIANTARG
is the type of a variant when passed as an argument; in C++ it's the same as VARIANT
.
It's sure more complicated than it looks in Visual Basic, huh?
In Visual Basic and in scripting languages, you usually don't worry about variants—once a variant is passed to these languages by an Automation component, the language run time, because variants are built-in types, automatically converts them as necessary.
So if you're programming in one of these languages, you don't have to worry much about our friend Mr. Variant.
In Visual J++, parameters and return types are generally converted for you as well. But sometimes you'll need to create a variant—such as for a return value. To create and manipulate variants in Visual J++, use the com.ms.com.Variant class, which contains all the methods you'll need.
By the way, to manipulate an object's properties in Visual J++, you'll have to use the get and set functions provided when you created a class to wrap the object—the Java language doesn't support accessing the properties as you can in Visual Basic or in Visual C++ with smart COM pointers.
Of course the story is much more complicated in C++. With all that power comes the responsibility of having to know what's going on. Thankfully, some of the techniques for working with variants are almost as simple as they are in Visual Basic.
If some other program is calling you and you've exposed a dual interface, you're in business: COM will do all the data conversions for you based on the information in the type library, and then call your methods with standard C++ data types. (And if the client calls you using the custom part of the dual interface, there are no conversions to and from variants involved at all.) The only time you'll need to deal with a variant is for return values to scripting clients, which must be variants. Use one of the methods described next to create a variant to pass back.
By the same token, if you're calling a component that supports a dual interface, you use the custom interface part of the dual interface, so there are no data conversions to and from variants involved. Just include the correct headers, create the object using your favorite technique, and go! (And of course, it's even easier with VC++ smart pointers.)
If your Automation-only interface has a type library (or if you can write one), use Visual C++ smart pointers, which also allow you to easily call components that implement only an Automation interface. The wrappers that #import generates have the correct code for Automation-only interfaces. And you get to access properties as if you were writing Visual Basic—pretty cool.
If you're using Microsoft Foundation Classes (MFC), you can also use ClassWizard to wrap an Automation object in a class. Just call up ClassWizard (press Ctrl+W), click the Automation tab, click the Add Class button, and tell it what type library to use. Note that this method will ONLY generate IDispatch calls, even if the object you're calling has a dual interface. And you'll have to use get and set methods to access properties. For both these reasons, Dr. GUI likes the smart pointers a little better.
Don't have a type library? Allergic to smart pointers? You'll recall from the previous article that calling methods and accessing properties using raw IDispatch::Invoke was more than a little bit of a pain: A simple method call that's one line in Visual Basic (or Visual J++ or Visual C++ with the methods just described) could expand into 5, 10, 20, or even more C++ statements. The good doctor would rather have his teeth removed without Novocain. You have to set up an array of variants for the parameters, set each variant properly (three statements each), and so forth—all the stuff that other languages and methods do for you. (And these things do have to be done if you're calling via IDispatch!) You have to do for yourself.
COM documents a set of functions for very basic variant manipulation. Most of their meanings are pretty obvious from their names: VariantInit (for new variants) and VariantClear (clearing an existing variant), VariantCopy (exact copy), and VariantCopyInd (dereferences if necessary).
There are also a set of currently undocumented macros you can use to access various fields of the variants if you like. For instance, you can access the type tag by using the V_VT macro, as in "V_VT(pVariant) = VT_I4." You can also access the data parts with similar macros. So, last time we wrote something similar to this:
VariantInit(&vararg[0]);
vararg[0].vt = VT_I4; // 32-bit integer
vararg[0].lVal = 5; // here's our 5!
We could have also written:
VariantInit(&vararg[0]);
V_VT(&vararg[0]) = VT_I4; // 32-bit integer
V_I4(&vararg[0]) = 5; // here's our 5!
While you could argue that this code isn't as easy to read, the macros do save you from having to know that the member name that corresponds with VT_I4
is lVal
. That can save you from mistakes the good doctor has made on occasion. (In case you've not guessed it already, the V_XX macros access the data specified by the same VT_XX macro. So if you need VT_R8, you use V_R8 to access.)
By the way, these macros can be used both to change the variant (as just shown) and to access it, as in:
long count = V_I4(pVariant);
Even though they're not yet documented, they're simple: Check them out in oleauto.h, which you can find in your Visual C++ include directory.
If you have a variant and you simply want to change its type, use VariantChangeType or VariantChangeTypeEx. VariantChangeTypeEx takes a locale ID (LCID) so that numbers and dates can be parsed correctly for the specified locale.
If you know the exact types you're converting from and to, there are also a slew of functions whose names have the form "VarXxxFromYyy," where Xxx is replaced by the type you're converting to and Yyy is replaced by the type you're converting from. You can convert to any of the following types, using the name in parentheses to replace Xxx: unsigned char (UI1), short (I2), long (I4), float (R4), double (R8), DATE (Date), CURRENCY (Cy), BSTR (Bstr), and BOOL (Bool).
The input to these conversion functions can be any of the preceding types plus OLECHAR * (<Xxx is Str—this replaces BSTR for input) and IDISPATCH * (Xxx
is Disp—meaning that you convert the object's value (DISPID_VALUE) property).
For example, to convert from float to currency type, the function name is VarCyFromR4. From an object to a string (BSTR), it's VarBstrFromDisp. And from a string (BSTR or OLECHAR *) to a long is VarI4FromStr. Dr. GUI won't do the whole list—you get the idea. The good doctor doesn't know if you're keeping track, but that's a total of 81 functions (nine output types times ten input types minus the nine missing functions that would convert a type to itself).
Well, actually there are more. Although they're not documented, the COM Automation header oleauto.h shows an additional four data types that you can convert to and from: signed char (I1), unsigned short (UI2), unsigned long (UI4), and DECIMAL (Dec). Using the preceding formula, that gives us 13 times 13, or 169 functions.
But that's not all: through macro definitions, you can also use the types Int (meaning I4) and UInt (meaning UI4), although all of the 56 additional names thus generated map to one of the 169 existing functions.
So there are a lot of ways to call these functions, or you can create variants and simply use VariantChangeType or VariantChangeTypeEx. (These APIs figure out what type is in the variant and call the correct function for you. Imagine the switch statements!)
In addition, there are a couple of cool functions that allow you fine-grained control over how strings are parsed into numbers: You first call VarParseNumFromStr to do the initial parsing, and then call VarNumFromParseNum to finish the job, modifying the intermediate results before the second call if you like.
It never ceases to amaze Dr. GUI what you can find when you dig through the headers. (It's always better than opening Al Capone's safe.) As it turns out, there's a whole set of functions for doing math (addition, and so forth) with variants. Without documentation, it's hard to figure out exactly what they do (especially if the types of the two operands are different). By the way, the COM team is planning on documenting these functions in a later release of the Platform SDK.
However, it's pretty easy to write a little test program. There's one provided as a sample with this article that you can modify to test anything. Note that these functions would be especially handy for implementing a scripting language (or, for that matter, Visual Basic).
There are the following binary operator functions: VarAdd, VarAnd, VarCat, VarDiv, VarEqv, VarIdiv, VarImp, VarMod, VarMul, VarOr, VarPow, VarSub, and VarXor. What most of these do is pretty obvious, but some are less obvious. VarCat does string concatenation, converting its operands as necessary. VarEqv and VarImp are the Visual Basic bitwise or logical Eqv and Imp operators, and IDiv is integer division that rounds rather than truncates its operands.
There are also the following unary operator functions: VarAbs, VarFix, VarInt, VarNeg, and VarNot. VarNeg is numeric negation (sign flip). VarFix and VarInt are the Visual Basic Fix and Int functions. VarRound rounds a number to a specified number of decimal places, while VarCmp appears to compare two variants, returning VTCMP_LT, VTCMP_EQ, VTCMP_GT, or VTCMP_NULL.
Finally, there's a set of very useful functions that begin with "VarFormat...," some of which are similar to their Visual Basic equivalents, which are named "Format...." The COM format functions are VarFormatCurrency, VarFormatDateTime, VarFormatNumber, and VarFormatPercent. The Visual Basic equivalents are, respectively, FormatCurrency, FormatDateTime, and so on. (There are some other complicated low-level functions, but Dr. GUI will wait until the documentation's done to tackle them.)
These functions take the same parameters as their Visual Basic equivalents, plus a DWORD, which normally should be zero. (The only other value currently allowed is VAR_CALENDAR_HIJRI, which specifies to use the Hijri Arabic lunar calendar rather than the Western calendar for date formatting. And, no, Dr. GUI does not know how to pronounce "Hijri." But he's sure one of you will tell him, which would be good.)
Finally, there's a reference parameter at the end for the formatted string, following the COM convention that the last parameter is the function's return value (because the regular return value is used for the HRESULT).
Because the rest of the parameters are the same, look up the Visual Basic functions to find out how they work. (There does seem to be a lot of overlap between Visual Basic and COM here.)
You're probably thinking there must be an easier way to initialize variants than by writing three statements per variant and having to keep track of the allocations. And you're right, there is: There are several helper classes available to make dealing with variants easier.
Encapsulating a variant in a C++ class is pretty obvious and easy, so, as it turns out, just about every C++ class library written at Microsoft does it.
If you're using MFC, you'll probably want to use the COleVariant class. It has a constructor for each possible type you can stuff in a variant (including CString) as well as members that change the type of, copy, and compare COleVariant objects. You can even read and write COleVariant objects using a CArchive object. The destructor makes sure the variant is cleared properly before it's destroyed.
If you're using ATL, you'll want to use the CComVariant class. This class is similar to MFC's COleVariant class except that it doesn't include support for CString and it supports reading and writing using a COM IStream pointer rather than an MFC CArchive. The sample program for testing undocumented variant functions makes use of CcomVariant, especially using the constructors to create temporary variants that are automatically destroyed when we're done with 'em!
If you're not using MFC or ATL, there's still hope: Visual C++ 5.0 and later support the _variant_t class. This class is unique among the three in that it doesn't support reading or writing variants. It's also unique in two other ways: its members will throw C++ exceptions if they encounter an error, and it supports a set of overloaded conversion operators for accessing the values.
If you're using ATL in an MFC program compiled with Visual C++ 5.0 or later, you can use any (or all) of the three classes. Pick the one that has the handiest features for your application.
Dr. GUI knows full well that you won't know whether you really know what we've talked about until you try it, so give it a shot!
This time, we talked variants. But wait—we didn't talk about the other COM data types. That's the subject of the next column.
After that, we'll start delving into the mysteries of events and the COM model for events, including connection points.
// Dr. GUI's demo program for some COM data
// Note that this is just a console app.
// Have fun modifying and single-stepping!
// Include of pre-compiled header file removed for clarity
//#include "stdafx.h"
// If you use precompiled headers, these should be in "stdafx.h"
#include <stdio.h>
#include <objbase.h>
#include <atlbase.h>
#include <comdef.h>
int main(int argc, char* argv[])
{
// must call CoInitialize or CoInitializeEx
HRESULT bOK = CoInitialize(NULL);
printf("CoInitialize returned %x\n\n", bOK);
// variant demo using ATL's CComVariant class (#include <atlbase.h>)
CComVariant result;
// Note that we're creating temporary variables of variant type!
VarAdd(&CComVariant(5), &CComVariant(4), &result);
result.ChangeType(VT_BSTR);
printf("a + b = %ls\n", V_BSTR(&result));
VarCat(&CComVariant("Hello, "), &CComVariant("world!"), &result);
result.ChangeType(VT_BSTR);
printf("a Cat b = %ls\n\n", V_BSTR(&result));
// BSTR demo
BSTR bstrMsg = SysAllocString(OLESTR("Hello"));
printf("SysAllocString returned %x\n", bstrMsg);
// call some method that expects a BSTR or LPOLESTR passing bstrMsg
//ptrInterface->Method(bstrMsg);
printf("Length is %u\n", SysStringLen(bstrMsg));
// print UINT stored four bytes before pointer
printf("Length word is is %u\n", *(UINT *)(((char *)bstrMsg) - 4) );
SysFreeString(bstrMsg);
return 0;
}
typedef [wire_marshal(wireVARIANT)] struct tagVARIANT VARIANT;
struct tagVARIANT {
union {
struct __tagVARIANT {
VARTYPE vt;
WORD wReserved1;
WORD wReserved2;
WORD wReserved3;
union {
LONG lVal; /* VT_I4 */
BYTE bVal; /* VT_UI1 */
SHORT iVal; /* VT_I2 */
FLOAT fltVal; /* VT_R4 */
DOUBLE dblVal; /* VT_R8 */
VARIANT_BOOL boolVal; /* VT_BOOL */
_VARIANT_BOOL bool; /* (obsolete) */
SCODE scode; /* VT_ERROR */
CY cyVal; /* VT_CY */
DATE date; /* VT_DATE */
BSTR bstrVal; /* VT_BSTR */
IUnknown * punkVal; /* VT_UNKNOWN */
IDispatch * pdispVal; /* VT_DISPATCH */
SAFEARRAY * parray; /* VT_ARRAY */
BYTE * pbVal; /* VT_BYREF|VT_UI1 */
SHORT * piVal; /* VT_BYREF|VT_I2 */
LONG * plVal; /* VT_BYREF|VT_I4 */
FLOAT * pfltVal; /* VT_BYREF|VT_R4 */
DOUBLE * pdblVal; /* VT_BYREF|VT_R8 */
VARIANT_BOOL *pboolVal; /* VT_BYREF|VT_BOOL */
_VARIANT_BOOL *pbool; /* (obsolete) */
SCODE * pscode; /* VT_BYREF|VT_ERROR */
CY * pcyVal; /* VT_BYREF|VT_CY */
DATE * pdate; /* VT_BYREF|VT_DATE */
BSTR * pbstrVal; /* VT_BYREF|VT_BSTR */
IUnknown ** ppunkVal; /* VT_BYREF|VT_UNKNOWN */
IDispatch ** ppdispVal; /* VT_BYREF|VT_DISPATCH */
SAFEARRAY ** pparray; /* VT_BYREF|VT_ARRAY */
VARIANT * pvarVal; /* VT_BYREF|VT_VARIANT */
PVOID byref; /* Generic ByRef */
CHAR cVal; /* VT_I1 */
USHORT uiVal; /* VT_UI2 */
ULONG ulVal; /* VT_UI4 */
INT intVal; /* VT_INT */
UINT uintVal; /* VT_UINT */
DECIMAL * pdecVal; /* VT_BYREF|VT_DECIMAL */
CHAR * pcVal; /* VT_BYREF|VT_I1 */
USHORT * puiVal; /* VT_BYREF|VT_UI2 */
ULONG * pulVal; /* VT_BYREF|VT_UI4 */
INT * pintVal; /* VT_BYREF|VT_INT */
UINT * puintVal; /* VT_BYREF|VT_UINT */
struct __tagBRECORD {
PVOID pvRecord;
IRecordInfo * pRecInfo;
} __VARIANT_NAME_4; /* VT_RECORD */
} __VARIANT_NAME_3;
} __VARIANT_NAME_2;
DECIMAL decVal;
} __VARIANT_NAME_1;
};
typedef VARIANT * LPVARIANT;
typedef VARIANT VARIANTARG;
typedef VARIANT * LPVARIANTARG;