在MFC中如何得到以后用户模式的内存64k以后的首地址

1697人阅读
CString 操作指南原著:Joseph M. Newcomer翻译:littleloach
原文出处:codeproject:CString Management通过阅读本文您能够学习如何有效地使用 CString。  CString 是一种很有用的数据类型。他们很大程度上简化了MFC中的许多操作,使得MFC在做字符串操作的时候方便了很多。不管怎样,使用CString有很多特别的技巧,特别是对于纯C背景下走出来的程式员来说有点难以学习。这篇文章就来讨论这些技巧。  使用CString能够让您对字符串的操作更加直截了当。这篇文章不是CString的完全手册,但囊括了大部分常见基本问题。这篇文章包括以下内容:
1.CString 对象的连接
2.格式化字符串(包括 int 型转化为 CString )
3.CString 型转化成 int 型
4.CString 型和 char* 类型的相互转化
5.char* 转化成 CString
6.CString 转化成 char* 之一:使用LPCTSTR强制转化
7.CString 转化成 char* 之二:使用CString对象的GetBuffer方法
8.CString 转化成 char* 之三: 和控件的接口
9.CString 型转化成 BSTR 型;
10.BSTR 型转化成 CString 型;
11.VARIANT 型转化成 CString 型;
12.载入字符串表资源;
13.CString 和临时对象;
14.CString 的效率;
下面我分别讨论。1、CString 对象的连接  能体现出 CString 类型方便性特点的一个方面就字符串的连接,使用 CString 类型,您能很方便地连接两个字符串,正如下面的例子:
CString gray("Gray");
CString cat("Cat");
CString graycat = gray +
要比用下面的方法好得多:
char gray[] = "Gray";
char cat[] = "Cat";
char * graycat = malloc(strlen(gray) + strlen(cat) + 1);
strcpy(graycat, gray);
strcat(graycat, cat);
2、格式化字符串  和其用 sprintf() 函数或 wsprintf() 函数来格式化一个字符串,还不如用 CString 对象的Format()方法:
s.Format(_T("The total is %d"), total);
  用这种方法的好处是您不用担心用来存放格式化后数据的缓冲区是否足够大,这些工作由CString类替您完成。  格式化是一种把其他不是字符串类型的数据转化为CString类型的最常用技巧,比如,把一个整数转化成CString类型,可用如下方法:
s.Format(_T("%d"), total);
  我总是对我的字符串使用_T()宏,这是为了让我的代码至少有Unicode的意识,当然,关于Unicode的话题不在这篇文章的讨论范围。_T()宏在8位字符环境下是如下定义的:
#define _T(x) x // 非Unicode版本(non-Unicode version)
而在Unicode环境下是如下定义的:
#define _T(x) L##x // Unicode版本(Unicode version)
所以在Unicode环境下,他的效果就相当于:
s.Format(L"%d", total);
  假如您认为您的程式可能在Unicode的环境下运行,那么开始在意用 Unicode 编码。比如说,不要用 sizeof() 操作符来获得字符串的长度,因为在Unicode环境下就会有2倍的误差。我们能够用一些方法来隐藏Unicode的一些细节,比如在我需要获得字符长度的时候,我会用一个叫做DIM的宏,这个宏是在我的dim.h文档中定义的,我会在我写的任何程式中都包含这个文档:
#define DIM(x) ( sizeof((x)) / sizeof((x)[0]) )
  这个宏不但能够用来解决Unicode的字符串长度的问题,也能够用在编译时定义的表格上,他能够获得表格的项数,如下:
class Whatever { ... };
Whatever data[] = {
&& { ... },
&& { ... },
for(int i = 0; i & DIM(data); i++) // 扫描表格寻找匹配项。
  这里要提醒您的就是一定要注意那些在参数中需要真实字节数的API函数调用,假如您传递字符个数给他,他将不能正常工作。如下:
TCHAR data[20];
lstrcpyn(data, longstring, sizeof(data) - 1); // WRONG!
lstrcpyn(data, longstring, DIM(data) - 1); // RIGHT
WriteFile(f, data, DIM(data), &bytesWritten, NULL); // WRONG!
WriteFile(f, data, sizeof(data), &bytesWritten, NULL); // RIGHT
造成以上原因是因为lstrcpyn需要一个字符个数作为参数,但是WriteFile却需要字节数作为参数。同样需要注意的是有时候需要写出数据的任何内容。假如您仅仅只想写出数据的真实长度,您可能会认为您应该这样做:
WriteFile(f, data, lstrlen(data), &bytesWritten, NULL); // WRONG
但是在Unicode环境下,他不会正常工作。正确的做法应该是这样:
WriteFile(f, data, lstrlen(data) * sizeof(TCHAR), &bytesWritten, NULL); // RIGHT
  因为WriteFile需要的是个以字节为单位的长度。(可能有些人会想“在非Unicode的环境下运行这行代码,就意味着总是在做一个多余的乘1操作,这样不会降低程式的效率吗?”这种想法是多余的,您必须要了解编译器实际上做了什么,没有哪一个C或C++编译器会把这种无聊的乘1操作留在代码中。在Unicode环境下运行的时候,您也不必担心那个乘2操作会降低程式的效率,记住,这只是个左移一位的操作而已,编译器也很乐意为您做这种替换。)  使用_T宏并不是意味着您已创建了一个Unicode的程式,您只是创建了一个有Unicode意识的程式而已。假如您在默认的8-bit模式下编译您的程式的话,得到的将是个普通的8-bit的应用程式(这里的8-bit指的只是8位的字符编码,并不是指8位的电脑系统);当您在Unicode环境下编译您的程式时,您才会得到一个Unicode的程式。记住,CString 在 Unicode 环境下,里面包含的可都是16位的字符哦。3、CString 型转化成 int 型  把 CString 类型的数据转化成整数类型最简单的方法就是使用标准的字符串到整数转换例程。  虽然通常您怀疑使用_atoi()函数是个好的选择,他也很少会是个正确的选择。假如您准备使用 Unicode 字符,您应该用_ttoi(),他在 ANSI 编码系统中被编译成_atoi(),而在 Unicode 编码系统中编译成_wtoi()。您也能够考虑使用_tcstoul()或_tcstol(),他们都能把字符串转化成任意进制的长整数(如二进制、八进制、十进制或十六进制),不同点在于前者转化后的数据是无符号的(unsigned),而后者相反。看下面的例子:
CString hex = _T("FAB");
CString decimal = _T("4011");
ASSERT(_tcstoul(hex, 0, 16) == _ttoi(decimal));
4、CString 型和 char* 类型的相互转化  这是初学者使用 CString 时最常见的问题。有了 C++ 的帮助,很多问题您无需深入的去考虑他,直接拿来用就行了,但是假如您不能深入了解他的运行机制,又会有很多问题让您迷惑,特别是有些看起来没有问题的代码,却偏偏不能正常工作。比如,您会奇怪为什么不能写向下面这样的代码呢:
CString graycat = "Gray" + "Cat";
CString graycat("Gray" + "Cat");
  事实上,编译器将抱怨上面的这些尝试。为什么呢?因为针对CString 和 LPCTSTR数据类型的各种各样的组合,“ +” 运算符 被定义成一个重载操作符。而不是两个 LPCTSTR 数据类型,他是底层数据类型。您不能对基本数据(如 int、char 或 char*)类型重载 C++ 的运算符。您能够象下面这样做:
CString graycat = CString("Gray") + CString("Cat");
CString graycat = CString("Gray") + "Cat";
研究一番就会发现:“ +”总是使用在至少有一个 CString 对象和一个 LPCSTR 的场合。注意,编写有 Unicode 意识的代码总是一件好事,比如:
CString graycat = CString(_T("Gray")) + _T("Cat");
这将使得您的代码能够直接移植。char* 转化为 CString  现在您有一个 char* 类型的数据,或说一个字符串。怎么样创建 CString 对象呢?这里有一些例子:
char * p = "This is a test";
或象下面这样更具备 Unicode 意识:
TCHAR * p = _T("This is a test")
LPTSTR p = _T("This is a test");
您能够使用下面任意一种写法:
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 =
CString s(p);
  用这些方法能够轻松将常量字符串或指针转换成 CString。需要注意的是,字符的赋值总是被拷贝到 CString 对象中去的,所以您能够象下面这样操作:
TCHAR * p = _T("Gray");
CString s(p);
p = _T("Cat");
结果字符串肯定是“GrayCat”。CString 类更有几个其他的构造函数,但是这里我们不考虑他,假如您有兴趣能够自己查看相关文档。事实上,CString 类的构造函数比我展示的要复杂,比如:
CString s = "This is a test";
  这是很草率的编码,但是实际上他在 Unicode 环境下能编译通过。他在运行时调用构造函数的 MultiByteToWideChar 操作将 8 位字符串转换成 16 位字符串。不管怎样,假如 char * 指针是网络上传输的 8 位数据,这种转换是很有用的。CString 转化成 char* 之一:强制类型转换为 LPCTSTR;  这是一种略微硬性的转换,有关“正确”的做法,人们在认识上还存在许多混乱,正确的使用方法有很多,但错误的使用方法可能和正确的使用方法相同多。  我们首先要了解 CString 是一种很特别的 C++ 对象,他里面包含了三个值:一个指向某个数据缓冲区的指针、一个是该缓冲中有效的字符记数连同一个缓冲区长度。 有效字符数的大小能够是从0到该缓冲最大长度值减1之间的任何数(因为字符串结尾有一个NULL字符)。字符记数和缓冲区长度被巧妙隐藏。  除非您做一些特别的操作,否则您不可能知道给CString对象分配的缓冲区的长度。这样,即使您获得了该0缓冲的地址,您也无法更改其中的内容,不能截短字符串,也 绝对没有办法加长他的内容,否则第一时间就会看到溢出。  LPCTSTR 操作符(或更明确地说就是 TCHAR * 操作符)在 CString 类中被重载了,该操作符的定义是返回缓冲区的地址,因此,假如您需要一个指向 CString 的 字符串指针的话,能够这样做:
CString s("GrayCat");
LPCTSTR p =
  他能够正确地运行。这是由C语言的强制类型转化规则实现的。当需要强制类型转化时,C++规测容许这种选择。比如,您能够将(浮点数)定义为将某个复数 (有一对浮点数)进行强制类型转换后只返回该复数的第一个浮点数(也就是其实部)。能够象下面这样:
Complex c(1.2f, 4.8f);
float realpart =
假如(float)操作符定义正确的话,那么实部的的值应该是1.2。  这种强制转化适合任何这种情况,例如,任何带有 LPCTSTR 类型参数的函数都会强制执行这种转换。 于是,您可能有这样一个函数(也许在某个您买来的DLL中):
BOOL DoSomethingCool(LPCTSTR s);
您象下面这样调用他:
CString file("c://myfiles//coolstuff")
BOOL result = DoSomethingCool(file);
  他能正确运行。因为 DoSomethingCool 函数已说明了需要一个 LPCTSTR 类型的参数,因此 LPCTSTR 被应用于该参数,在 MFC 中就是返回的串地址。假如您要格式化字符串怎么办呢?
CString graycat("GrayCat");
s.Format("Mew! I love %s", graycat);
  注意由于在可变参数列表中的值(在函数说明中是以“...”表示的)并没有隐含一个强制类型转换操作符。您会得到什么结果呢?  一个令人惊讶的结果,我们得到的实际结果串是:
"Mew! I love GrayCat"。
  因为 MFC 的设计者们在设计 CString 数据类型时很小心, CString 类型表达式求值后指向了字符串,所以这里看不到任何象 Format 或 sprintf 中的强制类型转换,您仍然能够得到正确的行为。描述 CString 的附加数据实际上在 CString 名义地址之后。  有一件事情您是不能做的,那就是修改字符串。比如,您可能会尝试用“,”代替“.”(不要做这样的,假如您在乎国际化问题,您应该使用十进制转换的 National Language Support 特性,),下面是个简单的例子:
CString v("1.00"); // 货币金额,两位小数
LPCTSTR p =
p[lstrlen(p) - 3] = '','';
  这时编译器会报错,因为您赋值了一个常量串。假如您做如下尝试,编译器也会错:
strcat(p, "each");
  因为 strcat 的第一个参数应该是 LPTSTR 类型的数据,而您却给了一个 LPCTSTR。  不要试图钻这个错误消息的牛角尖,这只会使您自己陷入麻烦!  原因是缓冲有一个计数,他是不可存取的(他位于 CString 地址之下的一个隐藏区域),假如您改变这个串,缓冲中的字符计数不会反映所做的修改。此外,假如字符串长度恰好是该字符串物理限制的长度(梢后还会讲到这个问题),那么扩展该字符串将改写缓冲以外的任何数据,那是您无权进行写操作的内存(不对吗?),您会毁换坏不属于您的内存。这是应用程式真正的死亡处方。
CString转化成char* 之二:使用 CString 对象的 GetBuffer 方法;  假如您需要修改 CString 中的内容,他有一个特别的方法能够使用,那就是 GetBuffer,他的作用是返回一个可写的缓冲指针。 假如您只是打算修改字符或截短字符串,您完万能够这样做:
CString s(_T("File.ext"));
LPTSTR p = s.GetBuffer();
LPTSTR dot = strchr(p, ''.''); // OK, should have used s.Find...
if(p != NULL)
*p = _T(''/0'');
s.ReleaseBuffer();
  这是 GetBuffer 的第一种用法,也是最简单的一种,不用给他传递参数,他使用默认值 0,意思是:“给我这个字符串的指针,我确保不加长他”。当您调用 ReleaseBuffer 时,字符串的实际长度会被重新计算,然后存入 CString 对象中。  必须强调一点,在 GetBuffer 和 ReleaseBuffer 之间这个范围,一定不能使用您要操作的这个缓冲的 CString 对象的任何方法。因为 ReleaseBuffer 被调用之前,该 CString 对象的完整性得不到保障。研究以下代码:
CString s(...);
LPTSTR p = s.GetBuffer();
//... 这个指针 p 发生了很多事情
int n = s.GetLength(); // 很糟D!!!!! 有可能给出错误的答案!!!
s.TrimRight(); // 很糟!!!!! 不能确保能正常工作!!!!
s.ReleaseBuffer(); // 现在应该 OK
int m = s.GetLength(); // 这个结果能够确保是正确的。
s.TrimRight(); // 将正常工作。
  假设您想增加字符串的长度,您首先要知道这个字符串可能会有多长,好比是声明字符串数组的时候用:
char buffer[1024];
表示 1024 个字符空间足以让您做任何想做得事情。在 CString 中和之意义相等的表示法:
LPTSTR p = s.GetBuffer(1024);
  调用这个函数后,您不但获得了字符串缓冲区的指针,而且同时还获得了长度至少为 1024 个字符的空间(注意,我说的是“字符”,而不是“字节”,因为 CString 是以隐含方式感知 Unicode 的)。  同时,还应该注意的是,假如您有一个常量串指针,这个串本身的值被存储在只读内存中,假如试图存储他,即使您已调用了 GetBuffer ,并获得一个只读内存的指针,存入操作会失败,并报告存取错误。我没有在 CString 上证实这一点,但我看到过大把的 C 程式员经常犯这个错误。  C 程式员有一个通病是分配一个固定长度的缓冲,对他进行 sprintf 操作,然后将他赋值给一个 CString:
char buffer[256];
sprintf(buffer, "%......", args, ...); // ... 部分省略许多细节
CString s =
虽然更好的形式能够这么做:
s.Format(_T("%...."), args, ...);
假如您的字符串长度万一超过 256 个字符的时候,不会破坏堆栈。  另外一个常见的错误是:既然固定大小的内存不工作,那么就采用动态分配字节,这种做法弊端更大:
int len = lstrlen(parm1) + 13& lstrlen(parm2) + 10 + 100;
char * buffer = new char[len];
sprintf(buffer, "%s is equal to %s, valid data", parm1, parm2);
CString s =
他能够能被简单地写成:
s.Format(_T("%s is equal to %s, valid data"), parm1, parm2);
  需要注意 sprintf 例子都不是 Unicode 就绪的,尽管您能够使用 tsprintf 连同用 _T() 来包围格式化字符串,但是基本 思路仍然是在走弯路,这这样很容易出错。CString to char * 之三:和控件的接口;  我们经常需要把一个 CString 的值传递给一个控件,比如,CTreeCtrl。MFC为我们提供了很多便利来重载这个操作,但是 在大多数情况下,您使用“原始”形式的更新,因此需要将墨某个串指针存储到 TVINSERTITEMSTRUCT 结构的 TVITEM 成员中。如下:
TVINSERTITEMSTRUCT
// ... 为s赋一些值。
tvi.item.pszText = // Compiler yells at you here
// ... 填写tvi的其他域
HTREEITEM ti = c_MyTree.InsertItem(&tvi);
  为什么编译器会报错呢?明明看起来很完美的用法啊!但是事实上假如您看看 TVITEM 结构的定义您就会明白,在 TVITEM 结构中 pszText&成员的声明如下:
LPTSTR pszT
int cchTextM
  因此,赋值不是赋给一个 LPCTSTR 类型的变量,而且编译器无法知道如何将赋值语句右边强制转换成 LPCTSTR。好吧,您说,那我就改成这样:
tvi.item.pszText = (LPCTSTR)s; //编译器依然会报错。
  编译器之所以依然报错是因为您试图把一个 LPCTSTR 类型的变量赋值给一个 LPTSTR 类型的变量,这种操作在C或C++中是被禁止的。您不能用这种方法 来滥用常量指针和很量指针概念,否则,会扰乱编译器的优化机制,使之不知如何优化您的程式。比如,假如您这么做:
const int i = ...;
//... do lots of stuff
... = a[i]; // usage 1
// ... lots more stuff
... = a[i]; // usage 2
  那么,编译器会以为既然 i 是 const ,所以 usage1和usage2的值是相同的,并且他甚至能事先计算好 usage1 处的 a[i] 的地址,然后保留着在后面的 usage2 处使用,而不是重新计算。假如您按如下方式写的话:
const int i = ...;
int * p = &i;
//... do lots of stuff
... = a[i]; // usage 1
// ... lots more stuff
(*p)++; // mess over compiler''s assumption
// ... and other stuff
... = a[i]; // usage 2
  编译器将认为 i 是常量,从而 a[i] 的位置也是常量,这样间接地破坏了先前的假设。因此,您的程式将会在 debug 编译模式(没有优化)和 release 编译模式(完全优化)中反映出不同的行为,这种情况可不好,所以当您试图把指向 i 的指针赋值给一个 可修改的引用时,会被编译器诊断为这是一种伪造。这就是为什么(LPCTSTR)强制类型转化不起作用的原因。  为什么不把该成员声明成 LPCTSTR 类型呢?因为这个结构被用于读写控件。当您向控件写数据时,文本指针实际上被当成 LPCTSTR,而当您从控件读数据 时,您必须有一个可写的字符串。这个结构无法区分他是用来读还是用来写。因此,您会常常在我的代码中看到如下的用法:
tvi.item.pszText = (LPTSTR)(LPCTSTR)s;
  他把 CString 强制类型转化成 LPCTSTR,也就是说先获得改字符串的地址,然后再强制类型转化成 LPTSTR,以便能够对之进行赋值操作。 注意这只有在使用 Set 或 Insert 之类的方法才有效!假如您试图获取数据,则不能这么做。  假如您打算获取存储在控件中的数据,则方法稍有不同,例如,对某个 CTreeCtrl 使用 GetItem 方法,我想获取项目的文本。我知道这些 文本的长度不会超过 MY_LIMIT,因此我能够这样写:
// ... assorted initialization of other fields of tvi
tvi.pszText = s.GetBuffer(MY_LIMIT);
tvi.cchTextMax = MY_LIMIT;
c_MyTree.GetItem(&tvi);
s.ReleaseBuffer();
  能够看出来,其实上面的代码对任何类型的 Set 方法都适用,但是并无需这么做,因为任何的类 Set 方法(包括 Insert方法)不会改变字符串的内容。但是当您需要写 CString 对象时,必须确保缓冲是可写的,这正是 GetBuffer 所做的事情。再次强调: 一旦做了一次 GetBuffer 调用,那么在调用 ReleaseBuffer 之前不要对这个 CString 对象做任何操作。5、CString 型转化成 BSTR 型  当我们使用 ActiveX 控件编程时,经常需要用到将某个值表示成 BSTR 类型。BSTR 是一种记数字符串,Intel平台上的宽字符串(Unicode),并且 能够包含嵌入的 NULL 字符。您能够调用 CString 对象的 AllocSysString 方法将 CString 转化成 BSTR:
s = ... ; // whatever
BSTR b = s.AllocSysString();
  现在指针 b 指向的就是个新分配的 BSTR 对象,该对象是 CString 的一个拷贝,包含终结 NULL字符。现在您能够将他传递给任何需要 BSTR 的接口。通常,BSTR 由接收他的组件来释放,假如您需要自己释放 BSTR 的话,能够这么做:
::SysFreeString(b);
  对于如何表示传递给 ActiveX 控件的字符串,在微软内部曾一度争论不休,最后 Visual Basic 的人占了上风,BSTR(“Basic String”的首字母缩写)就是这场争论的结果。6、BSTR 型转化成 CString 型  由于 BSTR 是记数 Unicode 字符串,您能够用标准转换方法来创建 8 位的 CString。实际上,这是 CString 内建的功能。在 CString 中 有特别的构造函数能够把 ANSI 转化成 Unicode,也能够把Unicode 转化成 ANSI。您同样能够从 VARIANT 类型的变量中获得 BSTR 类型的字符串,VARIANT 类型是 由各种 COM 和 Automation (自动化)调用返回的类型。例如,在一个ANSI程式中:
b = ...; // whatever
CString s(b == NULL ? L"" : b)
  对于单个的 BSTR 串来说,这种用法能够工作得很好,这是因为 CString 有一个特别的构造函数以LPCWSTR(BSTR正是这种类型) 为参数,并将他转化成 ANSI 类型。专门检查是必须的,因为 BSTR 可能为空值,而 CString 的构造函数对于 NULL 值情况考虑的不是很周到,(感谢 Brian Ross 指出这一点!)。这种用法也只能处理包含 NUL 终结字符的单字符串;假如要转化含有多个 NULL 字符 串,您得额外做一些工作才行。在 CString 中内嵌的 NULL 字符通常表现不尽如人意,应该尽量避免。  根据 C/C++ 规则,假如您有一个 LPWSTR,那么他别无选择,只能和 LPCWSTR 参数匹配。在 Unicode 模式下,他的构造函数是:
CString::CString(LPCTSTR);
正如上面所表示的,在 ANSI 模式下,他有一个特别的构造函数:
CString::CString(LPCWSTR);
  他会调用一个内部的函数将 Unicode 字符串转换成 ANSI 字符串。(在Unicode模式下,有一个专门的构造函数,该函数有一个参数是LPCSTR类型——一个8位 ANSI 字符串 指针,该函数将他加宽为 Unicode 的字符串!)再次强调:一定要检查 BSTR 的值是否为 NULL。  另外更有一个问题,正如上文提到的:BSTRs能够含有多个内嵌的NULL字符,但是 CString 的构造函数只能处理某个串中单个 NULL 字符。 也就是说,假如串中含有嵌入的 NUL字节,CString 将会计算出错误的串长度。您必须自己处理他。假如您看看 strcore.cpp 中的构造函数,您会发现 他们都调用了lstrlen,也就是计算字符串的长度。  注意从 Unicode 到 ANSI 的转换使用带专门参数的 ::WideCharToMultiByte,假如您不想使用这种默认的转换方式,则必须编写自己的转化代码。  假如您在 UNICODE 模式下编译代码,您能够简单地写成:
CString convert(BSTR b)
&&& if(b == NULL)
&&&&&&& return CString(_T(""));
&&& CString s(b); // in UNICODE mode
  假如是 ANSI 模式,则需要更复杂的过程来转换。注意这个代码使用和 ::WideCharToMultiByte 相同的参数值。所以您 只能在想要改变这些参数进行转换时使用该技术。例如,指定不同的默认字符,不同的标志集等。
CString convert(BSTR b)
&&& if(b == NULL)
// empty for NULL BSTR
#ifdef UNICODE
&&& 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();
  我并不担心假如 BSTR 包含没有映射到 8 位字符集的 Unicode 字符时会发生什么,因为我指定了::WideCharToMultiByte 的最后两个参数为 NULL。这就是您可能需要改变的地方。 7、VARIANT 型转化成 CString 型  事实上,我从来没有这么做过,因为我没有用 COM/OLE/ActiveX 编写过程式。但是我在microsoft.public.vc.mfc 新闻组上看到了 Robert Quirk 的一篇帖子谈到了这种转化,我觉得把他的文章包含在我的文章里是不太好的做法,所以在这里多做一些解释和演示。假如和他的文章有相孛的地方可能是我的疏忽。  VARIANT 类型经常用来给 COM 对象传递参数,或接收从 COM 对象返回的值。您也能自己编写返回 VARIANT 类型的方法,函数返回什么类型 依赖可能(并且常常)方法的输入参数(比如,在自动化操作中,依赖和您调用哪个方法。IDispatch::Invoke 可能返回(通过其一个参数)一个 包含有BYTE、WORD、float、double、date、BSTR 等等 VARIANT 类型的结果,(详见 MSDN 上的 VARIANT 结构的定义)。在下面的例子中,假设 类型是个BSTR的变体,也就是说在串中的值是通过 bsrtVal 来引用,其长处是在 ANSI 应用中,有一个构造函数会把 LPCWCHAR 引用的值转换为一个 CString(见 BSTR-to-CString 部分)。在 Unicode 模式中,将成为标准的 CString 构造函数,参见对缺省::WideCharToMultiByte 转换的告诫,连同您觉得是否能够接受(大多数情况下,您会满意的)。
VARIANT vaD
vaData = m_com.YourMethodHere();
ASSERT(vaData.vt == VT_BSTR);
CString strData(vaData.bstrVal);
您还能够根据 vt 域的不同来建立更通用的转换例程。为此您可能会考虑:
CString VariantToString(VARIANT * va)
&&& 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);
&&&&&& case VT_I4 | VT_BYREF:
&&&&&&&&& s.Format(_T("%d"), *va-&plVal);
&&&&&& case VT_R8:
&&&&&&&&& s.Format(_T("%f"), va-&dblVal);
&&&&&& ... 剩下的类型转换由读者自己完成
&&&&&& default:
&&&&&&&&& ASSERT(FALSE); // unknown VARIANT type (this ASSERT is optional)
&&&&&&&&& return CString("");
&&&&& } /* vt */
8、载入字符串表资源  假如您想创建一个容易进行语言版本移植的应用程式,您就不能在您的源代码中直接包含本土语言字符串 (下面这些例子我用的语言都是英语,因为我的本土语是英语),比如下面这种写法就很糟:
CString s = "There is an error";
  您应该把您任何特定语言的字符串单独摆放(调试信息、在发布版本中不出现的信息除外)。这意味着向下面这样写比较好:
s.Format(_T("%d - %s"), code, text);
  在您的程式中,文字字符串不是语言敏感的。不管怎样,您必须很小心,不要使用下面这样的串:
// fmt is "Error in %s file %s"
// readorwrite is "reading" or "writing"
s.Format(fmt, readorwrite, filename);
  这是我的切身体会。在我的第一个国际化的应用程式中我犯了这个错误,尽管我懂德语,知道在德语的语法中动词放在句子的最后面,我们的德国方面的发行人还是苦苦的抱怨他们不得不提取那些不可思议的德语错误提示信息然后重新格式化以让他们能正常工作。比较好的办法(也是我现在使用的办法)是使用两个字符串,一个用 于读,一个用于写,在使用时加载合适的版本,使得他们对字符串参数是非敏感的。也就是说加载整个格式,而不是加载串“reading”,“writing”:
// fmt is "Error in reading file %s"
// "Error in writing file %s"
s.Format(fmt, filename);
  一定要注意,假如您有好几个地方需要替换,您一定要确保替换后句子的结构不会出现问题,比如在英语中,能够是主语-宾语,主语-谓语,动词-宾语的结构等等。  在这里,我们并不讨论 FormatMessage,其实他比 sprintf/Format 还要有优势,但是不太容易和CString 结合使用。解决这种问题的办法就是我们按照参数出现在参数表中的位置给参数取名字,这样在您输出的时候就不会把他们的位置排错了。  接下来我们讨论我们这些单独的字符串放在什么地方。我们能够把字符串的值放入资源文档中的一个称为 STRINGTABLE 的段中。过程如下:首先使用 Visual Studio 的资源编辑器创建一个字符串,然后给每一个字符串取一个ID,一般我们给他取名字都以 IDS_开头。所以假如您有一个信息,您能够创建一个字符串资源然后取名为 IDS_READING_FILE,另外一个就取名为 IDS_WRITING_FILE。他们以下面的形式出现在您的 .rc 文档中:
STRINGTABLE
IDS_READING_FILE "Reading file %s"
IDS_WRITING_FILE "Writing file %s"
注意:这些资源都以 Unicode 的格式保存,不管您是在什么环境下编译。他们在Win9x系统上也是以Unicode 的形式存在,虽然 Win9x 不能真正处理 Unicode。然后您能够这样使用这些资源:// 在使用资源串表之前,程式是这样写的:
&&&&& if(...)
&&&&&&& fmt = "Reading file %s";
&&&&&& fmt = "Writing file %s";
&&& // much later
& s.Format(fmt, filename);
// 使用资源串表之后,程式这样写:
&&&&&&& if(...)
&&&&&&&&&& fmt.LoadString(IDS_READING_FILE);
&&&&&&& else
&&&&&&&&&& fmt.LoadString(DS_WRITING_FILE);
&&&&& // much later
&&& s.Format(fmt, filename);
  现在,您的代码能够移植到任何语言中去。LoadString 方法需要一个字符串资源的 ID 作为参数,然后他从 STRINGTABLE 中取出他对应的字符串,赋值给 CString 对象。 CString 对象的构造函数更有一个更加聪明的特征能够简化 STRINGTABLE 的使用。这个用法在 CString::CString 的文档中没有指出,但是在 构造函数的示例程式中使用了。(为什么这个特性没有成为正式文档的一部分,而是放在了一个例子中,我记不得了!)——【译者注:从这句话看,作者可能是CString的设计者。其实前面更有一句类似的话。说他没有对使用GetBuffer(0)获得的指针指向的地址是否可读做有效性检查 】。这个特征就是:假如您将一个字符串资源的ID强制类型转换为 LPCTSTR,将会隐含调用 LoadString。因此,下面两个构造字符串的例子具备相同的效果,而且其 ASSERT 在debug模式下不会被触发:
s.LoadString(IDS_WHATEVER);
CString t( (LPCTSTR)IDS_WHATEVER );
ASSERT(s == t);//不会被触发,说明s和t是相同的。
  现在,您可能会想:这怎么可能工作呢?我们怎么能把 STRINGTABLE ID 转化成一个指针呢?很简单:任何的字符串 ID 都在1~65535这个范围内,也就是说,他任何的高位都是0,而我们在程式中所使用的指针是不可能小于65535的,因为程式的低 64K 内存永远也不可能存在的,假如您试图访问0x到0x0000FFFF之间的内存,将会引发一个内存越界错误。所以说1~65535的值不可能是个内存地址,所以我们能够用这些值来作为字符串资源的ID。  我倾向于使用 MAKEINTRESOURCE 宏显式地做这种转换。我认为这样能够让代码更加易于阅读。这是个只适合在 MFC 中使用的标准宏。您要记住,大多数的方法即能够接受一个 UINT 型的参数,也能够接受一个 LPCTSTR 型的参数,这是依赖 C++ 的重载功能做到的。C++重载函数带来的 弊端就是造成任何的强制类型转化都需要显示声明。同样,您也能够给很多种结构只传递一个资源名。
s.LoadString(IDS_WHATEVER);
CString t( MAKEINTRESOURCE(IDS_WHATEVER));
ASSERT(s == t);
  告诉您吧:我不但只是在这里鼓吹,事实上我也是这么做的。在我的代码中,您几乎不可能找到一个字符串,当然,那些只是偶然在调试中出现的或和语言无关的字符串除外。9、CString 和临时对象  这是出现在 microsoft.public.vc.mfc 新闻组中的一个小问题,我简单的提一下,这个问题是有个程式员需要往注册表中写入一个字符串,他写道:  我试着用 RegSetValueEx() 配置一个注册表键的值,但是他的结果总是令我困惑。当我用char[]声明一个变量时他能正常工作,但是当我用 CString 的时候,总是得到一些垃圾:"&Y&Y&Y&Y...&Y&Y&Y&Y&Y&Y"为了确认是不是我的 CString 数据出了问题,我试着用 GetBuffer,然后强制转化成 char*,LPCSTR。GetBuffer 返回的值是正确的,但是当我把他赋值给 char* 时,他就变成垃圾了。以下是我的程式段:
char* szName = GetName().GetBuffer(20);
RegSetValueEx(hKey, "Name", 0, REG_SZ,
&&&&&&&&&&&& (CONST BYTE *) szName,
&&&&&&&&&&&& strlen (szName + 1));
这个 Name 字符串的长度小于 20,所以我不认为是 GetBuffer 的参数的问题。真让人困惑,请帮帮我。亲爱的 Frustrated,您犯了一个相当微妙的错误,聪明反被聪明误,正确的代码应该象下面这样:
CString Name = GetName();
RegSetValueEx(hKey, _T("Name"), 0, REG_SZ,
&&&&&&&&&&&&&&&&&&& (CONST BYTE *) (LPCTSTR)Name,
&&&&&&&&&&&&&&&&&&& (Name.GetLength() + 1) * sizeof(TCHAR));
  为什么我写的代码能行而您写的就有问题呢?主要是因为当您调用 GetName 时返回的 CString 对象是个临时对象。参见:《C++ Reference manual》§12.2  在一些环境中,编译器有必要创建一个临时对象,这样引入临时对象是依赖于实现的。假如编译器引入的这个临时对象所属的类有构造函数的话,编译器要确保这个类的构造函数被调用。同样的,假如这个类声明有析构函数的话,也要确保这个临时对象的析构函数被调用。  编译器必须确保这个临时对象被销毁了。被销毁的确切地点依赖于实现.....这个析构函数必须在退出创建该临时对象的范围之前被调用。  大部分的编译器是这样设计的:在临时对象被创建的代码的下一个执行步骤处隐含调用这个临时对象的析构函数,实现起来,一般都是在下一个分号处。因此,这个 CString 对象在 GetBuffer 调用之后就被析构了(顺便提一句,您没有理由给 GetBuffer 函数传递一个参数,而且没有使用ReleaseBuffer 也是不对的)。所以 GetBuffer 本来返回的是指向这个临时对象中字符串的地址的指针,但是当这个临时对象被析构后,这块内存就被释放了。然后 MFC 的调试内存分配器会重新为这块内存全部填上 0xDD,显示出来刚好就是“&Y”符号。在这个时候您向注册表中写数据,字符串的内容当然全被破坏了。  我们不应该立即把这个临时对象转化成 char* 类型,应该先把他保存到一个 CString 对象中,这意味着把临时对象复制了一份,所以当临时的 CString 对象被析构了之后,这个 CString 对象中的值依然保存着。这个时候再向注册表中写数据就没有问题了。  此外,我的代码是具备 Unicode 意识的。那个操作注册表的函数需要一个字节大小,使用lstrlen(Name+1) 得到的实际结果对于 Unicode 字符来说比 ANSI 字符要小一半,而且他也不能从这个字符串的第二个字符起开始计算,也许您的本意是 lstrlen(Name) + 1(OK,我承认,我也犯了同样的错误!)。不论如何,在 Unicode 模式下,任何的字符都是2个字节大小,我们需要处理这个问题。微软的文档令人惊讶地对此保持缄默:REG_SZ 的值究竟是以字节计算还是以字符计算呢?我们假设他指的是以字节为单位计算,您需要对您的代码做一些修改来计算这个字符串所含有的字节大小。10、CString 的效率  CString 的一个问题是他确实掩藏了一些低效率的东西。从另外一个方面讲,他也确实能够被实现得更加高效,您可能会说下面的代码:
CString s = SomeCString1;
s += SomeCString2;
s += SomeCString3;
s += SomeCString4;
比起下面的代码来,效率要低多了:
char s[1024];
lstrcpy(s, SomeString1);
lstrcat(s, SomeString2);
lstrcat(s, SomeString 3);
lstrcat(s, ",");
lstrcat(s, SomeString4);
  总之,您可能会想,首先,他为 SomeCString1 分配一块内存,然后把 SomeCString1 复制到里面,然后发现他要做一个连接,则重新分配一块新的足够大的内存,大到能够放下当前的字符串加上SomeCString2,把内容复制到这块内存 ,然后把 SomeCString2 连接到后面,然后释放第一块内存,并把指针重新指向新内存。然后为每个字符串重复这个过程。把这 4 个字符串连接起来效率多低啊。事实上,在很多情况下根本就无需复制源字符串(在 += 操作符左边的字符串)。  在 VC++6.0 中,Release 模式下,任何的 CString 中的缓存都是按预定义量子分配的。所谓量子,即确定为 64、128、256 或 512 字节。这意味着除非字符串很长,连接字符串的操作实际上就是 strcat 经过优化后的版本(因为他知道本地的字符串应该在什么地方结束,所以无需寻找字符串的结尾;只需要把内存中的数据拷贝到指定的地方即可)加上重新计算字符串的长度。所以他的执行效率和纯 C 的代码是相同的,但是他更容易写、更容易维护和更容易理解。  假如您还是不能确定究竟发生了怎样的过程,请看看 CString 的源代码,strcore.cpp,在您 vc98的安装目录的 mfc/src 子目录中。看看 ConcatInPlace 方法,他被在任何的 += 操作符中调用。啊哈!难道 CString 真的这么"高效"吗?比如,假如我创建
CString cat("Mew!");
  然后我并不是得到了一个高效的、精简的5个字节大小的缓冲区(4个字符加一个结束字符),系统将给我分配64个字节,而其中59个字节都被浪费了。  假如您也是这么想的话,那么就请准备好接受再教育吧。可能在某个地方某个人给您讲过尽量使用少的空间是件好事情。不错,这种说法的确正确,但是他忽略了事实中一个很重要的方面。  假如您编写的是运行在16K EPROMs下的嵌入式程式的话,您有理由尽量少使用空间,在这种环境下,他能使您的程式更健壮。但是在 500MHz, 256MB的机器上写 Windows 程式,假如您还是这么做,他只会比您认为的“低效”的代码运行得更糟。  举例来说。字符串的大小被认为是影响效率的首要因素,使字符串尽可能小能够提高效率,反之则降低效率,这是大家一贯的想法。但是这种想法是不对的,精确的内存分配的后果要在程式运行了好几个小时后才能体现得出来,那时,程式的堆中将充满小片的内存,他们太小以至于不能用来做任何事,但是他们增加了您程式的内存用量,增加了内存页面交换的次数,当页面交换的次数增加到系统能够忍受的上限,系统则会为您的程式分配更多的页面,直到您的程式占用了任何的可用内存。由此可见,虽然内存碎片是决定效率的次要因素,但正是这些因素实际控制了系统的行为,最终,他损害了系统的可靠性,这是令人无法接受的。  记住,在 debug 模式下,内存往往是精确分配的,这是为了更好的排错。  假设您的应用程式通常需要连续工作好几个月。比如,我常打开 VC++,Word,PowerPoint,Frontpage,Outlook Express,Forté Agent,Internet Explorer和其他的一些程式,而且通常不关闭他们。我曾夜以继日地连续用 PowerPoint 工作了好几天(反之,假如您不幸不得不使用像 Adobe FrameMaker 这样的程式的话,您将会体会到可靠性的重要;这个程式机会每天都要崩溃4~6次,每次都是因为用完了任何的空间并填满我任何的交换页面)。所以精确内存分配是不可取的,他会危及到系统的可靠性,并引起应用程式崩溃。  按量子的倍数为字符串分配内存,内存分配器就能够回收用过的内存块,通常这些回收的内存块马上就能够被其他的 CString 对象重新用到,这样就能够确保碎片最少。分配器的功能加强了,应用程式用到的内存就能尽可能保持最小,这样的程式就能够运行几个星期或几个月而不出现问题。  题外话:很多年以前,我们在 CMU 写一个交互式系统的时候,一些对内存分配器的研究显示出他往往产生很多内存碎片。Jim Mitchell,现在他在 Sun Microsystems 工作,那时侯他创造了一种内存分配器,他保留了一个内存分配状况的运行时统计表,这种技术和当时的主流分配器所用的技术都不同,且较为领先。当一个内存块需要被分割得比某一个值小的话,他并不分割他,因此能够避免产生太多小到什么事都干不了的内存碎片。事实上他在内存分配器中使用了一个浮动指针,他认为:和其让指令做长时间的存取内存操作,还不如简单的忽略那些太小的内存块而只做一些浮动指针的操作。(His observation was that the long-term saving in instructions by not having to ignore unusable small storage chunks far and away exceeded the additional cost of doing a few floating point operations on an allocation operation.)他是对的。  永远不要认为所谓的“最优化”是建立在每一行代码都高速且节省内存的基础上的,事实上,高速且节省内存应该是在一个应用程式的整体水平上考虑的。在软件的整体水平上,只使用最小内存的字符串分配策略可能是最糟糕的一种方法。  假如您认为优化是您在每一行代码上做的那些努力的话,您应该想一想:在每一行代码中做的优化很少能真正起作用。您能够看我的另一篇关于优化问题的文章《Your Worst Enemy for some thought-provoking ideas》。  记住,+= 运算符只是一种特例,假如您写成下面这样:
CString s = SomeCString1 + SomeCString2 + SomeCString3 + "," + SomeCString4;
则每一个 + 的应用会造成一个新的字符串被创建和一次复制操作。
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:41721次
排名:千里之外
原创:35篇
转载:25篇
评论:12条
(5)(4)(3)(2)(2)(1)(11)(8)(21)(4)

我要回帖

更多关于 虚拟内存 的文章

 

随机推荐