|
Hi Rod,
I definitely looked at vsnprintf when I was writing the ANSI version of Format() long ago. Unfortunately, the only docs I had to go on were MSDN and they listed vsnprintf as a non-standard function. That is why I ended up using vsprintf.
In fact, even with functions that are supposed to be standard I had all kinds of problems. If you look closely at all that code around vswprintf, you'll see the hoops I had to go through to make it build on all platforms. In short, it seems half the platforms out there do not conform to the standard even in the signature of the function!
If you get it to work with vsnprintf, great, I suppose I could change my lowest-common-denominator approach to allow people to at least TRY that function. It's just that I've always understood it could not be relied upon to be there.
(another function like this is the alloca() function. You'll see me using it all the time with Win32 because I know it's there. Actually it's on many other platforms too but, since it's not standard, I cannot rely upon it being there.
-Joe
|
|
|
|
|
I just noticed this when trying to trim a string that has extended ASCII characters. CStdString is using isspace to see if a character is a space or not before trimming it. Problem is, if you pass a character in that has a value < 0, it will wind up causing an ASSERT. This seems to be a problem with Visual .NET's isspace function... it tries to cast it to unsigned and see if it is less than 256, but it seems to be failing miserably.
Has anyone else seen this besides me? Is there any way to get around this? I could, in theory, hack up CStdString and add a quickie function to take care of this (e.g., adding "myisspace" that will return false if the character's ASCII value is less than 0).
But I want to make sure I'm not doing something wrong or missing something obvious. Thanks.
"When a man sits with a pretty girl for an hour, it seems like a minute. But let him sit on a hot stove for a minute and it's longer than any hour. That's relativity." - Albert Einstein
|
|
|
|
|
Sorry to have taken so long to respond to this. I've been very lax in checking this board lately and I don't know what's happening to the auto-notify emails.
Does this still happen with the very latest version of CStdString? If so, please let me know.
As far as the isspace function goes, I am torn between using good old "C" style isspace/iswspace and using std::isspace. I don't know if std::isspace will solve your problems but you might as well give it a try. The current version uses it.
My only reluctance to use it comes from a Dinkumware but that affects COM DLLs which use this function. I describe it in the header. But I am no longer using the "workaround" for that bug because the latest version of Dinkumware's library does not appear to have it.
|
|
|
|
|
FILE *fp;
fp = fopen("c:\\temp.txt", "w" );
CStdString str;
str = "Something here!!";
fprintf(fp, "%s\n", str ); // bug here
|
|
|
|
|
CStdString str;
str = "Something here!!";
fprintf(fp, "%s\n", str.GetBuffer(str.GetLength()) ); // OK here
str.ReleaseBuffer();
|
|
|
|
|
This is not a bug. You just need to be sure to call c_str() on the CStdString object whenever you supply it as one of the "..." arguments in a variadic function like fprintf, sprintf, CStdString::Format, etc.
It's true that MFCs CString allows you to get away with just this:
fprintf(fp, "%s\n", str);
But that is a hack and is not something you should rely upon. Even Microsoft recommends you cast CString when supplying it as one of the "..." arguments in a function like fprintf:
fprintf(fp, "%s\n", (const char*)str);
...or you could do this:
fprintf(fp, "%s\n", static_cast<const char*>(str));.
.and with CStdString, you can do this as well.
fprintf(fp, "%s\n", str.c_str());
All three examples above have the exact same effect -- turning the CStdString object into a const char*. A const char* is what fprintf needs to supply to the %s, not a CStdString.
Whatever you do, don't call GetBuffer(). That's for getting NON-const access to the string buffer. You don't need a writeable buffer string buffer. You need a read-only one, a const char*
If you read the other feedback to the CStdString article you'll see this question asked many times. You might need to adjust the date filter to see all the feedback. But once again, this is not a bug. It is an annoyance caused by variadic functions (i.e. functions that take a variable number of arguments) and it is something I cannot possibly design around. Read the other feedback for a more thorough explanation.
|
|
|
|
|
Thanks!
|
|
|
|
|
|
First off, thank you for this class. It's been extremely helpful. I'm compiling for the first time on UNIX (SunOS 5.8) and getting several errors (e.g., DWORD not defined) all within the "ssfmtmsg" function below. Based on the header comment, I'm guessing the #else may be out of place, but then I'd be surprised it wasn't caught sooner. Please let me know if I'm doing something wrong. Thanks for your help.
// -----------------------------------------------------------------------------
// ssfmtmsg: FormatMessage equivalents. Needed because I added a CString facade
// Again -- no equivalent of these on non-Win32 builds but their might one day
// be one if the message facet gets implemented
// -----------------------------------------------------------------------------
#if defined (SS_WIN32) && !defined(SS_ANSI)
#else
inline DWORD ssfmtmsg(DWORD dwFlags, LPCVOID pSrc, DWORD dwMsgId,
DWORD dwLangId, PSTR pBuf, DWORD nSize,
va_list* vlArgs)
{
return FormatMessageA(dwFlags, pSrc, dwMsgId, dwLangId,
pBuf, nSize,vlArgs);
}
inline DWORD ssfmtmsg(DWORD dwFlags, LPCVOID pSrc, DWORD dwMsgId,
DWORD dwLangId, PWSTR pBuf, DWORD nSize,
va_list* vlArgs)
{
return FormatMessageW(dwFlags, pSrc, dwMsgId, dwLangId,
pBuf, nSize,vlArgs);
}
#endif
Bill
|
|
|
|
|
Well a DWORD is an unsigned long, but I think that's not the problem here. The problem is that I appear to have misplaced my preprocessor macros. I put the
#else
line in the wrong place. It should be BELOW those ssfmtmsg functions, not above them. If you move it downward to the correct spot, you should be OK.
FormatMessageA and FormatMessageA only exist on Win32 platforms.
I will correct the online version tonight. Sorry about that.
-Joe
|
|
|
|
|
we develop windows program with vc(but not use MFC).
Our program complied ,and display follow warning:
/***************************************************/
warning C4786: 'std::_Tree<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::pair<std::basic_string<char,std::char_traits<char>,std::allocator<c
har> > const ,CMarkupSTL::SavedPos>,std::map<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,CMarkupSTL::SavedPos,std::less<std::basic_string<char,std::char_traits<char>,std::allocator<char> > >,std::allocator<cmarkupstl::savedp
os> >::_Kfn,std::less<std::basic_string<char,std::char_traits<char>,std::allocator<char> > >,std::allocator<cmarkupstl::savedpos> >' : identifier was truncated to '255' characters in the debug information
/***************************************************/
CMarkupSTL is the class that encapsulates the dom object.
As using the template, the symbol lenght may be less than 255.
Display the warning,why?
|
|
|
|
|
It is a warning very common with Visual C++ and the Standard C++ Library. What it is telling you is that the fully decorated C++ name of a class (instantiated from a template) was longer than 255 characters. That's a problem with templates and VC.
It is harmless but annoying.
If you wish to suppress this warning, you may use a #pragma as follows:
#pragma warning (disable:4786)
Place this pragma as early in the header as you can. I put all such #pragmas in my stdafx.h
Please note however that some VC headers actually turn ON warnings you might have disabled with this statement. In particular the header <yvals.h> does this. Therefore you might need to do something like this
#pragma warning (disable:4786) // disable it
#include <yvals.h> // this will re-enable it
#pragma warning (disable:4786) // disable it AGAIN
Bizarre but it works.
Again, don't worry about the warning. It is completely harmless.
-Joe
|
|
|
|
|
I'm pleasantly suprised that you reply soon.
I'm worring about the worning.
Thank you very much!
|
|
|
|
|
I'm pleasantly suprised that you reply soon.
I'm worring about the worning.
Thank you very much!
|
|
|
|
|
I'm working on a large (personal) project comprising of several DLLs and EXEs. I want to use this string class as a common string type for all projects so i can pass this to the DLLs exported functions. I understand if i use this string header in each separate project it might cause problems because it uses the stl and what if one of the DLLs uses a different stl library to the lib file? what will happen then?
So i wanted to put the implementation of this string class inside a lib file, so the stl implementation is common (at least in the lib) and then i can just include the header (which is not the class definition only) in the projects and also link to the lib file. Is this possible?
Thank you!
|
|
|
|
|
Well, if you have control over all modules which will be passing these objects back and forth (and I'm guessing you must) then as long as you set all of them to use the same DLL version of the CRT, everything should be just fine.
Think about why it would be dangerous for one module to use a static version and another to use the DLL version. Consider the following function, exposed by one of your DLLs:
CStdString Foo();
You have a situation where one module (the DLL) allocates memory and another (the app or whatever other module calls into the DLL) frees it. If they are not using the same heap, bad things happen.
When that returned CStdString object is destroyed by the app, it will be on a different CRT heap than the one that your DLL allocated it from. This will cause an exception.
Just set all modules to use the shared DLL version of the CRT and these problems go away. All modules then share the same heap.
Alternately, you can change the interface to NOT use CStdString objects at all, but rather to fill buffers supplied by the caller. For example, here is the above function rewritten to use this principle:
void Foo(char* buf, int maxSize);
It is far more cumbersome, but it removes any memory allocation issues sinc the memory is now allocated AND freed by the caller only.
Incidentally, you would have the EXACT same problem if you were merely using the STL's std::string. My class does derive from that one, but it adds only functions to the base class, no member data.
My recommendation -- do not try to export CStdString from a DLL or LIB at all. That is what I do. I just keep the header file in some common directory and rely upon the vendor-supplied basic_string<> implementations to be exported properly. The functions CStdString adds are mainly small, inline wrappers on existing basic_string<> functions already. The compiler should resolve most of them away to nothing anyway
If you are going to have DLL-exported functions which return string objects, definitely set all modules to use the DLL version of the CRT. But you do not need any special handling for CStdString. Allow its functions to be compiled into every module if necessary. The extra cost in module size will not be great. Most modules use only a small fraction of the member functions anyway
-Joe
|
|
|
|
|
OK Thanks
While i will initially be writing all the DLLs and EXE for the project there may eventually be other developers writing the DLLs (its a plugn architecture). Even if a programmer uses a different STL version to compile their DLL then this should in theory be ok?
Thanks
Andrew
|
|
|
|
|
If some other developer comes along and uses a different STL and tries to plug in and use your DLL function that returns a CStdString, then yes, you will have a problem. You would have exactly the same problem if the return value was a std::string or std::wstring -- different heaps.
In this case, the way around it is to change the interface of the plug-in functions to NOT use std::string or CStdString objects. For example, instead of this:
Function exported by the DLL
CStdStringA Foo()
{
return CStdStringA("this is a return value");
}
Main application:
void main(int)
{
CStdStringA value = Foo();
// do something with value
}
You could instead do something like this this (note - no error checking):
Function exported by DLL
char* Foo(char* buf, int maxSize)
{
return strncpy(buf, "this is a return value", maxSize);
}
Main application
void main(int)
{
char buf[100];
CStdStringA value(Foo(buf, 99));
// do something with buf
}
A lot more cumbersome I admit, but now there is no problem with heaps. That's the problem with plug-in architectures, I'm afraid. It's the same reason why COM uses the BSTR string type with all those dedicated functions to allocate and free them -- they need to ensure that the same heap is used.
-Joe
|
|
|
|
|
Ok, thanks for your quick and helpful responses. I have one more query (at least until I read your response ).
Lets say i have a function exported by my dll :
void GetDescription(CStdString *desc)
{
*desc = "This is the descrption";
}
Then in the exe :
int main()
{
CStdString *a, b;
a = new CStdString;
GetDescription(a);
GetDescription(b);
return 0;
}
The CStdString is declared in the exe and it passes a pointer to the GetDescription function. Now, once inside the dll the string pointers, which currently have a size internally of 0, will require some memory allocation to store the string data. Where does this get allocated? Will it break when main() exits and tries to destroy the string? Does it make a difference that a is on the heap and be is on the stack?
Thank you again
Andrew
|
|
|
|
|
You need to understand that there are TWO allocations taking place for each string you pass into this function. One allocation is of the string object itself, the other is of the internal buffer managed by the string object. To illustrate, here is a simplified class description for CStdString (imagine that CStdString was just a simple class not derived from anything with a bunch of operators & ctors omitted):
class CStdString
{
const char* buffer;
int length;
CStdString() : buffer(0), length(0) {}
~CStdString() { if ( 0 != buffer ) free(buffer); }
}
Now in the case of the string objects themselves, the EXE has total control over when they are allocated and freed. The stack one ('b') goes away at the end of main(). The heap one ('a') goes away whenever you call delete (I'm assuming you forgot to in your example).
But in the case of the internal buffers managed by the string objects, you do NOT always have control over when that memory is allocted and freed. All a module has to do is try to assign a value to your string object (the way that GetDescription does) and deep down inside the CRT, the malloc() function will likely be called. But since malloc is being called from GetDescription(), inside the DLL, it uses that DLLs heap. Not the EXEs. If the DLL and the EXE use different heaps... you are in trouble.
void main(int)
{
CStdString b; // object allocated, buffer is not
GetDescription(&b); // buffer allocated by the DLL's heap
} //--> b is destroyed. Its dtor frees the buffer but, since
// we're back in the EXE, it tries to free it from EXE's heap
-Joe
-Joe
|
|
|
|
|
I understand all that (I think from when i first posted to the forum to now the DLL / EXE heap penny has dropped!). Yes i forgot the delete call from my example.
In the example you just listed, I'm presuming when main exited bad things would happen as it tried to free something from the DLL's heap?
Will this happen even if both projects use the Multithreaded DLL runtime libs?
I thought I'd try answer this for myself, i set up a simple exe and dll project, in the exe i had this code:
int main(int)
{
Str str;
DoFoo(str);
return 0;
}
where DoFoo is a DLL exported function. The dll code looks like this:
extern "C" __declspec( dllexport ) void DoFoo(Str &str)
{
str.alloc(10);
}
the str class looks something like this:
class Str
{
char *str;
public:
Str()
{
str = NULL;
}
~Str()
{
delete str;
}
void alloc(int val)
{
str = new char[val];
}
};
This seems to work fine with the CRT types set to Multithreaded dll (it asserts with anything else). The desctructor gets called and it seems there are no memory leaks. I included the following to test for leaks:
#include <crtdbg.h>
#define _CRTDBG_MAP_ALLOC
So is this code all working as it seems? Does the memory get deleted properly etc? Or is it tricking me?
How would this work if i replaced Str with CStdString? Is it a whole different kettle of fish with the STL?
Thanks!
|
|
|
|
|
Andrew Newton wrote:
In the example you just listed, I'm presuming when main exited bad things would happen as it tried to free something from the DLL's heap?
Will this happen even if both projects use the Multithreaded DLL runtime libs?
No there will be no problem if both projects (i.e. modules) use exactly (and I do mean EXACTLY) the same DLL version of the runtime libs.
When you publish a module with functions like GetDescription (which modifies string objects) what you are doing is imposing a requirement on any other module that uses it (be it an EXE, another DLL, etc) that that module MUST use exactly the same DLL version of the CRT.
Even if you have 100 DLLs loaded, if they were all built with the same DLL version of the CRT, they all use the same heap so there are no problems.
This issue is the same whether you use Str, basic_string<>, or CStdString. Underneath they're all calling malloc() and free() eventually.
It is for reasons like this that all the Win32 functions use that more-difficult-to-use semantics like this
char* GetDescription(char* buf, int, maxSize);
This eliminates any heap issues altogether. In this were how you defined GetDescription, it wouldn't matter what version of the CRT you or the caller used. But it's no fun to code against...
-Joe
|
|
|
|
|
Excellent, thanks for all your help. I understand fully now.
I did at one time consider using syntax something like the following..
void GetDescription(char* buf, int maxSize);
...as it would prevent issues like the ones we discussed but decided to go with a much more friendly form of string classes. Now i won't have any hesitation to include your string lib in my code.
Your help is much appreciated
Andrew
|
|
|
|
|
This string class rocks. The only issue that I have is that when using the .NET debugger, I can't see the values of any CStdString variables... the watch windows in .NET just display garbage. To see the contents of the string, I have to TRACE() them in the code, or use OutputDebugString(). This is a hassle for sure.
Any reason why this would occur. Is this more to do with STL's basic_string over CStdString?
Thx!
Greg
|
|
|
|
|
Just an addition... it seems that this only occurs if the string is longer than 15 (?) characters. If it's less, it seems to work fine.
|
|
|
|
|