| < Free Open Study > |
|
In Chapter 3, we saw that text handling is a bit problematic in a multi-language, multi-platform system such as COM. Some systems are Unicode centric, some ANSI. Some languages want NULL-terminated character arrays and others want length prefixed. The solution we should agree on is the use of the BSTR data type, the string type of choice in COM development.
Working with BSTRs in C++ entails a small handful of system string functions (SysAllocString(), SysFreeString(), et. al.), as well as some functions to convert from ANSI to Unicode and visa versa. This was not all that difficult, but ATL can make your string chores much more rewarding.
First, ATL provides the CComBSTR class to help encapsulate the details of BSTR management. As well, ATL provides a number of conversion macros, which help you convert between the onslaught of string data types (wchar_t, BSTR, OLECHAR, char*, and so forth) bypassing direct API function calls. Here is a rundown of ATL's string support beginning with CComBSTR.
CComBSTR, defined in <atlbase.h>, is a C++ class wrapping an underlying BSTR data member named m_str. When you create an instance of CComBSTR, the underlying buffer is set to NULL. The destructor frees the m_str using SysFreeString():
// CComBSTR is here to help with painful BSTR maintenance. class CComBSTR { public: BSTR m_str; // The underlying BSTR. CComBSTR() { m_str = NULL; // Set to NULL. } ~CComBSTR() { ::SysFreeString(m_str); // Free underlying BSTR. } ... };
Note | Notice that the default constructor of CComBSTR sets the underlying BSTR to NULL, not an empty string. If your coclass maintains a private CComBSTR, be sure to set it to a valid empty string, or your client program may crash if accessing the BSTR before making an assignment! |
The public sector of CComBSTR provides a good number of methods to allow you to play with the underlying BSTR buffer. While this class is not as robust as MFC's CString or the _bstr_t compiler extension data type, it is a real improvement over raw BSTR manipulation. The functionality of CComBSTR may be grouped in the following high-level categories:
A set of overloaded constructors, allowing you to set m_str based on existing ANSI and Unicode strings, as well as an existing REFGUID (which creates a string representation of the 128-bit number).
Methods to copy (Copy()), concatenate (Append(), AppendBSTR()) and free (Empty(), Detach()), your underlying BSTR. ToUpper(), ToLower(), and Length() member functions are also defined. The LoadString() method allows you to stuff your underlying BSTR with data from your project's string table.
A full set of overloaded operators to allow you to work with CComBSTR objects in an intelligent manner. Among the most interesting operators are concatenation (+=) and assignment (=).
MSDN provides a listing for most of the methods of CComBSTR. Some (such as ToUpper() and ToLower()) are not found in online help, but do appear as a valid selection from the IntelliSense drop-down window of the Visual Studio IDE.
Here is the public interface of CComBSTR:
class CComBSTR { public: BSTR m_str; CComBSTR(); CComBSTR(int nSize); CComBSTR(int nSize, LPCOLESTR sz); CComBSTR(LPCOLESTR pSrc); CComBSTR(const CComBSTR& src); CComBSTR(REFGUID src); CComBSTR& operator=(const CComBSTR& src); CComBSTR& operator=(LPCOLESTR pSrc); ~CComBSTR(); unsigned int Length() const; operator BSTR() const; BSTR* operator&(); BSTR Copy() const; HRESULT CopyTo(BSTR* pbstr); void Attach(BSTR src); BSTR Detach(); void Empty(); bool operator!() const; HRESULT Append(const CComBSTR& bstrSrc); HRESULT Append(LPCOLESTR lpsz); HRESULT AppendBSTR(BSTR p); HRESULT Append(LPCOLESTR lpsz, int nLen); HRESULT ToLower(); HRESULT ToUpper(); bool LoadString(HINSTANCE hInst, UINT nID); bool LoadString(UINT nID); CComBSTR& operator+=(const CComBSTR& bstrSrc); bool operator<(BSTR bstrSrc) const; bool operator==(BSTR bstrSrc) const; bool operator<(LPCSTR pszSrc) const; bool operator==(LPCSTR pszSrc) const; HRESULT WriteToStream(IStream* pStream); HRESULT ReadFromStream(IStream* pStream); };
To illustrate CComBSTR in action, let's change the underlying implementation of the Name property provided by the IShapeID interface. First off, we will now want to maintain a private CComBSTR data type, as opposed to a raw BSTR. In CoHexagon's constructor, we can set the underlying m_str data member using CComBSTR's overloaded assignment operator, which has been implemented to work with existing CComBSTR objects or literal strings:
// CoHexagon working with the ATL CComBSTR data type. class ATL_NO_VTABLE CCoHexagon : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CCoHexagon, &CLSID_CoHexagon>, public IDraw, public IShapeEdit, public IErase, public IShapeID { public: CCoHexagon() { m_bstrName = L""; // Set to a empty string in constructor. } ... private: CComBSTR m_bstrName; };
We can now use the Copy() method to return a safe copy of the m_bstrName data member:
// Copy() safely returns a BSTR to the client. STDMETHODIMP CCoHexagon::get_Name(BSTR *pVal) { // Client wants a copy of the string. *pVal = m_bstrName.Copy(); return S_OK; }
When you want to return a BSTR to a client using CComBSTR you make a call to the Copy() method. This will call SysAllocStringLen() on your behalf and hand off the client a brand new BSTR which is identical to your own:
// CComBSTR::Copy() BSTR Copy() const { return ::SysAllocStringLen(m_str, ::SysStringLen(m_str)); }
In the put_Name implementation, use the overloaded operator=:
// Overloaded operator= will safely clean up m_str and reset it to newVal. STDMETHODIMP CCoHexagon::put_Name(BSTR newVal) { // Client wants to change the string. m_bstrName = newVal; return S_OK; }
The assignment operator has been overloaded twice for CComBSTR. One version allows you to assign a text literal, the other allows you to set one CComBSTR to another. The version used for the put_Name function is as follows:
// Operator= Take One. CComBSTR& operator=(LPCOLESTR pSrc) { ::SysFreeString(m_str); m_str = ::SysAllocString(pSrc); return *this; }
The other version of the overloaded assignment operator looks like so:
// Operator= Take Two. CComBSTR& operator=(const CComBSTR& src) { if (m_str != src.m_str) { if (m_str) ::SysFreeString(m_str); m_str = src.Copy(); } return *this; }
Notice that the assignment between two CComBSTR objects will not occur if the source is the same as the target. If you wish to examine the remaining methods of CComBSTR you may open <atlbase.h> (or online help) and take a look for yourself. Now that we have some help managing the de facto BSTR, let's see how ATL will help us work with string conversions.
Beyond the CComBSTR data type, ATL defines a number of conversion macros to use in place of the Unicode to ANSI to Unicode API functions we examined in Chapter 3. Reading the macros without a roadmap is just about impossible, so we need a heads up. All conversion macros take a similar form: X2Y. Here, X identifies the type of string you have and Y is the string you want. All macros take a single argument, that being the "string you have." The end result of using these macros is a new string of the Y variety. X or Y may be composed of the following shorthand combinations:
Macro Conversion Symbol | Meaning in Life |
---|---|
C | Represents a C++ const tag. |
BSTR | Represents a COM BSTR data type. |
A | Represents an ANSI character pointer type (char* or LPSTR) |
W | Represents a Unicode character pointer type (wchar_t* or LPWSTR). |
T | Represents Win32 TCHAR or LPTSTR types. |
OLE | Represents a LPOLESTR. |
Armed with this knowledge, here is a listing of the ATL conversion macros:
ANSI to... | OLE to... | TCHAR to... | wchar_t to... |
---|---|---|---|
A2BSTR | OLE2A | T2A | W2A |
A2COLE | OLE2BSTR | T2BSTR | W2BSTR |
A2CT | OLE2CA | T2CA | W2CA |
A2CW | OLE2CT | T2COLE | W2COLE |
A2OLE | OLE2CW | T2CW | W2CT |
A2T | OLE2T | T2OLE | W2OLE |
A2W | OLE2W | T2W | W2T |
In order to convert from type X to type Y, these macros need to establish a set of temporary variables to hold and swap the string buffers. These variables are provided for you automatically when you specify the USES_CONVERSION macro before you use the conversion macros:
// This macro expands to define a number of temporary variables used by the numerous // ATL conversion macros. #define USES_CONVERSION int _convert = 0; _convert; UINT _acp = CP_ACP; \ _acp; LPCWSTR _lpw = NULL; _lpw; LPCSTR _lpa = NULL; _lpa
Convention dictates that the USES_CONVERSION macro be the first line in some method scope. For example:
// This method takes an incoming BSTR and converts to ANSI using W2A. STDMETHODIMP CMyATLClass::ChangeThisBSTRToANSI(BSTR bstr) { USES_CONVERSION; MessageBox(NULL, W2A(bstr), "I converted a BSTR into a char*!", MB_OK); return S_OK; }
Using the conversion macros and CComBSTR can most certainly make your COM text programming endeavors more productive and less painful. If you are interested in seeing what the ATL conversion macros resolve to, check out <atlconv.h>. Now that you have seen the basics of working with the ATL CASE tools, we will finish up this chapter by examining ATL's debugging support.
| < Free Open Study > |
|