1. 一个Hello Windows程序:
#include <windows.h>
int WINAPI WinMain(
HINSTANCE hInstance, // 当前程序实例号
HINSTANCE hPrevInstance, // Win32中无用,16位机的历史遗留,永远为NULL
LPSTR lpCmdLine, // 运行App的命令行,有些App启动时用它来将程序文件装入内存
// 有时会碰到lpsz,其中sz=string terminated with a zero,即传统C串的意思
int nCmdShow // 指定程序最初如何显示,即刚打开程序时窗口的模样,最大化或最小化于任务栏中
)
{
// 相当于Hello World中的printf函数
// 弹出消息框
// 第一个参数是窗口句柄,第二个参数是消息框中显示的文本,第三个参数表示消息框标题栏中的文本
// 最后一个参数宏表示希望在对话框中显示何种类型的按钮
MessageBox( NULL, TEXT( "Hello, Windows!" ), TEXT( "HelloMsg" ), MB_OK );
// TEXT宏可以便捷地将字符串转化成Unicode
return 0;
}
一、windows.h中包含5个最为核心的头文件:
windef.h:基本数据类型定义
winnt.h:Unicode的支持
winbase.h:内核调用
winuser.h:用户界面支持
wingdi.h:图形设备接口
二、WinMain函数:
是系统API函数,原型定义在winbase.h中
WINAPI宏是一种调用约定,在windef.h中指定,原型为__stdcall,决定了函数参数如何入栈以及清栈(会命令编译器生成相关入栈清栈的汇编代码)
三、MB_宏:
定义在winuser.h中,一般0就表示MB_OK,所以经常这样便捷使用0
除按钮之外还可以定义消息框的图标,如MB_ICONERROR就表示消息框的图标是一个叉,表示错误,MB_ICONWARNING就是一个感叹号表示警告,所有特征使用位或|进行组合即可
返回值的宏也在winuser.h中定义,如IDOK、IDYES等,表示用户按了哪个按钮
2. Unicode——多国语言以及象形文字的编码解决方案:
C编译环境中Unicode为宽字符,即双字节16位表示一个字符(wchar_t类型)
为了方便编码转换,头128个Unicode字符为ASCII字符,接下来的128个字符为ASCII扩展字符(即最高位为1的8位ASCII字符),之后再各个特定区间定义世界上不同的语系
宽字符不一定是Unicode,Unicode只是宽字符的其中一种编码方案,但是Windows编程中可以把宽字符理解成Unicode编码
3. wchar_t类型:
在很多头文件中都被定义过,其中包括wchar.h,其实质就是typedef unsigned shortwchar_t
wchar_t字符串的声明:wchar_t a[] = L".....",其中L是一种编译命令,让编译器把字符串解析为宽字符字符串,每个字符占两个字节,由于机器都是小端的,都是先存低位再存高位,因此内存中实际的数据为0xXX 0x00 0xXX 0x00...,这点一定要注意了,同样数组大小会包括最后的那个空字符,并且空字符也占两个字节!
单字符赋值也类似:wchar_t c = L'A',但是去掉L也无妨,wchar_t c = 'A',因为扩展转化是安全的,同样也是小端存放,即0xXX 0x00
4. 宽字符函数库:
普通的单字节字符串函数库已经不再适用于wchar_t类型了,比如strlen一个宽字符串结果一般都是1,因为内存情况为0xXX 0x00,检查到第二个字节是就已经是空字符了!
一般这样冲突使用会编译器会警告,unsigned short和char类型冲突
所有跟宽字符有关的函数都会在wchar.h中定义,同时这些函数在正常函数的头文件中也会定义一遍,以方便程序开发,比如宽字符版本的strlen和printf为wcslen和wprintf,它们都在wchar.h中定义,但同时也分别于string.h和stdio.h中也有定义以方便编程,其中wcslen的声明为size_t __cdecl wcslen(const wchar_t *);,其中__cdecl为C函数的调用约定
5. Microsoft Visual C++编译器对字符集的维护:
这不是ANSI C标准的一部分,是MS自己的商业行为,为了维护Unicode和ASCII之间的通用性,实现是通过一些列的宏(定义在tchar.h中)来维护
tchar.h定义了所有有关字符串(处理字符串或者是含有字符串参数)的函数的宏名,都以_t或_tc作为前缀,比如_tprintf和_tcslen等,处理方式为:如果_UNICODE宏被定义过则#define _tprintf wprintf,否则就为#define _tprintf printf,同样对于_tcslen,如果_UNICODE被定义则#define _tcslen wcslen,否则#define _tcslen strlen
同样TCHAR通用类型也被如是定义,如果_UNICODE被定义则typedef wchar_t TCHAR,否则typedef char TCHAR
对于字符串常量的通用性维护也有一组宏可使用:_TEXT和_T
#define _T(x) __T(x)
#define _TEXT(x) __T(x)
如果_UNICODE被定义,则#define __T(x) L##(x),否则#define __T(x) x
这些都定义在C++编译器通用解决方案tchar.h中,一般用编译器解决通用问题就是用这两个宏_T和_TEXT,以及_t和_tc函数
6. 上面的是C++编译器的通用解决方案,现在再来看看Win32 API编程的通用解决方案(字符类型的定义):
首先要区分编译器提供的支持和Win32 API提供的支持,编译器的支持中类型都是小写的,而Win32 API的支持中类型都是大写的(这在后面将会看到),如果是写写控制台程序,就用编译器支持差不多就行了,但是如果要写Win32应用程序最好是使用大写的Win32 API支持
Windows内核层面上支持Unicode,底层全部使用宽字符以支持Unicode从而达到国际化的商业目的,但是毕竟还是有地方不使用宽字符的,这就需要两种字符集的转换,因此内核也提供了通用编程的支持
winnt.h提供了字符集以及通用性的支持,其中只有winnt中定义的:
typedef char CHAR;
typedef wchar_t WCHAR;
最常用的表意的类型:字符指针(用于数组中偏移),字符串首部指针(表示一个字符串)
typedef CHAR *LPCH, *LPSTR;
typedef CONST CHAR *LPCCH, *LPCSTR
typedef WCHAR *LPWCH, *LPWSTR;
typedef CONST WCHAR *LPCWCH, *LPCWSTR
Windows编程中推荐使用大写的Windows支持,小写的支持(如wchar_t)这种最好只用在仅仅编译器支持的简单控制台程序即可
winnt.h中也有通用性支持,
TCHAR
LPTCH、LPTSTR
LPCTCH、LPCTSTR
在定义_UNICODE时表示宽字符否则表示常规字符
字符串常量的通用性支持——TEXT:#define TEXT(x) __TEXT(x),当_UNICODE被定义时#define __TEXT(x) L##x,否则为#define __TEXT(x) x,因此编译器支持用_TEXT或_T,Windows编程就用TEXT就行了,推荐TEXT!
7. Win32通用性解决方案之API:
API中所有有关字符串处理的或者是接受字符串参数的函数都有两个入口点,具体讲就是有两个版本,一种版本以A作为后缀表示ASCII版本,另一种则是以W作为后缀表示宽字符版本,这些函数都定义在user32.dll、kernel32.dll库中,例如定义在user32.dll中的MessageBox其实是一个宏,有两个实际的入口点,一个是MessageBoxA函数另一个是MessageBoxW函数,定义方式和之前讲过的一样:
#ifdef UNICODE
#define MessageBox MessageBoxW
#else
#define MessageBox MessageBoxA
#endif
而这两个函数的原型分别为:
WINUSERAPI int WINAPI MessageBoxA( HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType );
WINUSERAPI int WINAPI MessageBoxW( HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType );
*Windows还将一些C语言的标准函数(编译器函数)包装成通用API函数,如:
lstrlen -> lstrlenA(即strlen的包装器)、lstrlenW(即wcslen的包装器)
lstrcpy、lstrcmp、lstrcpyn等,这些都定义在winbase.h中作为基础Windows API
8. 关于Win32程序中使用printf:
Windows程序中不允许使用printf函数,因为Win32程序时图形界面的,没有所谓的标准I/O,标准I/O是指键盘和命令行,由于图形界面没有命令行因此不能使用printf函数,因此只要不涉及标准I/O的函数还是可以在Win32程序中使用的,比如sprintf函数等
**关于用vsprintf函数来实现sprintf函数的问题:
用va_list系列宏实现可变参数函数:
#include <stdarg.h>
#include <stdio.h>
int
ShowArg( int n, ... ) { // ANSI C规定可变参数列表之前必须要有固定参数
va_list args; // va_list就是char *类型,它的意义就是把整个可变参数列表当成一个字符串看待
int arg;
va_start( args, n ); // va_start是一个参数宏,第一个参数是va_list
// 第二个参数为可变参数列表的前一个固定参数
// 可以使va_list指向可变参数列表的第一个参数
// va_arg参数宏可以从va_list中获取第一个参数
// 当然需要指定待获取参数的数据类型(这也是va_arg的第二个参数)
while ( n-- ) printf( "%d ", va_arg( args, int ) );
va_end( args ); // 将args置为NULL用作清空工作
return n;
}
//va_list等宏定义在stdarg.h头文件中
int
Max( int n, ... ) {
va_list args;
int ret;
int tmp;
va_start( args, n );
ret = va_arg( args, int );
n--;
while ( n-- )
if ( ( tmp = va_arg( args, int ) ) > ret )
ret = tmp;
return ret;
}
int
main() {
ShowArg( 5, 1, 2, 3, 4, 5 );
putchar('\n');
printf( "%d\n", Max( 5, 3, 4, 1, 5, 2 ) );
return 0;
}
利用vsprintf实现快速构造可变参数格式化字符串的功能:
#include <stdarg.h>
#include <stdio.h>
int
my_sprintf( char *buf, char *fmt, ... ) {
va_list args;
int ret;
va_start( args, fmt );
ret = vsprintf( buf, fmt, args );
va_end( args );
return ret;
}
// int __cdecl vsprintf( char *szBuf, const char *szFormat, va_list szArgs );
// 该函数的作用和sprintf一样,只不过是将众多可变参数合成了一个字符串而已!
// 因此可以利用vsprintf函数实现快速构造可变参数格式化字符串的功能
int
main() {
char buf[30];
my_sprintf( buf, "%.3f %d %c", 3.234, 5352, 'X' );
puts( buf );
return 0;
}
对于各种sprintf通用性的维护:
编译器标准版:sprintf、swprintf、_stprintf、vsprintf、vswprintf、_vstprintf
API版:wsprintfA、wsprintfW、wsprintf、wvsprintfA、wvsprintfW、wvsprintf(前缀w表示windows)
9. 程序范例——对MessageBox进行格式化字符串包装并显示屏幕分辨率:
// scrnsize.c
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
int CDECL MessageBoxPrintf( LPTSTR szCaption, LPCTSTR szFormat, ... )
{
TCHAR szBuffer[1024];
va_list pArgs;
va_start( pArgs, szFormat );
_vsntprintf( szBuffer, sizeof( szBuffer ) / sizeof( TCHAR ), szFormat, pArgs );
va_end( pArgs );
return MessageBox( NULL, szBuffer, szCaption, 0 );
}
// _vsntprintf定义在stdio.h中,是一个通用性函数,只不过第二个参数需要指定最多能格式化多少个字符
// 由于对于宽字符,一个字符占两个字节,因此需要使用sizeof除法计算缓冲区最多能存放多少个字符
// 当然这里也可以直接写1024,但是这里为了突出宽字符的特殊性
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,
int nCmdShow
)
{
int cxScreen, cyScreen;
// 该API可以获得系统中各种窗口的度量,比如宽、高,角的坐标等等
// 都是以像素Pixel为单位计算的
cxScreen = GetSystemMetrics( SM_CXSCREEN );
cyScreen = GetSystemMetrics( SM_CYSCREEN );
MessageBoxPrintf(
"Screen Size",
"The screen is %d pixels wide by %d pixels high.",
cxScreen, cyScreen
);
return 0;
}