JAL Computing

C++COMProgramming .NET Mac Palm CPP/CLI Hobbies

 

Home
Up

 

Using CComBSTR

The concept of strings (Unicode, ANSI, MBCS) is discussed in "ATL Internals" and "Article 3. Strings the OLE Way" listed in the references page. A BSTR is simply a typedef:

typedef wchar_t *BSTR;

However, a BSTR must adhere to a very specific set of rules that differs from a LPWSTR:

typedef wchar_t *LPWSTR;

Internally, the BSTR string is length prefixed, null terminated and may contain nulls within the body of the string. A null BSTR is interpreted as representing an empty string.  Externally, the BSTR is allocated to the system heap using SysAllocString so that a BSTR can be passed between processes such as a COM component and an ASP script. Once allocated, you cannot change the size of the string and you can only modify a string if you own the string. To avoid a memory leak, the owner of the string must deallocate the BSTR appropriately, ultimately by calling SysFreeString.

The rules of string ownership are documented in the article "Strings the OLE Way." In a nutshell, you own a BSTR that is returned to you by reference as an [out, retval] BSTR *pVal. You do not own a BSTR passed to you by value as [in] BSTR newVal. You can, however, copy a BSTR passed in by value. To return a BSTR by reference, you must create the BSTR, allocating memory for the string on the system heap. You should not free a BSTR that you plan to return by reference as an [out,retval]. It is up to the calling client to free the BSTR. This is similar to calling new() in the COM dll method and delete() in the client script.

CComBSTR is an ATL wrapper class that greatly simplifies the construction and management of BSTRs, automating the creation of BSTRs in the constructor and assignment operator and automating the release of BSTRs in the destructor. It provides methods Detach and CopyTo which can be used to return a BSTR by reference as an [out, retval]. Although CComBSTR does not provide high level string manipulation functions, CComBSTR can be used in conjunction with the <string> library. Alternatively, you can use low level access to the contained string using the m_str property and pointer arithmetic. Finally, there are ATL MACROS that automate conversions between the various types of strings.

bulletCComBSTR Automates Memory Allocation/Deallocation
bulletWhen to Use Detach() vs CopyTo(pVal)
bulletUsing the <string> Library with CComBSTR
bulletLow Level Access to the m_str
bulletATL String Conversion Macros

CComBSTR Automates Memory Allocation/Deallocation

Here is a do nothing verbose method that creates and releases a BSTR:

STDMETHODIMP CArray::DoNothing()
{
    BSTR bStr;
    bStr= SysAllocString(L"Hello World.");
    SysFreeString(bStr);
    bStr= NULL;
    return S_OK;
}

CComBSTR automates memory allocation/deallocation as is apparent by the following version of DoNothing:

STDMETHODIMP CArray::DoNothing()
{
    CComBSTR bStr(L"Hello World");
    return S_OK;
}

Memory for the BSTR is allocated in the CComBSTR constructor. When the CComBSTR object goes out of scope, the string memory is released in the CComBSTR destructor.

When to use Detach() vs CopyTo(pVal)

If you want to return a BSTR by reference as an [out,retval] you must construct the BSTR by allocating memory on the system heap. Although CComBSTR simplifies the construction and management of BSTRs, you must not return a CComBSTR as an [out,retval] by reference. If you return a local CComBSTR object, when the object goes out of scope, the BSTR will be released in the CComBSTR destructor and the client script will have a BSTR pointer to released memory. This is analogous to mistakenly returning a reference to a local object.  Moreover, if you return a member CComBSTR object, then there may be more than one BSTR pointer to the same string memory location and the BSTR in memory will be released when the member CComBSTR object goes out of scope.

An overly simplistic approach to this problem is to always use CopyTo(pVal) when returning a CComBSTR by reference as an [out,retval].

STDMETHODIMP CArray::GetAnyBSTR(BSTR *pVal)
{
    CComBSTR str(L"Hello World");
    str.CopyTo(pVal);
    return S_OK;
}

However, if CComBSTR is a local object, as in the above code snippet, this is inefficient calling SysAllocString twice, once in the constructor and once in CopyTo. Instead the general rule should be to use Detach() when returning a local CComBSTR by reference as an [out, retval]. Use CopyTo(pVal) when returning a member CComBSTR by reference as an [out, retval]. The following code snippets demonstrate this rule:

STDMETHODIMP CArray::GetLocalBSTR(BSTR *pVal)
{
    CComBSTR bStr("Hello World");
    *pVal= bStr.Detach();  // return local CComBSTR using Detach
    return S_OK;
}

STDMETHODIMP CArray::GetMemberBSTR(BSTR *pVal)
{
    m_str.CopyTo(pVal);  // return a member CComBSTR using CopyTo
    return S_OK;
}

The Detach() method clears the CComBSTR without releasing the BSTR from memory. As a result, there is no BSTR to be release when the CComBSTR object goes out of scope.

Using the <string> Library with CComBSTR

Although CComBSTR does not provide high level string manipulation routines, you can manipulate strings using the <string> library. You will need to add #include <string> to the header file.

You can convert a CComBSTR to a wstring like this:

 CComBSTR bStr("Hello World");
 wstring inString(bStr);

You can convert a wstring to a CComBSTR object like this:

wstring outString("Goodbye World");
CComBSTR bStr(outString.c_str());

The following code snippet parses a BSTR of comma separated tokens and returns the number of tokens found. It has been adapted from Shelley Powers "Developing ASP Components Second Edition".

STDMETHODIMP CArray::GetNumTokens(BSTR newVal, long *pVal)
{
    CComBSTR bStr(""); // default empty string, remember NULL --> empty string
    if (newVal != NULL) {
        bStr= newVal;  // you must copy a BSTR passed by value as  [in} BSTR newVal
    }
    long lTokens= 0;
    char cDelim= ',';
    wstring strToken;
    wstring inString(bStr);
    wstring::size_type iPos;
    iPos= inString.find(cDelim,0);
    if (iPos == wstring::npos && inString.length() <= 0) { // no tokens
        lTokens= 0;
        strToken= L"";
    }
    else if (iPos== wstring::npos && inString.length() > 0) { // one token
        lTokens = 1;
        strToken= inString;
    }
    else { // more than one token
        while (iPos != wstring::npos ) {
            lTokens+= 1;
            strToken= inString.substr(0,iPos);
            inString= inString.substr(iPos+1);
            iPos= inString.find(cDelim,0);
        }
        strToken= inString; // last token
        lTokens+= 1;
    }
    *pVal= lTokens;
    return S_OK;
}

You can convert the wstring strToken to a _variant_t like this:

_variant_t vOut;
vOut= L"<BR>";
m_piResponse->Write(vOut);
vOut= strToken.c_str();
m_piResponse->Write(vOut);

 Low Level Access to m_str

It is also possible to access a BSTR using the encapsulated m_str.  The m_str can be traversed using pointer arithmetic... not for the faint of heart. The following code snippet demonstrates low level read only access to the contained string. Remember, you should not change the size of the contained string and you can only change the contents of the contained string if you own the string. The following code snippet replaces double quotes in a string with the HTML &quot; for proper display in an HTML FORM:

CComBSTR outString("");
CComBSTR strTemp("copies \"correctly\"");
int length= strTemp.Length();
BOOL isQuote= FALSE;
wchar_t ch='"';
wchar_t *pString;
wchar_t *pLast;
pLast= pString= (wchar_t*)strTemp.m_str;
for(int i=0;i<length;i++, pString++) { // read only m_str
    if (*pString == ch ) {
        isQuote= TRUE;
        outString.Append(pLast,pString-pLast);
        outString.Append("&quot;");
        pLast= pString+1;

    }
}
if (isQuote) {
            outString.Append(pLast,pString-pLast);
            strTemp= outString; // calls SysFreeString(strTemp) and then Copy(outString)
 } // else do nothing

The CComBSTR strTemp now contains the "new" string.

For fun, here is the <string> version with appreciation for the folks at Compuserve's  Developer Application forum:

// replaces char '"' with wstring "&quot;"
inline void CSQL2Table::PrepString(std::wstring &strTemp)
{
    const char cDelim= '"';
    std::wstring::size_type iPos;
    iPos= strTemp.find(cDelim,0);
    if (iPos != std::wstring::npos) { // found delimiter, length>0
        std::wstring strPrep(L"");
        const std::wstring newDelim= L"&quot;";
        while (iPos != std::wstring::npos ) {
            strPrep+= strTemp.substr(0,iPos);
            strPrep+= newDelim;
            strTemp= strTemp.substr(iPos+1);
            iPos= strTemp.find(cDelim,0);
        }
        strPrep+= strTemp; // last token
        strTemp= strPrep;
        //strTemp.swap(strPrep);
    } // else do nothing
}

ATL String Conversion Macros

At times you may need to convert among the ANSI (A), Unicode (W) or OLE (T) string types. The ATL provides a large number of MACROS that will do this for you including:

A2W        LPSTR --> LPWSTR
T2W        LPTSTR --> LPWSTR
OLE2W    LPOLESTR --> LPWSTR 

You must add the line:

USES_CONVERSION;

as the first executable statement in the function to enable the ATL string macros. Be aware that the BSTR macros (A2BSTR, T2BSTR, OLE2BSTR) allocate memory on the system heap when converting to the type BSTR. You are still responsible for managing these BSTRs on the system heap. I have found the A2W macro the useful for converting long values to a CComBSTR. The following code snippet demonstrates the use of ltoa(), A2W, and CComBSTR to return a BSTR as an [out, retval] by reference.

STDMETHODIMP CCalendar::GetCalendar(int nYear, int nMonth, BSTR strURLLink, BSTR strURLPrevious,BSTR strURLNext, BSTR *pVal)
{
    // TODO: Add your implementation code here
    USES_CONVERSION;
    ...
    char cYear[20];
    ...
    std::wstring strOut(m_strTableTags);
    ...
    ltoa(nYear,cYear,10);
    ...
    strOut+= A2W(cYear);
    ...
    CComBSTR bstrOut(strOut.c_str());
    *pVal= bstrOut.Detach();
    return S_OK;
}

Have fun,
Jeff

Send mail to [email protected] with questions or comments about this web site. Copyright © 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 © 
Last modified: 08/04/09
Hosted by www.Geocities.ws

1