ATL::CStringA和std::string之间转换的一些误区

        对于刚做windows下VC的开发同学,类型转换应该是一个令其很苦恼的问题。我刚写工作的时候,也为这类问题不停的在网上搜索转换方法。最近工作中遇到一个“神奇”的bug(一般“神奇”的问题往往是低级错误导致的),最后跟踪发现还是类型转换问题。(转载请指明出处)

         ATL::CStringA和std::string都可以“接受”\0,也就是说,在CStringA的对象的内容和std::string类型数据中可以包含多个\0,而不是最后一位是\0,。这个可能是很多人对它们认识的一个误区。

        贴一下测试的相关代码

// string.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include 

std::string RetCommonString()
{
    std::string str = "ABCDE\0FGH";
    return str;
}

std::string RetBreakString()
{
    std::string str = "";

    char charrayp[9];
    charrayp[0] = 'A';
    charrayp[1] = 'B';
    charrayp[2] = 'C';
    charrayp[3] = 'D';
    charrayp[4] = 'E';
    charrayp[5] = '\0';
    charrayp[6] = 'F';
    charrayp[7] = 'G';
    charrayp[8] = 'H';

    str.append( charrayp, 9);

    return str;
}

ATL::CStringA RetCommonCStringA()
{
    ATL::CStringA strA = "ABCDE\0FGH";
    return strA;
}

ATL::CStringA RetBreakCStringA()
{
    ATL::CStringA strA = "";

    char charrayp[9];
    charrayp[0] = 'A';
    charrayp[1] = 'B';
    charrayp[2] = 'C';
    charrayp[3] = 'D';
    charrayp[4] = 'E';
    charrayp[5] = '\0';
    charrayp[6] = 'F';
    charrayp[7] = 'G';
    charrayp[8] = 'H';

    LPSTR lpTmp = strA.GetBuffer(9);
    if ( NULL !=  lpTmp )
    {
        memcpy( (void*)lpTmp, charrayp, 9 );
    }
    strA.ReleaseBuffer(9);

    return strA;
}

int _tmain(int argc, _TCHAR* argv[])
{
    ATL::CStringA strCommonCStringA = RetCommonCStringA();
    ATL::CStringA strBreakCStringA = RetBreakCStringA();

    void* pstrCommonCStringA = &strCommonCStringA;
    void* pstrBreakCStringA = &strBreakCStringA;

    std::string strCommonString = RetCommonString();
    std::string strBreakString = RetBreakString();

    void* pstrCommonString = &strCommonString;
    void* pstrBreakString = &strBreakString;

    {
        int nstrBreakCStringA = strBreakCStringA.GetLength();
        int nstrCommonCStringA = strCommonCStringA.GetLength();
        
        if ( nstrBreakCStringA == nstrCommonCStringA )
        {
            // 这儿不会相等
            ATLASSERT(FALSE);
        }

        std::string::size_type lstrBreakStringLength = strBreakString.length();
        std::string::size_type lstrCommonStringLength = strCommonString.length();

        if ( lstrCommonStringLength ==  lstrBreakStringLength )
        {
            // 这儿不会相等
            ATLASSERT(FALSE);
        }
    }

    // std::string转CStringA的正确方法,但存在长度限制
    {
        std::string::size_type lstringlength = strBreakString.length();

        ATL::CStringA CStringAobj = "";
        LPSTR lpCStringAobj = CStringAobj.GetBuffer( (int)lstringlength );
        memcpy( (void*) lpCStringAobj, strBreakString.c_str(), lstringlength );
        CStringAobj.ReleaseBuffer((int)lstringlength);

        std::string::size_type lstrBreakStringLength = strBreakString.length();
        int nCStringobj = CStringAobj.GetLength();

        if ( lstrBreakStringLength != nCStringobj )
        {
            ATLASSERT(FALSE);
        }
        // 内容就不比较了,直接在调试时看内存
    }

    // std::string转CStringA的错误方法
    {

        // ERROR: CStringAObj = stringobj.c_str()
        {
            ATL::CStringA CStringAobj = strBreakString.c_str();

            std::string::size_type lstrBreakStringLength = strBreakString.length();
            int nCStringobj = CStringAobj.GetLength();

            if ( lstrBreakStringLength != nCStringobj )
            {
                ATLASSERT(FALSE);
            }
        }

        // ERROR: CStringobj = CStringA( stringobj.c_str() );
        {
            ATL::CStringA CStringAobj( strBreakString.c_str() ) ;

            std::string::size_type lstrBreakStringLength = strBreakString.length();
            int nCStringobj = CStringAobj.GetLength();

            if ( lstrBreakStringLength != nCStringobj )
            {
                ATLASSERT(FALSE);
            }
        }

        // ERROR: CStringAobj.Format( "%s", stringobj.c_str() );
        {
            ATL::CStringA CStringAobj;
            CStringAobj.Format( "%s", strBreakString.c_str() );

            std::string::size_type lstrBreakStringLength = strBreakString.length();
            int nCStringobj = CStringAobj.GetLength();

            if ( lstrBreakStringLength != nCStringobj )
            {
                ATLASSERT(FALSE);
            }
        }

    }

    // CStringA转std::string的正确方法
    {
        int nstrBreakCStringALength = strBreakCStringA.GetLength();
        LPSTR lpstrBreakCStringA = strBreakCStringA.GetBuffer( nstrBreakCStringALength );
        strBreakCStringA.ReleaseBuffer( nstrBreakCStringALength );

        std::string strobj = "";
        strobj.append( lpstrBreakCStringA, nstrBreakCStringALength );

        std::string::size_type lstrobjLength = strobj.length();
        int nCStringobj = strBreakCStringA.GetLength();

        if ( lstrobjLength != nCStringobj )
        {
            ATLASSERT(FALSE);
        }
    }

    // ERROR: stringobj = CStringAObj
    {
        std::string strobj = strBreakCStringA;
     
        std::string::size_type lstrobjLength = strobj.length();
        int nCStringobj = strBreakCStringA.GetLength();

        if ( lstrobjLength != nCStringobj )
        {
            ATLASSERT(FALSE);
        }
    }

    return 0;
}

        调试这个程序,我们查看一下相关内存。

        std::string类型数据strBreakString(内容为"ABCDE\0FGH") 的在内存中的数据如下图

        红线标志的09就是这个strBreakString的长度。

        std::string类型数据strCommonString(内容为"ABCDE") 的在内存中的数据如下图

        红线标志的05就是这个strCommonString的长度。

        查看一下strBreakString和strCommonString的来源,可以看出,给std::string类型数据用=赋值,如果内容中包含\0,则std::string类型数据只能接受\0之前的数据。所以strCommonString的数据只有\0之前的ABCDE。而使用std::string的append方法,将会将\0也赋值进去。

        我们再看一下ATL::CStringA对象在内存中的数据形式。

        ATL::CStringA类型数据strBreakCStringA (内容为"ABCDE\0FGH") 的在内存中的数据如下图

        红线标志的09就是这个strBreakCStringA 的长度。

        ATL::CStringA类型数据strCommonCStringA (内容为"ABCDE") 的在内存中的数据如下图

        红线标志的05就是这个strCommonCStringA 的长度。

        查看一下strBreakCStringA 和strCommonCStringA 的来源,可以看出,给ATL::CStringA类型数据用=赋值,如果内容中包含\0,则ATL::CStringA类型数据只能接受\0之前的数据。所以strCommonCStringA 的数据只有\0之前的ABCDE。而使用ATL::CStringA的GetBuffer、ReleaseBuffer等方法,再加上memcpy,可以将\0也赋值进去。

        如果方便,可以调试一下这个例子。可以发现网上一些std::string和ATL::CStringA之间的转换方法存在错误。如:网上有些方法是CStringAObj = stringobj.c_str(),或者CStringAobj.Format( "%s", stringobj.c_str() ),这些方法都会导致ATL::CStringA对象的内容可能被std::string中的存在的\0截断。而正确的方法大致如下框架

{
        std::string::size_type lstringlength = strBreakString.length();

        ATL::CStringA CStringAobj = "";
        LPSTR lpCStringAobj = CStringAobj.GetBuffer( (int)lstringlength );
        memcpy( (void*) lpCStringAobj, strBreakString.c_str(), lstringlength );
        CStringAobj.ReleaseBuffer((int)lstringlength);

        std::string::size_type lstrBreakStringLength = strBreakString.length();
        int nCStringobj = CStringAobj.GetLength();

        if ( lstrBreakStringLength != nCStringobj )
        {
            ATLASSERT(FALSE);
        }
        // 内容就不比较了,直接在调试时看内存
 }

  (转载请指明出处)

你可能感兴趣的:(疑难杂症)