Introduction
Recently I found a program of mine doing all kind of unexpected things because of CString::CompareNoCase()
not returning what I expected it to return.
I live in Belgium where in the northern part of the country they speak Dutch while in the southern part they speak French.
My application was being used in the latter part. So the users provided names containing the characters: �, �, �,.
Somewhere in my app I used the given name to search the corresponding object in a list.
Since the given names had to be (case insensitive) unique I stored those names in uppercase format.
Afterwards I compared (in a case insensitive way) the given name with the uppercase name of each object in the list.
And that went unexpectedly wrong!
Look at the following code snippet:
CString strText("�l�ve - � la fa�on - ch�teau");
CString strTextUpper(strText);
strTextUpper.MakeUpper();
CString strTextLower(strTextUpper);
strTextLower.MakeLower();
CString strMsg;
if ( strTextUpper.CompareNoCase(strText) )
strMsg.Format("Original text: <%s>\n"
"Uppered: <%s>\n"
"CompareNoCase says they differ!\n"
"Lowered again: <%s>\n\n"
"Conclusion: CompareNoCase doesn't work correctly!",
strText, strTextUpper, strTextLower);
else
strMsg.Format("Original:%s\nUppered:%s\nLowered again:%s\n\n"
"Conclusion: CompareNoCase works as expected!",
strText, strTextUpper, strTextLower);
AfxMessageBox(strMsg);
The output is:
So
-
I ask to uppercase a
CString
object -
I ask to compare it (case insensitive) with the original one
-
Result: they differ!!!
While for
CString::CompareNoCase
I read:
Compares this CString object with another string using the generic-text function _tcsicmp.
The generic-text function _tcsicmp, which is defined in TCHAR.H, maps to either _stricmp, _wcsicmp, _mbsicmp
depending on the character set that is defined at compile time.
Each of these functions performs a case-insensitive comparison of the strings, and is not affected by locale.
|
While digging deeper in MSDN I discovered the existence of CString::CollateNoCase
for which I read:
Compares this CString object with another string using the generic-text function _tcscoll.
The generic-text function _tcscoll, which is defined in TCHAR.H, maps to either stricoll, wcsicoll, or _mbsicoll
depending on the character set that is defined at compile time.
Each of these functions performs a case-insensitive comparison of the strings, according to the code page currently in use.
|
and for �_strupr, _wcsupr, _mbsupr� I read:
The _strupr function converts, in place, each lowercase letter in string to uppercase.
The conversion is determined by the LC_CTYPE category setting of the current locale.
Other characters are not affected. For more information on LC_CTYPE, see setlocale.
|
Workaround
This knowledge led me to two workarounds:
-
Use
CString::CollateNoCase
instead of CompareNoCase
:
Result:

- Although the help says it works independent of the locale, before calling
CString::CompareNoCase
set (the LC_CTYPE
part of the) the locale. e.g.:
setlocale(LC_CTYPE, "french-belgian");
Result:
I don�t know if you agree with me, but I find this rather unnatural.
I call this a bug:
- You ask MFC to convert a
CString
to its uppercase variant (Man, you don�t care what locale rules it applies!)
- You ask MFC to compare the
CString
it just uppercased (using �some� rules)
with the original but in a case insensitive way
(so in fact you expect it to neglect the uppercase variant and to use the original)
- and MFC kindly tells you they differ! (:surprised:)
Conclusion
My confidence in MFC�s CString
comparison functions isn�t great after I discovered this.
And that�s why I wrote my own ever-working workaround:
int MyCompareNoCase(LPCTSTR str1, LPCTSTR str2)
{
CString strTmp1(str1);
CString strTmp2(str2);
strTmp1.MakeUpper();
strTmp2.MakeUpper();
return strTmp1.Compare(strTmp2);
}