📄 article5.htm
字号:
Debug.Print AddEmUp(, 9.4, , "24") ' 33.4
Debug.Print AddEmUp(7, "4") ' 31
</FONT></PRE><P>Wait a minute! How can you add strings to numbers? That's what Variants are for. The C++ side of the code looks like this: <P><PRE><FONT FACE="COURIER" SIZE="2">double DLLAPI AddEmUp(ParamArray & avParams)
{
try {
double dblRet = 0;
// Loop through the array, retrieving parameters.
avParams.Lock();
for (long i = 0; i < avParams.Elements(); i++) {
// Ignore missing ones.
if (!avParams[i].IsMissing()) {
dblRet += (double)avParams[i];
}
}
avParams.Unlock();
return dblRet;
} catch(Long e) {
ErrorHandler(e);
return 0.0;
}
}
</FONT></PRE><P>The ParamArray type is actually another typedef, which happens to be exactly the same as ArrayVariant. There's a matching ODL define in OLETYPE.ODL. You have to specify the <b>vararg</b><i> </i>attribute in the type library file so that Visual Basic will know that it should treat the last parameter as a <i>ParamArray</i>:<P><PRE><FONT FACE="COURIER" SIZE="2">[
entry("AddEmUp"),
helpstring("Tests ParamArrays"),
vararg
]
double WINAPI AddEmUp([in] ParamArray params);
</FONT></PRE><P>The event handler for the ParamArray button in the Cpp4VB sample program tests the <b>AddEmUp</b> function. <P><h3>SafeArray Implementation</h3><P>As with the <b>String</b> and <b>Variant</b> classes, you don't have to understand how <b>SafeArray</b> is implemented to use it. But I strongly recommend taking a look at the <b>SafeArray</b> implementation because it's not just another class. Implementing a class as a C++ template requires a whole different mind set. Things you always thought were cast in concrete turn out to be sinking in wet cement.<P><h3>Template Compilation and Overloading</h3><P>The first thing you need to discard is the quaint notion that you should put your implementation in a .Cpp file and your declarations in an .H file. There--I've already saved some of you many hours of wasted effort. I wish someone had told me that right up front. <P>What you need to know about template classes is that when you define one, you are actually defining as many classes as you can think up to replace the template placeholder. If the compiler treated this as a traditional C++ module, your hard disk would be filled with object files on every interminable compile. So the compiler only compiles the template code that you need. <P>For example, if your program uses the <b>SafeArray</b> template class for arrays of Variant, Long, and Single, those classes will be compiled as part of the module that uses them, but the Double, Currency, and String versions won't be compiled. So there really isn't such thing as a separate SafeArray object module, just a template definition. The module using the template needs to see both declarations and implementation, and therefore most C++ compilers require that everything must go in the .H file. There isn't any .Cpp file. Some people have worked around this by putting the implementation in a .Cpp file and then including it at the end of the .H file.
<P>It's probably better, though, to change old habits than to fit old techniques into new idioms. Templates require a new way of thinking. For example, you might have a bug that only shows up in certain instantiations of a template. A function overload that works fine with an array of Doubles might fail wretchedly with an array of Longs. Consider this: <P><PRE><FONT FACE="COURIER" SIZE="2">void Do(Long i, T & t);
void Do(T & t, Long i);
</FONT></PRE><P>When you create an array of Doubles, you get this: <P><PRE><FONT FACE="COURIER" SIZE="2">void Do(Long i, Double & t);
void Do(Double & t, long i);
</FONT></PRE><P>No problem. The compiler knows the different between Do(5.6, 5L) and Do(5L, 5.6). But if you create an array of Longs you get: <P><PRE><FONT FACE="COURIER" SIZE="2">void Do(Long i, Long & t);
void Do(Long & t, Long i);
</FONT></PRE><P>These are the same function and you'll get a compile-time error--but not until you try instantiating the Long version of the class. This is why (in short) I'm recommending that you create separate <b>SafeArray2</b> and <b>SafeArray3</b> classes for 2-D and 3-D arrays. If you think you can enhance the <b>SafeArray</b> class to handle multidimensional arrays without hitting this problem, be my guest. <P><h3>SafeArray Implementation</h3><P>I'm not going to go very deep into the implementation of the <b>SafeArray</b> class, but I will show you a few bits and pieces to give you the feel. Let's start with the class itself: <P><PRE><FONT FACE="COURIER" SIZE="2">template<class T, VARTYPE vt>
class SafeArray
{
public:
// Constructors
SafeArray();
SafeArray(SAFEARRAY * psaSrc);
SafeArray(Dim & dim);
// Copy constructor
SafeArray(const SafeArray & saSrc);
// Destructor
~SafeArray();
// Operator equal
const SafeArray & operator=(const SafeArray & saSrc);
// Indexing
T & Get(long i);
T & Set(T & t, long i);
T & operator[](const long i); // C++ style (0-indexed)
T & operator()(const long i); // Basic style (LBound-indexed)
.
.
.
private:
SAFEARRAY * psa;
void Destroy();
Boolean IsConstructed();
void Constructed(Boolean f);
};
</FONT></PRE><P>It looks just the same as any other class except for the <i>template<class T, VARTYPE vt></i> at the start. This says that <i>T </i>will represent whatever type the array will be instantiated to contain, and <i>vt </i>will represent the Variant type constant. (The <i>vt </i>parameter is actually needed in only one place--calls to <b>SafeArrayCreate</b> in constructors--but in that one case there's no alternative.) You can see how the <i>T </i>is used in the declarations of <b>Get</b>, <b>Set</b>, and the subscript and function operators. <P>The <b>SafeArray</b> class has only one data member--<i>psa</i>,<i> </i>which is a pointer to a SAFEARRAY structure. When you pass SAFEARRAY structures directly, you pass a pointer to a pointer to a SAFEARRAY. <P><PRE><FONT FACE="COURIER" SIZE="2">Variant DLLAPI TestSA(SAFEARRAY ** ppsaiInOut, SAFEARRAY ** ppsasOut);
</FONT></PRE><P>When you use SafeArrays, you pass a reference (or pointer) to a SafeArray, which contains a pointer to a SAFEARRAY. <P><PRE><FONT FACE="COURIER" SIZE="2">Variant DLLAPI TestSafeArray(ArrayInteger & aiInOut, ArrayString & asOut);
</FONT></PRE><P>It comes to the same thing. <P><h3>SafeArray Methods</h3><P>Implementations of <b>SafeArray</b> methods aren't nearly as clean as their declarations. You can probably guess the implementation of constructors (which simply call <b>SafeArrayCreate</b>), so let's skip to something more interesting:<P><PRE><FONT FACE="COURIER" SIZE="2">template<class T, VARTYPE vt>
inline T & SafeArray<T,vt>::Set(T & t, long i)
{
HRESULT hres = SafeArrayPutElement(psa, &i, (T *)&t);
if (hres) throw hres;
return t;
}
template<class T, VARTYPE vt>
inline T & SafeArray<T,vt>::operator[](const long i)
{
if (i < 0 || i > Elements() - 1) throw DISP_E_BADINDEX;
return ((T*)psa->pvData)[i];
}
template<class T, VARTYPE vt>
T & SafeArray<T,vt>::operator()(const long i)
{
if (i < LBound() || i > UBound()) throw DISP_E_BADINDEX;
return ((T*)psa->pvData)[i - LBound()];
}
</FONT></PRE><P>There's a whole lot of angle brackets floating around here, and there's no way to avoid them. <P>The <b>Set</b> function illustrates how system functions are called from template members. They almost always require the <i>psa </i>data member. Parameters of T type should normally be reference parameters so that if they turn out to be large data structures, they'll be passed by reference instead of on the stack.. Notice the use of the T type (whatever it may be) to cast the third parameter (a void pointer) of <b>SafeArrayPutElement</b> to a usable type. <P>In operator functions for indexing, the indexed value is returned by reference, so that you can either assign to it or read from it. So the statement: <P><PRE><FONT FACE="COURIER" SIZE="2">ai[2] = iVal;
</FONT></PRE><P>actually means: <P><PRE><FONT FACE="COURIER" SIZE="2">*(ai.operator[](2)) = iVal;
</FONT></PRE>
<P>The assignment actually takes place after the function call. That's why it wouldn't help to put <b>lock</b> and <b>unlock</b> statements in the operator function even if you wanted to.
<P><h2>What's Next?</h2>
<P>From here I could go on to one additional article about how to use objects passed by Visual Basic to your C++ DLLs. Maybe I'll get around to writing that article someday.
<P>But the real next step is to write your own OLE objects for Visual Basic. Objects can have many additional features, such as collections, Basic-style error handling, property pages, hidden or visible windows, and classification IDs. The problem is, how do you build objects?
<P>Until a few months ago, you didn't have much choice. You could use MFC to create objects that were sometimes a little slow and heavy. Alternately, you could slog through acres of OLE documentation, figuring out how to write objects in raw C++ or, if you were really a wizard (some might say a masochist), in plain C. Now you have several other options, one of which is described on the CD published with my book <i>Hardcore Visual Basic</i>. Other alternatives are either available now or will be soon, using C++, Delphi®, Visual Basic, and perhaps even Java™. But regardless of the tools you choose to create your objects, you'll be working with the same standard OLE types described in this series.
<P>
<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="article4.htm">Article 4. The Ultimate Data Type</A>
<P>
<P>
<P>
</BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -