|
CStdClass saved me a ton of time. Thanks to Joe and the other commentors for deligently making this class better and better
|
|
|
|
|
I am using your class in Xcode to pert my old MFC code... It works great except for this
<br />
#define _T(s) L ## s<br />
<br />
CStdString strA=_T("ABC");<br />
CStdString strB=_T("DEF");<br />
CStdString strC;<br />
strC.Format(_T("%s%s"), strA, strB);<br />
CStdString strD;<br />
strD = strA + strB;<br />
Any pointers hugely welcome... I am using the version from Januaey 10, 2005...
Regards,
Jerry
|
|
|
|
|
Hi,
Well if you get the latest version (which I can email you if you email me at the address you'll find at the top the header file StdString.h) it will fix your problem. Frankly I'm surprised your code doesn't just plain crash. What you are doing is something I generally warn against both in the header and in this article. Actually it's something Microsoft recommends against but let you get away with when you use CString. However if you get the latest version of StdString.h you will get a workaround that will let you do what you want and make your code work properly.
I go into the reason why fully inside the latest header file (search for the string "SS_SAFE_FORMAT" and you'll find a long series of comments explaining it). However. I'll try to go into it here too:
When you call function like Format() or sprintf() or anything that formats strings with variable arguments at the end, you should only supply "plain-old-datatypes" (POD) as the "..." arguments. The "%s" format specifier is expecting a const char*. In a UNICODE build it is expecting a const wchar_t*. It's a const TCHAR* and in this discussion I'll call it a "PCTSTR" for short
Now normally when you call a regular C++ function that expects a PCTSTR and you supply a CStdString object, you get away with it because the compiler is smart enough to see that the function you're calling expects PCTSTR and that CStdString has an implicit cast operator for it. So it calls that cast operator for you and you are all set.
void Foo(PCTSTR szString)
{
}
int main()
{
CStdString bar(_T("Joe"));
Foo(bar);
return 0;
}
But when you call Format(), the compiler can't do that. It doesn't know what type is expected. It's not smart enough to parse the format string, see the %s and realize that's what it is. That's the problem with "..." arguments (called "variadic" arguments). The compiler cannot do any type checking on them.
So what does it do? Well it supplies the first 4 bytes of whatever you pass. That's what Format() sees. If it's an int, the first 4 bytes are the int. If it's a PCTSTR, then the first 4 bytes are the pointer to that string. But if it's an object, the first 4 bytes could be ANYTHING. It all depends upon how the object is laid out in memory.
But if you've used MFC, you probably realize that it lets you get away with it. Why? Well the MFC designers created a hack of their own. The laid out CString so that the first 4 bytes of the class are always a pointer to the null-terminated string it wraps. They're always the exact same PCTSTR that operator() returns.
That's a complete hack. Unfortunately it's one I cannot reproduce with CStdString. Since CStdString is just a template, derived from whatever implementation of basic_string exists in your library, there is no way I can control the binary layout of the class.
The correct way to write this line
strC.Format(_T("%s%s"), strA, strB);
is like this:
strC.Format(_T("%s%s"), strA.GetString(), strB.GetString());
or like this:
strC.Format(_T("%s%s"), strA.c_str()(), strB.c_str());
or even like this:
strC.Format(_T("%s%s"), static_cast<PCTSTR>(strA), static_cast<PCTSTR>(strB));
In short, what you SHOULD do is let the compiler know explicitly what you are trying to pass for the "%s" argument.
But since I had so many people emailing me about this, I created my workaround (read: "hack"). It will allow you to get away with not doing any of that. However I do not recommend it for any new code as it ONLY works for Format(). But it should help you port your code with fewer changes needed.
Hope this helps. Please let me know if you have any questions.
-Joe
|
|
|
|
|
Hi Joe,
I really appreciate you took the time to reply in such detail... I believed I had the version WITH the hack... as 1) it compiles 2) I have those verbose multiple casts which I thought solved the problem!
I will send you an e-mail to get the latest version though...
As mentioned, I am porting some of my code to iPhone and I did an implementation of (dirty-ish) COleDateTime (code I gathered from the MFC source code and WINE)... there is a reduced implentation of localizations too!
Tell me if you're interested...Anyway, thanks for sharing,
Jerry
|
|
|
|
|
I answered to you over email but in the interests of answering this for everyone, I think the problem was much simpler than the explanation I wrote above. It seems you DO have the header with the Format() fix so that explanation does not apply.
The problem seems to be the #definition of the _T() macro. You should not need to define it but if you do, it should be defined like this:
#ifdef _UNICODE
# define _T(s) L ## s
#else
# define _T(s) s
#endif
And if you are building a UNICODE build then, as you can see, my header is counting on the macro flag _UNICODE to be defined. This is how the MS headers do it.
Now I am not familiar to iPhone but I suspect the build there is NOT unicode. So when you call Format() with the "%s" flag, the function is looking for ONE-byte characters. A one byte character string of "ABC" is laid out in memory literally like this:
0x42, 0x43, 0x44 0x00
The three ASCII character codes for 'A', 'B', and 'C' followed by a terminating NULL zero.
However since you defined _T() to translate to a wide string, your "ABC" string is laid out in 2-byte characters, like this:
0x42, 0x00, 0x43, 0x00, 0x44, 0x00, 0x00, 0x00
But in a non-unicode build Format() with a "%s" flag is looking for ONE byte characters. So it see 0x42 and then interprets the second, NULL byte (part of a 2-byte character - as the terminating string. And you get "A" instead of "ABC".
The fix, I would think, is to #define _T() as I have above. However I'll bet even with your code as is, if you instead changed the "%s" flags to "%ls", that would make it work too. That's not a solution, just a test however.
Regardless I think you've found a hole in my header in which I am not properly #defining the _T() macro.
-Joe
|
|
|
|
|
Hi Joe,
I have been a week away from my desk... and I was eager to test what you had sent as it made perfect sense.
Unfortunately the #def _UNICODE was set... the string is using the wide char definition... and when I run the Format step by step the vswprintf is indeed called.
So I suspect it's some Apple arcane wizardry involved!
Now %ls does work - therefore I am tempted to modify the FormatV and replace %s by %ls (which would not work if I used %.10s.... but then I don't)... if any Mac specialist could shed some light, that would be welcome!
I'd like to thank you again for the time you took replying to me, Joe!
Thanks,
(bad)Jerry
|
|
|
|
|
This is getting weirder
Do you have the docs for your compiler? Do they document the vwsprintf() function and the format specification fields you supply to it?
Because in MS world, when the vswprintf() function sees the "%s" flag, it is supposed to treat whatever string argument it sees as a WIDE string. You should not need to use the "l" prefix (i.e. "%ls"). I was under the impression that this was universal and a part of the standard
Try this simple test
wchar_t buffer[100];
const wchar_t* szName = L"Joe");
vswprintf(buffer, 100, L"%s", szName)
What does the the buffer variable hold at the end. If it holds "Joe", then I don't know what's going wrong. If it holds just "J", then it sounds like your implementation of vswprintf() is doing something wrong.
If it's working properly but your strings are still getting truncated with CStdString, either the wring formatting function must be getting called (vsprintf instead of vswprintf) or some compiler flag turning on a unicode build (something that G++ depends on that MS does not maybe?) is not being set
-Joe
|
|
|
|
|
The Apple compiler tells me that it expects szName to be a char * and not not a const wchar_t *.... don't get me started on Steve Jobs at this stage!
XCode is meant to be fully unicode compatible but there seems to be problems with the C++...
The documentation says that %s expects a char * and %ls or %S expects a wchar_t *... here is the link: http://developer.apple.com/library/IOS/#documentation/System/Conceptual/ManPages_iPhoneOS/man3/wprintf.3.html[^]
Anyway I have done what I described earlier... replaced %s by %ls in your FormatV and it works (although somehow unelegant )
Thanks again!
|
|
|
|
|
This is great class. In function StdCodeCvt another neat way to covert from to wide characters is:
To char:
std::string dst;<br />
std::wstring src = L"Convert me";<br />
<br />
dst.assign(src.begin(), src.end());
And to wchar_t:
std::wstring dst;<br />
std::string src = "Convert me";<br />
<br />
dst.assign(src.begin(), src.end());
Thank you for the great class
|
|
|
|
|
take care locale
Hardware-OS-Software
===== Bridge =======
|
|
|
|
|
Hi Joe,
I have ported the library onto MAC and using it to build extensions for firefox. I created two toolbars for firefox and both the toolbar has the stdstring.h libary used in it. When I load firefox with both the toolbars, the browser crashes and stack trace shows inside stdstring.h. When I load firefox with any one, then I see no crashes at all.
Pls. let me know if you have got any version of this library for MAC OS X 10.5.
Thanks,
krissam
|
|
|
|
|
Hi,
I really only have one version of the file. I use compiler directives to make it build on various plaforms. Unfortunately the version on CodeProject is a bit out of date and I have not updated it because I don't like the fact that they want me to apply some sort of license to it.
I am on vacation right now but will be happy to help you get this working when I get back on Monday, Oct 26. In the meantime, if you could email me (my address is right in the header file and is still valid ) and include the version of the file you are using and some indication of the code that's causing the problem as well as the error itself, I should be able to see what is up.
Actually the problem might just be that you have an out of date version of the file (again, due to my reluctance to update the version hosted here). So email me directly and I'll send you the latest version. It might help
-Joe
-Joe
|
|
|
|
|
Hi Krissam,
I am so sorry I never finished helping you with this. At first I thought I never responded at all (my reply was on the second page) but now I see that I did yet I never followed up.
I hope you were able to resolve the problem. If, by chance, you are still using the code there IS a new version here for download
-Joe
|
|
|
|
|
I've just attempted to compile this file in VS2010 Beta 2, but unfortunately it won't compile.
Other than a bunch of deprecation warning messages, the compiler gets stuck here:
inline CArchive& AFXAPI operator<<(CArchive& ar, const CStdStringA& strA)
{
CString strTemp = strA;
return ar << strTemp;
} ... error C2440: 'initializing' : cannot convert from 'const CStdStringA' to 'ATL::CStringT<basetype,stringtraits>'
Since the ClassWizard is back in VS2010, I am at last considering to move up from VC6, so any help would be very much apreciated in order to get this class up to date.
Thank you.
|
|
|
|
|
Hi,
Looks like you have an old copy of the code. I really need to update the one on CodeProject. The one I have locally is up-to-date. If you email me at jmoleary@earthlink.net I can send it to you.
If you don't feel like emailing me, the simplest solution is to delete all of the operator<< functions that include CArchive. They're all right there in a block of text so it's pretty quick to do. Unless you are using CArchive to serialize CStdString objects (something that few people are doing, I imagine) you will never need them.
OR... if you want to keep them and still don't want to email me, you can change that line to use a constructor instead of assignment. Make it look like this
CString strTemp(strA)
That was fix I applied way back when-Joe
|
|
|
|
|
Thanks a lot for this good class.
And:
1. How about hosting this code on codeplex, gcode or sf?
2. It could be better if it had UTF8 related conversion methods.
Regards,
modified on Thursday, January 21, 2010 3:51 AM
|
|
|
|
|
Joe you have outdone yourself here. This is the ultimate cstring
|
|
|
|
|
Thanks,
I've been updating the thing on and off for about 13 years now!
Be sure to check the link inside the header to make sure you have the very latest version. I haven't updated the CodeProject one for a while now. Keep meaning to get around to it.
-Joe
|
|
|
|
|
#define CStdString CStringEx
CStringEx GetLogHeader(BOOL bLocalTime = TRUE)
{
SYSTEMTIME st;
bLocalTime ? GetLocalTime(&st) : GetSystemTime(&st);
DWORD dwPID = GetCurrentProcessId();
DWORD dwTID = GetCurrentThreadId();
CStringEx strPID = _T("Unknown");
CStringEx strTID = _T("Unknown");
CStringEx strLogHeader;
strLogHeader.Format(_T("[%02d:%02d:%02d(%03d)][0x%04X:%s][0x%04X:%s]"),
st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, dwPID, strPID.c_str(), dwTID, strTID.c_str());
return strLogHeader;
}
void WriteLogDebug(TCHAR *ch, ...)
{
TCHAR buf[1024];
va_list arg_list;
va_start( arg_list, ch );
_vstprintf_s( buf, _countof(buf), ch, arg_list );
va_end( arg_list );
OutputDebugString(buf);
FILE *file = NULL;
errno_t err = _tfopen_s(&file, g_szLogFile, _T("a+"));
if (!err)
{
_ftprintf_s(file, _T("%s%s"), GetLogHeader().c_str(), buf);
fclose(file);
}
}
The above code works works fine except that it does not print Hangul(Korean) characters correctly. It prints them in question(0x3F marks (?????) only. The source code was built in UNICODE mode. Try the following call.
WriteLogDebug(_T("한글을 씁시다\n"));
I tried to figure out/fix this issue for so long, but failed.
Can someone help?
HR
|
|
|
|
|
I see you posted this in June. Sorry I did not notice it until now. It is probably far too late to help you but I will post this anyway.
The problem is likely one of locale. You need to set the locale to the global one
The way to set the global locale is as follows:
std::locale loc2 ("korean");<br />
std::locale::global(loc2);
You could do this once at the start of your program.
-Joe
|
|
|
|
|
|
The following code works wrong.
SYSTEMTIME st;<br />
GetLocalTime(&st);<br />
DWORD dwPID = GetCurrentProcessId();<br />
DWORD dwTID = GetCurrentThreadId();<br />
CStdString strPID = _T("UN");<br />
CStdString strTID = _T("UN");<br />
<br />
CStdString str;<br />
str.Format(_T("[%02d:%02d:%02d(%03d)][0x%04X:%s][0x%04X:%s]"),<br />
st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, dwPID, strPID, dwTID, strTID);<br />
CStdString str2;<br />
str2.Format(_T("[%02d:%02d:%02d(%03d)][0x%04X:%s][0x%04X:%s]"),<br />
st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, dwPID, strPID.c_str(), dwTID, strTID.c_str());<br />
FILE *file = NULL;<br />
errno_t err = _tfopen_s(&file, g_szLogFile, _T("w"));<br />
if (!err)<br />
{<br />
TCHAR buf[1024]; <br />
_ftprintf_s(file, _T("%s%s"), str, buf);<br />
_ftprintf_s(file, _T("%s%s"), str.c_str(), buf);<br />
_ftprintf_s(file, _T("%s%s"), str2, buf);<br />
_ftprintf_s(file, _T("%s%s"), str2.c_str(), buf);<br />
fclose(file);<br />
}<br />
This is run using VC2005/WinXP SP3.
Please check if fixable.
HR
|
|
|
|
|
The problem is exactly what you identified in the title of your post. You are using a CStdString object as a POD type when it is not one.
Whenever you pass a CStdString as one of the "variadic" parameters to a function like fprintf (i.e. one of the "..." parameters), you must be sure to properly cast it to the proper POD type first. Otherwise, the compiler will simply take the first 4 bytes of the object in binary layout.
So this line is bad:
_ftprintf_s(file, _T("%s%s"), str, buf);
but this line is OK
_ftprintf_s(file, _T("%s%s"), str.c_str(), buf);
Other variations that would be OK include the following:
_ftprintf_s(file, _T("%s%s"), str.GetString(), buf);
_ftprintf_s(file, _T("%s%s"), static_cast<PCTSTR>(str), buf);
Yes, I realize that MFC's CString lets you get away without doing this, but they specifically laid out the class in binary to allow it. In short, it's a hack they put in there to protect you from yourself.
Since I derive CStdString from whatever implementation of basic_string your library provides, I have no control over the binary layout of the class.
-Joe
|
|
|
|
|
Joe,
yes, I knew the .c_str() works, but I thought removing it would be ok using the extension posted by balint256 in this thread. And so asked him if this usage was one of his points of extension.
CStdString str(_T("world!"));
_tprintf(_T("Hello %s\n"), str);
// Output is "Hello world!"
Otherwise, I don't see what is the point of the extension.
|
|
|
|
|
Sorry, I somehow missed that this was a response to his post.
I must admit I haven't looked at his scheme until now. Now that I have, I would not recommend it. He adds to the size of the class, introduces multiple inheritance and must update a pointer after every single function call, yet he fails to update that pointer in all places needed. All to avoid a simple call to c_str() in rarely called functions.
The only reason I ever created my CStdString::Format "hack" in the first place, was because it improves the safety of the class, and in no way reduces it. Unfortunately, balint's fix does not do that. I do not mean to criticize balint. He clearly wanted to help. But I cannot recommend this fix as it is incomplete and decreases the safety of the code.
Also, my fix was purely for those converting legacy code. Not for new code. If you are writing new code, do yourself a favor: Every time you call a variadic function with a CStdString arguments, call the c_str() function on the argument. It's what I do.
-Joe
modified on Tuesday, June 16, 2009 9:49 AM
|
|
|
|
|