Sometimes, I am willing to disclose :) some secrets of real performance. Even in the .NET world, we can't avoid BSTR allocation, and in the unmanaged coding world, automation and COM will be at our path once more.
In my honest opinion, many, many programmers make or made the mistake, in the unmanaged world, of not reallocating resources, instead, they just destroy the resource, and allocate it again. MS tried to fix this 'bug' by caching allocations, as much as possible. I find this decision, to start caching allocations, I mean for BSTR allocation, not a good decision. This must be one of the reasons, that COM in a multitasked, MPS environment sometimes, simply cannot scale!
I'll explain why. In a single user environment, caching data for a thread is a good idea, since say MS Word, and scripts like in VBA and VBS, might reuse data/allocations. But as soon as our ASP/COM server environment starts to do this, and the code is reentrant, caching is useless, since threads that allocated data, might not be allowed to reuse zombie-data (if a caching-pattern is used) from another thread.
It could have been solved so easily! (Now, I might sound presumptuous to say that, I agree) How? Just don't cache but reallocate!
VB6 and automation clients for instance, uses the BSTR datatype all over the place, and ATL when used in a COM environement, does as well. If you look at the compiled code that programmers deliver, they never reallocate (only the runtime does sometimes). So for instance myString = "Hello" and myString = "bye" could have been compiled internally by:
myString = SysAllocString(L"Hello");
SysReAllocString(myString, L"bye");
And that's really all!
SysReAllocString(Len) internally uses CoTaskMemRealloc and that function tries to enlarge the memory allocation in-place, and this at its turn minimizes RAM-synchronization in MPS systems on the CPU.
So far, my theory. Am I just filling up your internet-html disk-cache and chit-chatting because I'm just idle for an hour? No.
Let's just try this out!
I've rewritten CComBSTR (from the ATL namespace) and you can find this in the platform SDK at \PlatSDK\Include\atl\atlbase.h (in case you have Visual Studio 2005, don't use this location, but use the most recent header files).
This silly little program does nothing but appending random (sort of) wide strings to a BSTR allocation. Let's rewrite CComBSTR and measure it!
I assume that you can get the headers right to get the program below compile and run.
int _tmain()
{
HRESULT hr = S_OK;
CoInitialize(NULL);
{
_tmain()
{
HRESULT hr = S_OK;
CoInitialize(NULL);
{ CComBSTR appendPlay;
DWORD timer = GetTickCount();
// Ethan Winer, an Assembly coding specialist, once thought me that loops counting down to zero are faster, this still is the case!, just a silly fact.
for(int xy = 10000; xy > 0; xy--)
{
PWSTR zy = xy % 2 == 0 ? L"hiya" : L"bye";
appendPlay.Append(zy);
}
wprintf(L"speed %d\n", GetTickCount() - timer);
}
CoUninitialize();
}
Now run the code and on my AMD 3200+ system, this takes 578 time ticks. This is even with the OLE BSTR cache enabled! (When caching is disabled, this takes 520 time ticks).
Let's improve the Append part of CComBSTR (make sure you keep the original atlbase.h intact). In my case, I just redefined CComBSTR to CComBSTR2 and copy-pasted all of it and rewrote the slow parts.
The slow original code is using the 'delete' 'allocate' sequence.
// very slow original
HRESULT __stdcall Append(LPCOLESTR lpsz, int nLen) throw()
{
if(lpsz == NULL)
{
if(nLen != 0)
return E_INVALIDARG;
else
return S_OK;
}
int n1 = Length();
if (n1+nLen < n1)
return E_OUTOFMEMORY;
BSTR b;
b = ::SysAllocStringLen(NULL, n1+nLen);
if (b == NULL)
return E_OUTOFMEMORY;
if(m_str != NULL)
memcpy(b, m_str, n1*sizeof(OLECHAR));
memcpy(b+n1, lpsz, nLen*sizeof(OLECHAR));
b[n1+nLen] = NULL;
SysFreeString(m_str);
m_str = b;
return S_OK;
}
And here goes the improved code. It has the Automation runtime resize the BSTR while the string in most cases remains at the same memory address. This is how the original BSTR programmers have designed for performance, while nobody is utilizing it! But we instead, do use it, as you understand.
HRESULT __stdcall Append(LPCOLESTR lpsz, int nLen) throw()
{
if (lpsz == NULL || (m_str != NULL && nLen == 0))
return S_OK;
int n1 = Length();
HRESULT hr = SetLength(n1 + nLen);
if ( SUCCEEDED(hr) )
memcpy(m_str+n1, lpsz, nLen*sizeof(OLECHAR));
return hr;
}
We need to append the _SetLength function, which is a static wrapper for SysReAllocStringLen(..)
// Cuts the length to specified but does not clear contents
HRESULT __stdcall SetLength(unsigned int length) throw()
{
return _SetLength(&m_str, length);
}
static HRESULT __stdcall _SetLength(BSTR * str, unsigned int length) throw()
{
return ::SysReAllocStringLen(str, NULL, length) == FALSE ? E_OUTOFMEMORY : S_OK;
}
I've included the full 99% compatible CComBSTR2 replacement for you, as a handy dowload so bother about that later. :)
Now, get me to the results please, how much faster would this code run now?
Yes, it takes a whopping 15 milliseconds! And figure that, against 578 milliseconds, which makes the improvement 3800%
// Cuts the length to specified but does not clear contents
HRESULT __stdcall SetLength(unsigned int length) throw()
{
return _SetLength(&m_str, length);
}
static HRESULT __stdcall _SetLength(BSTR * str, unsigned int length) throw()
{
return ::SysReAllocStringLen(str, NULL, length) == FALSE ? E_OUTOFMEMORY : S_OK;
}
I've included the full 99% compatible CComBSTR2 replacement for you, as a handy dowload so bother about that later. :)
Now, get me to the results please, how much faster would this code run now?
Yes, it takes a whopping 15 milliseconds! And figure that, against 578 milliseconds, which makes the improvement 3800%
Now you might understand why .NET had to be invented by MS :) they figured that the maximum scalability limit was hit on real MPS systems, and that COM never could perform the task of being scalable just because the BSTR sucks in performance! And now, you know that, I'm just now on the conspiracy path, and I'm lying :-).
Anyway, the conclusion is, that current COM clients, and applications and servers, could, if they would like to, improve a lot for free, by just removing the BSTR reallocation barrier and take advantage of the maximum 'unmanaged code' speed possible on a Windows (r) System.
The non-conspiracy theory is that caching was made in times, when Microsoft did not play a big role in server environments, and that computers were relativily slow. A conclusion would be that if MS would like to improve an old car (COM) for free, they'd just remove the caching and implement the idea that I've proven to be very good. This would be good for classic ASP pages as well that are still very popular on the internet.
Just a final question. Why bother? If you are an Automation developer, creating services that depend heavily on BSTRs?
The answer is, I bothered once a while ago, just because of some artistic feeling (good programmers are artists, not scientists :) ) that I could improve the enormous BSTR stress on my product here Isp Session Manager. Of course, the biggest part of such managed COM server (as COM+ was called in the past!) is doing talking to a DBMS. But after implementing the CComBSTR replacement, the performance on a MPS server, suddenly got very easy and the pages per second throughput went up and showed a flat line (that's the wet dream of each webmaster). Before, without using the CComBSTR replacement the throughtput was erratic, so this again proved my point, that not-reallocating BSTRs causes a huge demand on RAM synchronization and makes scalability limited because of wrong usage of the COM runtime.
Here, you got the CComBSTR whopper for your own downloads. Please do not forget to deploy it after download...
[edit, nov 2008] It seems that attached links on this BLOG are not supported anymore.
Therefore, the sourcecode inline.
#pragma once
#ifndef CComBSTR
#define CComBSTR CComBSTR2
#endif
#define CComVariant CComVariant2
namespace ATL
{
//typedef HRESULT (__stdcall*HASHDATA2) (LPBYTE, DWORD, LPBYTE, DWORD);
/////////////////////////////////////////////////////////////////////////////
// CComBSTR2
class CComBSTR2
{
public:
BSTR m_str;
CComBSTR2() throw()
{
m_str = NULL;
}
CComBSTR2(_In_ int nSize)
{
//if (nSize == 0) //BUG it should be possible to assign a L"" string
m_str = NULL;
HRESULT hr = SetLength(nSize);
if (FAILED(hr))
AtlThrow(hr);
ZeroMemory(m_str, nSize * sizeof(wchar_t));
}
CComBSTR2(_In_ int nSize, _In_opt_count_(nSize) LPCOLESTR sz)
{
if (nSize == 0)
m_str = NULL;
else
{
m_str = ::SysAllocStringLen(sz, nSize);
if (m_str == NULL)
AtlThrow(E_OUTOFMEMORY);
}
}
CComBSTR2(_In_opt_ LPCOLESTR pSrc)
{
if (pSrc == NULL)
m_str = NULL;
else
{
m_str = ::SysAllocString(pSrc);
if (m_str == NULL)
AtlThrow(E_OUTOFMEMORY);
}
}
CComBSTR2(_In_ const CComBSTR& src)
{
m_str = src.Copy();
if (!!src && m_str == NULL)
AtlThrow(E_OUTOFMEMORY);
}
CComBSTR2(_In_ REFGUID guid)
{
wchar_t szGUID[64];
m_str = ::SysAllocStringLen(szGUID,
::StringFromGUID2(guid, szGUID, 64)
);
if (m_str == NULL)
AtlThrow(E_OUTOFMEMORY);
}
CComBSTR2& operator=(_In_ const CComBSTR& src)
{
if (m_str != src.m_str)
{
if (::SysReAllocStringLen(&m_str, src, src.Length()) == FALSE)
AtlThrow(E_OUTOFMEMORY);
}
return *this;
}
CComBSTR2& operator=(_In_opt_ LPCOLESTR pSrc)
{
if (pSrc != m_str)
{
if (pSrc != NULL)
{
if (::SysReAllocString(&m_str, pSrc) == FALSE)
AtlThrow(E_OUTOFMEMORY);
}
else
Empty();
}
return *this;
}
//???
inline ~CComBSTR2() throw()
{
::SysFreeString(m_str);
}
static ULONG GetStreamSize(BSTR bstr)
{
ULONG ulSize=sizeof(ULONG);
#pragma warning(push)
#pragma warning(disable:4068)
#pragma prefast(push)
#pragma prefast(disable:325, "The semantics of this function are about allocation, not content")
if (bstr != NULL)
#pragma prefast(pop)
#pragma warning(pop)
{
ulSize += SysStringByteLen(bstr) + sizeof(wchar_t);
}
return ulSize;
}
unsigned int __stdcall Length() const throw()
{
return ::SysStringLen(m_str);
}
unsigned int __stdcall ByteLength() const throw()
{
return ::SysStringByteLen(m_str);
}
operator BSTR() const throw()
{
return m_str;
}
#ifndef ATL_CCOMBSTR_ADDRESS_OF_ASSERT
// Temp disable CComBSTR::operator& Assert
#define ATL_NO_CCOMBSTR_ADDRESS_OF_ASSERT
#endif
BSTR* operator&() throw()
{
#ifndef ATL_NO_CCOMBSTR_ADDRESS_OF_ASSERT
#pragma warning(push)
#pragma warning(disable:4068)
#pragma prefast(push)
#pragma prefast(disable:325, "We are deliberately checking if this has already been allocated")
ATLASSERT(!*this);
#pragma prefast(pop)
#pragma warning(pop)
#endif
return &m_str;
}
BSTR __stdcall Copy() const throw()
{
if (*this == NULL)
return NULL;
else
{
unsigned int copyLen = ByteLength();
BSTR retVal = ::SysAllocStringByteLen(NULL, copyLen);
if (copyLen > 0 && retVal != NULL)
{
memcpy_s(retVal, copyLen, m_str, copyLen);
}
return retVal;
}
}
//modified by E.N. pbstr is now in/out!
// you must care for it that it properly gets initialized
_Check_return_ STDMETHODIMP CopyTo(__inout_opt BSTR* pbstr) const throw()
{
ATLASSERT(pbstr != NULL);
HRESULT hr;
if (pbstr == NULL)
hr = E_POINTER;
else
{
#ifdef SysReAllocStringByteLen
unsigned int copyLen = ByteLength();
hr = SysReAllocStringByteLen(pbstr, NULL, copyLen) == FALSE ? E_OUTOFMEMORY : S_OK;
if (hr == S_OK && copyLen > 0)
{
memcpy_s(*pbstr, copyLen, m_str, copyLen);
}
#else
*pbstr = Copy();
#pragma warning(push)
#pragma warning(disable:4068)
#pragma prefast(push)
#pragma prefast(disable:325, "We are checking allocation semantics here")
if ((*pbstr == NULL) && (m_str != NULL))
{
return E_OUTOFMEMORY;
}
#pragma prefast(pop)
#pragma warning(pop)
#endif
}
return hr;
}
// *** returns true if length equals zero characters or when unallocated(null pointer)
bool __stdcall IsEmpty (void) throw()
{
return m_str == NULL || Length() == 0;
}
/* added by may 2005 e.n. needs #include 'wchar.h'*/
HRESULT __stdcall Format(__in PCWSTR pszFormat, __in va_list args) throw()
{
size_t len = _vscwprintf( pszFormat, args );
HRESULT hr = SetLength((UINT)len) ;
if(SUCCEEDED(hr))
if (vswprintf( m_str, len + 1, pszFormat, args ) < 0)
hr = E_INVALIDARG;
return hr;
}
/* added by may 2005 e.n. needs #include 'wchar.h'*/
HRESULT __cdecl Format(__in PCWSTR pszFormat, ...) throw()
{
va_list args;
va_start( args, pszFormat );
HRESULT hr = Format(pszFormat, args);
va_end(args);
return hr;
}
private:
HRESULT __stdcall Insert(__in unsigned int atPosition, __in_opt LPCOLESTR lpsz, __in unsigned int nLen) throw()
{
unsigned int curLen = Length();
HRESULT hr = S_OK;
if (atPosition > curLen || lpsz == NULL)
hr = E_INVALIDARG;
else
hr = SetLength(curLen + nLen);
if (SUCCEEDED(hr) && curLen != 0 && nLen != 0)
{
wmemmove(&m_str[atPosition + nLen], &m_str[atPosition],
curLen - atPosition);
wmemcpy_s(&m_str[atPosition], curLen + nLen - atPosition, lpsz, nLen );
}
return hr;
}
public:
HRESULT __stdcall Insert(__in unsigned int atPosition, __in_opt PCWSTR value) throw()
{
return Insert(atPosition, value, (unsigned int)wcslen(value));
}
HRESULT __stdcall Insert(__in unsigned int atPosition, __in const CComBSTR& value) throw()
{
return Insert(atPosition, value.m_str, value.Length());
}
HRESULT TrimStart(__in_opt PCWSTR trimwhat = NULL) throw()
{
PCWSTR trim = trimwhat == NULL? L" ": trimwhat;
if (IsEmpty()) return S_OK;
unsigned int trimLen = (unsigned int)wcslen(trim);
while(StartsWith(trim))
Remove(0, trimLen);
return S_OK;
}
HRESULT TrimEnd(__in_opt PCWSTR trimwhat = NULL) throw()
{
PCWSTR trim = trimwhat == NULL? L" ": trimwhat;
if (IsEmpty()) return S_OK;
unsigned int trimLen = (unsigned int)wcslen(trim);
while(EndsWith(trim))
SetLength(Length() - trimLen);
return S_OK;
}
//** removes in-place characters from this BSTR
HRESULT __stdcall Remove(
//** zero based starting position where you start to remove characters
//** if this number is outside valid bounds, E_INVALIDARG is returned
__in unsigned int startIndex,
//** the number of characters, you want to remove
//** if this number is outside valid bounds, it is corrected
__in unsigned int count) throw()
{
unsigned int maxIdx = Length();
// avoid buffer overflow
if (count + startIndex > maxIdx) count = maxIdx - startIndex;
HRESULT hr = S_OK;
if (startIndex < maxIdx)
{
if (maxIdx - startIndex - count != 0)
//copy back, overlapped memory
wmemmove_s(&m_str[startIndex], maxIdx - startIndex, &m_str[startIndex + count] ,
maxIdx - startIndex - count);
// shrink to new length
hr = SetLength(maxIdx - count);
}
else
hr = E_INVALIDARG;
return hr;
}
// original string john smith
// merge with west at position 6
// result john westh
// won't extend string length!
void __stdcall MergeString(__in unsigned int startIndex, __in const BSTR value) throw()
{
unsigned int maxIdx = Length();
if (startIndex > maxIdx || value == NULL) return; // illegal operation
unsigned int mergeLen = SysStringLen(value);
if (mergeLen + startIndex > maxIdx)
mergeLen = maxIdx - startIndex;
wmemcpy_s(&m_str[startIndex], maxIdx - startIndex, value, mergeLen);
}
BSTR __stdcall Substring(__in unsigned int startIndex) throw()
{
unsigned int maxIdx = Length();
if (m_str != NULL && startIndex >= 0 && startIndex <= maxIdx)
{
return ::SysAllocStringLen(m_str + startIndex, maxIdx - startIndex);
}
else
return NULL;
}
HRESULT __stdcall SetByteLength(__in unsigned int length) throw()
{
return _SetByteLength(&m_str, length);
}
// Cuts the length to specified but does not clear contents
HRESULT __stdcall SetLength(__in unsigned int length) throw()
{
return _SetLength(&m_str, length);
}
private:
static HRESULT __stdcall _SetByteLength(_Inout_ BSTR *str, __in unsigned int length) throw()
{
#ifdef SysReAllocStringByteLen
return SysReAllocStringByteLen2(str, NULL, length) == FALSE ? E_OUTOFMEMORY : S_OK;
#else
BSTR Copy = NULL;
UINT origLen = SysStringByteLen(*str);
if (origLen != 0)
{
Copy = SysAllocStringByteLen((LPCSTR)*str, length);
SysFreeString(*str);
*str = Copy;
}
return *str == NULL ? E_OUTOFMEMORY : S_OK;
#endif
}
static HRESULT __stdcall _SetLength(_Inout_ BSTR * str, __in unsigned int length) throw()
{
return ::SysReAllocStringLen(str, NULL, length) == FALSE ? E_OUTOFMEMORY : S_OK;
}
public:
int __stdcall TokenCount(__in PCWSTR find, __in bool caseInsensitive) throw()
{
int strLen = (int)Length();
int tokenCount = 0;
int findLen = (int)wcslen(find);
for(int x = 0;
(x = IndexOf((const PWSTR)find, x, caseInsensitive)) >= 0;)
{
x += findLen;
tokenCount++;
}
return tokenCount;
}
/// <summary>
/// Replaces a find token with a specified token
/// By E.N.
/// </summary>
/// <param name="find">token to be replace</param>
/// <param name="replace">token that will replace</param>
/// <param name="caseInsensitive">if true, will do a case insensitive replacement</param>
/// <example>
/// CComBSTR2 myReplace(L"the dog jumps over the");
/// myReplace(CComBSTR2(L"the"), CComBSTR2(L"big"));
/// </example>
/// <returns>The example would modify the string to "big dog jumps over big" </returns>
HRESULT __stdcall Replace(__in_opt BSTR find, __in_opt BSTR replace, __in bool caseInsensitive) throw()
{
HRESULT hr = S_OK;
if (m_str == NULL || find == NULL || replace == NULL)
hr = E_INVALIDARG;
else
{
int countTokens = TokenCount(find, caseInsensitive);
if (countTokens == 0) return hr;
int findLen = SysStringLen(find);
int replaceLen = SysStringLen(replace);
int lengthDiff = replaceLen - findLen;
int oldLen = (int)Length() ;
int newLen = oldLen + (countTokens * lengthDiff);
if (lengthDiff > 0)
{
hr = SetLength(newLen);
if (FAILED(hr)) return hr;
}
for(int x = 0;
(x = IndexOf(find, x, caseInsensitive)) >= 0
; )
{
int offset = x + findLen;
if (lengthDiff != 0)
{
wmemmove_s(&m_str[offset + lengthDiff],
(lengthDiff < 0 ? oldLen : newLen) - offset,
&m_str[offset],
oldLen - offset);
oldLen += lengthDiff;
}
if (replaceLen > 0)
{
MergeString(x, replace);
x += replaceLen;
}
}
if (lengthDiff < 0)
{
hr = SetLength(newLen);
}
}
return hr;
}
/// <summary>
/// Replaces a find token with a specified token
/// <param name="find">token to be replace</param>
/// <param name="replace">token that will replace</param>
/// </summary>
/// <example>
/// CComBSTR2 myReplace(L"the dog jumps over the");
/// myReplace(CComBSTR2(L"the"), CComBSTR2(L"big"));
/// </example>
/// <returns>The example would modify the string to "big dog jumps over big" </returns>
HRESULT __stdcall Replace(__in_opt const BSTR find, __in_opt const BSTR replace) throw()
{
return Replace(find, replace, false);
}
SAFEARRAY* __stdcall Split(__in_opt PCWSTR expression, __in const bool caseInsenstive) throw()
{
return Split(CComBSTR(expression), caseInsenstive);
}
/// <summary>
/// Split and copies this instance of CComBSTR2 into a SAFEARRAY* of VTYPE = VT_BSTR
/// </summary>
/// <example>
/// CComSafeArray<BSTR> myArray;
/// CComBSTR2 joined(L"John|Smith");
/// myArray.Attach(joined.Split(CComBSTR2(L"|")))
/// </example>
/// <returns>The example would return a safearray with 2 VT_BSTR elements containing "John" and "Smith"</returns>
SAFEARRAY* __stdcall Split(__in const CComBSTR& expression, __in const bool caseInsensitive)
{
SAFEARRAY* retval = NULL;
HRESULT hr = S_OK;
if (m_str == NULL)
AtlThrow(E_POINTER);
else
{
unsigned int exprLen = expression.Length();
unsigned int mLen = Length();
int x = 0;
//contains the number of found expression tokens
unsigned int found = mLen == 0 ? -1 : 0;
//find until no more...
if (expression != NULL && found >= 0)
{
for (;;)
{
x = IndexOf(expression, x, caseInsensitive);
if (x == -1) break;
found++;
x += exprLen;
}
}
SAFEARRAYBOUND rgsa = {found + 1, 0};
retval = ::SafeArrayCreate(VT_BSTR, 1, &rgsa);
if (retval == NULL)
return retval;
else if (mLen > 0)
{
BSTR* paBSTR ;//(BSTR*)retval->pvData;
hr = SafeArrayAccessData(retval, (void**)&paBSTR);
int prevPos = 0;
x = 0;
for (unsigned int curEl = 0; curEl <= found; curEl++)
{
x = IndexOf(expression, x, caseInsensitive);
paBSTR[curEl] = x < 0 ? Substring(prevPos) : Substring(prevPos, x - prevPos);
if (paBSTR[curEl] == NULL)
{
hr = E_OUTOFMEMORY;
break;
}
x += exprLen;
prevPos = x;
}
SafeArrayUnaccessData(retval);
}
if (FAILED(hr))
{
AtlTrace(L"general split fail %x", hr);
if (retval != NULL)
::SafeArrayDestroy(retval);
retval = NULL;
AtlThrow(hr);
}
}
return retval;
}
SAFEARRAY* __stdcall Split(__in_opt PCWSTR expression) throw()
{
return Split(CComBSTR(expression), false);
}
/// <summary>
/// Added by E.N.
/// Joins a SAFEARRAY to a single BSTR
/// </summary>
/// <example>
/// CComSafeArray<BSTR> myArray;
/// myArray.Add(CComBSTR2(L"John"));
/// myArray.Add(CComBSTR2(L"Smith"));
/// CComBSTR2 joined;
/// joined.Attach(CComBSTR2::Join(myArray.m_psa, CComBSTR2(L"|") ) ); ///
/// </example>
/// <returns>The example would return "John|Smith"</returns>
static BSTR __stdcall Join(__in_opt SAFEARRAY *psa, __in const BSTR delimiter) //throw()
{
BSTR retval = NULL;
HRESULT hr = S_OK;
if (psa != NULL && psa != NULL)
{
VARTYPE vt = VT_EMPTY;
unsigned int delLen = ::SysStringLen(delimiter);
hr = ::SafeArrayGetVartype(psa, &vt);
if (vt != VT_BSTR || psa->cDims != 1)
AtlThrow(E_INVALIDARG);
SAFEARRAYBOUND *rgsa = psa->rgsabound;
ULONG elements = rgsa->cElements - rgsa->lLbound;
unsigned int totalLen = 0;
BSTR* paBSTR = (BSTR*) psa->pvData;
ULONG els = elements;
while(els != 0)
{
if (paBSTR[--els] != NULL)
totalLen += ::SysStringLen(paBSTR[els]);
if (els != 0)
totalLen += delLen;
}
hr = _SetLength(&retval, totalLen);
if (FAILED(hr))
AtlThrow(hr);
else
{
totalLen = 0;
els = elements;
ULONG curel = 0;
while (els != 0)
{
els--;
if (paBSTR[curel] != NULL)
{
unsigned int curLen = ::SysStringLen(paBSTR[curel]);
wmemcpy_s(retval + totalLen, curLen, paBSTR[curel], curLen);
totalLen += curLen;
}
curel++;
if (els != 0 && delLen != 0)
{
wmemcpy_s(retval + totalLen, delLen, delimiter, delLen);
totalLen += delLen;
}
}
} //success allocate string
}
return retval;
}
/// <summary>
/// Added by E.N.
/// Returns a new instance of a BSTR which is a Substring starting at the 'startindex' character
/// </summary>
/// <example>
/// CComBSTR2 john(L"John Smith"), subbed;
/// subbed.Attach(john.Substring(5));
/// </example>
/// <returns>The example would return "Smith"</returns>
BSTR __stdcall Substring(__in const unsigned int startIndex, __in unsigned int length) throw()
{
unsigned int maxIdx = Length();
//if (length < 0) length = 0;
if (startIndex + length > maxIdx)
length = maxIdx - startIndex;
if (m_str != NULL && startIndex >= 0 && startIndex <= maxIdx)
{
return ::SysAllocStringLen(m_str + startIndex, length);
}
else
return NULL;
}
private:
bool __stdcall local_Test(__in_opt PCWSTR src, __in unsigned int startIndex) throw()
{
bool retval = false;
if (src != NULL)
{
unsigned int compLen = lstrlenW(src);
unsigned int thisLen = Length();
if (compLen <= thisLen)
{
DWORD dwCompFlags = 0;
retval = ::CompareStringW(::GetThreadLocale(), dwCompFlags,
&m_str[thisLen - startIndex], compLen, src, compLen) == CSTR_EQUAL;
}
}
return retval;
}
public:
bool __stdcall EndsWith(__in_opt PCWSTR src) throw()
{
return local_Test(src, lstrlenW(src));
}
bool __stdcall StartsWith(__in_opt PCWSTR src) throw()
{
return local_Test(src, Length());
}
int __stdcall LastIndexOf(__in const wchar_t src, __in_opt const unsigned int startIndex = 0, __in const bool caseInsensitive = false) throw()
{
wchar_t src2[] = {src, NULL}; //create zero terminated PWSTR
return LastIndexOf(src2, startIndex, caseInsensitive);
}
int __stdcall LastIndexOf(__in_opt const wchar_t *src, __in const unsigned int startIndex = 0, __in const bool caseInsensitive = false, unsigned int count = 0) throw()
{
int result = -1;
if (m_str != NULL && src != NULL)
{
LCID lcid = ::GetThreadLocale();
DWORD dwCmpFlags = caseInsensitive ? NORM_IGNORECASE : 0;
unsigned int compLen = (unsigned int)wcslen(src);
unsigned int maxLen = Length();
unsigned int examinedChars = 0;
if (compLen <= maxLen)
{
for(unsigned int x = maxLen - compLen; x >= startIndex; )
{
bool doCompare = caseInsensitive ? true : m_str[x] == src[0];
if (doCompare)
doCompare= ::CompareStringW(lcid, dwCmpFlags, &m_str[x],
compLen, src, compLen) == CSTR_EQUAL ;
if (doCompare)
{
&nbs