2006年09月11日

堆和栈的区别

一、预备知识—程序的内存分配
一个由C/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其
操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回
收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的
全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另
一块区域。 – 程序结束后由系统释放。
4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。

二、例子程序
这是一个前辈写的,非常详细
//main.cpp
int a = 0; 全局初始化区
char *p1; 全局未初始化区
main()
{
int b; 栈
char s[] = "abc"; 栈
char *p2; 栈
char *p3 = "123456"; 123456\0在常量区,p3在栈上。
static int c =0; 全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"
优化成一个地方。
}

二、堆和栈的理论知识
2.1申请方式
stack:
由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空

heap:
需要程序员自己申请,并指明大小,在c中malloc函数
如p1 = (char *)malloc(10);
在C++中用new运算符
如p2 = new char[10];
但是注意p1、p2本身是在栈中的。

2.2
申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢
出。
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,
会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表
中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的
首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。
另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部
分重新放入空闲链表中。

2.3申请大小的限制
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意
思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有
的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将
提示overflow。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储
的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小
受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

 

2.4申请效率的比较:
栈由系统自动分配,速度较快。但程序员是无法控制的。
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是
直接在进程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活。
 

2.5堆和栈中的存储内容
栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可
执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈
的,然后是函数中的局部变量。注意静态变量是不入栈的。
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地
址,也就是主函数中的下一条指令,程序由该点继续运行。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。

2.6存取效率的比较

char s1[] = "aaaaaaaaaaaaaaa";
char *s2 = "bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa是在运行时刻赋值的;
而bbbbbbbbbbb是在编译时就确定的;
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
比如:
#include
void main()
{
char a = 1;
char c[] = "1234567890";
char *p ="1234567890";
a = c[1];
a = p[1];
return;
}
对应的汇编代码
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到
edx中,再根据edx读取字符,显然慢了。

2.7小结:
堆和栈的区别可以用如下的比喻来看出:
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就
走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自
由度小。
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由
度大。 (经典!)

 

2006年09月06日

昨天无意中发现ifstream的unget()方法在VC6下居然不能运行正常,在VC7下能正常运行!

LV_ITEM lvItem;
lvItem.mask=LVIF_IMAGE;
lvItem.iImage=1;   // 图标在ImageList里的位置(例如第二个图标)
lvItem.iItem = nItem;
m_list.SetItem(&lvItem); //更改成功

这段代码在Debug下运行正常,但在Release下就不正常了,初看起来没什么毛病,其实这也是一个Debug和Release下变量初始化不同的典型例子。只需要改为:

LV_ITEM lvItem = {0};

以前我只注意到BOOL型变量的初始化问题,看来以后也要注意结构变量初始化了!

C++字符串完全指南 – Win32字符编码

关键词字符串    c++                                          

 

前言

字符串的表现形式各异,象TCHAR,std::string,BSTR等等,有时还会见到怪怪的用_tcs起头的宏。这个指南的目的就是说明各种字符串类型及其用途,并说明如何在必要时进行类型的相互转换。

在指南的第一部分,介绍三种字符编码格式。理解编码的工作原理是致为重要的。即使你已经知道字符串是一个字符的数组这样的概念,也请阅读本文,它会让你明白各种字符串类之间的关系。

指南的第二部分,将阐述各个字符串类,什么时候使用哪种字符串类,及其相互转换。

字符串基础 – ASCII, DBCS, Unicode

所有的字符串类都起源于C语言的字符串,而C语言字符串则是字符的数组。首先了解一下字符类型。有三种编码方式和三种字符类型。

第一种编码方式是单字节字符集,称之为SBCS,它的所有字符都只有一个字节的长度。ASCII码就是SBCS。SBCS字符串由一个零字节结尾。

第二种编码方式是多字节字符集,称之为MBCS,它包含的字符中有单字节长的字符,也有多字节长的字符。Windows用到的MBCS只有二种字符类型,单字节字符和双字节字符。因此Windows中用得最多的字符是双字节字符集,即DBCS,通常用它来代替MBCS。

在DBCS编码中,用一些保留值来指明该字符属于双字节字符。例如,Shift-JIS(通用日语)编码中,值0×81-0×9F 和 0xE0-0xFC 的意思是:“这是一个双字节字符,下一个字节是这个字符的一部分”。这样的值通常称为前导字节(lead byte),总是大于0×7F。前导字节后面是跟随字节(trail byte)。DBCS的跟随字节可以是任何非零值。与SBCS一样,DBCS字符串也由一个零字节结尾。

第三种编码方式是Unicode。Unicode编码标准中的所有字符都是双字节长。有时也将Unicode称为宽字符集(wide characters),因为它的字符比单字节字符更宽(使用更多内存)。注意,Unicode不是MBCS – 区别在于MBCS编码中的字符长度是不同的。Unicode字符串用二个零字节字符结尾(一个宽字符的零值编码)。

单字节字符集是拉丁字母,重音文字,用ASCII标准定义,用于DOS操作系统。双字节字符集用于东亚和中东语言。Unicode用于COM和Windows NT内部。

读者都很熟悉单字节字符集,它的数据类型是char。双字节字符集也使用char数据类型(双字节字符集中的许多古怪处之一)。Unicode字符集用wchar_t数据类型。Unicode字符串用L前缀起头,如:

  wchar_t  wch = L’1′;      // 2 个字节, 0×0031

  wchar_t* wsz = L"Hello";  // 12 个字节, 6 个宽字符

字符串的存储

单字节字符串顺序存放各个字符,并用零字节表示字符串结尾。例如,字符串"Bob"的存储格式为:

Unicode编码中,L"Bob"的存储格式为:

用0×0000 (Unicode的零编码)结束字符串。

DBCS 看上去有点象SBCS。以后我们会看到在串处理和指针使用上是有微妙差别的。字符串"日本语" (nihongo) 的存储格式如下(用LB和TB分别表示前导字节和跟随字节):

注意,"ni"的值不是WORD值0xFA93。值93和FA顺序组合编码为字符"ni"。(在高位优先CPU中,存放顺序正如上所述)。

字符串处理函数

C语言字符串处理函数,如strcpy(), sprintf(), atol()等只能用于单字节字符串。在标准库中有只用于Unicode字符串的函数,如wcscpy(), swprintf(), _wtol()。

微软在C运行库(CRT)中加入了对DBCS字符串的支持。对应于strxxx()函数,DBCS使用_mbsxxx()函数。在处理DBCS字符串(如日语,中文,或其它DBCS)时,就要用_mbsxxx()函数。这些函数也能用于处理SBCS字符串(因为DBCS字符串可能就只含有单字节字符)。

现在用一个示例来说明字符串处理函数的不同。如有Unicode字符串L"Bob":

x86 CPU的排列顺序是低位优先(little-endian)的,值0×0042的存储顺序为42 00。这时如用strlen()函数求字符串的长度就发生问题。函数找到第一个字节42,然后是00,意味着字符串结尾,于是返回1。反之,用wcslen()函数求"Bob"的长度更糟糕。wcslen()首先找到0×6F42,然后是0×0062,以后就在内存缓冲内不断地寻找00 00直至发生一般性保护错(GPF)。

strxxx()及其对应的_mbsxxx()究竟是如何运作的?二者之间的不同是非常重要的,直接影响到正确遍历DBCS字符串的方法。下面先介绍字符串遍历,然后再回来讨论strxxx()和 _mbsxxx()。

 

 

字符串遍历

我们中的大多数人都是从SBCS成长过来的,都习惯于用指针的 ++ 和 — 操作符来遍历字符串,有时也使用数组来处理字符串中的字符。这二种方法对于SBCS 和 Unicode 字符串的操作都是正确无误的,因为二者的字符都是等长的,编译器能够的正确返回我们寻求的字符位置。

但对于DBCS字符串就不能这样了。用指针访问DBCS字符串有二个原则,打破这二个原则就会造成错误。

1. 不可使用 ++ 算子,除非每次都检查是否为前导字节。

2. 绝不可使用 — 算子来向后遍历。

先说明原则2,因为很容易找到一个非人为的示例。假设,有一个配制文件,程序启动时要从安装路径读取该文件,如:C:\Program Files\MyCoolApp\config.bin。文件本身是正常的。

假设用以下代码来配制文件名:

bool GetConfigFileName ( char* pszName, size_t nBuffSize )
{
char szConfigFilename[MAX_PATH];
    // 这里从注册表读取文件的安装路径,假设一切正常。
    // 如果路径末尾没有反斜线,就加上反斜线。
    // 首先,用指针指向结尾零:
char* pLastChar = strchr ( szConfigFilename, '\0' );
    // 然后向后退一个字符:
    pLastChar--;  
    if ( *pLastChar != '\\' )
        strcat ( szConfigFilename, "\\" );
    // 加上文件名:
    strcat ( szConfigFilename, "config.bin" );
    // 如果字符串长度足够,返回文件名:
    if ( strlen ( szConfigFilename ) >= nBuffSize )
        return false;
    else
        {
        strcpy ( pszName, szConfigFilename );
        return true;
        }
}

这段代码的保护性是很强的,但用到DBCS字符串还是会出错。假如文件的安装路径用日语表达:C:\ヨウユソ,该字符串的内存表达为:

这时用上面的GetConfigFileName()函数来检查文件路径末尾是否含有反斜线就会出错,得到错误的文件名。

错在哪里?注意上面的二个十六进制值0×5C(蓝色)。前面的0×5C是字符"\",后面则是字符值83 5C,代表字符"ソ"。可是函数把它误认为反斜线了。

正确的方法是用DBCS函数将指针指向恰当的字符位置,如下所示:

bool FixedGetConfigFileName ( char* pszName, size_t nBuffSize )
{
char szConfigFilename[MAX_PATH];
    // 这里从注册表读取文件的安装路径,假设一切正常。
    // 如果路径末尾没有反斜线,就加上反斜线。
    // 首先,用指针指向结尾零:
char* pLastChar = _mbschr ( szConfigFilename, '\0' );
    // 然后向后退一个双字节字符:
    pLastChar = CharPrev ( szConfigFilename, pLastChar );
    if ( *pLastChar != '\\' )
        _mbscat ( szConfigFilename, "\\" );
    // 加上文件名:
    _mbscat ( szConfigFilename, "config.bin" );
    // 如果字符串长度足够,返回文件名:
    if ( _mbslen ( szInstallDir ) >= nBuffSize )
        return false;
    else
        {
        _mbscpy ( pszName, szConfigFilename );
        return true;
        }
} 

这个改进的函数用CharPrev() API 函数将指针pLastChar向后移动一个字符。如果字符串末尾的字符是双字节字符,就向后移动2个字节。这时返回的结果是正确的,因为不会将字符误判为反斜线。

 

现在可以想像到第一原则了。例如,要遍历字符串寻找字符":",如果不使用CharNext()函数而使用++算子,当跟随字节值恰好也是":"时就会出错。

与原则2相关的是数组下标的使用:

 2a. 绝不可在字符串数组中使用递减下标。

出错原因与原则2相同。例如,设置指针pLastChar为:

char* pLastChar = &szConfigFilename [strlen(szConfigFilename) - 1];

结果与原则2的出错一样。下标减1就是指针向后移动一个字节,不符原则2。

再谈strxxx() _mbsxxx()

现在可以清楚为什么要用 _mbsxxx() 函数了。strxxx() 函数不认识DBCS字符而 _mbsxxx()认识。如果调用strrchr("C:\\", ‘\\’)函数可能会出错,但 _mbsrchr()认识双字节字符,所以能返回指向最后出现反斜线字符的指针位置。

最后提一下strxxx() 和 _mbsxxx() 函数族中的字符串长度测量函数,它们都返回字符串的字节数。如果字符串含有3个双字节字符,_mbslen()将返回6。而Unicode的函数返回的是wchar_ts的数量,如wcslen(L"Bob") 返回3

C++字符串完全指南 – Win32字符编码(二)
翻译:连波
15/11/2002
URL: http://www.zdnet.com.cn/developer/tech/story/0,2000081602,39098306,00.htm

 

Win32 API中的MBCS Unicode

API的二个字符集

也许你没有注意到,Win32的API和消息中的字符串处理函数有二种,一种为MCBS字符串,另一种为Unicode字符串。例如,Win32中没有SetWindowText()这样的接口,而是用SetWindowTextA()和 SetWindowTextW()函数。后缀A (表示ANSI)指明是MBCS函数,后缀W(表示宽字符)指明是Unicode函数。

编写Windows程序时,可以选择用MBCS或Unicode API接口函数。用VC AppWizards向导时,如果不修改预处理器设置,缺省使用的是MBCS函数。但是在API接口中没有SetWindowText()函数,该如何调用呢?实际上,在winuser.h头文件中做了以下定义:

BOOL WINAPI SetWindowTextA ( HWND hWnd, LPCSTR lpString );
BOOL WINAPI SetWindowTextW ( HWND hWnd, LPCWSTR lpString );
#ifdef UNICODE
 #define SetWindowText  SetWindowTextW
#else
 #define SetWindowText  SetWindowTextA
#endif

编写MBCS应用时,不必定义UNICODE,预处理为:

#define SetWindowText  SetWindowTextA

然后将SetWindowText()处理为真正的API接口函数SetWindowTextA() (如果愿意的话,可以直接调用SetWindowTextA() 或SetWindowTextW()函数,不过很少有此需要)。

如果要将缺省应用接口改为Unicode,就到预处理设置的预处理标记中去掉 _MBCS标记,加入UNICODE 和 _UNICODE (二个标记都要加入,不同的头文件使用不同的标记)。不过,这时要处理普通字符串反而会遇到问题。如有代码:

HWND hwnd = GetSomeWindowHandle();
char szNewText[] = "we love Bob!";
SetWindowText ( hwnd, szNewText );

编译器将"SetWindowText"置换为"SetWindowTextW"后,代码变为:

HWND hwnd = GetSomeWindowHandle();
char szNewText[] = "we love Bob!";
SetWindowTextW ( hwnd, szNewText );

看出问题了吧,这里用一个Unicode字符串处理函数来处理单字节字符串。

第一种解决办法是使用宏定义:
HWND hwnd = GetSomeWindowHandle();
#ifdef UNICODE
 wchar_t szNewText[] = L"we love Bob!";
#else
 char szNewText[] = "we love Bob!";
#endif
SetWindowText ( hwnd, szNewText );

要对每一个字符串都做这样的宏定义显然是令人头痛的。所以用TCHAR来解决这个问题:

TCHAR的救火角色

TCHAR 是一种字符类型,适用于MBCS 和 Unicode二种编码。程序中也不必到处使用宏定义。

TCHAR的宏定义如下:

#ifdef UNICODE
 typedef wchar_t TCHAR;
#else
 typedef char TCHAR;
#endif

所以,TCHAR中在MBCS程序中是char类型,在Unicode中是 wchar_t 类型。

对于Unicode字符串,还有个 _T() 宏,用于解决 L 前缀:

#ifdef UNICODE
 #define _T(x) L##x
#else
 #define _T(x) x
#endif

## 是预处理算子,将二个变量粘贴在一起。不管什么时候都对字符串用 _T 宏处理,这样就可以在Unicode编码中给字符串加上L前缀,如:

TCHAR szNewText[] = _T("we love Bob!");

SetWindowTextA/W 函数族中还有其它隐藏的宏可以用来代替strxxx() 和 _mbsxxx() 字符串函数。例如,可以用 _tcsrchr 宏取代strrchr(),_mbsrchr(),或 wcsrchr()函数。_tcsrchr 根据编码标记为_MBCS 或 UNICODE,将右式函数做相应的扩展处理。宏定义方法类似于SetWindowText。

不止strxxx()函数族中有TCHAR宏定义,其它一些函数中也有。例如,_stprintf (取代sprintf()和swprintf()),和 _tfopen (取代fopen() 和 _wfopen())。MSDN的全部宏定义在"Generic-Text Routine Mappings"栏目下。

String 和 TCHAR 类型定义

Win32 API 文件中列出的函数名都是通用名(如"SetWindowText"),所有的字符串都按照TCHAR类型处理。(只有XP除外,XP只使用Unicode类型)。下面是MSDN给出的常用类型定义:

 

类型

MBCS 编码中的意义

Unicode 编码中的意义

WCHAR

wchar_t

wchar_t

LPSTR

zero-terminated string of char (char*)

zero-terminated string of char (char*)

LPCSTR

constant zero-terminated string of char (constchar*)

constant zero-terminated string of char (constchar*)

LPWSTR

zero-terminated Unicode string (wchar_t*)

zero-terminated Unicode string (wchar_t*)

LPCWSTR

constant zero-terminated Unicode string (const wchar_t*)

constant zero-terminated Unicode string (const wchar_t*)

TCHAR

char

wchar_t

LPTSTR

zero-terminated string of TCHAR (TCHAR*)

zero-terminated string of TCHAR (TCHAR*)

LPCTSTR

constant zero-terminated string of TCHAR (const TCHAR*)

constant zero-terminated string of TCHAR (const TCHAR*)

何时使用TCHAR 和Unicode

可能会有疑问:“为什么要用Unicode?我一直用的都是普通字符串。”

在三种情况下要用到Unicode:

  1. 程序只运行于Windows NT。
  2. 处理的字符串长于MAX_PATH定义的字符数。
  3. 程序用于Windows XP中的新接口,那里没有A/W版本之分。

大部分Unicode API不可用于Windows 9x。所以如果程序要在Windows 9x上运行的话,要强制使用MBCS API (微软推出一个可运行于Windows 9x的新库,叫做Microsoft Layer for Unicode。但我没有试用过,无法说明它的好坏)。相反,NT内部全部使用Unicode编码,使用Unicode API可以加速程序运行。每当将字符串处理为MBCS API时,操作系统都会将字符串转换为Unicode并调用相应的Unicode API 函数。对于返回的字符串,操作系统要做同样的转换。尽管这些转换经过了高度优化,模块尽可能地压缩到最小,但毕竟会影响到程序的运行速度。

NT允许使用超长文件名(长于MAX_PATH 定义的260),但只限于Unicode API使用。Unicode API的另外一个优点是程序能够自动处理输入的文字语言。用户可以混合输入英文,中文和日文作为文件名。不必使用其它代码来处理,都按照Unicode编码方式处理。

最后,作为Windows 9x的结局,微软似乎抛弃了MBCS API。例如,SetWindowTheme() 接口函数的二个参数只支持Unicode编码。使用Unicode编码省却了MBCS与Unicode之间的转换过程。

如果程序中还没有使用到Unicode编码,要坚持使用TCHAR和相应的宏。这样不但可以长期保持程序中DBCS编码的安全性,也利于将来扩展使用到Unicode编码。那时只要改变预处理中的设置即可!

C++字符串完全指南(2) – 各种字符串类(一)
翻译:连波
19/11/2002
URL: http://www.zdnet.com.cn/developer/tech/story/0,2000081602,39098621,00.htm

 

前言

C语言的字符串容易出错,难以管理,并且往往是黑客到处寻找的目标。于是,出现了许多字符串包装类。可惜,人们并不很清楚什么情况下该用哪个类,也不清楚如何将C语言字符串转换到包装类。

本文涉及到Win32 API,MFC,STL,WTL和Visual C++运行库中使用到的所有的字符串类型。说明各个类的用法,如何构造对象,如何进行类转换等等。Nish为本文提供了Visual C++ 7的managed string 类的用法。

阅读本文之前,应完全理解本指南第一部分中阐述的字符类型和编码。

字符串类的首要原则:

不要随便使用类型强制转换,除非转换的类型是明确由文档规定的。

之所以撰写字符串指南这二篇文章,是因为常有人问到如何将X类型的字符串转换到Z类型。提问者使用了强制类型转换(cast),但不知道为什么不能转换成功。各种各样的字符串类型,特别是BSTR,在任何场合都不是三言二语可以讲清的。因此,我以为这些提问者是想让强制类型转换来处理一切。

除非明确规定了转换算子,不要将任何其它类型数据强制转换为string。一个字符串不能用强制类型转换到string类。例如:

void SomeFunc ( LPCWSTR widestr );
main()
{
  SomeFunc ( (LPCWSTR) "C:\\foo.txt" );  // 错!
}

这段代码100%错误。它可以通过编译,因为类型强制转换超越了编译器的类型检验。但是,能够通过编译,并不证明代码是正确的。

下面,我将指出什么时候用类型强制转换是合理的。
C语言字符串与类型定义

如指南的第一部分所述,Windows API定义了TCHAR术语。它可用于MBCS或Unicode编码字符,取决于预处理设置为_MBCS 或 _UNICODE标记。关于TCHAR的详细说明请阅指南的第一部分。为便于叙述,下面给出字符类型定义:

Type

Meaning

WCHAR

Unicode character (wchar_t)

TCHAR

MBCS or Unicode character, depending on preprocessor settings

LPSTR

string of char (char*)

LPCSTR

constant string of char (constchar*)

LPWSTR

string of WCHAR (WCHAR*)

LPCWSTR

constant string of WCHAR (const WCHAR*)

LPTSTR

string of TCHAR (TCHAR*)

LPCTSTR

constant string of TCHAR (const TCHAR*)

另外还有一个字符类型OLECHAR。这是一种对象链接与嵌入的数据类型(比如嵌入Word文档)。这个类型通常定义为wchar_t。如果将预处理设置定义为OLE2ANSI,OLECHAR将被定义为char类型。现在已经不再定义OLE2ANSI(它只在MFC 3以前版本中使用),所以我将OLECHAR作为Unicode字符处理。

下面是与OLECHAR相关的类型定义:

Type

Meaning

OLECHAR

Unicode character (wchar_t)

LPOLESTR

string of OLECHAR (OLECHAR*)

LPCOLESTR

constant string of OLECHAR (const OLECHAR*)

还有以下二个宏让相同的代码能够适用于MBCS和Unicode编码:

Type

Meaning

_T(x)

Prepends L to the literal in Unicode builds.

OLESTR(x)

Prepends L to the literal to make it an LPCOLESTR.

宏_T有几种形式,功能都相同。如: — TEXT, _TEXT, __TEXT, 和 __T这四种宏的功能相同。

 

COM中的字符串 – BSTR VARIANT

许多COM接口使用BSTR声明字符串。BSTR有一些缺陷,所以我在这里让它独立成章。

BSTR是Pascal类型字符串(字符串长度值显式地与数据存放在一起)和C类型字符串(字符串长度必须通过寻找到结尾零字符来计算)的混合型字符串。BSTR属于Unicode字符串,字符串中预置了字符串长度值,并且用一个零字符来结尾。下面是一个"Bob"的BSTR字符串:

注意,字符串长度值是一个DWORD类型值,给出字符串的字节长度,但不包括结尾零。在上例,"Bob"含有3个Unicode字符(不计结尾零),6个字节长。因为明确给出了字符串长度,所以当BSTR数据在不同的处理器和计算机之间传送时,COM库能够知道应该传送的数据量。

附带说一下,BSTR可以包含任何数据块,不单是字符。它甚至可以包容内嵌零字符数据。这些不在本文讨论范围。

C++中的BSTR变量其实就是指向字符串首字符的指针。BSTR是这样定义的:

typedef OLECHAR* BSTR;

这个定义很糟糕,因为事实上BSTR与Unicode字符串不一样。有了这个类型定义,就越过了类型检查,可以混合使用LPOLESTR和BSTR。向一个需要LPCOLESTR (或 LPCWSTR)类型数据的函数传递BSTR数据是安全的,反之则不然。所以要清楚了解函数所需的字符串类型,并向函数传递正确类型的字符串。

要知道为什么向一个需要BSTR类型数据的函数传递LPCWSTR类型数据是不安全的,就别忘了BSTR必须在字符串开头的四个字节保留字符串长度值。但LPCWSTR字符串中没有这个值。当其它的处理过程(如Word)要寻找BSTR的长度值时就会找到一堆垃圾或堆栈中的其它数据或其它随机数据。这就导致方法失效,当长度值太大时将导致崩溃。

许多应用接口都使用BSTR,但都用到二个最重要的函数来构造和析构BSTR。就是SysAllocString()和SysFreeString()函数。SysAllocString()将Unicode字符串拷贝到BSTR,SysFreeString()释放BSTR。示例如下:

BSTR bstr = NULL;
bstr = SysAllocString ( L"Hi Bob!" );
if ( NULL == bstr )
    // 内存溢出
   // 这里使用bstr
SysFreeString ( bstr );

当然,各种BSTR包装类都会小心地管理内存。

自动接口中的另一个数据类型是VARIANT。它用于在无类型语言,诸如JScript,VBScript,以及Visual Basic,之间传递数据。VARIANT可以包容许多不用类型的数据,如long和IDispatch*。如果VARIANT包含一个字符串,这个字符串是BSTR类型。在下文的VARIANT包装类中我还会谈及更多的VARIANT。
C++字符串完全指南(2) – 各种字符串类- CRT类
翻译:连波
20/11/2002
URL: http://www.zdnet.com.cn/developer/tech/story/0,2000081602,39098682,00.htm

_bstr_t

 

字符串包装类

我已经说明了字符串的各种类型,现在讨论包装类。对于每个包装类,我都会说明它的对象构造过程和如何转换成C类型字符串指针。应用接口的调用,或构造另一个不同类型的字符串类,大多都要用到C类型指针。本文不涉及类的其它操作,如排序和比较等。

再强调一下,在完全了解转换结果之前不要随意使用强制类型转换。

CRT类

_bstr_t

_bstr_t 是BSTR的完全包装类。实际上,它隐含了BSTR。它提供多种构造函数,能够处理隐含的C类型字符串。但它本身却不提供BSTR的处理机制,所以不能作为COM方法的输出参数[out]。如果要用到BSTR* 类型数据,用ATL的CComBSTR类更为方便。

_bstr_t 数据可以传递给需要BSTR数据的函数,但必须满足以下三个条件:

首先,_bstr_t 具有能够转换为wchar_t*类型数据的函数。

其次,根据BSTR定义,使得wchar_t* 和BSTR对于编译器来说是相同的。

第三,_bstr_t内部保留的指向内存数据块的指针 wchar_t* 要遵循BSTR格式。

满足这些条件,即使没有相应的BSTR转换文档,_bstr_t 也能正常工作。示例如下:

 // 构造
_bstr_t bs1 = "char string";        // 从LPCSTR构造
_bstr_t bs2 = L"wide char string"; // 从LPCWSTR构造
_bstr_t bs3 = bs1;              // 拷贝另一个 _bstr_t
_variant_t v = "Bob";
_bstr_t bs4 = v;              // 从一个含有字符串的 _variant_t 构造
// 数据萃取
LPCSTR psz1 = bs1;              // 自动转换到MBCS字符串
LPCSTR psz2 = (LPCSTR) bs1;     // cast OK, 同上
LPCWSTR pwsz1 = bs1;            // 返回内部的Unicode字符串
LPCWSTR pwsz2 = (LPCWSTR) bs1;  // cast OK, 同上
BSTR    bstr = bs1.copy();      // 拷贝bs1, 返回BSTR
// ...
  SysFreeString ( bstr );

注意,_bstr_t 也可以转换为char* 和 wchar_t*。这是个设计问题。虽然char* 和 wchar_t*不是常量指针,但不能用于修改字符串,因为可能会打破内部BSTR结构。

_variant_t
_variant_t

_variant_t 是VARIANT的完全包装类。它提供多种构造函数和数据转换函数。本文仅讨论与字符串有关的操作。

// 构造
_variant_t v1 = "char string"; // 从LPCSTR 构造
_variant_t v2 = L"wide char string"; // 从LPCWSTR 构造
_bstr_t bs1 = "Bob";
_variant_t v3 = bs1; // 拷贝一个 _bstr_t 对象
// 数据萃取
_bstr_t bs2 = v1; // 从VARIANT中提取BSTR
_bstr_t bs3 = (_bstr_t) v1; // cast OK, 同上

注意,_variant_t 方法在转换失败时会抛出异常,所以要准备用catch 捕捉_com_error异常。

另外要注意 _variant_t 不能直接转换成MBCS字符串。要建立一个过渡的_bstr_t 变量,用其它提供转换Unicode到MBCS的类函数,或ATL转换宏来转换。

与_bstr_t 不同,_variant_t 数据可以作为参数直接传送给COM方法。_variant_t 继承了VARIANT类型,所以在需要使用VARIANT的地方使用_variant_t 是C++语言规则允许的。
C++字符串完全指南(2) – STL和ATL类
翻译:连波
21/11/2002
URL: http://www.zdnet.com.cn/developer/tech/story/0,2000081602,39098845,00.htm

STL类

 

STL类

STL只有一个字符串类,即basic_string。basic_string管理一个零结尾的字符数组。字符类型由模板参数决定。通常,basic_string被处理为不透明对象。可以获得一个只读指针来访问缓冲区,但写操作都是由basic_string的成员函数进行的。

basic_string预定义了二个特例:string,含有char类型字符;which,含有wchar_t类型字符。没有内建的TCHAR特例,可用下面的代码实现:

// 特例化
typedef basic_string tstring; // TCHAR字符串
// 构造
string str = "char string"; // 从LPCSTR构造
wstring wstr = L"wide char string"; // 从LPCWSTR构造
tstring tstr = _T("TCHAR string"); // 从LPCTSTR构造
// 数据萃取
LPCSTR psz = str.c_str(); // 指向str缓冲区的只读指针
LPCWSTR pwsz = wstr.c_str(); // 指向wstr缓冲区的只读指针
LPCTSTR ptsz = tstr.c_str(); // 指向tstr缓冲区的只读指针

与_bstr_t 不同,basic_string不能在字符集之间进行转换。但是如果一个构造函数接受相应的字符类型,可以将由c_str()返回的指针传递给这个构造函数。例如:

// 从basic_string构造_bstr_t
_bstr_t bs1 = str.c_str();  // 从LPCSTR构造 _bstr_t
_bstr_t bs2 = wstr.c_str(); // 从LPCWSTR构造 _bstr_t
ATL类
CComBSTR

CComBSTR 是ATL的BSTR包装类。某些情况下比_bstr_t 更有用。最主要的是,CComBSTR允许操作隐含BSTR。就是说,传递一个CComBSTR对象给COM方法时,CComBSTR对象会自动管理BSTR内存。例如,要调用下面的接口函数:

// 简单接口
struct IStuff : public IUnknown
{
  // 略去COM程序...
  STDMETHOD(SetText)(BSTR bsText);
  STDMETHOD(GetText)(BSTR* pbsText);
};

CComBSTR 有一个BSTR操作方法,能将BSTR直接传递给SetText()。还有一个引用操作(operator &)方法,返回BSTR*,将BSTR*传递给需要它的有关函数。

CComBSTR bs1;
CComBSTR bs2 = "new text";
pStuff->GetText ( &bs1 );       // ok, 取得内部BSTR地址
  pStuff->SetText ( bs2 );        // ok, 调用BSTR转换
  pStuff->SetText ( (BSTR) bs2 ); // cast ok, 同上

CComVariant
CComBSTR有类似于 _bstr_t 的构造函数。但没有内建MBCS字符串的转换函数。可以调用ATL宏进行转换。

// 构造
CComBSTR bs1 = "char string"; // 从LPCSTR构造
CComBSTR bs2 = L"wide char string"; // 从LPCWSTR构造
CComBSTR bs3 = bs1; // 拷贝CComBSTR
CComBSTR bs4;
bs4.LoadString ( IDS_SOME_STR ); // 从字符串表加载
// 数据萃取
BSTR bstr1 = bs1; // 返回内部BSTR,但不可修改!
BSTR bstr2 = (BSTR) bs1; // cast ok, 同上
BSTR bstr3 = bs1.Copy(); // 拷贝bs1, 返回BSTR
BSTR bstr4;
bstr4 = bs1.Detach(); // bs1不再管理它的BSTR
// ...
SysFreeString ( bstr3 );
SysFreeString ( bstr4 );

上面的最后一个示例用到了Detach()方法。该方法调用后,CComBSTR对象就不再管理它的BSTR或其相应内存。所以bstr4就必须调用SysFreeString()。

最后讨论一下引用操作符(operator &)。它的超越使得有些STL集合(如list)不能直接使用CComBSTR。在集合上使用引用操作返回指向包容类的指针。但是在CComBSTR上使用引用操作,返回的是BSTR*,不是CComBSTR*。不过可以用ATL的CAdapt类来解决这个问题。例如,要建立一个CComBSTR的队列,可以声明为:

  std::list< CAdapt> bstr_list;

CAdapt 提供集合所需的操作,是隐含于代码的。这时使用bstr_list 就象在操作一个CComBSTR队列。

CComVariant

CComVariant 是VARIANT的包装类。但与 _variant_t 不同,它的VARIANT不是隐含的,可以直接操作类里的VARIANT成员。CComVariant 提供多种构造函数和多类型操作。这里只介绍与字符串有关的操作。

// 构造
CComVariant v1 = "char string";       // 从LPCSTR构造
CComVariant v2 = L"wide char string"; // 从LPCWSTR构造
CComBSTR bs1 = "BSTR bob";
CComVariant v3 = (BSTR) bs1;          // 从BSTR拷贝
// 数据萃取
CComBSTR bs2 = v1.bstrVal;            // 从VARIANT提取BSTR

跟_variant_t 不同,CComVariant没有不同VARIANT类型之间的转换操作。必须直接操作VARIANT成员,并确定该VARIANT的类型无误。调用ChangeType()方法可将CComVariant数据转换为BSTR。

CComVariant v4 = ... // 从某种类型初始化 v4
CComBSTR bs3;
if ( SUCCEEDED( v4.ChangeType ( VT_BSTR ) ))
    bs3 = v4.bstrVal;

跟 _variant_t 一样,CComVariant不能直接转换为MBCS字符串。要建立一个过渡的_bstr_t 变量,用其它提供转换Unicode到MBCS的类函数,或ATL转换宏来转换。

ATL转换宏

ATL转换宏

ATL的字符串转换宏可以方便地转换不同编码的字符,用在函数中很有效。宏按照[source type]2[new type] 或 [source type]2C[new type]格式命名。后者转换为一个常量指针 (名字内含"C")。类型缩写如下:

 A:MBCS字符串,char* (A for ANSI)
 W:Unicode字符串,wchar_t* (W for wide)
 T:TCHAR字符串,TCHAR*
 OLE:OLECHAR字符串,OLECHAR* (实际等于W)
 BSTR:BSTR (只用于目的类型)

例如,W2A() 将Unicode字符串转换为MBCS字符串,T2CW()将TCHAR字符串转换为Unicode字符串常量。

要使用宏转换,程序中要包含atlconv.h头文件。可以在非ATL程序中使用宏转换,因为头文件不依赖其它的ATL,也不需要 _Module全局变量。如在函数中使用转换宏,在函数起始处先写上USES_CONVERSION宏。它表明某些局部变量由宏控制使用。

转换得到的结果字符串,只要不是BSTR,都存储在堆栈中。如果要在函数外使用这些字符串,就要将这些字符串拷贝到其它的字符串类。如果结果是BSTR,内存不会自动释放,因此必须将返回值分配给一个BSTR变量或BSTR的包装类,以避免内存泄露。

下面是若干宏转换示例:

// 带有字符串的函数:
void Foo ( LPCWSTR wstr );
void Bar ( BSTR bstr );
// 返回字符串的函数:
void Baz ( BSTR* pbstr );
#include 
main()
{
using std::string;
USES_CONVERSION;    // 声明局部变量由宏控制使用
// 示例1:送一个MBCS字符串到Foo()
LPCSTR psz1 = "Bob";
string str1 = "Bob";
Foo ( A2CW(psz1) );
  Foo ( A2CW(str1.c_str()) );
// 示例2:将MBCS字符串和Unicode字符串送到Bar()
LPCSTR psz2 = "Bob";
LPCWSTR wsz = L"Bob";
BSTR bs1;
CComBSTR bs2;
bs1 = A2BSTR(psz2);         // 创建 BSTR
  bs2.Attach ( W2BSTR(wsz) ); // 同上,分配到CComBSTR
Bar ( bs1 );
  Bar ( bs2 );
SysFreeString ( bs1 );      // 释放bs1
  // 不必释放bs2,由CComBSTR释放。
// 示例3:转换由Baz()返回的BSTR
BSTR bs3 = NULL;
string str2;
Baz ( &bs3 );          // Baz() 填充bs3内容
str2 = W2CA(bs3);      // 转换为MBCS字符串
  SysFreeString ( bs3 ); // 释放bs3
}

可以看到,向一个需要某种类型参数的函数传递另一种类型的参数,用宏转换是非常方便的。
C++字符串完全指南(2) – MFC类
翻译:连波
22/11/2002
URL: http://www.zdnet.com.cn/developer/tech/story/0,2000081602,39098983,00.htm

MFC类

 

MFC类

CString

MFC的CString含有TCHAR,它的实际字符类型取决于预处理标记的设置。通常,CString象STL字符串一样是不透明对象,只能用CString的方法来修改。CString比STL字符串更优越的是它的构造函数接受MBCS和Unicode字符串。并且可以转换为LPCTSTR,因此可以向接受LPCTSTR的函数直接传递CString对象,不必调用c_str()方法。

// 构造
CString s1 = "char string"; // 从LPCSTR构造
CString s2 = L"wide char string"; // 从LPCWSTR构造
CString s3 ( ' ', 100 ); // 预分配100字节,填充空格
CString s4 = "New window text";
// 可以在LPCTSTR处使用CString:
SetWindowText ( hwndSomeWindow, s4 );
// 或者,显式地做强制类型转换:
SetWindowText ( hwndSomeWindow, (LPCTSTR) s4 );

也可以从字符串表加载字符串。CString通过LoadString()来构造对象。用Format()方法可有选择地从字符串表读取一定格式的字符串。

// 从字符串表构造/加载
CString s5 ( (LPCTSTR) IDS_SOME_STR );  // 从字符串表加载
CString s6, s7;
// 从字符串表加载
  s6.LoadString ( IDS_SOME_STR );
// 从字符串表加载打印格式的字符串
  s7.Format ( IDS_SOME_FORMAT, "bob", nSomeStuff, ... );

第一个构造函数看上去有点怪,但它的确是文档标定的字符串加载方式。

注意,CString只允许一种强制类型转换,即强制转换为LPCTSTR。强制转换为LPTSTR (非常量指针)是错误的。按照老习惯,将CString强制转换为LPTSTR只能伤害自己。有时在程序中没有发现出错,那只是碰巧。转换到非常量指针的正确方法是调用GetBuffer()方法。

下面以往队列加入元素为例说明如何正确地使用CString:

CString str = _T("new text");
LVITEM item = {0};
item.mask = LVIF_TEXT;
  item.iItem = 1;
  item.pszText = (LPTSTR)(LPCTSTR) str; // 错!
  item.pszText = str.GetBuffer(0);      // 正确
ListView_SetItem ( &item );
  str.ReleaseBuffer();  // 将队列返回给str

pszText成员是LPTSTR,一个非常量指针,因此要用str的GetBuffer()。GetBuffer()的参数是CString分配的最小缓冲区。如果要分配一个1K的TCHAR,调用GetBuffer(1024)。参数为0,只返回指向字符串的指针。

上面示例的出错语句可以通过编译,甚至可以正常工作,如果恰好就是这个类型。但这不证明语法正确。进行非常量的强制类型转换,打破了面向对象的封装原则,并逾越了CString的内部操作。如果你习惯进行这样的强制类型转换,终会遇到出错,可你未必知道错在何处,因为你到处都在做这样的转换,而代码也都能运行。

 

知道为什么人们总在抱怨有缺陷的软件吗?不正确的代码就臭虫的滋生地。然道你愿意编写明知有错的代码让臭虫有机可乘?还是花些时间学习CString的正确用法让你的代码能够100%的正确吧。

CString还有二个函数能够从CString中得到BSTR,并在必要时转换成Unicode。那就是AllocSysString()和SetSysString()。除了SetSysString()使用BSTR*参数外,二者一样。

// 转换成BSTR
CString s5 = "Bob!";
BSTR bs1 = NULL, bs2 = NULL;
bs1 = s5.AllocSysString();
  s5.SetSysString ( &bs2 );
// ...
  SysFreeString ( bs1 );
  SysFreeString ( bs2 );

COleVariant 与CComVariant 非常相似。COleVariant 继承于VARIANT,可以传递给需要VARIANT的函数。但又与CComVariant 不同,COleVariant 只有一个LPCTSTR的构造函数,不提供单独的LPCSTR和LPCWSTR的构造函数。在大多情况下,没有问题,因为总是愿意把字符串处理为LPCTSTR。但你必须知道这点。COleVariant 也有接受CString的构造函数。

// 构造
CString s1 = _T("tchar string");
COleVariant v1 = _T("Bob"); // 从LPCTSTR构造
COleVariant v2 = s1; // 从CString拷贝

对于CComVariant,必须直接处理VARIANT成员,用ChangeType()方法在必要时将其转换为字符串。但是,COleVariant::ChangeType() 在转换失败时会抛出异常,而不是返回HRESULT的出错码。

// 数据萃取
COleVariant v3 = ...; // 从某种类型构造v3
BSTR bs = NULL;
try
    {
    v3.ChangeType ( VT_BSTR );
    bs = v3.bstrVal;
    }
  catch ( COleException* e )
    {
    // 出错,无法转换
    }
SysFreeString ( bs );

WTL类

 

WTL类

CString

WTL的CString与MFC的CString的行为完全相同,参阅上面关于MFC CString的说明即可。

CLR 及 VC 7 类

System::String 是.NET的字符串类。在其内部,String对象是一个不变的字符序列。任何操作String对象的String方法都返回一个新的String对象,因为原有的String对象要保持不变。String类有一个特性,当多个String都指向同一组字符集时,它们其实是指向同一个对象。Managed Extensions C++ 的字符串有一个新的前缀S,用来表明是一个managed string字符串。

// 构造
String* ms = S"This is a nice managed string";

可以用unmanaged string字符串来构造String对象,但不如用managed string构造String对象有效。原因是所有相同的具有S前缀的字符串都指向同一个对象,而unmanaged string没有这个特点。下面的例子可以说明得更清楚些:

String* ms1 = S"this is nice";
String* ms2 = S"this is nice";
String* ms3 = L"this is nice";
Console::WriteLine ( ms1 == ms2 ); // 输出true
Console::WriteLine ( ms1 == ms3);  // 输出false

要与没有S前缀的字符串做比较,用String::CompareTo()方法来实现,如:

  Console::WriteLine ( ms1->CompareTo(ms2) );
  Console::WriteLine ( ms1->CompareTo(ms3) );

二者都输出0,说明字符串相等。

在String和MFC 7的CString之间转换很容易。CString可以转换为LPCTSTR,String有接受char* 和 wchar_t* 的二种构造函数。因此可以直接把CString传递给String的构造函数:

  CString s1 ( "hello world" );
  String* s2 ( s1 );  // 从CString拷贝

反向转换的方法也类似:

  String* s1 = S"Three cats";
  CString s2 ( s1 );

可能有点迷惑。从VS.NET开始,CString有一个接受String对象的构造函数,所以是正确的。

  CStringT ( System::String* pString );

为了加速操作,有时可以用基础字符串(underlying string):

String* s1 = S"Three cats";
Console::WriteLine ( s1 );
const __wchar_t __pin* pstr = PtrToStringChars(s1);
for ( int i = 0; i < wcslen(pstr); i++ )
    (*const_cast<__wchar_t*>(pstr+i))++;
Console::WriteLine ( s1 );

PtrToStringChars() 返回指向基础字符串的 const __wchar_t* 指针,可以防止在操作字符串时,垃圾收集器去除该字符串。
C++字符串完全指南(2) – 总结
翻译:连波
23/11/2002
URL: http://www.zdnet.com.cn/developer/tech/story/0,2000081602,39099061,00.htm

 

字符串类的打印格式函数

对字符串包装类使用printf()或其它类似功能的函数时要特别小心。包括sprintf()函数及其变种,以及TRACE 和ATLTRACE 宏。它们的参数都不做类型检验,一定要给它们传递C语言字符串,而不是整个string对象。

例如,要向ATLTRACE()传递一个_bstr_t 里的字符串,必须显式用(LPCSTR)或 (LPCWSTR)进行强制类型转换:

  _bstr_t bs = L"Bob!";
  ATLTRACE("The string is: %s in line %d\n", (LPCSTR) bs, nLine);

如果忘了用强制类型转换,直接把整个 _bstr_t 对象传递给ATLTRACE,跟踪消息将输出无意义的东西,因为_bstr_t 变量内的所有数据都进栈了。

所有类的总结

常用的字符串类之间的转换方法是:将源字符串转换为C类型字符串指针,然后将该指针传递给目标类的构造函数。下面列出将字符串转换为C类型指针的方法,以及哪些类的构造函数接受C类型指针。

Class

string
type

convert to char*?

convert to constchar*?

convert to wchar_t*?

convert to const wchar_t*?

convert to BSTR?

construct from char*?

construct from wchar_t*?

_bstr_t

BSTR

yes, cast1

yes, cast

yes, cast1

yes, cast

yes2

yes

yes

_variant_t

BSTR

no

no

no

cast to
_bstr_t3

cast to
_bstr_t3

yes

yes

string

MBCS

no

yes, c_str()
method

no

no

no

yes

no

wstring

Unicode

no

no

no

yes, c_str()
method

no

no

yes

CComBSTR

BSTR

no

no

no

yes, cast
to BSTR

yes, cast

yes

yes

CComVariant

BSTR

no

no

no

yes4

yes4

yes

yes

CString

TCHAR

no6

in MBCS
builds, cast

no6

in Unicode
builds, cast

no5

yes

yes

COleVariant

BSTR

no

no

no

yes4

yes4

in MBCS builds

in Unicode builds

附注:

  1. 虽然 _bstr_t 可以转换为非常量指针,但对内部缓冲区的修改可能导致内存溢出,或在释放BSTR时导致内存泄露。
  2. bstr_t 的BSTR内含 wchar_t* 变量,所以可将const wchar_t* 转换到BSTR。但这个用法将来可能会改变,使用时要小心。
  3. 如果转换到BSTR失败,将抛出异常。
  4. 用ChangeType()处理VARIANT的bstrVal。在MFC,转换失败将抛出异常。
  5. 虽然没有BSTR的转换函数,但AllocSysString()可返回一个新的BSTR。
  6. 用GetBuffer()方法可临时得到一个非常量TCHAR指针。

转自:http://lwe20.blogchina.com/3885861.html

2006年08月23日

VC常见入门问题总结

1:fatal error C1010: unexpected end of file while looking for precompiled header directive该如何解决
    如果发生错误的文件是由其他的C代码文件添加进入当前工程而引起的,则Alt+F7进入当前工程的Settings,选择C/C++选项卡,从Category组合框中选中Precompiled Headers,选择Not Using Precompiled headers。确定
    如果发生错误的文件原本是该工程中的,则检查该文件头部有没有#include "stdafx.h"语句,没有的话添加。
    如果还不行,也有可能是定义的类或结构体等最后忘了加分号,注意一下。
2:fatal error RC1015: cannot open include file ‘afxres.h’.该如何解决
    #include "afxres.h"语句是在.rc文件中的,而afxres.h文件在VC的安装目录中的.\VC98\MFC\INCLUDE目录中,所以着重查一下Tools菜单中Options对话框中的Directories中的包括文件的路径是否正确,是否在VC的安装路径中,不是的话,改过来,如果这方面没问题,则到其他机器中拷贝afxres.h到相应的目录中
3:Dll分配的内存块,应用程序释放,结果报异常。
    用GlobalAlloc()代替new, 用GlobalFree() 代替delete就不会出错了
    其实还有一个办法,就是把dll的Settings的C/C++选项卡的Code Generation的Use Run-time liberary改成Debug Multithreaded DLL,在Release版本中改成Multithreaded DLL,就可以直接使用new和delete了,没问题
    比较规范点的做法一般是DLL分配的内存由DLL释放。在DLL中加一个函数释放内存不是更好吗。
4:发现打印预览的图形明显比屏幕显示图形小,怎么办?
    这多半是CDC映射模式的选择引起的,缺省状态下,选择的是MM_TEXT模式,MM_TEXT以设备的像素点为单位,而不同设备的像素点的大小不同,打印机的分辨率比显示器要高很多,所以导致同样图形在打印时候变小。解决之道是统一使用其他定长的映射模式,比如MM_HIMETRIC等等(CDC::SetMapMode()改变映射模式)
5:CString、char*、string、int、_bstr_t、CTime、COleDateTime等等的相互转换,如何判断一个字符串是一个浮点数?

#include<string>
using namespace std;
#include <COMDEF.H>

{
      CString strCString="ABC";
      char strchar[256],*pstr;
       
      pstr=(LPSTR)(LPCTSTR)strCString;                 //CString—->char*
      strcpy(strchar,(LPSTR)(LPCTSTR)strCString);         //CString—->char[]
       
      _bstr_t strbstr=pstr;                                 //char*—->_bstr_t
      WCHAR *strWCHAR=strbstr;                         //b_str_t—>UNICODE

      strbstr=strWCHAR;
      pstr=strbstr;                                         //UNICODE—->char*

      strCString="10";
      int istr=atoi((LPSTR)(LPCTSTR)strCString);         //CString、char[]、char*——>int
      strCString.Format("%d",istr);                         //int—–>CString
      sprintf(strchar,"%d",istr);                         //int—–>char[]
       
      pstr=new char[256];                                 //字符串申请空间
      strcpy(pstr,"ABC");                                 //字符串赋值
      delete []pstr;                                         //字符串释放
       
      string         strstring="ABC";
      pstr=(char*)strstring.c_str();                         //string—->char*

      strCString="2003-10-27 6:24:37";                 //CString—>COleDateTime
      COleVariant vtime(strCString);
      vtime.ChangeType(VT_DATE);
      COleDateTime time4=vtime;
       

      COleDateTime time1(1977,4,16,2,2,2);                 //COleDataTime—>CTime
      SYSTEMTIME systime;
      VariantTimeToSystemTime(time1, &systime);
      CTime tm(systime);

      time_t time2=tm.GetTime();                         //CTime—>time_t
      COleDateTime time3(time2);                         //time_t—>COleDateTime

      //判断字符串是否是某种类型
      CString sValue("123.1");
      COleVariant vValue(sValue);
       
      BOOL bStrIsFloat = (SUCCEEDED(VariantChangeType(&vValue, &vValue, 0, VT_R8)) && sValue.Find(‘.’) != -1);
      if(bStrIsFloat)
      {
              AfxMessageBox("浮点");
      }
}

6:如何建立一个UNICODE应用程序?

建立一个应用程序,打开Alt+F7 settings选项,选择C/C++选项卡,在Preprocessor definenation中加上_UNICODE,在Link选项卡中,在Category选择框中选择Output,在Entry-point symbol编辑框中,添加wWinMainCRTStartup确定。

注意调试UNICODE程序时,需要在安装时VC选择所有选项,否则会缺少动态库和相应的.lib文件

 
7:ADO操作数据库表,更新出现问题
在打开数据库前,添加如下语句试一下pRecordSet->CursorLocation = adUseClient;

2006年08月17日
********vc中debug版程序正常但release版崩溃*******
I. 内存分配问题
1. 变量未初始化。
为debug中会自动给变量初始化found=FALSE,而在release版中
则不会。所以尽可能的给变量、类或结构初始化。
2. 数据溢出的问题
如:char buffer[10];
int counter;
lstrcpy(buffer, "abcdefghik");
在debug版中buffer的NULL覆盖了counter的高位,但是除非counter>16M,什么问题也没
有。但是在release版中,counter可能被放在寄存器中,这样NULL就覆盖了buffer下面
的空间,可能就是函数的返回地址,这将导致ACCESS ERROR。
3. DEBUG版和RELEASE版的内存分配方式是不同的 。如果你在DEBUG版中申请
ele 为 6*sizeof(DWORD)=24bytes,实际上分配给你的是32bytes(debug版以32bytes
为单位分配), 而在release版,分配给你的就是24bytes(release版以8bytes为单位
),所以在debug版中如果你写ele[6],可能不会有什么问题,而在release版中,就有A
CCESS VIOLATE。
II. ASSERT和VERIFY
1. ASSERT在Release版本中是不会被编译的。
假如你在这些语句中加了程序中必须要有的代

比如
ASSERT(pNewObj = new CMyClass);
pNewObj->MyFunction();
这种时候Release版本中的pNewObj不会分配到空间
所以执行到下一个语句的时候程序会报该程序执行了非法操作的错误。这时可以用VERIFY
III. 参数问题:
自定义消息的处理函数,必须定义如下:
afx_msg LRESULT OnMyMessage(WPARAM, LPARAM);
返回值必须是HRESULT型,否则Debug会过,而Release出错
IV. 内存分配
保证数据创建和清除的统一性:如果一个DLL提供一个能够创建数据的函数,那么这个D
LL同时应该提供一个函数销毁这些数据。数据的创建和清除应该在同一个层次上。
V. DLL的灾难
人们将不同版本DLL混合造成的不一致性形象的称为 “动态连接库的地狱“(DLL Hell)
如果你的程序使用你自己的DLL时请注意:
1. 不能将debug和release版的DLL混合在一起使用。debug都是debug版,releas
e版都是release版。
解决办法是将debug和release的程序分别放在主程序的debug和release目录下
2. 千万不要以为静态连接库会解决问题,那只会使情况更糟糕
VI. RELEASE板中的调试 :
1. 将ASSERT() 改为 VERIFY() 。找出定义在"#ifdef _DEBUG"中的代码,如果
在RELEASE版本中需要这些代码请将他们移到定义外。查找TRACE(…)中代码,因为这些
代码在RELEASE中也不被编译。 请认真检查那些在RELEASE中需要的代码是否并没有被便
宜。
2. 变量的初始化所带来的不同,在不同的系统,或是在DEBUG/RELEASE版本间
都存在这样的差异,所以请对变量进行初始化。
3. 是否在编译时已经有了警告?请将警告级别设置为3或4,然后保证在编译时没
有警告出现.
VII. 将Project Settings" 中 "C++/C " 项目下优化选项改为Disbale(Debug)。编
译器的优化可能导致许多意想不到的错误,请参考[url]http://www.pgh.net/~newcomer/deb[/url]
ug_release.htm
1. 此外对RELEASE版本的软件也可以进行调试,请做如下改动:
在"Project Settings" 中 "C++/C " 项目下设置 "category" 为 "General" 并且将"D
ebug Info"设置为 "Program Database"。
在"Link"项目下选中"Generate Debug Info"检查框。
"Rebuild All"
如此做法会产生的一些限制:
无法获得在MFC DLL中的变量的值。
必须对该软件所使用的所有DLL工程都进行改动。
*************************************************************
*******句柄是一个标识符,是拿来标识对象或者项目*****
的,它就象我们的姓名一样,每个人都会有一个,不同的人的姓名不一样,但是,也可
能有一个名字和你一样的人。从数据类型上来看它只是一个16位的无符号整数。应用程
序几乎总是通过调用一个WINDOWS函数来获得一个句柄,之后其他的WINDOWS函数就可以
使用该句柄,以引用相应的对象
通用的句柄,就是HANDLE,比如下面的语句:
HINSTANCE hInstance;
可以改成:
HANDLE hInstance;

一般情况下我们在编程的时候,给应用程序分配的
内存都是可以移动的或者是可以丢弃的,这样能使有限的内存资源充分利用,所以,在
某一个时候我们分配的那块内存的地址是不确定的,因为他是可以移动的,所以得先锁
定那块内存块,这儿应用程序需要调用API函数GlobalLock函数来锁定句柄。如下:
lpMem=GlobalLock(hMem);
这样应用程序才能存取这块内存。
**********************************************************
要使程序的主窗口不可见,并且不在任务栏上出现任务按钮,要做到这两点,需分
别设置主边框窗口的风格和扩展风格:

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

{

cs.style =WS_POPUP;//使主窗口不可见

cs.dwExStyle |=WS_EX_TOOLWINDOW;//不显示任务按钮

return CFrameWnd::PreCreateWindow(cs);
}
*************************************************
**CString str = "Hello\r\nHow are you\r\n";
: 为什么pDC->TextOut(0, 0, str);显示时不能够自动换行?
如果你的视图类是CEditView,这样做是行的通的
但是,如果在CView里面,这样就不行,这也很容易理解
CView里面没有行的概念,它就像按象素编址的白纸(一个二维平面)
在白纸上换行,显然是行不通的,因为它不知道下一行的具体位置
*************************************
*我想在子线程中直接给视图窗口绘图,而不调用视图类的函数OnDraw(),
在子线程中获得资源,
可以在子线程的构造函数中传入CMyThread(CWnd* pWnd,HDC hDC,。。)
这样,在主线程中创建子线程时 mythread(this,m_pDC->m_hDC)
其中m_pDC=m_pDC=new CClientDC(this);
**********************************
*我在程序中需要生成随机数,
: 但是rand()生成的是0~指定的数之间的随机数
: 例如我想生成10~20之间的5个随机数,应该如何做?
int result[5];
srand();
for(int i=0;i<5;i++)
{
result[i]=rand()%11+10;
srand();
}
**************************************
*VC中让程序发出声因
beep()
********************************
*获取当前屏幕分辨率
int nScreenWidth=GetSystemMetrics(SM_CXSCREEN);
int nScreenHeight=GetSystemMetrics(SM_CYSCREEN);
************************************
*把消息从一个view发到另外一个view
在CWinApp里面保存view的指针。然后再任意地方就可以通过AfxGetApp()调用获得
view的指针。That is ok!
***********************************
*如何在视图类函数中得到HDC句柄
得到可以用GetDC,释放用ReleaseDC,句柄是CDC的成员变量
返回的是CDC对象,但里面有一个成员变量,好像是m_hCDC之类的
是句柄
*********************************
*注册控件
regsvr32
-**************************************
*CTypePtrArray跟CObArray本质区别
CTypePtrArray有两种基本框架类型,CObArray和CPtrArray,里面的数据类型可以是对象
或者指向对象的指针;所以,它是一个模板类,它的可串行化依赖于它到底是以CObArray
还是以CPtrArray为基本框架,它是类型安全的.
CObArray,顾名思义,是对象的数组,所以里面是存放对象的.它不是模板类,可串行化,
不是类型安全的.
**************************************
*vc中的extern "c" 如何使用?
发信站: 武汉白云黄鹤站 (2003年05月16日21:57:11 星期五), 站内信件

假设某个C 函数的声明如下:
void foo(int x, int y);
该函数被C 编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int
之类的名字用来支持函数重载和类型安全连接。由于编译后的名字不同,C++程序不能
直接调用C 函数。C++提供了一个C 连接交换指定符号extern“C”来解决这个问题。
例如:
extern “C”
{
void foo(int x, int y);
. // 其它函数
}
或者写成
extern “C”
{
#include “myheader.h”
. // 其它C 头文件
}
这就告诉C++编译译器,函数foo 是个C 连接,应该到库中找名字_foo 而不是找
_foo_int_int。C++编译器开发商已经对C 标准库的头文件作了extern“C”处理,
*****************************************************
*VC++下怎样设置栈的大小
方法一:STACKSIZE 定义.def文件

语法:STACKSIZE reserve[,commit]
reserve:栈的大小;commit:可选项,与操作系统有关,在NT上只一次分配物理内存
的大小

方法二:设定/STACK

打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Outpu
t,然后
在Reserve中设定堆栈的最大值和commit。

注意:reserve默认值为1MB,最小值为4Byte;commit是保留在虚拟内存的页文件里面,它
设置的较
大会使栈开辟较大的值,可能增加内存的开销和启动时间
**********************************************************
*鼠标移动到适当的位置时改变光标
void CM_curosrView::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
SetCursor(Cross);
//

CView::OnMouseMove(nFlags, point);
}

其中HCURSOR Cross在构造函数中被初始化成
Cross = AfxGetApp()->LoadStandardCursor(IDC_CROSS);

只要移动,即为十字标
********************************************************
*如何发送一个关于某个按钮单击的消息
试试这两种:
1、GetDlgItem(IDOK)->PostMessage(BM_CLICK,0,0);
这种是某位高人说的,没试过。这种是直接发送给按钮窗口的,是窗口消息。

2、PostMessage(WM_COMMAND,IDOK|BN_CLICKED<<16,GetDlgItem(IDOK)->m_hWnd);
这个是我自己找的一种方法。这个是发送给按钮父窗口的,是通知消息。
******************************************************
*什么函数能获取本地IP
虽然感觉很滥,直接调用ipconfig。
这个程序肯定是每台机器有得,
windows自己保证,所以你调用就可以方便获得了。
**********************************
*得到windows和system目录路径的函数
getwindowsdirectory
getsystemdirectory
***********************************
1:使用vc开发项目时,
常会遇到这种情况:
即明明只改动了一个文件,却要把整个项目全部重新编译连接一次。
刚刚连接好,一运行,又提示重新编译连接一次。非常讨厌。
这是因为出现了 未来文件 的缘故。(vc5的bug?)
解决方法:找出对应文件夹下的debug目录,将未来文件全部delete,
再rebuild all一次。(未来 文件即其创建和修改时间都比系统时间靠后)

2:
有时,workspace中的classview显示混乱。其表现如下:
1:添加的成员变量或函数不能显示;
2:即使显示出来了变量或函数,双击后不能跳至正确的位置。
解决方法:
删除.ncb文件,再rebuld all.

3:
如何干净的删除一个类?
1:先从workspace中的FileView中删除对应的.h和.cpp文件。
2.再关闭项目,从实际的文件夹中删除对应的.h和.cpp文件。
3.灾删除.clw文件。
打开项目,rebuld all。

4:
初学者常有这样的疑惑:
直接望工程文件里加入一个CPP原文件再编译连接的话
老是提示没有找到预编译头
解决方法:
#include "stdafx.h"

5:
如何向项目中加入自己定义的类?
方法很多,介绍一个简便的:
选择Insert/New Class菜单
弹出对话框;
选择Class Type为generic;
输入类名。
当然,也可以自己决定该类的基类
6. 通过DLL共享变量
a. 用#pragma data_seg(“segname”) 创建一个节
#pragma data_seg(“share”)
LONG g_lShareData=0;
#pragma data_seg()
b. 在连接器的命令行中使用/SECTION:name attributes开关
//SECTION:share,RWS
或在源代码中嵌入连接开关:
#pragma comment(linker,”/SECTION:share,RWS”)

7. 动态连接库的隐式连接
a. DLL输出函数的头文件:输出函数前加_declspec(dllexport);EXE输入函数的头文件:
输入函数前加_delcspec(dllimport)。
b. 在EXE工程项目设置的Link页面的Object/Libaray Modules 中添加DLL对应的LIB文件(
该文件是在生成DLL的同时生成的)。

8. 用FindFirstChangeNotification(…)来检测文件系统的变化。
先用FindFirstChangeNotification登记要监测的文件夹,然后用WaitForSingleObject等
函数来等待文件变化事件的到来,并处理该时间。在处理后如果需要继续监测,那么还要
调用FindNextChangeNotification(…)。
***********************************************
*怎样在c++里得到文件长度
HANDLE hFile;
hFile = CreateFile(lpcTheFile, GENERIC_READ | GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

if (hFile != INVALID_HANDLE_VALUE) {
DWORD dwFileSize = GetFileSize(hFile, NULL);
CloseHandle(hFile);
}
*********************************************
*MessageBox()与AfxMessageBox()的区别
C 中可调用的涵数大致可分三类:
1.类自己的涵数,只对类自己的数据成员有作用;
2.AFX小组在设计 Application Framworks 时设计的全局函数,多冠在Afx前缀,在包含
了MFC库/框架的工程中可用;
3.Windows API的全局函数。对所有Windows平台下的程序设计都可以调用,如Vb,Vc,De
phi等等。
你说的 MessageBox是属于 CWnd 类的成员函数,只能在 CWnd 和CWnd的派生类的对象中
调用;AfxMessageBox则可在任何地方调用。
另外对应的还有: ::MessageBox()这个windows API的全局函数。
上述中1和3一般有一个区别,就是1要比3少一个参数,即窗口句柄。大家知道,这个句
柄是通过 this 指针曲折转换得到的,不用程序员操心了。
*************************************************
*VC开发小技巧20个
发信站: 武汉白云黄鹤站 (2002年11月29日12:56:15 星期五), 转信

一、打开CD-ROM
mciSendString("Set cdAudio door open wait",NULL,0,NULL);
二、关闭CD_ROM
mciSendString("Set cdAudio door closed wait",NULL,0,NULL);
三、关闭计算机
OSVERSIONINFO OsVersionInfo; //包含操作系统版本信息的数据结构
OsVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&OsVersionInfo); //获取操作系统版本信息
if(OsVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
{
//Windows98,调用ExitWindowsEx()函数重新启动计算机
DWORD dwReserved;
ExitWindowsEx(EWX_REBOOT,dwReserved); //可以改变第一个参数,实现注销用户、
//关机、关闭电源等操作
// 退出前的一些处理程序
}
四、重启计算机
typedef int (CALLBACK *SHUTDOWNDLG)(int); //显示关机对话框函数的指针
HINSTANCE hInst = LoadLibrary("shell32.dll"); //装入shell32.dll
SHUTDOWNDLG ShutDownDialog; //指向shell32.dll库中显示关机对话框函数的指针
if(hInst != NULL)
{
//获得函数的地址并调用之
ShutDownDialog = (SHUTDOWNDLG)GetProcAddress(hInst,(LPSTR)60);
(*ShutDownDialog)(0);
}
五、枚举所有字体
LOGFONT lf;
lf.lfCharSet = DEFAULT_CHARSET; // Initialize the LOGFONT structure
strcpy(lf.lfFaceName,"");
CClientDC dc (this);
// Enumerate the font families
::EnumFontFamiliesEx((HDC) dc,&lf,
(FONTENUMPROC) EnumFontFamProc,(LPARAM) this,0);
//枚举函数
int CALLBACK EnumFontFamProc(LPENUMLOGFONT lpelf,
LPNEWTEXTMETRIC lpntm,DWORD nFontType,long lparam)
{
// Create a pointer to the dialog window
CDay7Dlg* pWnd = (CDay7Dlg*) lparam;
// add the font name to the list box
pWnd ->m_ctlFontList.AddString(lpelf ->elfLogFont.lfFaceName);
// Return 1 to continue font enumeration
return 1;
}
其中m_ctlFontList是一个列表控件变量
六、一次只运行一个程序实例,如果已运行则退出
if( FindWindow(NULL,"程序标题")) exit(0);
七、得到当前鼠标所在位置
CPoint pt;
GetCursorPos(&pt); //得到位置
八、上下文菜单事件触发事件:OnContextMenu事件
九、显示和隐藏程序菜单
CWnd *pWnd=AfxGetMainWnd();
if(b_m) //隐藏菜单
{
pWnd->SetMenu(NULL);
pWnd->DrawMenuBar();
b_m=false;
}
else
{
CMenu menu;
menu.LoadMenu(IDR_MAINFRAME); ////显示菜单 也可改变菜单项
pWnd->SetMenu(&menu);
pWnd->DrawMenuBar();
b_m=true;
menu.Detach();
}
十、获取可执行文件的图标
HICON hIcon=::ExtractIcon(AfxGetInstanceHandle(),_T("NotePad.exe"),0);
if (hIcon &&hIcon!=(HICON)-1)
{
pDC->DrawIcon(10,10,hIcon);
}
DestroyIcon(hIcon);
十一、窗口自动靠边程序演示
BOOL AdjustPos(CRect* lpRect)
{//自动靠边
int iSX=GetSystemMetrics(SM_CXFULLSCREEN);
int iSY=GetSystemMetrics(SM_CYFULLSCREEN);
RECT rWorkArea;
BOOL bResult = SystemParametersInfo(SPI_GETWORKAREA, sizeof(RECT), &rWorkAre
a, 0);
CRect rcWA;
if(!bResult)
{//如果调用不成功就利用GetSystemMetrics获取屏幕面积
rcWA=CRect(0,0,iSX,iSY);
}
else
rcWA=rWorkArea;
int iX=lpRect->left;
int iY=lpRect->top;
if(iX < rcWA.left + DETASTEP && iX!=rcWA.left)
{//调整左
//pWnd->SetWindowPos(NULL,rcWA.left,iY,0,0,SWP_NOSIZE);
lpRect->OffsetRect(rcWA.left-iX,0);
AdjustPos(lpRect);
return TRUE;
}
if(iY < rcWA.top + DETASTEP && iY!=rcWA.top)
{//调整上
//pWnd->SetWindowPos(NULL ,iX,rcWA.top,0,0,SWP_NOSIZE);
lpRect->OffsetRect(0,rcWA.top-iY);
AdjustPos(lpRect);
return TRUE;
}
if(iX + lpRect->Width() > rcWA.right – DETASTEP && iX !=rcWA.right-lpRect->W
idth())
{//调整右
//pWnd->SetWindowPos(NULL ,rcWA.right-rcW.Width(),iY,0,0,SWP_NOSIZE);
lpRect->OffsetRect(rcWA.right-lpRect->right,0);
AdjustPos(lpRect);
return TRUE;
}
if(iY + lpRect->Height() > rcWA.bottom – DETASTEP && iY !=rcWA.bottom-lpRect
->Height())
{//调整下
//pWnd->SetWindowPos(NULL ,iX,rcWA.bottom-rcW.Height(),0,0,SWP_NOSIZE);
lpRect->OffsetRect(0,rcWA.bottom-lpRect->bottom);
return TRUE;
}
return FALSE;
}
//然后在ONMOVEING事件中使用所下过程调用
CRect r=*pRect;
AdjustPos(&r);
*pRect=(RECT)r;
十二、给系统菜单添加一个菜单项
给系统菜单添加一个菜单项需要进行下述三个步骤:
首先,使用Resource Symbols对话(在View菜单中选择Resource Symbols...可以显
示该对话)定义菜单项ID,该ID应大于0×0F而小于0xF000;
其次,调用CWnd::GetSystemMenu获取系统菜单的指针并调用CWnd:: Appendmenu将菜单
项添加到菜单中。下例给系统菜单添加两个新的
int CMainFrame:: OnCreate (LPCREATESTRUCT lpCreateStruct)
{

//Make sure system menu item is in the right range.
ASSERT(IDM_MYSYSITEM<0xF000);
//Get pointer to system menu.
CMenu* pSysMenu=GetSystemMenu(FALSE);
ASSERT_VALID(pSysMenu);
//Add a separator and our menu item to system menu.
CString StrMenuItem(_T ("New menu item"));
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_MYSYSITEM, StrMenuItem);

}
十三、运行其它程序
//1、运行EMAIL或网址
char szMailAddress[80];
strcpy(szMailAddress,"mailto:netvc@21cn.com");
ShellExecute(NULL, "open", szMailAddress, NULL, NULL, SW_SHOWNORMAL);
//2、运行可执行程序
WinExec("notepad.exe",SW_SHOW); //运行计事本
十四、动态增加或删除菜单
1、 增加菜单
//添加
CMenu *mainmenu;
mainmenu=AfxGetMainWnd()->GetMenu(); //得到主菜单
(mainmenu->GetSubMenu (0))->AppendMenu (MF_SEPARATOR);//添加分隔符
(mainmenu->GetSubMenu (0))->AppendMenu(MF_STRING,ID_APP_ABOUT,_T("Always on
&Top")); //添加新的菜单项
DrawMenuBar(); //重画菜单
2、 删除菜单
//删除
CMenu *mainmenu;
mainmenu=AfxGetMainWnd()->GetMenu(); //得到主菜单
CString str ;
for(int i=(mainmenu->GetSubMenu (0))->GetMenuItemCount()-1;i>=0;i–) //取得菜
单的项数。
{
(mainmenu->GetSubMenu (0))->GetMenuString(i,str,MF_BYPOSITION);
//将指定菜单项的标签拷贝到指定的缓冲区。MF_BYPOSITION的解释见上。
if(str=="Always on &Top") //如果是刚才我们增加的菜单项,则删除。
{
(mainmenu->GetSubMenu (0))->DeleteMenu(i,MF_BYPOSITION);
break;
}
十五、改变应用程序的图标
静态更改: 修改图标资源IDR_MAINFRAME。它有两个图标,一个是16*16的,另一个是3
2*32的,注意要一起修改。
动态更改: 向主窗口发送WM_SETICON消息.代码如下:
HICON hIcon=AfxGetApp()->LoadIcon(IDI_ICON);
ASSERT(hIcon);
AfxGetMainWnd()->SendMessage(WM_SETICON,TRUE,(LPARAM)hIcon);
十六、另一种改变窗口标题的方法
使用语句 CWnd* m_pCWnd = AfxGetMainWnd( ),然后,再以如下形式调用SetWindowTe
xt()函数:
SetWindowText( *m_pCWnd,(LPCTSTR)m_WindowText);// m_WindowText可以是一个CSt
ring类的变量。
十七、剪切板上通过增强元文件拷贝图像数据
下面代码拷贝通过元文件拷贝图像数据到任何应用程序,其可以放置在CView派生类的函
数中。
CMetaFileDC * m_pMetaDC = new CMetaFileDC();
m_pMetaDC->CreateEnhanced(GetDC(),NULL,NULL,"whatever");
//draw meta file
//do what ever you want to do: bitmaps, lines, text…
//close meta file dc and prepare for clipboard;
HENHMETAFILE hMF = m_pMetaDC->CloseEnhanced();
//copy to clipboard
OpenClipboard();
EmptyClipboard();
::SetClipboardData(CF_ENHMETAFILE,hMF);
CloseClipboard();
//DeleteMetaFile(hMF);
delete m_pMetaDC;
十八、剪切板上文本数据的传送
把文本放置到剪接板上:
CString source;
//put your text in source
if(OpenClipboard())
{
HGLOBAL clipbuffer;
char * buffer;
EmptyClipboard();
clipbuffer = GlobalAlloc(GMEM_DDESHARE, source.GetLength()+1);
buffer = (char*)GlobalLock(clipbuffer);
strcpy(buffer, LPCSTR(source));
GlobalUnlock(clipbuffer);
SetClipboardData(CF_TEXT,clipbuffer);
CloseClipboard();
}
从剪接板上获取文本:
char * buffer;
if(OpenClipboard())
{
buffer = (char*)GetClipboardData(CF_TEXT);
//do something with buffer here
//before it goes out of scope
}
CloseClipboard();
十九、将捕捉屏幕图像到剪切版中
void CShowBmpInDlgDlg::OnCutScreen()
{
ShowWindow(SW_HIDE);
RECT r_bmp={0,0,::GetSystemMetrics(SM_CXSCREEN),
::GetSystemMetrics(SM_CYSCREEN)};
HBITMAP hBitmap;
hBitmap=CopyScreenToBitmap(&r_bmp);
//hWnd为程序窗口句柄
if (OpenClipboard())
{
EmptyClipboard();
SetClipboardData(CF_BITMAP, hBitmap);
CloseClipboard();
}
ShowWindow(SW_SHOW);
}
HBITMAP CShowBmpInDlgDlg::CopyScreenToBitmap(LPRECT lpRect)
{
//lpRect 代表选定区域
{
HDC hScrDC, hMemDC;
// 屏幕和内存设备描述表
HBITMAP hBitmap, hOldBitmap;
// 位图句柄
int nX, nY, nX2, nY2;
// 选定区域坐标
int nWidth, nHeight;
// 位图宽度和高度
int xScrn, yScrn;
// 聊环直媛?
// 确保选定区域不为空矩形
if (IsRectEmpty(lpRect))
return NULL;
//为屏幕创建设备描述表
hScrDC = CreateDC("DISPLAY", NULL, NULL, NULL);
//为屏幕设备描述表创建兼容的内存设备描述表
hMemDC = CreateCompatibleDC(hScrDC);
// 获得选定区域坐标
nX = lpRect->left;
nY = lpRect->top;
nX2 = lpRect->right;
nY2 = lpRect->bottom;
// 获得 聊环直媛?
xScrn = GetDeviceCaps(hScrDC, HORZRES);
yScrn = GetDeviceCaps(hScrDC, VERTRES);
//确保选定区域是可见的
if (nX<0)
nX = 0;
if (nY<0)
nY = 0;
if (nX2>xScrn)
nX2 = xScrn;
if (nY2>yScrn)
nY2 = yScrn;
nWidth = nX2 – nX;
nHeight = nY2 – nY;
// 创建一个与屏幕设备描述表兼容的位图
hBitmap = CreateCompatibleBitmap
(hScrDC, nWidth, nHeight);
// 把新位图选到内存设备描述表中
hOldBitmap =(HBITMAP)SelectObject(hMemDC, hBitmap);
// 把屏幕设备描述表拷贝到内存设备描述表中
BitBlt(hMemDC, 0, 0, nWidth, nHeight,
hScrDC, nX, nY, SRCCOPY);
//得到屏幕位图的句柄
hBitmap = (HBITMAP)SelectObject(hMemDC, hOldBitmap);
//清除
DeleteDC(hScrDC);
DeleteDC(hMemDC);
// 返回位图句柄
return hBitmap;
}
}
二十、如何将位图缩放显示在Static控件中
//在Staic控件内显示位图
void CShowBmpInDlgDlg::ShowBmpInStaic()
{
CBitmap hbmp;
HBITMAP hbitmap;
//将pStatic指向要显示的地方
CStatic *pStaic;
pStaic=(CStatic*)GetDlgItem(IDC_IMAGE);
//装载资源 MM.bmp是我的一个文件名,用你的替换
hbitmap=(HBITMAP)::LoadImage (::AfxGetInstanceHandle(),"MM.bmp",
IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_CREATEDIBSECTION);
hbmp.Attach(hbitmap);
//获取图片格式
BITMAP bm;
hbmp.GetBitmap(&bm);
CDC dcMem;
dcMem.CreateCompatibleDC(GetDC());
CBitmap *poldBitmap=(CBitmap*)dcMem.SelectObject(hbmp);
CRect lRect;

pStaic->GetClientRect(&lRect);
lRect.NormalizeRect();
//显示位图
pStaic->GetDC()->StretchBlt(lRect.left ,lRect.top ,lRect.Width(),lRect.Heigh
t(),
&dcMem,0 ,0,bm.bmWidth,bm.bmHeight,SRCCOPY);
dcMem.SelectObject(&poldBitmap);
}
***************************************
*如何在程序里知道硬盘的剩余空间
CString s;
ULONGLONG
ullFreeBytesAvailableToCaller,
ullTotalNumberOfBytes,
ullTotalNumberOfFreeBytes;
if(GetDiskFreeSpaceEx(
_T("C:\\"),
(PULARGE_INTEGER)&ullFreeBytesAvailableToCaller,
(PULARGE_INTEGER)&ullTotalNumberOfBytes,
(PULARGE_INTEGER)&ullTotalNumberOfFreeBytes))
{
s.Format(_T("磁盘剩余空间:%lKB"), ullTotalNumberOfFreeBytes/1024);
MessageBox(s);
}
***********************************************
*如何unable对话框上的x
标 题: Re: 如何unable对话框上的x?
发信站: 武汉白云黄鹤站 (2002年08月13日01:33:12 星期二), 转信

CMenu *pMenu=this->GetSystemMenu(FALSE);
pMenu->EnableMenuItem(SC_CLOSE,MF_BYCOMMAND|MF_GRAYED);

【 在 eleven (eleven) 的大作中提到: 】
: 但保留最大化和最小化按钮?
: 谢了先!
**********************************
*怎么把CString型的数字转变成int
先得到CString的字串char*,
再用atoi
******************************************
*已知HDC,怎么得到CDC
static CDC* PASCAL FromHandle( HDC hDC );
CDC* GetDC( );
***********************************
*把某个盘设为共享,有这样的函
NetShareAdd
**************************************
*如何映射网络驱动器
WNetAddConnection
**********************
*注册表中有没有启动加载DLL的设置
HKEY_LOCAL_MACHINE\Software\Microsoft
\Windows\CurrentVersion\SharedDLLs.好像是的吧
*********************************
*怎样把进程注册为服务
HKEY_CLASSES_ROOT\APPID
******************************
*在应用程序类中,有个InitInstance()函数,
: 在这个函数中假如这样的两行:
: RegisterShellFileType();
: EnableShellOpen();
RegisterShellFileType()作用就是允许文件关联。如notepad可以打开text文本文件,

如果你创建了一个新的文件类型,操作系统默认以你的程序
打开你创建的文件类型。
EnableShellOpen():当你拖动文件到你程序时,可以打开相应的文档。入IE,当你拖动文
档到IE
时可以打开该文档。
*******************************
*怎么得到DLL的输出函数
使用dumpbin.exe

不过没有参************
***************************************
*谁给我解答一下SPY++用法的几点疑惑
发信站: 武汉白云黄鹤站 (Wed Aug 30 09:32:35 2000) , 站内信件

【 在 jinyix (jyx) 的大作中提到: 】
: (1)用 find window 看到 子窗口和主窗口properties中
: 的Instance handle怎么会不一样?
: 我想都应该是程序的实例句柄(唯一)。
:
:
: (2)WINDOW View中有很多"Default IME" IME
: 表示什么窗口?
首先必须明白的是: 对于按钮,列表框等等东西,他们都是一个窗口,只是窗口
的风格不一样,因此它们也有自己句柄.
Default IME是指缺省的输入法,每个窗口都有一个缺省的输入法.
**********************************************
*如何调用DLL中的资源(位图…)
使用方法如下:
HMODULE hMoudle = (HMODULE)LoadLibrary("XXXXX.dll");
HRSRC hRes = FindResource(hModule,MAKEINTRESOURCE(IDB_MYBITMAP),"RT_BITMAP")
;
HBITMAP hBmp = (HBITMAP)LoadResource(hModule,hRes);
Remarks
Both Windows 95 and Windows NT automatically free resources. You do not need
to call the FreeResource function to free a resource loaded by using the Lo
adResource function.
但是这一句是需要的:
FreeLibrary(hMoudle);
*****************************************
*如何在VC程序中执行*.lnk文件
Try ShellExecuteEx().
************************************
*把指定目录下的所有文件拷到另一个指定目
SHFILEOPSTRUCT fileop;
CString Title = "Copy Files";
fileop.hwnd = this->m_hWnd;
fileop.pFrom = "C:/Winnt/*.*";
fileop.pTo = "d:/temp"
fileop.wFunc = FO_COPY;
fileop.fFlags = FOF_NOCONFIRMMKDIR;
fileop.lpszProgressTitle = (LPCSTR)Title;
SHFileOperation(&fileop);

********************************************
* 怎么得到主窗口的句柄
AfxGetMainWnd()->GetSafeHwnd()
******************************************
*动态的声明一个二维数组(两个维数都未知)
double ** twoPtrAlloc(LONG h,LONG w)
{
double ** pd;
pd=new double* [h];
if(!pd)
{
return NULL;
}
for (int i=0;i<h;i++)
{
pd[i]=new double[w];
if(!pd[i])
{
return NULL;
}
}
return pd;
}
**************************************
*MDI程序中,在不同的子窗口中切换时系统会产生何种消息?

WM_MDIACTIVATE
****************************************************
*有效的跟踪WM_PAINT消息
可以在
OnDraw中打印一个自增的数。看看运行时是否进入了
OnDraw中,也可以用MessageBeep听听声音
****************************************
*在非视图非文档类中得到视图指针 MDI
CMainFrame * pFrame=(CMainFrame *)(AfxGetApp()->m_pMainWnd());
ASSERT(pFrame!=NULL);
(CYourView *)pView=(CYourView *)(pFrame->MDIGetActive()->GetActiveView());
ASSERT(pView!=NULL);
**********************************
*VC5中MFC DLL的分类及特点

  在VC5中有三种形式的MFC DLL(在该DLL中可以使用和继承已有的MFC类)可供选择,即Regular statically linked to MFC DLL(标准静态链接MFC DLL)和Regular using the shared MFC DLL(标准动态链接MFC DLL)以及Extension MFC DLL(扩展MFC DLL)。第一种DLL的特点是,在编译时把使用的MFC代码加入到DLL中,因此,在使用该程序时不需要其他MFC动态链接类库的存在,但占用磁盘空间比较大;第二种DLL的特点是,在运行时,动态链接到MFC类库,因此减少了空间的占用,但是在运行时却依赖于MFC动态链接类库;这两种DLL既可以被MFC程序使用也可以被Win32程序使用。第三种DLL的特点类似于第二种,做为MFC类库的扩展,只能被MFC程序使用。

*******************************************
*Win32全局钩子的运行机制

  钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。对每种类型的钩子由系统来维护一个钩子链,最近安装的钩子放在链的开始,而最先安装的钩子放在最后,也就是后加入的先获得控制权。要实现Win32的系统钩子,必须调用SDK中的API函数SetWindowsHookEx来安装这个钩子函数,这个函数的原型是HHOOK SetWindowsHookEx(int idHook,HOOKPROC lpfn,HINSTANCE hMod,DWORD dwThreadId);,其中,第一个参数是钩子的类型;第二个参数是钩子函数的地址;第三个参数是包含钩子函数的模块句柄;第四个参数指定监视的线程。如果指定确定的线程,即为线程专用钩子;如果指定为空,即为全局钩子。其中,全局钩子函数必须包含在DLL(动态链接库)中,而线程专用钩子还可以包含在可执行文件中。得到控制权的钩子函数在完成对消息的处理后,如果想要该消息继续传递,那么它必须调用另外一个SDK中的API函数CallNextHookEx来传递它。钩子函数也可以通过直接返回TRUE来丢弃该消息,并阻止该消息的传递。

*************************************************
*在VC5中全局共享数据的实现

  在主文件中,用#pragma data_seg建立一个新的数据段并定义共享数据,其具体格式为:

  #pragma data_seg ("shareddata")

  HWND sharedwnd=NULL;//共享数据

  #pragma data_seg()

  仅定义一个数据段还不能达到共享数据的目的,还要告诉编译器该段的属性,有两种方法可以实现该目的(其效果是相同的),一种方法是在.DEF文件中加入如下语句:

  SETCTIONS

  shareddata READ WRITE SHARED

  另一种方法是在项目设置链接选项中加入如下语句:

  /SECTION:shareddata,rws
***********************************************************
*怎么由句柄得到指针
CWnd有一个静态函数叫做FromHandlePermanent(HWND hWnd)
如果是通过窗口句柄得到自己进程内的窗口指针,这个函数将会正常工作
如果是得到其它进程的窗口指针就不对了

创建的窗口在系统中有一张表维护指针和句柄之间的关系
CWnd的FromHandlePermanent(HWND hWnd)就是通过这张表查找到窗口指针的

CHandleMap* pMap = afxMapHWND();//首先取得这张表
CWnd* pWnd = NULL;
if (pMap != NULL)
{
//查找窗口句柄对应的CWnd对象
pWnd = (CWnd*)pMap->LookupPermanent(hWnd);
ASSERT(pWnd == NULL || pWnd->m_hWnd == hWnd);
}
return pWnd;
*********************************************************
*怎么调试一个Automation服务器
Project->Setting->Debug->Program arguments

加入/Embedding
*********************************************
*如何使窗口在一开始就不可见
看看有没有WS_HIDE,如果没有,则加入WS_MINIMIZED,然后在

OnEraseBkGnd的消息里面加入:
static BOOL bFirst=TRUE;
if(bFirst)
{
bFirst=FALSE;
ShowWindow(SW_HIDE);
return TRUE;
}
************************************************
*.dll如何在VC中应用
如果有这个动态连接库的调用格式资料的话,问题很简单.
1)动态连接
LoadLibery()
GetProcessAddress()
2)静态连接
首先生成这个动态连接库的引入库;
再用到这个库的文件开始加上
#progma comment(lib,"libname");
************************************************
*新式的用CSocket获得本机地址方法。
CSocket socket;
socket.Create();
socket.Connect("pop3.sina.com.cn", 110);//或连接pop.tom.com等等
CString strAddress;
UINT uPort;
socket.GetSockName( strAddress, uPort);
CClientDC dc(this);
dc.TextOut( 30, 30, "你的IP:" + strAddress);
socket.Close();  
************************************************
*如何用VC创建ODBC数据源,而不是人工在控制面板的ODBC数据源创建?
如建一个为MyTax的Access 2000数据源,Tax.mdb在D:\
if( !SQLConfigDataSource(
NULL,
ODBC_ADD_DSN, //加在USER DSN,若要加在SYS DSN则为ODBC_ADD_SYSDSN
"Microsoft Access Driver (*.mdb)",
"DSN = MyTaxDBQ = D:\\Tax.mdb")
//注意,XXXX.mdb必须为英文,若为中文在VC6会报错的,VC.NET 2003 没测试过
)
AfxMessageBox("创建失败!");
 要加上头文件#include <odbcinst.h>,在Link处加上odbccp32.lib。
************************************************
*怎么样用VC调用 regsvr32.exe 命令
WinExec( "regsvr32.exe Test.dll", SW_SHOW);

shellexecute
************************************************
* 隐藏WINDOWS系统任务栏
::ShowWindow (::FindWindow("Shell_TrayWnd",NULL),SW_HIDE);
************************************************
*全屏的实现关键代码
int CX = GetSystemMetrics(SM_CXSCREEN);
int CY = GetSystemMetrics(SM_CYSCREEN);

::MoveWindow(hwnd,0,0,CX,CY,TRUE);
ShowWindow(hwnd, SW_SHOWMAXIMIZED);
********************************
* 怎样使应用程序窗口在最前
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
cs.dwExStyle|=WS_EX_TOPMOST;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~only add this sentence
return TRUE;
}
********************************
* 去除框架窗口的标题
LONG style=::GetWindowLong(m_wndToolBar,GWL_STYLE);
style&=~(WS_CAPTION);
::SetWindowLong(m_wndToolBar,GWL_STYLE,style);
********************************
* 怎样除掉工具栏的标题(浮动时出现的)
LONG style=::GetWindowLong(m_wndToolBar.GetParentFrame()->m_hWnd,GWL_STYLE);
style&=~(WS_CAPTION);
::SetWindowLong(m_wndToolBar.GetParentFrame()->m_hWnd,GWL_STYLE,style);
m_wndToolBar.GetParentFrame()->ModifyStyle(0,MFS_MOVEFRAME|MFS_4THICKFRAME|MFS_THICKFRAME);
注意工具栏的父窗口并不是你的CMainFrame!!
********************************
*把cstring对象转换成普通字符串char
CString str="***********";
char *p=str.GetBuffer(str.GetLength()+1);
********************************
*如何调用DLL中的资源(位图…)
]使用方法如下:
HMODULE hMoudle = (HMODULE)LoadLibrary("XXXXX.dll");
HRSRC hRes = FindResource(hModule,MAKEINTRESOURCE(IDB_MYBITMAP),"RT_BITMAP")
;
HBITMAP hBmp = (HBITMAP)LoadResource(hModule,hRes);
Remarks
Both Windows 95 and Windows NT automatically free resources. You do not need
to call the FreeResource function to free a resource loaded by using the Lo
adResource function.
但是这一句是需要的:
FreeLibrary(hMoudle);
*****************************************************************
*dll中定义的全局变量和静态变量,共享变量
dll中定义的全局变量和静态变量在每一个使用它的exe中都有拷贝,不能
共享。
如果要共享用#pragma data_seg建立一个新的数据
段并定义共享数据,其具体格式为:
  #pragma data_seg ("shareddata")
  HWND sharedwnd=NULL;//共享数据
  #pragma data_seg()
仅定义一个数据段还不能达到共享数据的目的,还要告诉编译器该段的属性,有两种方
法可以实现该目的(其效果是相同的),
一种方法是在.DEF文件中加入如下语句:
  SETCTIONS
  shareddata READ WRITE SHARED

***********************************************************************
*fopen()打开一个文件后想读其中第90行的第19个字符
先用fseek(fp,offset,SEEK_SET)把指针定位到要读取的位置,然后fread(void *buffe
r,int size,int count,FILE *fp);读取/
到网上搜索一下函数的用法。

***********************************************************************
*如何锁定键盘和鼠标
typedef BOOL ( __stdcall *BlockInput)(BOOL Flag);
BlockInput p = (BlockInput)GetProcAddress(::LoadLibrary("user32.dll"), "
Blo
ckInput");
if (p)
{
p(FALSE); //这样就可以了
}
***********************************************************************
* 请问如何做一个安装程序
简单介绍一下Installshield for VC++6.0的用法如下:?
1。首先按照File->new->project wizard操作生成新project。
2。在File Groups中,对各个文件夹的link右键点击,加入对应的files或者new folder。
3。对Components的included files groups双击,弹出对话框,从而导入需要
的File groups中的对应文件夹。你高兴也可以修改status text等。
4。加入开始菜单中的快捷操作方式:
在resources中,
shell objects->explorer shell->desktop->start menu->programs,然后对programs
右键点击操作,加入快捷方式,然后对第一行双击,会谈出对话框,在target中
加入<TARGETDIR>\***.exe,icon选择0,即可。
5。对media的操作,菜单build->media build wizard…->finish。
6。菜单build->send media to操作导出安装。
7。如有其他细节如是否压缩等,自己看一看就明白了。

2006年06月01日

1.  256色及更低色位图由三部分组成。一是位图信息头,定义了位图的大小等信息。二是颜色表,定义了各象素点值对应的红、绿、蓝三色分量。三是位图的象素数据块,包含位图中的每个象素点的数据,它们与位图信息头中的颜色表一起决定了象素的最终显示颜色。对于大于256色的位图,通常由两区域组成,即位图信息头和象素数据块,象素数据块中的每个象素点的数据包含了红、绿、蓝三色分量,具体细节,请参看相关的专业书籍。

2.  每个设备描述表中都包含有一个位图,CreateCompatibleDC返回的兼容设备描述表所包含的位图大小仅为一个象素,并且是单色图像。

3.  CreateCompatibleBitmap返回的位图对象只包含相应设备表述表中的位图的位图信息头,不包含颜色表和象素数据块。因此,选入该位图对象的设备描述表不能象选入普通位图对象的设备描述表一样应用,必须在SelectObject函数之后,调用BitBlt将原始设备描述表的颜色表及象素数据块拷贝到兼容设备描述表。

4.  在获取光标图像的程序代码中,GetCursorPos所取回的坐标点是光标的中间点的位置,而DrawIconEx函数所要求的坐标值为光标的左上角坐标,因此,要调用GetIconInfo对坐标点进行转换。

5.  OnDraw函数的实现代码中,我在BitBlt函数之前调用了SelectObject将屏幕的兼容位图选入CgrabScreenView窗口的设备描述表,有人告诉我,说不用SelectObject这一步,也是一样的效果。我问,如果,你要将一台计算机上的屏幕传送到网上的另外一台计算机上显示,效果还是一样吗?结果是不尽相同的。重要的一点是,BitBlt函数只拷贝颜色表及象素数据块,而不含位图信息头。只是,本例中的CgrabScreenView窗口的设备描述表中所包含的位图对象的位图信息头与m_hbmScreen中的一样,所以,有了以上疑问。

1.新建一个Win32 Dynamic-Link Library空工程,新建一个Test.cpp文件,加入如下代码:

int add(int x, int y)
{
 return x+y;
}

extern "C" _declspec (dllexport) int add3(int x, int y, int z)
{
 return add(x,y) + z;
}

调用时两种方法:
a.建一个MFC对话框工程,加一个Button并添加Click事件,添加以下代码

extern "C" _declspec (dllimport) int add3(int x, int y, int z);
void CDLLClientTestDlg::OnButton1()
{
 CString str;
 str.Format("%d", add3(1,2,3));
 MessageBox(str);
}
b.添加以下代码
void CDLLClientTest1Dlg::OnButton1()
{
 CString str;
 typedef int (*PADD3)(int x, int y, int z);
 PADD3 add3;
 HINSTANCE hDll = LoadLibrary("DLLTest");
 add3 = (PADD3)GetProcAddress(hDll, "add3");
 str.Format("%d", add3(1, 2, 4));
 MessageBox(str);
 FreeLibrary(hDll);
}

2.新建一个Win32 Dynamic-Link Library工程,选择exports some symbols,去掉代码仅保留.h文件中的
#ifdef DLLTESTH_EXPORTS
#define DLLTESTH_API __declspec(dllexport)
#else
#define DLLTESTH_API __declspec(dllimport)
#endif
注意生成这个工程时会在工程中预定义DLLTESTH_EXPORTS,在Project->settings->c/c++->preprocessor definition中可以看到
在.h文件中添加
DLLTESTH_API int add3(int x, int y, int z);
在.cpp文件中添加
int add(int x, int y)
{
 return x + y;
}

int add3(int x, int y, int z)
{
 return add(x, y) + z;
}
调用时,新建一个MFC对话框工程,加一个Button并添加Click事件,添加以下代码
void CDLLClientTestHDlg::OnButton1()
{
 CString str;
 str.Format("%d", add3(1, 2, 4));
 MessageBox(str);
}
把前面的.h文件和编译生成的.lib文件copy到工程的目录下,把生成的.dll文件copy到Debug目录下即可,这种调用方法更值得推荐

2006年05月10日

GDI+  is  a  class-based  application  programming  interface  (API)  for  C/C++  programmers.    
 
利用  GDI+  进行开发分为四个步骤:  
 
1.  确保  VC.NET  已正确安装,然后在项目源程序中加上正确的头文件和链接库:  
 
   #include  <gdiplus.h>  
   #pragma  comment(lib,  "gdiplus.lib")  
   using  namespace  Gdiplus;  
 
2.  初始化GDI+库(在能够正确地引用  GDI+  类之前必须进行初始化):  
 
   GdiplusStartupInput  gdiplusStartupInput;  
   ULONG_PTR                      gdiplusToken;  
   GdiplusStartup(&gdiplusToken,  &gdiplusStartupInput,  NULL);  
 
 
3.  用  GDI+  进行图形绘制,例如,在某一窗口类的  OnPaint()  消息函数中:  
 
   Graphics  graphics(dc);    //dc  为  HDC  类型的图形设备上下文句柄  
   Bitmap  bitmap(L"c:\\abc.bmp");  
   graphics.DrawImage(&bitmap,  0,0,  100,100);  
   Pen  pen(Color(105,255,0,0));  
   graphics.DrawLine(&pen,  0,0,100,100);  
 
4.  程序结束前:  
 
   GdiplusShutdown(gdiplusToken);  

2006年04月26日

  1.引言

  C++语言的创建初衷是“a better C”,但是这并不意味着C++中类似C语言的全局变量和函数所采用的编译和连接方式与C语言完全相同。作为一种欲与C兼容的语言,C++保留了一部分过程式语言的特点(被世人称为“不彻底地面向对象”),因而它可以定义不属于任何类的全局变量和函数。但是,C++毕竟是一种面向对象的程序设计语言,为了支持函数的重载,C++对全局函数的处理方式与C有明显的不同。

  2.从标准头文件说起

  某企业曾经给出如下的一道面试题:

  面试题
  为什么标准头文件都有类似以下的结构?

#ifndef __INCvxWorksh
#define __INCvxWorksh
#ifdef __cplusplus
extern "C" {
#endif
/*…*/
#ifdef __cplusplus
}
#endif
#endif /* __INCvxWorksh */


  分析
  显然,头文件中的编译宏“#ifndef __INCvxWorksh、#define __INCvxWorksh、#endif” 的作用是防止该头文件被重复引用。

  那么

#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif


  的作用又是什么呢?我们将在下文一一道来。
  3.深层揭密extern "C"

  extern "C" 包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”的;其次,被它修饰的目标是“C”的。让我们来详细解读这两重含义。

  被extern "C"限定的函数或变量是extern类型的;

  extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。记住,下列语句:

  extern int a;


  仅仅是一个变量的声明,其并不是在定义变量a,并未为a分配内存空间。变量a在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。

  通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数。

  与extern对应的关键字是static,被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其不可能被extern “C”修饰。

  被extern "C"修饰的变量和函数是按照C语言方式编译和连接的;

  未加extern “C”声明时的编译方式

  首先看看C++中对类似C的函数是怎样编译的。

  作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:

void foo( int x, int y );


  该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。

  _foo_int_int这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。
  同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。

  未加extern "C"声明时的连接方式

  假设在C++中,模块A的头文件如下:

// 模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
int foo( int x, int y );
#endif


  在模块B中引用该函数:

// 模块B实现文件 moduleB.cpp
#include "moduleA.h"
foo(2,3);


  实际上,在连接阶段,连接器会从模块A生成的目标文件moduleA.obj中寻找_foo_int_int这样的符号!

  加extern "C"声明后的编译和连接方式

  加extern "C"声明后,模块A的头文件变为:

// 模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
extern "C" int foo( int x, int y );
#endif


  在模块B的实现文件中仍然调用foo( 2,3 ),其结果是:

  (1)模块A编译生成foo的目标代码时,没有对其名字进行特殊处理,采用了C语言的方式;

  (2)连接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号名_foo。

  如果在模块A中函数声明了foo为extern "C"类型,而模块B中包含的是extern int foo( int x, int y ) ,则模块B找不到模块A中的函数;反之亦然。

  所以,可以用一句话概括extern “C”这个声明的真实目的(任何语言中的任何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更深入地理解许多问题):
  实现C++与C及其它语言的混合编程。
  明白了C++中extern "C"的设立动机,我们下面来具体分析extern "C"通常的使用技巧。
  4.extern "C"的惯用法

  (1)在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:

extern "C"
{
#include "cExample.h"
}


  而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。

  笔者编写的C++引用C函数例子工程中包含的三个文件的源代码如下:

/* c语言头文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x,int y);
#endif
/* c语言实现文件:cExample.c */
#include "cExample.h"
int add( int x, int y )
{
return x + y;
}
// c++实现文件,调用add:cppFile.cpp
extern "C"
{
#include "cExample.h"
}
int main(int argc, char* argv[])
{
add(2,3);
return 0;
}


  如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern "C" { }。

  (2)在C中引用C++语言中的函数和变量时,C++的头文件需添加extern "C",但是在C语言中不能直接引用声明了extern "C"的该头文件,应该仅将C文件中将C++中定义的extern "C"函数声明为extern类型。
  笔者编写的C引用C++函数例子工程中包含的三个文件的源代码如下:

//C++头文件 cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern "C" int add( int x, int y );
#endif
//C++实现文件 cppExample.cpp
#include "cppExample.h"
int add( int x, int y )
{
return x + y;
}
/* C实现文件 cFile.c
/* 这样会编译出错:#include "cExample.h" */
extern int add( int x, int y );
int main( int argc, char* argv[] )
{
add( 2, 3 );
return 0;
}


  如果深入理解了第3节中所阐述的extern "C"在编译和连接阶段发挥的作用,就能真正理解本节所阐述的从C++引用C函数和C引用C++函数的惯用法。对第4节给出的示例代码,需要特别留意各个细节。

http://www.pconline.com.cn/pcedu/empolder/gj/c/0508/693175.html