写在前面
可以说CString在所有Visual C++ 程序员中占有很重要的地位,可以说是MFC Library里面最风光的一个类。据说当年还有很多程序员因为MFC中拥有好用的CString类,从其他开发工具跳槽到Visual C++来。我对CString的认识是从99年的Visual C++ 6.0中带的MFC 4.2版本中的CString类,那时候是小菜鸟,就是觉得CString好用。等变成老菜鸟的时候,发觉CString也不是那么完美的……
今天说的CString是MFC 9.0 中的CString类。照比以前MFC4.2版本的CString,功能上、性能上都改进了许多。最大的特点是自MFC7.0起CString类已经变成了一个模板类。
获取源代码
需要安装Microsoft Visual Studio 2005或者2008 ,并安装了Visual C++ 的组件。以Visual Studio2008为例,其源代码就在:\Microsoft Visual Studio 9.0\VC\atlmfc\include\ 路径下。其主要代码在cstringt.h文件和cstringt.inl 两个文件中。cstringt.h的内容是CString类的声明和实现,cstringt.inl文件包含了几个CString类常用的方法的实现,为了提高性能,这些方法被声明成了内联函数(inline)。
为什么把代码写在了cstringt.h文件中,而不是写在cstringt.cpp文件中呢?这还是要追溯到Visual C++ 6.0。在Visual C++ 6.0 以前(包括VC6),其自带的编译器MS C++(也就是cl.exe)是不支持将模板类的声明和实现分开写在.h和.cpp文件里的。所以整个的ATL库源代码都是写在.h的头文件里的。这个习俗就一直保留了下来。
不得不说的CStringData 结构
CStringData结构体是用来保存CString对象中字符串数据的结构体。也就是说,这个CStringData才是真正管理字符串缓冲区的对象。源代码在atlsimpstr.h文件中。其声明如下:
struct CStringData
1struct CStringData
2{
3 IAtlStringMgr* pStringMgr; // String manager for this CStringData
4 int nDataLength; // Length of currently used data in XCHARs (not including terminating null)
5 int nAllocLength; // Length of allocated data in XCHARs (not including terminating null)
6 long nRefs; // Reference count: negative == locked
7 // XCHAR data[nAllocLength+1] // A CStringData is always followed in memory by the actual array of character data
8
9 void* data() throw()
10 {
11 return (this+1);
12 }
13
14 void AddRef() throw()
15 {
16 ATLASSERT(nRefs > 0);
17 _AtlInterlockedIncrement(&nRefs);
18 }
19 bool IsLocked() const throw()
20 {
21 return nRefs < 0;
22 }
23 bool IsShared() const throw()
24 {
25 return( nRefs > 1 );
26 }
27 void Lock() throw()
28 {
29 ATLASSERT( nRefs <= 1 );
30 nRefs--; // Locked buffers can't be shared, so no interlocked operation necessary
31 if( nRefs == 0 )
32 {
33 nRefs = -1;
34 }
35 }
36 void Release() throw()
37 {
38 ATLASSERT( nRefs != 0 );
39
40 if( _AtlInterlockedDecrement( &nRefs ) <= 0 )
41 {
42 pStringMgr->Free( this );
43 }
44 }
45 void Unlock() throw()
46 {
47 ATLASSERT( IsLocked() );
48
49 if(IsLocked())
50 {
51 nRefs++; // Locked buffers can't be shared, so no interlocked operation necessary
52 if( nRefs == 0 )
53 {
54 nRefs = 1;
55 }
56 }
57 }
58};
59
如果您奇怪结构体中为什么还有方法?这个…… 简单的说一下,其实class 和 struct 的区别不大,只是默认情况下class的访问和继承权限都是私有的(private),struct的访问和集成权限都是共有的(public)。能不能包含函数,并不是class和struct的重要区别之一。他们两个关键字都是允许同时包含变量和方法的。
CStringData对象中首先包含了一个指向IAtlStringMgr对象的指针,这个指针所指向对象的主要作用是对字符串缓冲区进行内存管理。而后跟随着两个变量nDataLength 和nAllocLength 一个表示实际字符串的长度,另一个表示申请内存空间的长度。
再粗略的扫视一下CStringData的代码,有新发现了吗? 反正我看到了int nRef; AddRef()、Release()。 这是啥?了解COM技术的人都知道,这是COM技术最最根本最最基础的接口IUnknown 的成员。虽然这里CStringData没有显式的继承IUnknown接口,但实际上也是采用了引用计数的方式进行管理的。nRef的在AddRef()进行递增和Release()进行递减操作是通过_AtlInterlockedIncrement 和 _AtlInterlockedDecrement 这对儿进行的。在互斥区中访问nRef 目的是为了保证CStringData是线程安全的。不会致使nRef产生脏数据。Lock/UnLock这对儿方法,可以在单一使用者(nRef = 1)的情况下,标记字符串缓冲区是否被锁定。以便使用者可以通过独占的方式访问字符串。不过很遗憾,这两个方法并不是线程安全的。有点儿靠不住的说。
Data() 方法可以返回实际的字符串缓冲区首地址。return (this+1); this代表了指向当前CStringData对象内存首地址的指针,this + 1 就是指向在内存中紧跟CStringData对象的首地址。这里的1不是代表数值1,而是代表了1个CStringData对象。啊?为什么?答案请参照谭浩强的《C语言程序设计》有关指针操作的部分。
字符串缓冲区的管理
上面提到,在CStringData类中,包含了一个指向IAtlStringMgr接口的指针,这个IAtlStringMgr接口定义了字符串缓冲区管理的基本方法,成员如下:
Allocate 申请新的内存缓冲区
Clone 拷贝一个当前的字符串缓冲区内存管理对象。当然,所管理的内存缓冲区也被同时复制了。
Free 释放已申请的字符串缓冲区
GetNilString 清空字符串缓冲区,用’\0’填充
Reallocate 重新申请字符串缓冲区
对于这个接口,ATL库带有默认的实现,实现代码在atlstr.h文件中。有一个类CAtlStringMgr继承了IAtlStringMgr接口。代码如下:
class CAtlStringMgr
1class CAtlStringMgr :
2 public IAtlStringMgr
3{
4public:
5 CAtlStringMgr( _In_opt_ IAtlMemMgr* pMemMgr = NULL ) throw() :
6 m_pMemMgr( pMemMgr )
7 {
8 m_nil.SetManager( this );
9 }
10 virtual ~CAtlStringMgr() throw()
11 {
12 }
13
14 void SetMemoryManager( __reserved IAtlMemMgr* pMemMgr ) throw()
15 {
16 ATLASSUME( m_pMemMgr == NULL );
17 m_pMemMgr = pMemMgr;
18 }
19
20// IAtlStringMgr
21public:
22 _Ret_opt_bytecap_x_(sizeof(CStringData) + nChars*nCharSize) virtual CStringData* Allocate( _In_ int nChars, _In_ int nCharSize ) throw()
23 {
24 size_t nTotalSize;
25 CStringData* pData;
26 size_t nDataBytes;
27
28 nChars = AtlAlignUp( nChars + 1, 8 ); // Prevent excessive reallocation. The heap will usually round up anyway.
29
30 if( FAILED(::ATL::AtlMultiply(&nDataBytes, static_cast<size_t>(nChars), static_cast<size_t>(nCharSize))) ||
31 FAILED(::ATL::AtlAdd(&nTotalSize, static_cast<size_t>(sizeof( CStringData )), nDataBytes)))
32 {
33 return NULL;
34 }
35 pData = static_cast< CStringData* >( m_pMemMgr->Allocate( nTotalSize ) );
36 if( pData == NULL )
37 {
38 return( NULL );
39 }
40 pData->pStringMgr = this;
41 pData->nRefs = 1;
42 pData->nAllocLength = nChars - 1;
43 pData->nDataLength = 0;
44
45 return( pData );
46 }
47 virtual void Free( _Inout_ CStringData* pData ) throw()
48 {
49 ATLASSERT( pData->pStringMgr == this );
50 m_pMemMgr->Free( pData );
51 }
52 _Ret_opt_bytecap_x_(sizeof(CStringData) + nChars*nCharSize) virtual CStringData* Reallocate( _Inout_ CStringData* pData, _In_ int nChars, _In_ int nCharSize ) throw()
53 {
54 CStringData* pNewData;
55 ULONG nTotalSize;
56 ULONG nDataBytes;
57
58 ATLASSERT( pData->pStringMgr == this );
59 nChars = AtlAlignUp( nChars+1, 8 ); // Prevent excessive reallocation. The heap will usually round up anyway.
60
61 if( FAILED(::ATL::AtlMultiply(&nDataBytes, static_cast<ULONG>(nChars), static_cast<ULONG>(nCharSize))) ||
62 FAILED(::ATL::AtlAdd(&nTotalSize, static_cast<ULONG>(sizeof( CStringData )), nDataBytes)))
63 {
64 return NULL;
65 }
66 pNewData = static_cast< CStringData* >( m_pMemMgr->Reallocate( pData, nTotalSize ) );
67 if( pNewData == NULL )
68 {
69 return NULL;
70 }
71 pNewData->nAllocLength = nChars - 1;
72
73 return pNewData;
74 }
75 virtual CStringData* GetNilString() throw()
76 {
77 m_nil.AddRef();
78 return &m_nil;
79 }
80 virtual IAtlStringMgr* Clone() throw()
81 {
82 return this;
83 }
84
85protected:
86 IAtlMemMgr* m_pMemMgr;
87 CNilStringData m_nil;
88};
89
我们先从Allocate() 说起。nChars = AtlAlignUp( nChars+1, 8 ); 这行代码调用了ATL内存管理的一个AtlAlignUp函数。这个函数的原型在atlmem.h文件中,其作用是将给定的长度(N n)按照规定的布局(ULONG nAlign)凑齐对应的字节,请看:
template< typename N >
inline N WINAPI AtlAlignUp( N n, ULONG nAlign ) throw()
{
return( N( (n+(nAlign-1))&~(N( nAlign )-1) ) );
}
这行代码要是换我写,我就直接让n+nAlign 再除以nAlign取商了。但是MS的工程师没有那么做,而是通过位运算实现。这无疑提高了这个模板函数的运算性能。快,就一个字。
而后,函数中使用了AtlMultply和AtlAdd方法进行了要申请的总字节数的计算。使用这两个方法进行乘、加运算,是为了避免运算结果超界带来不必要的麻烦。把AtlAdd摆出来看看:
AtlAdd
1template<>
2class AtlLimits<int _ATL_W64>
3{
4public:
5 static const int _Min=INT_MIN;
6 static const int _Max=INT_MAX;
7};
8/**//* generic version */
9template<typename T>
10inline HRESULT AtlAdd(T* ptResult, T tLeft, T tRight)
11{
12 if(::ATL::AtlLimits<T>::_Max-tLeft < tRight)
13 {
14 return E_INVALIDARG;
15 }
16 *ptResult= tLeft + tRight;
17 return S_OK;
18}
19
AtlMultply的代码也大同小异,如想更深入了解,可在atlalloc.h文件中看到函数的原型。有意思的是这个AtlMultply函数还曾经有一个bug,请参考http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=119531 意思是说,原始版本中如果tLeft参数传入0,为了避免出现除零的情况,直接返回S_OK,但返回前,没有给ptResult赋值,以致出现野指针的情况。呵呵,有兴趣的可以通过上面的链接了解到更多的信息,反正我安装的Visual Studio 2008 中的ATL代码已经修正了这个问题(多了一行 *ptResult = 0; 搞定!)。
在计算好实际要申请的字节后,使用位于atlmem.h文件中的CCRTHeap类(这个类实现了IAtlMemMgr接口)的Allocate()方法申请了存放字符串的内存。代码如下:
virtual void* Allocate( size_t nBytes ) throw()
{
return( malloc( nBytes ) );
}
看到malloc() 倍感亲切呀,哈哈。我也总算是一杆子杵到底了。看明白了上面的内容,对于CAtlStringMgr的Free、Reallocate就一通百通了,到了最后,就是依靠调用CRT的Free和Reallocate实现的。这里不再赘述。
根据CAtlStringMgr的内存管理代码,我们了解到以下内容:
1) 申请字符串缓冲区空间时,实际是以8个字符进行了对齐的。例如申请50个字符的空间时,实际申请的内存会是56个字符的空间;
2) 对缓冲区内存的管理最底层是通过CRT的内存管理函数实现的。这样一来申请内存就要遵照CRT定义的阀值。记得《Visual C++技术内
幕》上写的CRT的阀值是480个字节,不知道当前版本的CRT阀值有没有改变;
3) 申请的内存大小应该是CStringData结构体的大小 + 字符个数以8对气候 * 字符所占空间的字节数;
ChTraitsBase 和 ChTraitsCRT
ChTraitsBase
1ChTraitsBase :
2template< typename BaseType = char >
3class ChTraitsBase
4{
5public:
6 typedef char XCHAR;
7 typedef LPSTR PXSTR;
8 typedef LPCSTR PCXSTR;
9 typedef wchar_t YCHAR;
10 typedef LPWSTR PYSTR;
11 typedef LPCWSTR PCYSTR;
12};
13
这个类中没有任何的成员变量和成员函数,而是定义了一些数据类型,简单的typedef就不用多说了吧。
ChTraitsCRT 继承了ChTraitsBase ,并实现了n多个静态的工具函数,包括判断字符是否是数字的、字符是否是空格的、以及字符串查找的、字符串比较的、格式化字符串的…… 这些静态方法,构成了CString类的字符串操作函数。这个chTraitsCRT类在cstringt.h文件中提供了两个版本,一个版本是char类型的,另一个是wchar_t类型的,也就是说CString类可以分别处理ansi字符和unicode字符。虽然有点儿长,但还是值得把代码贴一下,因为使用者比较看重的字符串操作方法的实现全都在这里了:
这个类中没有任何的成员变量和成员函数,而是定义了一些数据类型,简单的typedef就不用多说了吧。
ChTraitsCRT 继承了ChTraitsBase ,并实现了n多个静态的工具函数,包括判断字符是否是数字的、字符是否是空格的、以及字符串查找的、字符串比较的、格式化字符串的…… 这些静态方法,构成了CString类的字符串操作函数。这个chTraitsCRT类在cstringt.h文件中提供了两个版本,一个版本是char类型的,另一个是wchar_t类型的,也就是说CString类可以分别处理ansi字符和unicode字符。虽然有点儿长,但还是值得把代码贴一下,因为使用者比较看重的字符串操作方法的实现全都在这里了:
class ChTraitsCRT
1template< typename _CharType = char >
2class ChTraitsCRT :
3 public ChTraitsBase< _CharType >
4{
5public:
6 static char* __cdecl CharNext( _In_z_ const char* p ) throw()
7 {
8 return reinterpret_cast< char* >( _mbsinc( reinterpret_cast< const unsigned char* >( p ) ) );
9 }
10
11 static int __cdecl IsDigit( _In_ char ch ) throw()
12 {
13 return _ismbcdigit( ch );
14 }
15
16 static int __cdecl IsSpace( _In_ char ch ) throw()
17 {
18 return _ismbcspace( ch );
19 }
20
21 static int __cdecl StringCompare( _In_z_ LPCSTR pszA, _In_z_ LPCSTR pszB ) throw()
22 {
23 return _mbscmp( reinterpret_cast< const unsigned char* >( pszA ), reinterpret_cast< const unsigned char* >( pszB ) );
24 }
25
26 static int __cdecl StringCompareIgnore( _In_z_ LPCSTR pszA, _In_z_ LPCSTR pszB ) throw()
27 {
28 return _mbsicmp( reinterpret_cast< const unsigned char* >( pszA ), reinterpret_cast< const unsigned char* >( pszB ) );
29 }
30
31 static int __cdecl StringCollate( _In_z_ LPCSTR pszA, _In_z_ LPCSTR pszB ) throw()
32 {
33 return _mbscoll( reinterpret_cast< const unsigned char* >( pszA ), reinterpret_cast< const unsigned char* >( pszB ) );
34 }
35
36 static int __cdecl StringCollateIgnore( _In_z_ LPCSTR pszA, _In_z_ LPCSTR pszB ) throw()
37 {
38 return _mbsicoll( reinterpret_cast< const unsigned char* >( pszA ), reinterpret_cast< const unsigned char* >( pszB ) );
39 }
40
41 static LPCSTR __cdecl StringFindString( _In_z_ LPCSTR pszBlock, _In_z_ LPCSTR pszMatch ) throw()
42 {
43 return reinterpret_cast< LPCSTR >( _mbsstr( reinterpret_cast< const unsigned char* >( pszBlock ),
44 reinterpret_cast< const unsigned char* >( pszMatch ) ) );
45 }
46
47 static LPSTR __cdecl StringFindString( _In_z_ LPSTR pszBlock, _In_z_ LPCSTR pszMatch ) throw()
48 {
49 return( const_cast< LPSTR >( StringFindString( const_cast< LPCSTR >( pszBlock ), pszMatch ) ) );
50 }
51
52 static LPCSTR __cdecl StringFindChar( _In_z_ LPCSTR pszBlock, _In_ char chMatch ) throw()
53 {
54 return reinterpret_cast< LPCSTR >( _mbschr( reinterpret_cast< const unsigned char* >( pszBlock ), (unsigned char)chMatch ) );
55 }
56
57 static LPCSTR __cdecl StringFindCharRev( _In_z_ LPCSTR psz, _In_ char ch ) throw()
58 {
59 return reinterpret_cast< LPCSTR >( _mbsrchr( reinterpret_cast< const unsigned char* >( psz ), (unsigned char)ch ) );
60 }
61
62 static LPCSTR __cdecl StringScanSet( _In_z_ LPCSTR pszBlock, _In_z_ LPCSTR pszMatch ) throw()
63 {
64 return reinterpret_cast< LPCSTR >( _mbspbrk( reinterpret_cast< const unsigned char* >( pszBlock ),
65 reinterpret_cast< const unsigned char* >( pszMatch ) ) );
66 }
67
68 static int __cdecl StringSpanIncluding( _In_z_ LPCSTR pszBlock, _In_z_ LPCSTR pszSet ) throw()
69 {
70 return (int)_mbsspn( reinterpret_cast< const unsigned char* >( pszBlock ), reinterpret_cast< const unsigned char* >( pszSet ) );
71 }
72
73 static int __cdecl StringSpanExcluding( _In_z_ LPCSTR pszBlock, _In_z_ LPCSTR pszSet ) throw()
74 {
75 return (int)_mbscspn( reinterpret_cast< const unsigned char* >( pszBlock ), reinterpret_cast< const unsigned char* >( pszSet ) );
76 }
77
78 _ATL_INSECURE_DEPRECATE("You must pass an output size to ChTraitsCRT::StringUppercase")
79 static LPSTR __cdecl StringUppercase( _Inout_ LPSTR psz ) throw()
80 {
81#pragma warning (push)
82#pragma warning(disable : 4996)
83 return reinterpret_cast< LPSTR >( _mbsupr( reinterpret_cast< unsigned char* >( psz ) ) );
84#pragma warning (pop)
85 }
86
87 _ATL_INSECURE_DEPRECATE("You must pass an output size to ChTraitsCRT::StringLowercase")
88 static LPSTR __cdecl StringLowercase( _Inout_ LPSTR psz ) throw()
89 {
90#pragma warning (push)
91#pragma warning(disable : 4996)
92 return reinterpret_cast< LPSTR >( _mbslwr( reinterpret_cast< unsigned char* >( psz ) ) );
93#pragma warning (pop)
94 }
95
96 static LPSTR __cdecl StringUppercase( _Inout_cap_(size) LPSTR psz, _In_ size_t size ) throw()
97 {
98 Checked::mbsupr_s(reinterpret_cast< unsigned char* >( psz ), size);
99 return psz;
100 }
101
102 static LPSTR __cdecl StringLowercase( _Inout_cap_(size) LPSTR psz, _In_ size_t size ) throw()
103 {
104 Checked::mbslwr_s( reinterpret_cast< unsigned char* >( psz ), size );
105 return psz;
106 }
107
108 static LPSTR __cdecl StringReverse( _Inout_ LPSTR psz ) throw()
109 {
110 return reinterpret_cast< LPSTR >( _mbsrev( reinterpret_cast< unsigned char* >( psz ) ) );
111 }
112
113 static int __cdecl GetFormattedLength( _In_ _Printf_format_string_ LPCSTR pszFormat, va_list args ) throw()
114 {
115 return _vscprintf( pszFormat, args );
116 }
117
118 _ATL_INSECURE_DEPRECATE("You must pass an output size to ChTraitsCRT::Format")
119 static int __cdecl Format( _Out_ LPSTR pszBuffer, _In_ _Printf_format_string_ LPCSTR pszFormat, va_list args ) throw()
120 {
121 #pragma warning (push)
122 #pragma warning(disable : 4996)
123 return vsprintf( pszBuffer, pszFormat, args );
124 #pragma warning (pop)
125
126 }
127 static int __cdecl Format
128 ( _Out_cap_post_count_(nlength, return) LPSTR pszBuffer, _In_ size_t nlength, _In_ _Printf_format_string_ LPCSTR pszFormat, va_list args ) throw()
129 {
130 return vsprintf_s( pszBuffer, nlength, pszFormat, args );
131 }
132
133 static int __cdecl GetBaseTypeLength( _In_z_ LPCSTR pszSrc ) throw()
134 {
135 // Returns required buffer length in XCHARs
136 return int( strlen( pszSrc ) );
137 }
138
139 static int __cdecl GetBaseTypeLength( _In_count_(nLength) LPCSTR pszSrc, int nLength ) throw()
140 {
141 (void)pszSrc;
142 // Returns required buffer length in XCHARs
143 return nLength;
144 }
145
146 static int __cdecl GetBaseTypeLength( _In_z_ LPCWSTR pszSource ) throw()
147 {
148 // Returns required buffer length in XCHARs
149 return ::WideCharToMultiByte( _AtlGetConversionACP(), 0, pszSource, -1, NULL, 0, NULL, NULL )-1;
150 }
151
152 static int __cdecl GetBaseTypeLength( _In_count_(nLength) LPCWSTR pszSource, int nLength ) throw()
153 {
154 // Returns required buffer length in XCHARs
155 return ::WideCharToMultiByte( _AtlGetConversionACP(), 0, pszSource, nLength, NULL, 0, NULL, NULL );
156 }
157
158 static void __cdecl ConvertToBaseType( _Out_cap_(nDestLength) LPSTR pszDest, _In_ int nDestLength,
159 _In_z_ LPCSTR pszSrc, int nSrcLength = -1 ) throw()
160 {
161 if (nSrcLength == -1) { nSrcLength=1 + GetBaseTypeLength(pszSrc); }
162 // nLen is in XCHARs
163 Checked::memcpy_s( pszDest, nDestLength*sizeof( char ),
164 pszSrc, nSrcLength*sizeof( char ) );
165 }
166
167 static void __cdecl ConvertToBaseType( _Out_cap_(nDestLength) LPSTR pszDest, _In_ int nDestLength,
168 _In_z_ LPCWSTR pszSrc, _In_ int nSrcLength = -1) throw()
169 {
170 // nLen is in XCHARs
171 ::WideCharToMultiByte( _AtlGetConversionACP(), 0, pszSrc, nSrcLength, pszDest, nDestLength, NULL, NULL );
172 }
173
174 _ATL_INSECURE_DEPRECATE("You must pass an output size to ChTraitsCRT::ConvertToOem")
175 static void ConvertToOem(_Inout_ _CharType* pstrString) throw()
176 {
177 BOOL fSuccess=::CharToOemA(pstrString, pstrString);
178 // old version can't report error
179 ATLASSERT(fSuccess);
180 }
181
182 _ATL_INSECURE_DEPRECATE("You must pass an output size to ChTraitsCRT::ConvertToAnsi")
183 static void ConvertToAnsi(_Inout_ _CharType* pstrString) throw()
184 {
185 BOOL fSuccess=::OemToCharA(pstrString, pstrString);
186 // old version can't report error
187 ATLASSERT(fSuccess);
188 }
189
190 static void ConvertToOem(_Inout_cap_(size) _CharType* pstrString, _In_ size_t size)
191 {
192 if(size>UINT_MAX)
193 {
194 // API only allows DWORD size
195 AtlThrow(E_INVALIDARG);
196 }
197 DWORD dwSize=static_cast<DWORD>(size);
198 BOOL fSuccess=::CharToOemBuffA(pstrString, pstrString, dwSize);
199 if(!fSuccess)
200 {
201 AtlThrowLastWin32();
202 }
203 }
204
205 static void ConvertToAnsi(_Inout_cap_(size) _CharType* pstrString, _In_ size_t size)
206 {
207 if(size>UINT_MAX)
208 {
209 // API only allows DWORD size
210 AtlThrow(E_INVALIDARG);
211 }
212 DWORD dwSize=static_cast<DWORD>(size);
213 BOOL fSuccess=::OemToCharBuffA(pstrString, pstrString, dwSize);
214 if(!fSuccess)
215 {
216 AtlThrowLastWin32();
217 }
218 }
219
220 static void __cdecl FloodCharacters( _In_ char ch, _In_ int nLength, _Out_capcount_(nLength) char* pch ) throw()
221 {
222 // nLength is in XCHARs
223 memset( pch, ch, nLength );
224 }
225
226 static BSTR __cdecl AllocSysString( _In_count_(nDataLength) const char* pchData, int nDataLength ) throw()
227 {
228 int nLen = ::MultiByteToWideChar( _AtlGetConversionACP(), 0, pchData, nDataLength,
229 NULL, NULL );
230 BSTR bstr = ::SysAllocStringLen( NULL, nLen );
231 if( bstr != NULL )
232 {
233 ::MultiByteToWideChar( _AtlGetConversionACP(), 0, pchData, nDataLength,
234 bstr, nLen );
235 }
236
237 return bstr;
238 }
239
240 static BOOL __cdecl ReAllocSysString( _In_count_(nDataLength) const char* pchData, _Out_ BSTR* pbstr, _In_ int nDataLength ) throw()
241 {
242 int nLen = ::MultiByteToWideChar( _AtlGetConversionACP(), 0, pchData, nDataLength, NULL, NULL );
243 BOOL bSuccess = ::SysReAllocStringLen( pbstr, NULL, nLen );
244 if( bSuccess )
245 {
246 ::MultiByteToWideChar( _AtlGetConversionACP(), 0, pchData, nDataLength, *pbstr, nLen );
247 }
248
249 return bSuccess;
250 }
251
252 static DWORD __cdecl _AFX_FUNCNAME(FormatMessage)( _In_ DWORD dwFlags, LPCVOID pSource,
253 _In_ DWORD dwMessageID, _In_ DWORD dwLanguageID, _Out_cap_(nSize) LPSTR pszBuffer,
254 DWORD nSize, va_list* pArguments ) throw()
255 {
256 return ::FormatMessageA( dwFlags, pSource, dwMessageID, dwLanguageID,
257 pszBuffer, nSize, pArguments );
258 }
259
260#if defined(_AFX)
261 static DWORD __cdecl FormatMessage( _In_ DWORD dwFlags, LPCVOID pSource,
262 _In_ DWORD dwMessageID, _In_ DWORD dwLanguageID, _Out_cap_(nSize) LPSTR pszBuffer,
263 DWORD nSize, va_list* pArguments ) throw()
264 {
265 return _AFX_FUNCNAME(FormatMessage)(dwFlags, pSource, dwMessageID, dwLanguageID, pszBuffer, nSize, pArguments);
266 }
267#endif
268
269 static int __cdecl SafeStringLen( _In_opt_z_ LPCSTR psz ) throw()
270 {
271 // returns length in bytes
272 return (psz != NULL) ? int( strlen( psz ) ) : 0;
273 }
274
275 static int __cdecl SafeStringLen( _In_opt_z_ LPCWSTR psz ) throw()
276 {
277 // returns length in wchar_ts
278 return (psz != NULL) ? int( wcslen( psz ) ) : 0;
279 }
280
281 static int __cdecl GetCharLen( _In_z_ const wchar_t* pch ) throw()
282 {
283 (void)pch;
284 // returns char length
285 return 1;
286 }
287
288 static int __cdecl GetCharLen( _In_z_ const char* pch ) throw()
289 {
290 // returns char length
291 return int( _mbclen( reinterpret_cast< const unsigned char* >( pch ) ) );
292 }
293
294 static DWORD __cdecl GetEnvironmentVariable( _In_z_ LPCSTR pszVar,
295 _Out_opt_cap_(dwSize) LPSTR pszBuffer, _In_ DWORD dwSize ) throw()
296 {
297 return ::GetEnvironmentVariableA( pszVar, pszBuffer, dwSize );
298 }
299};
300
301// specialization for wchar_t
302template<>
303class ChTraitsCRT< wchar_t > :
304 public ChTraitsBase< wchar_t >
305{
306 static DWORD __cdecl _GetEnvironmentVariableW( _In_z_ LPCWSTR pszName, _Out_opt_cap_post_count_(nSize, return) LPWSTR pszBuffer, _In_ DWORD nSize ) throw()
307 {
308 return ::GetEnvironmentVariableW( pszName, pszBuffer, nSize );
309 }
310
311public:
312 static LPWSTR __cdecl CharNext( _In_z_ LPCWSTR psz ) throw()
313 {
314 return const_cast< LPWSTR >( psz+1 );
315 }
316
317 static int __cdecl IsDigit( _In_ wchar_t ch ) throw()
318 {
319 return iswdigit( static_cast<unsigned short>(ch) );
320 }
321
322 static int __cdecl IsSpace( _In_ wchar_t ch ) throw()
323 {
324 return iswspace( static_cast<unsigned short>(ch) );
325 }
326
327 static int __cdecl StringCompare( _In_z_ LPCWSTR pszA, _In_z_ LPCWSTR pszB ) throw()
328 {
329 return wcscmp( pszA, pszB );
330 }
331
332 static int __cdecl StringCompareIgnore( _In_z_ LPCWSTR pszA, _In_z_ LPCWSTR pszB ) throw()
333 {
334 return _wcsicmp( pszA, pszB );
335 }
336
337 static int __cdecl StringCollate( _In_z_ LPCWSTR pszA, _In_z_ LPCWSTR pszB ) throw()
338 {
339 return wcscoll( pszA, pszB );
340 }
341
342 static int __cdecl StringCollateIgnore( _In_z_ LPCWSTR pszA, _In_z_ LPCWSTR pszB ) throw()
343 {
344 return _wcsicoll( pszA, pszB );
345 }
346
347 static LPCWSTR __cdecl StringFindString( _In_z_ LPCWSTR pszBlock, _In_z_ LPCWSTR pszMatch ) throw()
348 {
349 return wcsstr( pszBlock, pszMatch );
350 }
351
352 static LPWSTR __cdecl StringFindString( _In_z_ LPWSTR pszBlock, _In_z_ LPCWSTR pszMatch ) throw()
353 {
354 return( const_cast< LPWSTR >( StringFindString( const_cast< LPCWSTR >( pszBlock ), pszMatch ) ) );
355 }
356
357 static LPCWSTR __cdecl StringFindChar( _In_z_ LPCWSTR pszBlock, _In_ wchar_t chMatch ) throw()
358 {
359 return wcschr( pszBlock, chMatch );
360 }
361
362 static LPCWSTR __cdecl StringFindCharRev( _In_z_ LPCWSTR psz, _In_ wchar_t ch ) throw()
363 {
364 return wcsrchr( psz, ch );
365 }
366
367 static LPCWSTR __cdecl StringScanSet( _In_z_ LPCWSTR pszBlock, _In_z_ LPCWSTR pszMatch ) throw()
368 {
369 return wcspbrk( pszBlock, pszMatch );
370 }
371
372 static int __cdecl StringSpanIncluding( _In_z_ LPCWSTR pszBlock, _In_z_ LPCWSTR pszSet ) throw()
373 {
374 return (int)wcsspn( pszBlock, pszSet );
375 }
376
377 static int __cdecl StringSpanExcluding( _In_z_ LPCWSTR pszBlock, _In_z_ LPCWSTR pszSet ) throw()
378 {
379 return (int)wcscspn( pszBlock, pszSet );
380 }
381
382 _ATL_INSECURE_DEPRECATE("You must pass an output size to ChTraitsCRT::StringUppercase")
383 static LPWSTR __cdecl StringUppercase( _Inout_ LPWSTR psz ) throw()
384 {
385#pragma warning (push)
386#pragma warning(disable : 4996)
387 return _wcsupr( psz );
388#pragma warning (pop)
389 }
390
391 _ATL_INSECURE_DEPRECATE("You must pass an output size to ChTraitsCRT::StringLowercase")
392 static LPWSTR __cdecl StringLowercase( _Inout_ LPWSTR psz ) throw()
393 {
394#pragma warning (push)
395#pragma warning(disable : 4996)
396 return _wcslwr( psz );
397#pragma warning (pop)
398 }
399
400 static LPWSTR __cdecl StringUppercase( _Inout_cap_(size) LPWSTR psz, _In_ size_t size ) throw()
401 {
402 errno_t err = _wcsupr_s( psz, size );
403 return (err == 0) ? psz : NULL;
404 }
405
406 static LPWSTR __cdecl StringLowercase( _Inout_cap_(size) LPWSTR psz, _In_ size_t size ) throw()
407 {
408 errno_t err = _wcslwr_s( psz, size );
409 return (err == 0) ? psz : NULL;
410 }
411
412 static LPWSTR __cdecl StringReverse( _Inout_ LPWSTR psz ) throw()
413 {
414 return _wcsrev( psz );
415 }
416
417 static int __cdecl GetFormattedLength( _In_ _Printf_format_string_ LPCWSTR pszFormat, va_list args) throw()
418 {
419 return _vscwprintf( pszFormat, args );
420 }
421
422 _ATL_INSECURE_DEPRECATE("You must pass an output size to ChTraitsCRT::Format")
423 static int __cdecl Format( _Out_ LPWSTR pszBuffer, _In_ _Printf_format_string_ LPCWSTR pszFormat, va_list args) throw()
424 {
425 #pragma warning (push)
426 #pragma warning(disable : 4996)
427 return vswprintf( pszBuffer, pszFormat, args );
428 #pragma warning (pop)
429 }
430 static int __cdecl Format
431 ( _Out_cap_(nLength) LPWSTR pszBuffer, _In_ size_t nLength, _In_ __format_string LPCWSTR pszFormat, va_list args) throw()
432 {
433 return vswprintf_s( pszBuffer, nLength, pszFormat, args );
434 }
435
436 static int __cdecl GetBaseTypeLength( _In_z_ LPCSTR pszSrc ) throw()
437 {
438 // Returns required buffer size in wchar_ts
439 return ::MultiByteToWideChar( _AtlGetConversionACP(), 0, pszSrc, -1, NULL, 0 )-1;
440 }
441
442 static int __cdecl GetBaseTypeLength( _In_count_(nLength) LPCSTR pszSrc, _In_ int nLength ) throw()
443 {
444 // Returns required buffer size in wchar_ts
445 return ::MultiByteToWideChar( _AtlGetConversionACP(), 0, pszSrc, nLength, NULL, 0 );
446 }
447
448 static int __cdecl GetBaseTypeLength( _In_z_ LPCWSTR pszSrc ) throw()
449 {
450 // Returns required buffer size in wchar_ts
451 return (int)wcslen( pszSrc );
452 }
453
454 static int __cdecl GetBaseTypeLength( _In_count_(nLength) LPCWSTR pszSrc, _In_ int nLength ) throw()
455 {
456 (void)pszSrc;
457 // Returns required buffer size in wchar_ts
458 return nLength;
459 }
460
461 static void __cdecl ConvertToBaseType( _Out_cap_(nDestLength) LPWSTR pszDest, _In_ int nDestLength,
462 _In_z_ LPCSTR pszSrc, _In_ int nSrcLength = -1) throw()
463 {
464 // nLen is in wchar_ts
465 ::MultiByteToWideChar( _AtlGetConversionACP(), 0, pszSrc, nSrcLength, pszDest, nDestLength );
466 }
467
468 static void __cdecl ConvertToBaseType( _Out_cap_(nDestLength) LPWSTR pszDest, _In_ int nDestLength,
469 _In_z_ LPCWSTR pszSrc, int nSrcLength = -1 ) throw()
470 {
471 if (nSrcLength == -1) { nSrcLength=1 + GetBaseTypeLength(pszSrc); }
472 // nLen is in wchar_ts
473 Checked::wmemcpy_s(pszDest, nDestLength, pszSrc, nSrcLength);
474 }
475
476 static void __cdecl FloodCharacters( _In_ wchar_t ch, _In_ int nLength, _Out_capcount_(nLength) LPWSTR psz ) throw()
477 {
478 // nLength is in XCHARs
479 for( int i = 0; i < nLength; i++ )
480 {
481 psz[i] = ch;
482 }
483 }
484
485 static BSTR __cdecl AllocSysString( _In_count_(nDataLength) const wchar_t* pchData, _In_ int nDataLength ) throw()
486 {
487 return ::SysAllocStringLen( pchData, nDataLength );
488 }
489
490 static BOOL __cdecl ReAllocSysString( _In_count_(nDataLength) const wchar_t* pchData, _Inout_ BSTR* pbstr, _In_ int nDataLength ) throw()
491 {
492 return ::SysReAllocStringLen( pbstr, pchData, nDataLength );
493 }
494
495 static int __cdecl SafeStringLen( _In_opt_z_ LPCSTR psz ) throw()
496 {
497 // returns length in bytes
498 return (psz != NULL) ? (int)strlen( psz ) : 0;
499 }
500
501 static int __cdecl SafeStringLen( _In_opt_ LPCWSTR psz ) throw()
502 {
503 // returns length in wchar_ts
504 return (psz != NULL) ? (int)wcslen( psz ) : 0;
505 }
506
507 static int __cdecl GetCharLen( _In_z_ const wchar_t* pch ) throw()
508 {
509 (void)pch;
510 // returns char length
511 return 1;
512 }
513
514 static int __cdecl GetCharLen( _In_z_ const char* pch ) throw()
515 {
516 // returns char length
517 return (int)( _mbclen( reinterpret_cast< const unsigned char* >( pch ) ) );
518 }
519
520 static DWORD __cdecl GetEnvironmentVariable( _In_z_ LPCWSTR pszVar, _Out_opt_cap_(dwSize) LPWSTR pszBuffer, _In_ DWORD dwSize ) throw()
521 {
522 return _GetEnvironmentVariableW( pszVar, pszBuffer, dwSize );
523 }
524
525 static void __cdecl ConvertToOem( __reserved LPWSTR /**//*psz*/ )
526 {
527 ATLENSURE(FALSE); // Unsupported Feature
528 }
529
530 static void __cdecl ConvertToAnsi( __reserved LPWSTR /**//*psz*/ )
531 {
532 ATLENSURE(FALSE); // Unsupported Feature
533 }
534
535 static void __cdecl ConvertToOem( __reserved LPWSTR /**//*psz*/, size_t )
536 {
537 ATLENSURE(FALSE); // Unsupported Feature
538 }
539
540 static void __cdecl ConvertToAnsi( __reserved LPWSTR /**//*psz*/, size_t )
541 {
542 ATLENSURE(FALSE); // Unsupported Feature
543 }
544
545#ifdef _UNICODE
546public:
547 static DWORD __cdecl _AFX_FUNCNAME(FormatMessage)( _In_ DWORD dwFlags, LPCVOID pSource,
548 _In_ DWORD dwMessageID, _In_ DWORD dwLanguageID, _Out_cap_(nSize) LPWSTR pszBuffer,
549 _In_ DWORD nSize, va_list* pArguments ) throw()
550 {
551 return ::FormatMessageW( dwFlags, pSource, dwMessageID, dwLanguageID,
552 pszBuffer, nSize, pArguments );
553 }
554
555#if defined(_AFX)
556 static DWORD __cdecl FormatMessage( _In_ DWORD dwFlags, LPCVOID pSource,
557 _In_ DWORD dwMessageID, _In_ DWORD dwLanguageID, _Out_cap_(nSize) LPWSTR pszBuffer,
558 _In_ DWORD nSize, va_list* pArguments ) throw()
559 {
560 return _AFX_FUNCNAME(FormatMessage)(dwFlags, pSource, dwMessageID, dwLanguageID, pszBuffer, nSize, pArguments);
561 }
562#endif
563
564#else
565 static DWORD __cdecl _AFX_FUNCNAME(FormatMessage)( DWORD /**//*dwFlags*/, LPCVOID /**//*pSource*/,
566 DWORD /**//*dwMessageID*/, DWORD /**//*dwLanguageID*/, __reserved LPWSTR /**//*pszBuffer*/,
567 DWORD /**//*nSize*/, va_list* /**//*pArguments*/ )
568 {
569 ATLENSURE(FALSE); // Unsupported Feature
570 return 0;
571 }
572
573#if defined(_AFX)
574 static DWORD __cdecl FormatMessage( DWORD dwFlags, LPCVOID pSource,
575 DWORD dwMessageID, DWORD dwLanguageID, __reserved LPWSTR pszBuffer,
576 DWORD nSize, va_list* pArguments )
577 {
578 return _AFX_FUNCNAME(FormatMessage)(dwFlags, pSource, dwMessageID, dwLanguageID, pszBuffer, nSize, pArguments);
579 }
580#endif
581
582#endif
583
584};
585
CString类的构造函数
看过了上面的n多代码和介绍,我们终于可以看看真正的CString类了。声明如下:
template< typename BaseType, class StringTraits >
class CStringT :
public CSimpleStringT< BaseType, _CSTRING_IMPL_::_MFCDLLTraitsCheck<BaseType, StringTraits>::c_bIsMFCDLLTraits >
您可能不禁要问,不是要说CString么,怎么是CStringT呢? 其实CString实际上是一个宏而已,在afxstr.h文件中有如下定义:
typedef ATL::CStringT< wchar_t, StrTraitMFC_DLL< wchar_t > > CStringW;
typedef ATL::CStringT< char, StrTraitMFC_DLL< char > > CStringA;
typedef ATL::CStringT< TCHAR, StrTraitMFC_DLL< TCHAR > > CString;
由此,我们可以确切的知道,我们使用的CString就是CStringT模板类的一个特例。如果您是从头看到尾的话,就会知道,我们已经讲了CString的重要成员CStringData、包含了CString字符串操作的方法ChTraitsCRT。那CString里面还剩下什么没有说呢?基本上也就是构造函数、操作符重载了。一个类也就包括了这么多内容,再无其它。
先说构造函数,CString这个帅类提供了一共17种构造重载,够狠!全说没必要,要说的就是静态构造函数:
static void __cdecl Construct( CStringT* pString )
{
new( pString ) CStringT;
}
这是一个拷贝构造函数,传入一个有效的指向CString的指针,这个构造就会为你创建一个新的对象,并把这个对象的首地址赋值给传入的指针变量。
MSDN给出了构造函数使用的完整Demo:
CAtlString s1; // Empty string
CAtlString s2(_T("cat")); // From a C string literal
CAtlString s3 = s2; // Copy constructor
CAtlString s4(s2 + _T(" ") + s3); // From a string expression
CAtlString s5(_T('x')); // s5 = "x"
CAtlString s6(_T('x'), 6); // s6 = "xxxxxx"
CAtlString s7((LPCSTR)ID_FILE_NEW); // s7 = "Create a new document"
VARIANT var;
V_VT(&var) = VT_BSTR;
V_BSTR(&var) = ::SysAllocString(L"Football is a fun sport.");
CAtlString s8(var); // s8 = "Football is a fun sport."
// The following statement does not call the assignment operator.
// The compiler considers the following statement equivalent to
// CAtlString city("Paris")
CAtlString city = _T("Paris");
关于Format函数
这个函数的奇特地方就是支持变长参数列表。在上次说道.NET下的String类时,使用的是C#的关键字Params 关键字来实现变长参数的,在C++中采用变长参数列表来实现。Format函数的声明如下:
inline void __cdecl CStringT<BaseType, StringTraits>::Format( _In_ _Printf_format_string_ PCXSTR pszFormat, ... )
这最后的… 在语法上就代表了变长参数列表。请注意,这个变长参数列表必须放在函数列表的最后声明,如写成(…,int iLength) 是不对的。
这个变长参数列表的声明在stdarg.h文件中。实际上va_list、va_start、va_end 是三个宏。代码如下:
ATLASSERT( AtlIsValidString( pszFormat ) );
va_list argList;
va_start( argList, pszFormat );
FormatV( pszFormat, argList );
va_end( argList );
LoadString 函数
这个函数也是值得一说的,正因为有了这个LoadString函数和String Table,我们才可以很容易的让我们的程序支持国际化。LoadString的代码是这样的:
_Check_return_ BOOL LoadString( _In_ HINSTANCE hInstance, _In_ UINT nID, _In_ WORD wLanguageID )
{
const ATLSTRINGRESOURCEIMAGE* pImage = AtlGetStringResourceImage( hInstance, nID, wLanguageID );
if( pImage == NULL )
{
return( FALSE );
}
int nLength = StringTraits::GetBaseTypeLength( pImage->achString, pImage->nLength );
PXSTR pszBuffer = GetBuffer( nLength );
StringTraits::ConvertToBaseType( pszBuffer, nLength, pImage->achString, pImage->nLength );
ReleaseBufferSetLength( nLength );
return( TRUE );
}
其最主要的函数是AtlGetStringResourceImage 函数,这个函数通过资源ID能够获取保存在应用程序文件或动态库中的字符串资源。AtlGetStringResourceImage 函数的功能实现最终还是通过Windows API FindResource 函数实现的。有兴趣的话,可以查看一下AtlGetStringResourceImage 函数的源代码。
写在后面
说起Windows 下的CString,那真是有说不尽的话题。比如,不同类型的字符串之间的转换,这个话题就够再写一篇博客的。本篇只能尽力起到一个抛砖引玉的作用。
我在Google上搜索到了一篇叫做《谈新手对CString的使用》一文,http://www.poptool.net/server/p52/J526413.shtml 这篇文章被n多网站转载,出处不明。文章中提到了一个问题,说“为了优化效率,就采用在系统软件内部广
泛使用的"写时复制"概念.即当从一个cstring产生另一个cstring并不复制它的字符缓
冲区内容,而只是将字符缓冲区的"引用计数"加1.当需要改写字符缓冲区内的内容时,才
分配内存,并复制内容”这部分倒是值得大家留意,这也正和前面说道的CStringData引入的引用计数机制不谋而合。