mfc >> How to Duplicate CString's Default Data Type

by Jonathan Wood » Thu, 09 Mar 2006 09:21:30 GMT

I designed a template class for storing an array of any data type. Then I
used it to store characters as follows:

CBuff<TCHAR> charbuff;
// ...
printf("%s", charbuff);

The problem is that even though I have a TCHAR operator, the second prinft
argument is untyped, so the compiler doesn't know what to pass. So it
creates an entire copy of the charbuff object and passes the address of
that.

I realize I can fix this using a type case.

printf("%s", (TCHAR*)charbuff);

But I recall that the CString class does a trick so that the compiler knows
to pass a TCHAR* to untyped arguments. Does anyone recall how that was
accomplished as I'd like to do the same with my class.

Finally, I was hoping for a way to do this that should run on compilers
other than Microsoft too.

Thanks!

--
Jonathan Wood
SoftCircuits
http://www.softcircuits.com




mfc >> How to Duplicate CString's Default Data Type

by Joseph M. Newcomer » Thu, 09 Mar 2006 15:53:20 GMT


CString does this by serious trickery. Watch the little shells, now which one has the pea
under it?

The trick is the serious type data is stored "invisibly" behind the nominal CString value,
so that it gives the illusion of being an actual pointer. This is why you can't subclass
CString and add virtual methods.

Study the CString code (which is harder in VS.NET than in VS6 because VS.NET is far more
sophisticated)
joe



Joseph M. Newcomer [MVP]
email: XXXX@XXXXX.COM
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm



mfc >> How to Duplicate CString's Default Data Type

by Tom Serface » Fri, 10 Mar 2006 00:11:22 GMT

CStrings have an overloaded operator (LPCTSTR) that makes it possible to
pass them to just about anywhere and they are converted by the compiler.

As Joe suggests, you might want to just peruse the code.

Tom







How to Duplicate CString's Default Data Type

by Jonathan Wood » Fri, 10 Mar 2006 02:49:57 GMT

Tom,


My class already has this. But, as I indicated, if you pass the object as an
untyped argument (as in my printf example), the compiler doesn't know you
want a string pointer. Instead, it assumes you want to pass a copy of the
entire object and the overloaded operator does not come into play. A type
cast will correct this, but there is enormous overhead if the object is used
and the cast is forgotten. With CString, a type cast is not necessary. It
has some way to default to a string pointer when the compiler does not know
what type should be passed.

--
Jonathan Wood
SoftCircuits
http://www.softcircuits.com




How to Duplicate CString's Default Data Type

by Jonathan Wood » Fri, 10 Mar 2006 02:53:38 GMT

Joseph,


Well, I did take a look and the first thing I see (which I already knew) is
that there really isn't a CString class. It's an alias to another class,
which depending on the character mode, seems to rely on other classes.

I was hoping someone could tell me the basics of what is happening and
whether or not it is portable. But, so far, I've been unable to find a thing
on this on the Web. "Serious type data is stored 'invisibly' behind the
nominal CString value?" Nope, I'm afraid that doesn't ring too many bells.

Thanks.

--
Jonathan Wood
SoftCircuits
http://www.softcircuits.com




How to Duplicate CString's Default Data Type

by Tom Serface » Fri, 10 Mar 2006 03:24:01 GMT

CString will do that, but in my experience it only works reliable as the
first argument. I typically put (LPCTSTR) cs (I.E., cast it) just to be
sure.

Tom







How to Duplicate CString's Default Data Type

by Jonathan Wood » Fri, 10 Mar 2006 06:13:07 GMT

On later versions at least, CString objects work just fine as an untyped
argument to printf. I even remember it being mentioned somewhere some time
ago. Like I said, I sure can't find anything on it now.

--
Jonathan Wood
SoftCircuits
http://www.softcircuits.com
Available for consulting: http://www.softcircuits.com/jwood/resume.htm









How to Duplicate CString's Default Data Type

by jolest » Fri, 10 Mar 2006 07:03:42 GMT

This (passing a CString to printf acts the same as passing a simple
string) has nothing to do with overloaded operators and everything to
do with a clever hack by someone in the MFC team. What they did was to
have ONLY one variable in a CString. That variable is a simple pointer
to the beginning of the string. So, when it gets passed to printf, the
only thing that gets put on the stack is the pointer to the string
itself. The extra data that CString uses internally is stuffed away in
extra memory allocated just before the string data.

Like I said ... a clever hack...

... Jolest



How to Duplicate CString's Default Data Type

by Jonathan Wood » Fri, 10 Mar 2006 09:13:06 GMT

Jolest,


Right, which is what I've been saying since my original post.


With my class, it actually created a copy of the entire object and passed a
pointer to that and then called delete after the function returned. So I'm
afraid I still don't get how this would work, and how the compiler knows not
to pass a copy of the CString object.

I guess I'll need to examine the code better when I get some time. As I
mentioned elsewhere, CString is large and convoluted, consisting of several
different classes it seems. It looks like I'll just need to work through
this when I get a chance.

Thanks.

--
Jonathan Wood
SoftCircuits
http://www.softcircuits.com




How to Duplicate CString's Default Data Type

by Joseph M. Newcomer » Fri, 10 Mar 2006 11:10:36 GMT

es, in VS.NET, CString is a *very* complex set of relationships, with CString, CStringT,
CStringW and CStringA, not to mention some generic CString "base class" and a few other
truly wondrous and convoluted complexities. It was a lot more straightforward in
VS6/MFC6.

Essentially, the CString data type is created as a simple pointer value to a character
string. The LPCTSTR cast exposes the pointer, but it is really more for compiler
happiness than having any functionality (if you use a CString in a parameter position
where an LPCTSTR is needed, the (LPCTSTR) operator lets the compiler transparently
"unwrap" the real pointer and not give you an error message about mismatched types).

If you use these techniques, they *might* be portable.

Let's take a look here. First, take the VS6 CString class and strip out all the methods
up to a point. We are left with

class CString
{
public:

operator LPCTSTR() const;
protected:
LPTSTR m_pchData; // pointer to ref counted string data

// implementation helpers
CStringData* GetData() const;
}

Note that a CString has exactly one data member, an LPTSTR. That's it. That's the only
data member. This is the pointer to the string.

Now, let's look at where the *real* structure data is stored. It is in a data structure
called CStringData:

struct CStringData
{
long nRefs; // reference count
int nDataLength; // length of data (including terminator)
int nAllocLength; // length of allocation
// TCHAR data[nAllocLength]

TCHAR* data() // TCHAR* to managed data
{ return (TCHAR*)(this+1); }
};

Watch out for the phrase "managed data". This has *nothing* to do with .NET "managed
data" at all; remember, CString dates back many years, and this is a reference to the data
managed by the CString data type.

Now look at how this is obtained. Given the pointer to a CString, the GetData() method
can be invoked. It returns a pointer to a block of data that is -1 off the m_pchData
pointer. But in C, -1 in this context means -1 * sizeof(CStringData) from the pointer.

_AFX_INLINE CStringData* CString::GetData() const
{ ASSERT(m_pchData != NULL); return ((CStringData*)m_pchData)-1; }

Suppose we have, in an ANSI app,

CString p = "ABC";

The assignment to the CString will create a buffer of size 4 bytes to hold the text of the
string, but it will actually allocate sizeof(CStringData) + strlen(value) bytes of data,
and return a pointer not to the beginning of the structure, but to the middle of the
structure. So the one-and-only value in the CString, m_pchData, points to the middle of a
structure which is of the form

| CStringData | 'A' | 'B' | 'C' |

where the CString points to the position of the character 'A' in this sequence of bytes.
GetData() returns a pointer to the beginning of the CStringData object, and the data()
member of the CStringData class returns a pointer to the 'A' character. All the
manipulations to the CString data type are in terms of the CStringData internally, but
what is presented to the application programmer is the CString type. The LPCTSTR operator
is defined as

_AFX_INLINE CString::operator LPCTSTR() const
{ return m_pchData; }

so notice that it simply returns the data pointer.

This is why you can't sublcass CString and add virtual methods. Virtual methods will
change the structure by putting a vtable pointer at the start of the structure

How to Duplicate CString's Default Data Type

by Jonathan Wood » Fri, 10 Mar 2006 11:38:05 GMT

hanks Joseph. I've printed your post and will go over it in detail a bit
later.

--
Jonathan Wood
SoftCircuits
http://www.softcircuits.com
Available for consulting: http://www.softcircuits.com/jwood/resume.htm

"Joseph M. Newcomer" < XXXX@XXXXX.COM > wrote in message
news: XXXX@XXXXX.COM ...




How to Duplicate CString's Default Data Type

by David Wilkinson » Fri, 10 Mar 2006 21:49:16 GMT

oseph M. Newcomer wrote:

[snip]

Joe:

That's very interesting. But this trickery is only needed for printf(),
etc, right? But surely with iostream the cast operator should be all
you need? But there is something that puzzles me (I posted the below in
a few days ago in another thread, but got no reply).

Consider (in VS 2003) the following code:

#include <iostream>

int main()
{
CStringA strA = "Testing A";
std::cout << strA << std::endl;
std::cout << (const char*)strA << std::endl;

CStringW strW = L"Testing W";
std::wcout << strW << std::endl;
std::wcout << (const wchar_t*)strW << std::endl;

return 0;
}

In VS2003 the output on my machine is

Testing A
Testing A
00A952C8
Testing W

even if you choose w_char_t to be a built-in type. Why are 8-bit and
16-bit characters different here?

David Wilkinson



How to Duplicate CString's Default Data Type

by jolest » Sat, 11 Mar 2006 00:51:12 GMT

> So I'm afraid I still don't get how this would

The whole point is, that the compiler DOES "pass a copy of the CString
object"... The only data in a CString object is a "m_pchData" pointer
to the beginning of the string...

So pushing a CString object onto the stack for a printf call is EXACTLY
the same as pushing a simple pointer-to-a-string onto the stack.

What you would need to do to duplicate the CString "printf
functionality" is:
1) Your class can not be a subclass of any other class **.
2) The ONLY variable in your class must be a single char pointer.
3) that pointer must always point to the string representation of the
object, which might not necessarily be the beginning of the allocated
memory.
4) any extra data you need to achieve your class's functionality must
be copied into extra bytes allocated in front of the memory allocated
for your string.

Example: Let's say your CPseudoString class requires an extra DWORD
for some reason and you want it to be able to printf the string
"Hello". It would need to allocate at least 4 bytes for an extra DWORD
variable, plus 5 bytes for the string, plus 1 byte for the null
termination => 10 bytes. The layout of data in the allocated block of
memory would be:

{byte 1 of DWORD}
{byte 2 of DWORD}
{byte 3 of DWORD}
{byte 4 of DWORD}
'H' <<-- your m_pchData variable points here
'e'
'l'
'l'
'o'
'\0'

Is that any clearer?

... Jolest

PS: ** This printf functionality is also why you can't easily subclass
a CString and why CString must be a root class... If you do try to
subclass it, the compiler adds an extra hidden pointer to the data in
instances of the the child class and passing an instance of the child
class to printf (without using the "(LPCTSTR)" casting operator) pushes
two pointers onto the stack, not one.



How to Duplicate CString's Default Data Type

by Joseph M. Newcomer » Sat, 11 Mar 2006 02:49:26 GMT

ertainly it is necessary for printf and similar methods.

I'd have to look at all the various overloads of the ostream to figure out why it doesn't
work in the case you show, but I've seen a lot of problems like this with the stream
stuff.
joe

On Fri, 10 Mar 2006 08:49:16 -0500, David Wilkinson < XXXX@XXXXX.COM > wrote:

Joseph M. Newcomer [MVP]
email: XXXX@XXXXX.COM
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm


How to Duplicate CString's Default Data Type

by Joseph M. Newcomer » Sat, 11 Mar 2006 02:52:16 GMT

If you have VS6 installed, you can also study the full CString code. It is vastly easier
to read in VS6. VS.NET actually implements the same thing, but by the time you work
through all the classes, it is fairly unintelligible.

I've played this game with data types for, well, about 38 years, having written a whole
lot of storage allocators in my career, and this technique is really fundamental to a lot
of storage allocation paradigms.
joe



Joseph M. Newcomer [MVP]
email: XXXX@XXXXX.COM
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm


Similar Threads

1. How to convert CString object to data type?

How can I convert CString from a EDIT control Dialog to data type for 
calculation?

thanks, 


2. how to convert CString object to data type

3. default data type for global variables

"qazmlp" < XXXX@XXXXX.COM > wrote in message
news: XXXX@XXXXX.COM ...
> This one compiles fine:
>
> // fileName.C
> someName = 2 ;
>
> int main()
> {
>   someName = someName + 2 ;
> }

It probably compiles because of the .c extension in file name. The .c
extension causes some compilers to behave like a C compiler. In C++ you
must always specify the type, which is a good practice anyway, even in
C.

> But, the following gives compilation error:
>
> int main()
> {
>   someName = 2 ;
>   someName = someName + 2 ;
> }
>
> Why? I would like to have the C++ standard word also about this.
> Also, what is the default data type assumed in the first case?

This is is really a C question, it is not valid C++ code. C defaults to
int when no type is specified.


4. C# data type keywords v. .Net data types - CSharp/C#

5. How to convert CString type to LPCSTR type

Hi,
    I have a CString type variable, and I need to convert it's to LPCSTR 
type ,what I can do!

example:
    CString m_strExecFile(_T("C:\\\\aa.exe"));
    next I want to call WinExec Command to execute this file
    LPCSTR lpcstr ;
    lpcstr = m_strExecFile.GetBuffer();
    WinExec(lpcstr,SW_SHOW);

    BUT the compilier tells me it can not convert w_char * to LPCSTR type!

Why?

ps. I use vs2005 program in UNICODE mode.

Any helpful ideas would be highly appreciated!

-joseph 


6. Default type-value for template parameter type

7. Derived CAsyncSocket sends duplicate data?

I'm at a loss here. I've looked at it, and it's probably something
simple, but can someone explain why my derived class sends duplicate
sets of data occasionally (IE 1 call to Send makes it received twice on
the other end)?

As far as I can tell, it only occurs when the system is under high CPU
load.

Thanks in advance.

Josh McFarlane

--------
>From Header File:
enum	SendStatus				//State of the Send Buffer
	{
		SEND_EMPTY,					//Empty Send Queue
		SEND_ACTIVE					//Sending currently in progress
	};
	CCriticalSection	ctSender;
	SendStatus			c_SendStatus;
	size_t				bytesSent;
	size_t				sendlength;
	std::vector<BYTE>	outBuffer;
-----------

>From CPP:

int		CInternalSocket::Send(const void* lpBuf, int nBufLen, int)
{
	//Need to add DWORD to buffer before sending
	DWORD TotalLength = nBufLen + sizeof(DWORD);
	DWORD Length = htonl(nBufLen);
	{
		CSingleLock lock(&ctSender);
		lock.Lock();
		switch (c_SendStatus)
		{
		case SEND_EMPTY:
			{
				bytesSent = 0;
				sendlength = TotalLength;
				outBuffer.resize(TotalLength, 0);
				BYTE* p = &outBuffer[0];
				memcpy(p, &Length, sizeof(DWORD));
				p += sizeof(DWORD);
				memcpy(p, lpBuf, nBufLen);
				c_SendStatus = SEND_ACTIVE;
				break;
			}

		case SEND_ACTIVE:
			{
				size_t oldSize = outBuffer.size();
				sendlength = oldSize + TotalLength;
				outBuffer.resize(sendlength, 0);
				BYTE* p = &outBuffer[oldSize];
				memcpy(p, &Length, sizeof(DWORD));
				p += sizeof(DWORD);
				memcpy(p, lpBuf, nBufLen);
				break;
			}

		}
	}
	OnSend(ERROR_SUCCESS);
	return nBufLen;
}

void	CInternalSocket::OnSend(int nErrorCode)
{
	CSingleLock lock(&ctSender);
	lock.Lock();
	if (nErrorCode != ERROR_SUCCESS)
	{
		//Handle initial socket errors here.
		if(pInterface)
		{
			pInterface->PostThreadMessage(UWM_SOCKET_SENDERROR, nErrorCode,
NULL);
		}
		return;
	}
	//Check buffer to make sure that it has data to be sent.
	size_t bytesToSend = sendlength - bytesSent;
	BYTE* p = &outBuffer[bytesSent];
	while(bytesToSend > 0)
	{ //Bytes to send
		int len = CAsyncSocket::Send(p, bytesToSend);
		if (len == SOCKET_ERROR)
		{ //Send Error
			int lastErr = ::GetLastError();
			if (WSAEWOULDBLOCK == lastErr || WSAENOTCONN == lastErr)
			{
				break;
			}
			else
			{
				if(pInterface)
				{
					pInterface->PostThreadMessage(UWM_SOCKET_SENDERROR, lastErr,
NULL);
				}
				break;
			}
		} //Send Error
		bytesToSend -= len;
		bytesSent += len;
		p += len;
	} //Bytes to Send
	if (bytesSent >= outBuffer.size())
	{
		bytesSent = 0;
		c_SendStatus = SEND_EMPTY;
	}
}

8. std::list Duplicates with Different Data