Tuesday, March 08, 2005

 

Some notes on CString

1.
CString Management 及对应翻译 (简化版)

原始出处
http://www.pgh.net/~newcomer/cstring.htm
翻译出处
http://www.vccode.com/showthread.php?s=&threadid=5800

CString 对MFC Programmer 来说是不陌生。它比 std::basic_string 来的简单, 而且它自动支持UNICODE, 不像 STL或standard C++ , 需要用到wstring/wstringstream/wchar (UNICODE) 或string/stringstream/char (ASCII/MBCS) 来分辨和特别处理. 

问题是MSDN并没有完整的提供它的使用方法。很多人还是用一些比较复杂的函数来做简单的事。我在此会给一些建议于

Part 1
-------
- 字串合拼

CString country("Malaysia");
CString state("KL");
CString address = country + state;

- 数字转成字串

CString s;
s.Format(_T("The total is %d"), total);

- 字串转成数字

CString decimal = _T("4011");
int i = _ttoi(decimal));

因为_ttoi是UNICODE aware

- 几个 char *字串 转成 CString (按:此处CoolStar好像没有翻译)

So you have a char *, or a string. How do you create a CString. Here are some examples:

char * p = "This is a test"
or, in Unicode-aware applications

TCHAR * p = _T("This is a test")
or

LPTSTR p = _T("This is a test");
you can write any of the following:

CString s = "This is a test"; // 8-bit only
CString s = _T("This is a test"); // Unicode-aware
CString s("This is a test"); // 8-bit only
CSTring s(_T("This is a test"); // Unicode-aware
CString s = p;
CString s(p);
Any of these readily convert the constant string or the pointer to a CString value. Note that the characters assigned are always copied into the CString so that you can do something like

TCHAR * p = _T("Gray");
CString s(p);
p = _T("Cat");
s += p;

and be sure that the resulting string is "GrayCat".
There are several other methods for CString constructors, but we will not consider most of these here; you can read about them on your own.

- CString 字串转成 LPCTSTR 字串

CString s("Anthony Yio");
LPCTSTR p = s;
or
LPCTSTR p = s.operator LPCTSTR ( );

- CString 字串转成 char *字串 使用 GetBuffer

CString str(...);
LPTSTR pTemp = str.GetBuffer(1024);
str.ReleaseBuffer();
注意:不要改pTemp的字串,当你还没调用 s.ReleaseBuffer(); 函数过后 。因为那会改到 str的.

- CString 字串去LPTSTR.

CString strToBeConverted;
LPSTR pszTemp = (LPTSTR)(LPCTSTR)strToBeConverted;
注意:pszTemp 不能被改。除非你用strcpy来制做另一个变量。


Part 2
-------
- CString 字串转成 BSTR

CString s;
s = _T("Hello world") ;
BSTR b = s.AllocSysString()
::SysFreeString(b);


要记得Call 函数 ::SysFreeString(b), 不然会有Memory leak

- BSTR 字串转成 CString

完整函数代码。


CString convert(BSTR b)
{
CString s;
if(b == NULL)
return s; // empty for NULL BSTR
#ifdef UNICODE
s = b;
#else
LPSTR p = s.GetBuffer(SysStringLen(b) + 1);
::WideCharToMultiByte(CP_ACP, // ANSI Code Page
0, // no flags
b, // source widechar string
-1, // assume NUL-terminated
p, // target buffer
SysStringLen(b)+1, // target buffer length
NULL, // use system default char
NULL); // don''t care if default used
s.ReleaseBuffer();
#endif
return s;
}
ANSI 字串会比UNICODE 字串来的复杂。因为BSTR本身就是UNICODE 字串。 CString 有constructor 去接纳BSTR, 不过, 要注意NULL,因为CString 对BSTR 得NULL管理不敏感。所以代码要写成。
if(b == NULL)
return s; // empty for NULL BSTR
ANSI 也是MBCS. 所以要用到::WideCharToMultiByte 转移 BSTR 里的Variant (UNICODE) 字串 去MBCS.

- VARIANT 字串转成 CString

VARIANT vaData;

vaData = m_com.YourMethodHere();
ASSERT(vaData.vt == VT_BSTR);

CString strData(vaData.bstrVal);

或较完整的函数

CString VariantToString(VARIANT * va)
{
CString s;
switch(va->vt)
{ /* vt */
case VT_BSTR:
return CString(vaData->bstrVal);
case VT_BSTR | VT_BYREF:
return CString(*vaData->pbstrVal);
case VT_I4:
s.Format(_T("%d"), va->lVal);
return s;
case VT_I4 | VT_BYREF:
s.Format(_T("%d"), *va->plVal);
case VT_R8:
s.Format(_T("%f"), va->dblVal);
return s;
... remaining cases left as an Exercise For The Reader
default:
ASSERT(FALSE); // unknown VARIANT type (this ASSERT is optional)
return CString("");
} /* vt */
}

- CString 字串去 STRINGTABLE resources

CString s;
s.LoadString(IDS_WHATEVER);
或WIN32 style
CString s;
s.LoadString(IDS_WHATEVER);
CString t( MAKEINTRESOURCE(IDS_WHATEVER));

- CString 字串顺畅管理

代码1
CString s = _T("Hello") + _T("I am") + "saving memory space";
代码2
CString s += _T("hello");
s += _T("I");
s += _T("am");
s += _T("little");
s += _T("inefficient");
CString变量 的初缓冲是有几种. 如果是代码2, CString变量 的初缓冲会是比较大.因为compiler编译器 要保留CString变量伸展到最大的可能性。compiler编译器预算不到初缓冲要多大。

- Programmer 常犯的CString错误

CString str = _T("hello);
if(str == "hello")
...
这会crash.
应该是
CString str = _T("hello);
if(str.CompareNoCase("hello") == 0)
...
尽量用 CString 里的函数来做字串管理。不要用 ''='' operator。除非你清楚你在做什么。
因为CString 不type safe.它能接纳任何data type, 但是这很危险。
你能CString str = 1; CString 不管的。

2.
CString到char的转换问题再探

原作:dai2255

CString类功能强大,比STL的string类有过之无不及.新手使用CString时,都会被它强大
的功能所吸引.然而由于对它内部机制的不了解,新手在将CString向C的字符数组转换时
容易出现很多问题.因为CString已经重载了LPCTSTR运算符,所以CString类向const
char *转换时没有什么麻烦,如下所示:
char a[100];
CString str("aaaaaa");
strncpy(a,(LPCTSTR)str,sizeof(a));
或者如下:
strncpy(a,str,sizeof(a));
以上两种用法都是正确地.因为strncpy的第二个参数类型为const char *.所以编译器
会自动将CString类转换成const char *.很多人对LPCTSTR是什么东西迷惑不解,让我们
来看看:
1.LP表示长指针,在win16下有长指针(LP)和短指针(P)的区别,而在win32下是没有区别
的,都是32位.所以这里的LP和P是等价的.
2.C表示const
3.T是什么东西呢,我们知道TCHAR在采用UNICODE方式编译时是wchar_t,在普通时编译成char
那么就可以看出LPCTSTR(PCTSTR)在UINCODE时是const wchar_t *,PCWSTR,LPCWSTR,在
多字节字符模式时是const char *,PCSTR,LPCSTR.
接下来我们看在非UNICODE情况下,怎样将CString转换成char *,很多初学者都为了方便
采用如下方法:
(char *)(LPCSTR)str.这样对吗?我们首先来看一个例子:
CString str("aa");
strcpy((char *)(LPCTSTR)str,"aaaaaaaa");
cout<<(LPCTSTR)str<< endl;
在Debug下运行出现了异常,我们都知道CString类内部有自己的字符指针,指向一个已分
配的字符缓冲区.如果往里面写的字符数超出了缓冲区范围,当然会出现异常.但这个程
序在Release版本下不会出现问题.原来对CString类已经进行了优化.当需要分配的内存
小于64字节时,直接分配64字节的内存,以此类推,一般CString类字符缓冲区的大小为
64,128,256,512...这样是为了减少内存分配的次数,提高速度.
那有人就说我往里面写的字符数不超过它原来的字符数,不就不会出错了,比如
CString str("aaaaaaa");
strcpy((char *)(LPCTSTR)str,"aa");
cout<<(LPCTSTR)str<< endl;
这样看起来是没什么问题.我们再来看下面这个例子:
CString str("aaaaaaa");
strcpy((char *)(LPCTSTR)str,"aa");
cout<<(LPCTSTR)str<< endl;
cout<< str.GetLength()<< endl;
我们看到str的长度没有随之改变,继续为7而不是2.还有更严重的问题:
CString str("aaaaaaa");
CString str1 = str;
strcpy((char *)(LPCTSTR)str,"aa");
cout<<(LPCTSTR)str<< endl;
cout<<(LPCTSTR)str1<< endl;
按说我们只改变了str,str1应该没有改变呀,可是事实时他们都变成了"aa".难道str和
str1里面的字符指针指向的缓冲区是一个.我们在Effective C++里面得知,如果你的类
内部有包含指针,请为你的类写一个拷贝构造函数和赋值运算符.不要让两个对象内部的
指针指向同一区域,而应该重新分配内存.难道是微软犯了错?
原来这里还有一个"写时复制"和"引用计数"的概念.CString类的用途很广,这样有可能
在系统内部产生大量的CString临时对象.这时为了优化效率,就采用在系统软件内部广
泛使用的"写时复制"概念.即当从一个CString产生另一个CString并不复制它的字符缓
冲区内容,而只是将字符缓冲区的"引用计数"加1.当需要改写字符缓冲区内的内容时,才
分配内存,并复制内容.以后我会给出一个"写时复制"和"引用计数"的例子
我们回到主题上来,当我们需要将CString转换成char *时,我们应该怎么做呢?其时只是
麻烦一点,如下所示:
CString str("aaaaaaa");
strcpy(str.GetBuffer(10),"aa");
str.ReleaseBuffer();
当我们需要字符数组时调用GetBuffer(int n),其中n为我们需要的字符数组的长度.使
用完成后一定要马上调用ReleaseBuffer();
还有很重要的一点就是,在能使用const char *的地方,就不要使用char *。

3.
CString 在VC.Net下不能使用的问题

在微软的网站上找到答案:
http://support.microsoft.com/default.aspx?scid=kb;en-us;309801

好文章一篇,赶快收藏。
Microsoft Knowledge Base Article - 309801
PRB: Linking Errors When You Import CString-Derived Classes
The information in this article applies to:
Microsoft Visual C++ .NET (2002)

This article was previously published under Q309801
SYMPTOMS
When you build a Visual C++ .NET application that uses a CString-derived class from a DLL file, you may receive an error message similar to one of the following:

ClientProject error LNK2019: unresolved external symbol "__declspec(dllimport) public: __thiscall ATL::CStringT< char,class StrTraitMFC< char,class ATL::ChTraitsCRT< char>>>::CStringT< char,class StrTraitMFC< char,class ATL::ChTraitsCRT>>(char const *)" (__imp_??0?$CStringT@DV?$StrTraitMFC@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@PBD@Z) referenced in function _main
-or-



ClientProject error LNK2005: "public: __thiscall ATL::CStringT>>::~CStringT>>(void)" (??1?$CStringT@DV?$StrTraitMFC@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@QAE@XZ) already defined in Simple.obj
-or-



ClientProject fatal error LNK1169: one or more multiply defined symbols found
CAUSE
This behavior may occur if you use a class derived from CString that is also exported from a DLL.

In Microsoft Visual Studio .NET, the CString class has changed to a template class, as demonstrated by the following lines of code taken from afxstr.h: typedef ATL::CStringT< wchar_t, StrTraitMFC< wchar_t > > CStringW;
typedef ATL::CStringT< char, StrTraitMFC< char > > CStringA;
typedef ATL::CStringT< TCHAR, StrTraitMFC< TCHAR > > CString;

Visual Studio .NET automatically exports the appropriate template instantiation for the CStringT template class that you derive from. However, when you import the class, Visual Studio .NET sometimes does not correctly import that instantiation. This results in the linker error messages listed in the "Symptoms" section of this article.
RESOLUTION
To resolve this issue, explicitly import the template class for CStringT and CSimpleStringT in the precompiled header (stdafx.h) file, as follows: template class __declspec(dllimport) CStringT > >;
template class __declspec(dllimport) CSimpleStringT;

MORE INFORMATION
Steps to Reproduce the Problem
In Visual C++ .NET, create a default MFC DLL project.
On the Project menu, click Add New Item.
Name the file MyString.h. Select the Header File template, and then click Open.
Add the following code to MyString.h:#ifdef _USRDLL
#define IMPEXP __declspec(dllexport)
#else
#define IMPEXP __declspec(dllimport)
#endif

class IMPEXP CMyString: public CString
{
public:
CMyString();
virtual ~CMyString();

void MyMethod();
};

Add a new C++ source file to the project named MyString.cpp.
Add the following code to MyString.cpp:#include "StdAfx.h"
#include "mystring.h"

CMyString::CMyString(void) {}
CMyString::~CMyString(void) {}

void CMyString::MyMethod(void)
{
AfxMessageBox("foo is called");
}

Save and build the DLL project.
Create a new Win32 Project. In the wizard, change the setting under Application Settings to the Console Application type, and then click to select the MFC checkbox to add MFC support.
In the new application's main source file, include MyString.h from the DLL project. Verify that the correct full path or relative path is being used.
Before the main function, add a #pragma to link to the library for the DLL (again, be sure to use a correct full path or relative path), as follows:#pragma comment(lib, "..\\Debug\\TestDll.lib")

Add the following code in the else block of the main function:// TODO: code your application's behavior here.
CString strHello = "Hello";
CMyString* myStr = new CMyString();
myStr->MyMethod();

Build the project.

The build fails with the "LNK2019" error.
In the console application, add a new C++ source file named Simple.cpp.
Add the following code to Simple.cpp:#include "stdafx.h"
CString bar()
{
CString s = "Hi";
return s;
}

Build the project again.

You receive the "LNK2005" and "LNK1169" error messages.
Apply the solution provided in the "Resolution" section of this article.

The project should now build without errors.

4.
MFC的CString(VC6) 内存管理分析

By roscoe
From http://blog.csdn.net/roscoe/archive/2005/05/22/377949.aspx

CString 类是我们经常用到的类,所以有必要对它的内存管理模式分析一下.
CString 内存管理的演变过程如下:
VC5 单纯的使用new delete方法。 因为字符串操作需要频繁调整内存大小.而采用C++操作符 new 与 delete 是没有与realloc相应功能的。结果就是每一次的改变内存大小都需要额外 增加一次拷贝操作。 而 new 与delete 在实现中在进程堆中分配。频繁地在堆上进行小内存分配与释放 必然在堆上产生大量碎片。堆碎片过多直接影响了程序效率。 于是MFC在VC6版本对此进行了改进。 VC6 对于大于512字节的内存和DEBUG模式下,CString仍然使用 new 和 delete来操纵。 在Release模式下不大于512字节的内存分配操作采用了内存池管理。 并将之细分为 <=64, <=128, <=256, <=512 字节4个内存池管理。 这样在不大于512字节的情况下CString有了很好的效率。 但是传说中有解决一个BUG就会产生另外一个BUG的定律。 CString 显然也无法避免它。 于是在VC7中又改了。 VC7 恢复使用C 的内存管理调用方式。即采用 alloc, free, realloc. CString存在的问题 就是由于new与delete没有realloc重新调整内存大小的功能。之前产生的问题导致最终 还是采用了C的管理方法。
在VC6中为了解决CString小内存操纵的性能问题 MFC在Release版本下对于不大于512字节的内存分配采用的内存池管理来进行优化。其他情况下仍旧使用new 与delete.
Release版本下CString在处理不大于512Byte字串的内存时调用如下VC6 中CString 分配内存与释放内存调用次序如下
CString::AllocBuffer CFixedAlloc::Alloc CPlex::Create
CString::FreeData CFixedAlloc::Free=========================================================================================相关代码引用如下:FILE:MFC\SRC\STRCORE.CPPvoid CString::AllocBuffer(int nLen) //用来分配内存{ ... #ifndef _DEBUG // 在Release 版本并且是不大于512字节 if (nLen <= 64) { pData = (CStringData*)_afxAlloc64.Alloc(); pData->nAllocLength = 64; } else 分别为<= 1128, <=256 , <=512 { ... } else#endif // DEBUG 和Release下大于512的 { pData = (CStringData*) new BYTE[sizeof(CStringData) + (nLen+1)*sizeof(TCHAR)]; pData->nAllocLength = nLen; } ...}
void FASTCALL CString::FreeData(CStringData* pData) // 释放内存{#ifndef _DEBUG 在Release 版本并且是不大于512字节 int nLen = pData->nAllocLength; if (nLen == 64) // 根据内存大小分别调用管理器 _afxAlloc64.Free(pData); else if (nLen == 128) _afxAlloc128.Free(pData); else if (nLen == 256) _afxAlloc256.Free(pData); else if (nLen == 512) _afxAlloc512.Free(pData); else { ASSERT(nLen > 512); delete[] (BYTE*)pData; }#else // DEBUG 和Release下大于512的 delete[] (BYTE*)pData;#endif}
_afxAlloc[64,128,256,512] 是CFixedAlloc类的全局对象。
我们分析一下CFixedAlloc是整样进行内存池管理的它在使用中又产生了什么问题?
class CFixedAlloc //定义在 MFC\SRC\FIXALLOC.H文件中{public: CFixedAlloc(UINT nAllocSize, UINT nBlockSize = 64); UINT GetAllocSize() { return m_nAllocSize; }public: void* Alloc(); //分配 由CString调用 void Free(void* p); //释放 由CString调用 void FreeAll(); //释放所有 被析构函数调用public: ~CFixedAlloc();protected: struct CNode{//这个是用来实现一个单向链表 CNode* pNext; }; UINT m_nAllocSize; // 需要分配对象的大小仅由构造函数传入 UINT m_nBlockSize; // 预分配的数目即池的大小,由构造函数赋予,可知默认为64 CPlex* m_pBlocks; // 池的链表指针。CPlex对象含有一个CPlex* pNext指针对象, CNode* m_pNodeFree; // 被释放块链表的头指针,实际是应看做可用内存块链表 CRITICAL_SECTION m_protect;//临界区对象};
/* 在Alloc的实现中我们可以看到,当池中没有可用块的时候 调用 CPlex::Create建立一块 m_nAllocSize * m_nBlockSize的内存池 如果有的话则从m_pNodeFree中弹出一块来使用*/void* CFixedAlloc::Alloc(){ if (m_pNodeFree == NULL){ //如果没有可用的内存块就进行分配一个池 CPlex* pNewBlock = NULL; TRY{ // 分配内存块 默认是64个m_nAllocSize. pNewBlock = CPlex::Create(m_pBlocks, m_nBlockSize, m_nAllocSize); }CATCH_ALL(e){ ...异常 }END_CATCH_ALL // 下面的代码是将内存块压入m_pNodeFree链表中待用。 CNode* pNode = (CNode*)pNewBlock->data(); (BYTE*&)pNode += (m_nAllocSize * m_nBlockSize) - m_nAllocSize; for (int i = m_nBlockSize-1; i >= 0; i--, (BYTE*&)pNode -= m_nAllocSize) { pNode->pNext = m_pNodeFree; m_pNodeFree = pNode; } } // 这两句是弹出一块内存给调用者使用。 void* pNode = m_pNodeFree; m_pNodeFree = m_pNodeFree->pNext; ... return pNode;}/* 当调用者调用Free时,只是将这块内存重新压入m_pNodeFree链表中 并非释放,而是标志为可用块以待后用。*/void CFixedAlloc::Free(void* p){ if (p != NULL) { EnterCriticalSection(&m_protect); CNode* pNode = (CNode*)p; pNode->pNext = m_pNodeFree; m_pNodeFree = pNode; LeaveCriticalSection(&m_protect); }}void CFixedAlloc::FreeAll(){ EnterCriticalSection(&m_protect); m_pBlocks->FreeDataChain(); m_pBlocks = NULL; m_pNodeFree = NULL; LeaveCriticalSection(&m_protect);}/* 在析构函数中 调用FreeAll进行释放内存*/CFixedAlloc::~CFixedAlloc(){ FreeAll(); DeleteCriticalSection(&m_protect);}
/* MFC\INCLUDE\AFXPLEX_.H*/struct CPlex // warning variable length structure{ CPlex* pNext; void* data() { return this+1; } static CPlex* PASCAL Create(CPlex*& head, UINT nMax, UINT cbElement); void FreeDataChain(); // free this one and links};
/* MFC\SRC\PLEX.CPP*/CPlex* PASCAL CPlex::Create(CPlex*& pHead, UINT nMax, UINT cbElement){ CPlex* p = (CPlex*) new BYTE[sizeof(CPlex) + nMax * cbElement]; p->pNext = pHead; pHead = p; // 加入链表 return p;}void CPlex::FreeDataChain() // free this one and links{ CPlex* p = this; while (p != NULL){ BYTE* bytes = (BYTE*) p; CPlex* pNext = p->pNext; delete[] bytes; p = pNext; }}
============================================================================现在我们用一个实例来看一下在Release版本下的实际内存动作
以分配10000个 含有"abcdefghijklmnopqrstuvwxyz"串的CString数组
CString * strArray[10000];
for( int =0;i < 10000; i++ ) strArray[i] = new CString("abcdefghijklmnopqrstuvwxyz");
因为字符串小于64所以调用了_afxAlloc64::Alloc;
----------------------------------------------------------------_afxAlloc64在STRCORE.CPP中被定义如下:AFX_STATIC CFixedAlloc _afxAlloc64(ROUND4(65*sizeof(TCHAR)+sizeof(CStringData)));
在ANSI版本下 sizeof(TCHAR) = 1sizeof( CStrginData ) = 12;65*sizeof(TCHAR)+sizeof(CStringData) = 77;
ROUND4定义用下,将之圆整为4的倍数,#define ROUND(x,y) (((x)+(y-1))&~(y-1))#define ROUND4(x) ROUND(x, 4)所以_afxAlloc64(ROUND4(65*sizeof(TCHAR)+sizeof(CStringData))) 实际上宏展开最终为 extern CFixedAlloc _afxAlloc64( 80,64);----------------------------------------------------------------
在CPlex中分配池的大小sizeof(CPlex) + nMax * cbElement = 4+80*64 = 5124 BYTE.
因为10000不是64的整数倍 = 要分配157个池实际分配内存 = 157*5124 = 804468 BYTE = 804KB.
释放CString对象for( int =0;i < 10000; i++ ) delete strArray[i];
此时CString 的调用_afxAlloc64.Free.
由CFixedAlloc::Free的实现可知此时并没有真正释放内存,只是将这该块重新加入m_pNodeFree链表中待用.
因为CFixedAlloc释放内存操作是在析构函数调用,而_afxAlloc64是被定义为全局对象.它的析构函数要到程序退出才能被调用.
所以CFixedAlloc分配的内存在程序结束之前只会增加而不能回收.
而如果我们重新分配10000个 字符串>64 <=128的的CString对象时_afxAlloc64的内存占用依旧,而_afxAlloc128则重新分配了 157*(4+144*64) = 157*9220=1447540= 1.44754MB再释放它,此时内存占用则为 1.44754MB+804KB = 2.252008MB.
与使用char*对象做比较:
char* chArray[10000];分配 "abcdefghijklmnopqrstuvwxyz" 实际内存是 27*10000 = 270KB释放后内存即被回收再分配128字串 10000个 内存是 129*10000 = 1.29MB.释放后内存即被回收
结论:VC6中的CString采用内存池技术在改进小内存new与delete的性能与堆碎片问题后又产生了一个不是内存泄露的内存泄露。
其实VC5,VC6中CString产生的问题是因为教条地尊守C++应当采用new与delete来管理内存的规则造成的
最终在VC7中 CString仍旧回到使用C方法上.



<< Home

This page is powered by Blogger. Isn't yours?