Developers Workshop to COM and ATL 3.0

 < 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: Lovingly Wrapping a Raw BSTR

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:

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); };

Using a CComBSTR Object

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.

ATL Conversion Macros

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 > 

Категории