Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does CString::GetBuffer() with no size parameter do?

Tags:

c++

mfc

Perhaps I'm going insane, but I have tried every search combination I can think of, and I can't find a definition for CString::GetBuffer() with no parameters. Every reference I look up describes CString::GetBuffer( int ), where the int parameter passed in is the max buffer length. The definition in the header is for CSimpleStringT::GetBuffer(). That gave me the following link, which at least acknowledges the existence of the parameterless version, but offers no description of its behavior. https://msdn.microsoft.com/en-us/library/sddk80xf.aspx#csimplestringt__getbuffer

I'm looking at existing C++ (Visual Studio) code that I don't want to change if I don't have to, but I need to know the expected behavior of CString::GetBuffer(). I'd appreciate it if someone could explain it or point me to some documentation on it.

like image 580
Gregg H Avatar asked Sep 11 '18 19:09

Gregg H


3 Answers

Although the msdn documentation doesn't really say what GetBuffer without a parameter does, the MFC source code reveals the answer:

return( m_pszData );

So it just returns a pointer to the underlying character buffer. (It also checks to see if the internal data is shared and forks/copies it first).

The code is in atlsimpstr.h

Complete function:

PXSTR GetBuffer()
{
    CStringData* pData = GetData();
    if( pData->IsShared() )
    {
        Fork( pData->nDataLength );
    }

    return( m_pszData );
}
like image 169
edtheprogrammerguy Avatar answered Oct 21 '22 15:10

edtheprogrammerguy


tl;dr

Call CString::GetString().


This is asking the wrong question for the wrong reasons. Just to get it out of the way, here is the answer from the documentation:

Return Value
An PXSTR pointer to the object's (null-terminated) character buffer.

This is true for both overloads, with and without an explicit length argument. When calling the overload taking a length argument, the internal buffer may get resized to accommodate for increased storage requirements, prior to returning a pointer to that buffer.

From this comment, it becomes apparent, that the question is asking for the wrong thing altogether. To learn why, you need to understand what the purpose of the GetBuffer() family of class members is: To temporarily disable enforcement of CString's class invariants1 for modification, until establishing them again by calling one of the ReleaseBuffer() members. The primary use case for this is to interface with C code (like the Windows API).

The important information is:

  • GetBuffer() should only be called, if you plan to directly modify the contents of the stored character sequence.
  • Every call to GetBuffer() must be matched with a call to ReleaseBuffer(), before using any other CString class member2. Note in particular, that operator PCXSTR() and the destructor are class members.
  • As long as you follow that protocol, the controlled character sequence will always be null-terminated.

Given your actual use case (Log.Print("%s\n", myCstring.GetBuffer())), none of the previous really applies. Since you do not plan to actually modify the string contents, you should access the immutable CString interface (e.g. GetString() or operator PCXSTR()) instead. This requires const-correct function signatures (TCHAR const* vs. TCHAR*). Failing that, use a const_cast if you can ensure, that the callee will not mutate the buffer.

There are several benefits to this:

  • It is semantically correct. If all you want is a view into the character string, you do not need a pointer to a mutable buffer.
  • There are no superfluous copies of the contents. CString implements copy-on-write semantics. Requesting a mutable buffer necessitates copying the contents for shared instances, even if you are going to throw that copy away immediately after evaluating the current expression.
  • The immutable interface cannot fail. No exceptions are thrown when calling operator PXCSTR() or GetString().

1The relevant invariants are: 1 The controlled sequence of characters is always null-terminated. 2 GetLength() returns the count of characters in the controlled sequence, excluding the null terminator.

2It is only strictly required to call one of the ReleaseBuffer() implementations, if the contents were changed. This is often not immediately obvious from looking at the source code, so always calling ReleaseBuffer() is the safe option.

like image 3
IInspectable Avatar answered Oct 21 '22 15:10

IInspectable


Documentation is inconclusive. Looking at ATL sources available here (https://github.com/dblock/msiext/blob/d8898d0c84965622868b1763958b68e19fd49ba8/externals/WinDDK/7600.16385.1/inc/atl71/atlsimpstr.h - I do not claim to know if they are official or not) it looks like GetBuffer() without arguments returns the current buffer, cloning it before if it is shared.

On the other hand, GetBuffer(int) with size is going to check (through the call to PrepareWrite and possibly PrepareWrite2) if the current buffer size is greater than requested, and if it is not, it will allocate the new buffer - thus matching MSDN description.

On a side note, PrepareWrite seems to become quite creative in how it checks for two conditions:

PXSTR PrepareWrite( __in int nLength )
{
    CStringData* pOldData = GetData();
    int nShared = 1-pOldData->nRefs;  // nShared < 0 means true, >= 0 means false
    int nTooShort = pOldData->nAllocLength-nLength;  // nTooShort < 0 means true, >= 0 means false
    if( (nShared|nTooShort) < 0 )  // If either sign bit is set (i.e. either is less than zero), we need to copy data
    {
        PrepareWrite2( nLength );
    }

    return( m_pszData );
}
like image 1
SergeyA Avatar answered Oct 21 '22 16:10

SergeyA