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

📄 article3.htm

📁 The code for this article was written for version 1.0 of the Active Template Library (ATL). The cu
💻 HTM
📖 第 1 页 / 共 5 页
字号:
</FONT></PRE><P>The BCSTR is a <b>typedef</b> that should have been defined by OLE, but wasn't. I define it like this in OleType.H:<P><PRE><FONT FACE="COURIER" SIZE="2">typedef const wchar_t * const BCSTR;
</FONT></PRE><P>If you declare input parameters for your functions this way, the C++ compiler will enforce the law by failing on most attempts to change either the contents or the pointer. <P>The Object Description Language (ODL) statement for the same function looks like this:<P><PRE><FONT FACE="COURIER" SIZE="2">void WINAPI TakeThisStringAndCopyIt([in] BCSTR bsIn);
</FONT></PRE><P>The BCSTR type is simply an alias for BSTR because MKTYPLIB doesn't recognize const. The [in] attribute allows MKTYPLIB to compile type information indicating the unchangeable nature of the BSTR. OLE clients such as Visual Basic will see this type information and assume you aren't going to change the string. If you violate this trust, the results are unpredictable. <P><h4>Rule 5: You own any BSTR passed to you by reference as an in/out parameter.</H4><P>You can modify the contents of the string, or you can replace the original pointer with a new one (using <b>SysReAlloc</b> functions). A BSTR passed by reference looks like this in C++:<P><PRE><FONT FACE="COURIER" SIZE="2">void DLLAPI TakeThisStringAndGiveMeAnother(BSTR * pbsInOut);
</FONT></PRE><P>Notice that the parameter doesn't use BCSTR because both the string and the pointer are modifiable. In itself the prototype doesn't turn a reference BSTR into an in/out BSTR. You do that with the following ODL statement:<P><PRE><FONT FACE="COURIER" SIZE="2">void WINAPI TakeThisStringAndGiveMeAnother([in, out] BSTR * pbsInOut);
</FONT></PRE><P>The [in, out] attribute tells MKTYPLIB to compile type information indicating that the string will have a valid value on input, but that you can modify that value and return something else if you want. For example, your function might do something like this: <P><PRE><FONT FACE="COURIER" SIZE="2">// Copy input string.
bsNew = SysAllocString(*pbsInOut);
// Replace input with different output.
f = SysReAllocString(pbsInOut, L&quot;Take me home&quot;); 
// Use the copied string for something else.
UseString(bsNew);
</FONT></PRE><P><h4>Rule 6: You must create any BSTR passed to you by reference as an out string.</H4><P>The string parameter you receive isn't really a string--it's a placeholder. The caller expects you to assign an allocated string to the unallocated pointer, and you'd better do it. Otherwise the caller will probably crash when it tries to perform string operations on the uninitialized pointer. The prototype for an out parameter looks the same as one for an in/out parameter, but the ODL statement is different:<P><PRE><FONT FACE="COURIER" SIZE="2">void WINAPI TakeNothingAndGiveMeAString([out] BSTR * pbsOut);
</FONT></PRE><P>The [out] attribute tells MKTYPLIB to compile type information indicating that the string has no valid input but expects valid output. A container such as Visual Basic will see this attribute and will free any string assigned to the passed variable before calling your function. After the return the container will assume the variable is valid. For example, you might do something like this:<P><PRE><FONT FACE="COURIER" SIZE="2">// Allocate an output string.
*pbsOut = SysAllocString(L&quot;As you like it&quot;);
</FONT></PRE><P><h4>Rule 7: You must create a BSTR in order to return it.</H4><P>A string returned by a function is different from any other string. You can't just take a string parameter passed to you, modify the contents, and return it. If you did, you'd have two string variables referring to the same memory location, and unpleasant things would happen when different parts of the client code tried to modify them. So if you want to return a modified string, you allocate a copy, modify the copy, and return it. You prototype a returned BSTR like this:<P><PRE><FONT FACE="COURIER" SIZE="2">BSTR DLLAPI TransformThisString(BCSTR bsIn);
</FONT></PRE><P>The ODL version looks like this:<P><PRE><FONT FACE="COURIER" SIZE="2">BSTR WINAPI TransformThisString([in] BSTR bsIn);
</FONT></PRE><P>You might code it like this:<P><PRE><FONT FACE="COURIER" SIZE="2">// Make a new copy.
BSTR bsRet = SysAllocString(bsIn);
// Transform copy (uppercase it).
_wcsupr(bsRet);
// Return copy.
return bsRet;
</FONT></PRE><P><h4>Rule 8: A null pointer is the same as an empty string to a BSTR.</H4><P>Experienced C++ programmers will find this concept startling because it certainly isn't true of normal C++ strings. An empty BSTR is a pointer to a zero-length string. It has a single null character to the right of the address being pointed to, and a long integer containing zero to the left. A null BSTR is a null pointer pointing to nothing. There can't be any characters to the right of nothing, and there can't be any length to the left of nothing. Nevertheless, a null pointer is considered to have a length of zero (that's what <b>SysStringLen</b> returns).<P>When dealing with BSTRs, you may get unexpected results if you fail to take this into account. When you receive a string parameter, keep in mind that it may be a null pointer. For example, Visual Basic 4.0 makes all uninitialized strings null pointers. Many C++ run-time functions that handle empty strings without any problem fail rudely if you try to pass them a null pointer. You must protect any library function calls:<P><PRE><FONT FACE="COURIER" SIZE="2">if (bsIn != NULL) {
    wcsncat(bsRet, bsIn, SysStringLen(bsRet));
}
</FONT></PRE><P>When you call Win32 API functions that expect a null pointer, make sure you're not accidentally passing an empty string:<P><PRE><FONT FACE="COURIER" SIZE="2">cch = SearchPath(wcslen(bsPath) ? bsPath : (BSTR)NULL, bsBuffer, 
                 wcslen(bsExt) ? bsExt : (BSTR)NULL, cchMax, bsRet, pBase);
</FONT></PRE><P>When you return functions (either in return values or through out parameters), keep in mind that the caller will treat null pointers and empty strings the same. You can return whichever is most convenient. In other words, you have to clearly understand and distinguish between null pointers and empty strings in your C++ functions so that callers can ignore the difference in Basic. <P>In Visual Basic, a null pointer (represented by the constant <b>vbNullString</b>) is equivalent to an empty string. Therefore, the following statement prints True:<P><PRE><FONT FACE="COURIER" SIZE="2">Debug.Print vbNullString = &quot;&quot;
</FONT></PRE><P>If you need to compare two strings in a function designed to be called from Visual Basic, make sure you respect this equality. <P>Those are the rules. What is the penalty for breaking them? If you do something that's clearly wrong, you may just crash. But if you do something that violates the definition of a BSTR (or a VARIANT or SAFEARRAY, as we'll learn later) without causing an immediate failure, results vary. <P>When you're debugging under Windows NT (but not under Windows 95) you may hit a breakpoint in the system heap code if you fail to properly allocate or deallocate resources. You'll see a message box saying &quot;User breakpoint called from code at 0xXXXXXXX&quot; and you'll  see an <i>int 3 </i>instruction pop up in the disassembly window with no clue as to where you are or what caused the error. If you continue running (or if you run the same code outside the debugger or under Windows 95), you may or may not encounter a fate too terrible to speak of. This is not my idea of a good debugging system. An exception or an error dialog box would be more helpful, but something is better than nothing, which is what you get under Windows 95. <P><h3>A BSTR Sample</h3><P>The Test.Cpp module contains two functions that test BSTR arguments. They're the basis of much of what I just passed on as the eight rules. The <b>TestBStr</b> function exercises each of the BSTR operations. This function doesn't have any output or arguments, but you can run it in the C++ debugger to see exactly what happens when you allocate and reallocate BSTRs. The <b>TestBStrArgs</b> function tests some legal and illegal BSTR operations. The illegal ones are commented out so that the sample will compile and run. This article is about the <b>String</b> class, not raw BSTR operations, so I'll leave you to figure out these functions on your own. It's probably more interesting to study this code than to run it, but the BSTR button in the Cpp4VB sample program does call these functions. <P>Before you start stepping through this sample with the Microsoft Developer Studio, you'll have to tell the debugger about Unicode. You must decide whether you want arrays of unsigned shorts to be displayed as integer arrays or as Unicode strings. The choice is pretty obvious for this project, but you'll be up a creek if you happen to have both unsigned short arrays and Unicode strings in some other project. The debugger can't tell the difference. You probably won't have this kind of problem if your compiler and debugger interpret wchar_t as an intrinsic type. <P>To get the Microsoft debugger to display wchar_t arrays as Unicode, you must edit a file called AUTOEXP.DAT in the \MSDEV\BIN directory. Add the following lines to the bottom of this file:<P><PRE><FONT FACE="COURIER" SIZE="2">[Unicode]
DisplayUnicode=1
</FONT></PRE><P>Alternatively, you can use the <i>su</i> format specifier on all Unicode variables in your watch window (although this won't help you in the locals window). To get a little ahead of ourselves, you can add the following line to make the <b>String</b> class described in the next section display its internal BSTR member as a Unicode string: <P><PRE><FONT FACE="COURIER" SIZE="2">; from BString.h
String =&lt;m_bs,su&gt;
</FONT></PRE><P>Comments in AUTOEXP.DAT the syntax of format definitions. <P><h2>The String Class</h2><P>One reason for writing server DLLs is to hide ugly details from clients. We'll take care of all the Unicode conversions in the server so that clients don't have to, but handling those details in every other line of code would be an ugly way to program. C++ provides classes so that we can hide ugly details even deeper. The <b>String</b> class is designed to make BSTR programming look almost as easy as programming with Basic's <b>String</b> type. Unfortunately, structural problems (or perhaps lack of imagination on my part) make this worthy goal unachievable. Still, I think you'll find the String type useful. <P><blockquote><b>Note: </b>    I know that it's presumptuous of me to name my BSTR class wrapper String, my VARIANT class wrapper Variant, and my SAFEARRAY class wrapper SafeArray. Most vendors of classes have the courtesy to use some sort of class naming convention that avoids stealing the most obvious names from the user's namespace. But I've been using the Basic names for other OLE types through <b>typedef</b>s. Why not use them for the types that require classes? After all, the goal is to make my classes look and work as much like intrinsic types as possible. The include filename, however, is BString.H because the name string.h is already used by the C++ run-time library. </blockquote><P>Rather than getting into String theory, let's just plunge into a sample. The goal is to implement the Visual Basic <b>GetTempFile</b> function. If you read my earlier book, <i>Hardcore Visual Basic</i>, you may remember this function. It's a thin wrapper for the Win32 <b>GetTempFileName</b> function. Like most API functions, <b>GetTempFileName</b> is designed for C programmers. <b>GetTempFile</b> is designed for Basic programmers. You call it the obvious way: <P><PRE><FONT FACE="COURIER" SIZE="2">sTempFile = GetTempFile(&quot;C:\TMP&quot;, &quot;VB&quot;)
</FONT></PRE><P>The first argument is the directory where you want to create the temporary file, the second is an optional prefix for the file name, and the return value is a full file path. You might get back a filename such as C:\TMP\VB6E.TMP. This name is guaranteed to be unique in its directory. You can create the file and fill it with data without being concerned about it overwriting any other temporary file or even a permanent file that happens (incredibly) to have the same name. <P><h3>A String API Sample</h3><P>It would probably be easier to write the <b>GetTempFile</b> wrapper in Visual Basic, but we're going to do it in C++ to prove a point. Besides, some of the more complex samples we'll be looking at later really do need C++. <P>The <b>GetTempFile</b> function is tested by the event handler attached to the Win32 button in the sample program. This code also tests other Win32 emulation functions and, when possible, the raw Win32 functions from which they are created. You can study the Basic code in Cpp4VB.Frm and the C++ code in Win32.Cpp. <P>Here's the <b>GetTempFile</b> function:<P><PRE><FONT FACE="COURIER" SIZE="2">BSTR DLLAPI GetTempFile(
    BSTR bsPathName,
    BSTR bsPrefix
    )
{
  try {
    String sPathName = bsPathName;
    String sPrefix = bsPrefix;
    String sRet(ctchTempMax); 
    if (GetTempFileName(sPathName, sPrefix, 0, Buffer(sRet)) == 0) {
        throw (Long)GetLastError();
    }
    sRet.ResizeZ();
    return sRet;
  } catch(Long e) {
    ErrorHandler(e);
    return BNULL;
  }
}
</FONT></PRE><P><h4>Exception Handling</H4><P>This function, like many of the other functions you'll see in this series of articles, uses C++ exception handling. I'm not going to say much about this except that all the normal code goes in the <i>try </i>block and the <i>catch </i>block gets all the exceptions. The <b>ErrorHandler</b> function is purposely elsewhere so that we can change the whole error system just by changing this function. For now, we're only interested in the normal branch of the code. <P>You can see that if the <b>GetTempFileName</b> API function returns zero, we throw an error having the value of the last API error. This will transfer control to the catch block where the error will be handled. What you can't see (yet) is that constructors and methods of the <b>String</b> class can also throw exceptions, and when they do, the errors will bubble up through as many levels of nested String code as necessary and be handled by this outside catch block. Instead of handling errors where they happen, you defer them to one place in client code. <P>In other words, C++ exception handling works a lot like Visual Basic's error handling. Throwing an exception in C++ is like calling the <b>Raise</b> method of the <b>Err</b> object, and catching an exception is like trapping an error with Basic's <b>On Error</b> statement. We'll revisit exception handling again in Article 4. <P><h4>Initializing String Variables</H4><P>The first thing we do is assign the BSTR parameters to String variables. This is an unfortunate requirement. It would be much nicer if we could just pass String parameters. Unfortunately, a String variable requires more storage than a BSTR variable and you can't just use the two interchangeably. You'll understand this later when you get a brief look inside the String type, but for now just be aware that the performance and size overhead for this assignment is very low and well worth the cost, especially on functions that are larger than <b>GetTempFile</b>. <P>The second thing we do is initialize the <i>sRet </i>variable, which will be the return value. The String type has several constructors and one of them creates an empty buffer of a given length. The constant <i>ctchTempMax </i>is the maximum Win32 file length--256 characters. That's a lot more than you'll need for a temporary filename on most disks, but we're being safe. If you watch the code in a debugger, you'll see that in debug builds the buffer is filled with an unusual padding character--the @ sign. The only purpose is so that you can see exactly what's going on. The data is left uninitialized in release builds.<P>In the extremely unlikely case that you don't have 256 bytes of memory left in your system, the initialization will fail and throw an out-of-memory exception. <P><h4>Buffers for Output Strings</H4><P>Now we're ready to call the Win32 <b>GetTempFileName</b> function. The <i>sPathName </i>and <i>sPrefix </i>arguments provide the input, and the <i>sRet </i>argument is a buffer that the function will fill. There's only one problem. Strings are Unicode internally, but <b>GetTempFileName</b> will usually be <b>GetTempFileNameA</b> and will expect ANSI string arguments. Of course if you're building for Windows NT only, you can do a Unicode build and call <b>GetTempFileNameW</b>. Either way, the String type should do the right thing, and do it automatically. <P>Well, that worthy goal isn't as easy as you might expect. It's not too bad for the input arguments because the String type has a conversion operator that knows how to convert the internal Unicode character string to a separate internal ANSI character string and return the result. The conversion just happens automatically. But the buffer in the <i>sRet </i>variable is a little more difficult because the conversion must be two-way. <P>The API function has to get an ANSI string, and the ANSI string created by the function must be converted back to a Unicode BSTR. That's why we pass the <b>Buffer</b> object rather than passing the <i>sRet </i>argument directly. You might think from the syntax that <b>Buffer</b> is a function. Wrong! Buffer is a class that has a constructor taking a String argument. A temporary <b>Buffer</b> object is constructed on the stack when <b>GetTempFileName</b> is called. This temporary object is destroyed when <b>GetTempFileName</b> returns. And that's the whole point of the object. The destructor for the Buffer object forces the automatic Unicode conversion. <P>Let's step through what happens if you're doing an ANSI build. You call the <b>GetTempFileName</b> function. The <b>Buffer</b> object is constructed on the stack by assigning the <i>sRet </i>String variable to an internal variable inside the <b>Buffer</b> object. But <i>sRet </i>contains a Unicode string and <b>GetTempFileName</b> expects an ANSI string. No problem. Buffer provides a conversion operator that converts the Unicode string to an ANSI string and returns it for access by the ANSI API function. <b>GetTempFileName</b> fills this buffer with the temporary filename. Now the ANSI copy is right, but the Unicode buffer is untouched. That's OK because when <b>GetTempFileName</b> returns, the destructor for the Buffer object will convert the ANSI copy of the buffer to Unicode in the real string buffer. Sounds expensive, but all these operations are actually done with inline functions and the cost is acceptable. You can check out the details in BString.H. <P>Now, what happens during a Unicode build? Pretty much nothing. The Buffer constructor is called, but it just stores the BSTR pointer. The <b>Buffer</b> class also has a conversion operator that makes the buffer return a Unicode character buffer, but it just returns the internal BSTR pointer. The destructor checks to see if anything needs to be converted to Unicode, but nothing does. That's pretty much how the String type works throughout. It performs Unicode conversion behind the scenes only when necessary. <P><h4>String Returns</H4><P>Let's continue with the rest of the function. After calling <b>GetTempFileName</b>, the <b>GetTempFile</b> function has the filename followed by a null in the <i>sRet </i>variable. That's what a C program would want, but it's not what a Basic program wants because the length of <i>sRet </i>is still 256. If you passed the variable back as is, you'd see a whole lot of junk characters following the null in the Visual Basic debugger. So we first call the <b>ResizeZ</b> method to truncate the string to its first null. Later we'll see a <b>Resize</b> method that truncates to a specified length. Unlike most API string functions, <b>GetTempFile</b> doesn't return the length, so we have to figure it out from the position of the null. <P>Finally, we return the <i>sRet </i>variable and exit from the <b>GetTempFile</b> function. The destructors for all three String variables are called. At this point, all the temporary ANSI buffers are destroyed, but the destructors don't destroy the internal Unicode strings because they're owned by the caller. Visual Basic will destroy those strings when it gets good and ready, and it wouldn't be very happy if our String destructor wiped them out first--especially the return value. <P>If this seems hopelessly complicated, don't sweat it. You don't have to understand the implementation to use the String type. It's a lot simpler (and shorter) than doing the same operations with the BSTR type. You just have to understand a few basic principles. <P><h3>A String Warm-up</h3><P>Let's take a look at some of the specific things you can do with strings. The most important thing you'll be doing with them is passing them to and receiving them from functions. Here's how it's done in the mother of all String functions, <b>TestString</b>. <b>TestString</b> puts the String class through its paces, testing all the methods and operators and writing the results to a returned string for analysis. <P><PRE><FONT FACE="COURIER" SIZE="2">BSTR DLLAPI TestString(
    BCSTR bsIn, 
    BSTR * pbsInOut,
    BSTR * pbsOut)
{
</FONT></PRE><P>This doesn't mean much without its ODL definition:
<P><PRE><FONT FACE="COURIER" SIZE="2">   [
   entry(&quot;TestString&quot;),
   helpstring(&quot;Returns modified BSTR manipulated with String type&quot;),
   ]
   BSTR WINAPI TestString([in] BSTR bsIn,
                          [in, out] BSTR * pbsInOut,

⌨️ 快捷键说明

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