目录
控制台程序 与 Windows程序
Windows程序与Console程序入口的区别
代码编写区别
SDK 基本概念
VS2019 SDK
VS2019创建桌面应用程序
ANSI 与 Unicode
TCHAR char wchat_t
解决编码的影响
MessageBox()
第一个SDK程序
MessageBox函数
控制台程序 与 Windows程序
控制台机制:主要使用顺序的,过程驱动的程序设计方法。顺序的,过程驱动的程序有一个明显的开始,明显的过程及一个明显的结束,因此程序能直接控制程序事件或过程的顺序。虽然在顺序的过程驱动的程序中也有很多处理异常的方法,但这样的异常处理也仍然是顺序的,过程驱动的结构。
Windows程序:消息驱动,不由事件的顺序来控制,而是由事件的发生来控制,所有的事件都是无序的,所为一个程序员,在你编写程序时,你并不知道用户先按哪个按纽,也不知道程序先触发哪个消息。你的任务就是对正在开发的应用程序要发出或要接收的消息进行排序和管理。事件驱动程序设计是密切围绕消息的产生与处理而展开的,一条消息是关于发生的事件的消息。
Windows程序与Console程序入口的区别
代码编写区别
Windows应用程序的编程就围绕这两个部份进行的。其中WinMain函数为应用程序的入口点,它的名字一定要是WinMain。
在Windows中,应用程序通过要求Windows完成指定操作,而承担这项通信任务的API函数就是Windows的相应窗口函数WindowProc。
在dos里,程序能直接控制事件的发生顺序,结果等。而在Windows里,应用程序不直接调用任何窗口函数,而是等待Windows调用窗口函数,请求完成任务或返回信息。为保证Windows调用这个窗口函数,这个函数必须先向Windows登记,然后在Windows实施相应操作时回调,所以窗口过程函数又称为回调函数。WindowProc是一个主回调函数,Windows至少有一个回调函数。
SDK 基本概念
SDK —— software development kit 软件开发包(套件)
SDK(软件开发工具包)是一组用于开发软件的工具、库和文档的集合。它提供了一系列的接口、功能和示例代码,以帮助开发人员更快速、更高效地构建应用程序。这里我们主要学习使用SDK开发GUI界面
通常情况下,SDK会包含以下内容:
-
库文件:SDK提供一组现成的库文件,包含了各种功能模块和工具,例如网络通信、图形处理、数据存储等。开发人员可以直接调用这些库文件,加快开发速度,并且可以避免从头开始编写复杂的功能模块。
-
API文档:SDK通常会提供详细的API文档,其中包含了所有可用的函数、类和方法的说明。开发人员可以通过查阅API文档了解每个接口的用法、参数说明以及返回值,从而正确地使用SDK提供的功能。
-
示例代码:SDK通常提供一些示例代码,展示如何使用SDK进行开发。这些示例代码可以作为开发人员学习和参考的范例,帮助他们快速上手并理解各种功能的实现方式。
-
工具软件:一些SDK还会提供一些辅助性的工具软件,用于调试、测试、模拟等目的。这些工具可以简化开发过程中的一些任务,提高开发效率。
SDK可以用于各种不同的应用领域,如移动应用开发、Web开发、物联网开发等。通过使用SDK,开发人员可以快速构建出功能完善、稳定可靠的软件,并且降低了开发过程中的复杂性和风险。
VS2019 SDK
SDK被集成在VS中,以VS2019为例:
安装位置:C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC
查看方式:项目属性 -- VC++目录 -- 库目录
VS2019创建桌面应用程序
开发参考文档:使用 Win32 API 生成桌面 Windows 应用 - Win32 apps | Microsoft Learn
创建Windows桌面应用程序,即可使用SDK开发
项目依赖的库
ANSI 与 Unicode
ANSI(American National Standards Institute)编码是指使用ASCII码表的字符编码方式,ANSI编码的字符范围是0-127,共计128个字符。其中包括了大写和小写字母、数字、标点符号以及一些控制字符。ANSI编码是单字节编码,每个字符只占用一个字节的存储空间。这意味着ANSI编码只能表示有限的字符集,并且不支持多国语言的字符。
Unicode编码是一种用于表示文字字符的标准编码系统。它旨在统一世界上所有的字符,包括各种语言的字母、符号、标点符号和特殊字符等。Unicode编码使用了固定的位数来表示每个字符,常见的有UTF-8、UTF-16和UTF-32等不同的编码方案。
- UTF-8使用变长字节表示字符,通常使用1至4个字节来表示不同的字符。UTF-8编码保留了ASCII码表中的字符,只需一个字节表示,这使得UTF-8与ASCII兼容。
- UTF-16采用固定的16位(2字节)来表示大部分常用的字符,对于非常用字符采用双字节或四字节的方式表示。UTF-16编码可以兼容ASCII编码,但与UTF-8相比,存储空间可能更大。
- UTF-32则使用固定的32位(4字节)来表示每个字符,不论字符是否为常用字符。UTF-32编码能够提供与其他Unicode编码方案之间的更好的互操作性,但也会占用更多的存储空间。
ASNI对比Unicode:
- ASNI仅适用于英语,占空间比Unicode更小
- Unicode兼容所有国家语言,但是占空间大
VS2019配置项目编码:两者的区别就是编译器会自动加上宏定义
TCHAR char wchat_t
THAR:就是当你的字符设置为什么就是什么
- 程序编译为 ANSI, TCHAR 就是相当于 CHAR
- 当程序编译为 UNICODE, TCHAR 就相当于 WCHAR
如果在程序中既包括ANSI又包括Unicode编码,需要包括头文件tchar.h。TCHAR是定义在该头文件中的宏,它视你是否定义了_UNICODE宏而定义成:
- 定义了_UNICODE: typedef wchar_t TCHAR ;
- 没有定义_UNICODE: typedef char TCHAR ;
#ifdef UNICODE
typedef char TCHAR;
#else
typede wchar_t TCHAR;
#endif
_T( )也是定义在该头文件中的宏,视是否定义了_UNICODE宏而定义成:
- 定义了_UNICODE: #define _T(x) L##x
- 没有定义_UNICODE: #define _T(x) x
注意:如果在程序中使用了TCHAR,那么就不应该使用ANSI的strXXX函数或者Unicode的wcsXXX函数了,而必须使用tchar.h中定义的_tcsXXX函数
char :单字节变量类型,最多表示256个字符,
wchar_t :宽字节变量类型,用于表示Unicode字符,它实际定义在<string.h>里:typedef unsigned short wchar_t。
为了让编译器识别Unicode字符串,必须以在前面加一个“L”
wchar_t a[] = L"Hello!" ;
宽字节类型每个变量占用2个字节,故上述数组a的sizeof(a) = 14
在字符串前加一个L作用:
如 L"我的字符串" 表示将ANSI字符串转换成unicode的字符串,就是每个字符占用两个字节。
strlen("asd") = 3;
strlen(L"asd") = 6;
_T宏可以把一个引号引起来的字符串,根据你的环境设置,使得编译器会根据编译目标环境选择合适的(Unicode还是ANSI)字符处理方式
- 如果你定义了UNICODE,那么_T宏会把字符串前面加一个L。这时 _T("ABCD") 相当于 L"ABCD" ,这是宽字符串。
- 如果没有定义,那么_T宏不会在字符串前面加那个L,_T("ABCD") 就等价于 "ABCD"
TEXT,_TEXT 和_T 一样的 ,如下面三语句:
TCHAR szStr1[] = TEXT("str1");
char szStr2[] = "str2";
WCHAR szStr3[] = L("str3");
那么第一句话在定义了UNICODE时会解释为第三句话,没有定义时就等于第二句话。但二句话无论是否定义了UNICODE都是生成一个ANSI字符串,而第三句话总是生成UNICODE字符串。
解决编码的影响
背景:一家公司A,有一款应用,要推广到其他国家,为了兼容语言,需要把原先ASNI编码替换为Unicode。
在SDK中,很多函数都有两种形态,比如:MessageBox,这个本质是一个宏,有两个函数,分别是:MessageBoxA,MessageBoxW分别适合于不同的字符集,实际上在操作系统底层A的使用还是要先转化为W
在上述背景,如何解决字符集使用带来的问题?
方式一:直接全部使用A;或者全部使用W,字符串前需加L
MessageBoxA(NULL,"ANSI","51asm",MB_YESNO)
MessageBoxW(NULL,L"Unicode",L"51asm",MB_YESNO)
方式二:使用宏,MessageBox本质是一个宏
#ifdef UNICODE
#define MessageBox MessageBoxW
#else
#define MessageBox MessageBoxA
#endif // !UNICODE
若有定义# define UNICODE,则为W,否则为A
//#define UNICODE
#include <Windows.h>
int WINAPI WinMain(HINSTANCE hInstace, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
MessageBox(NULL, "Hello World", "51asm", MB_YESNO);
return 0;
}
#define UNICODE
#include <Windows.h>
int WINAPI WinMain(HINSTANCE hInstace, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
MessageBox(NULL, L"Hello World", L"51asm", MB_YESNO);
return 0;
}
项目依然很难维护
方式三:使用TCHAR,需包含<tchar.h>,也类似于上面,是一个宏定义
ASNI版本
#include <Windows.h>
#include <tchar.h>
int WINAPI WinMain(HINSTANCE hInstace, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
MessageBox(NULL, _T("Hello World"), _T("51asm"), MB_YESNO);
return 0;
}
Unicode版本
#define _UNICODE
#define UNICODE
#include <Windows.h>
#include <tchar.h>
int WINAPI WinMain(HINSTANCE hInstace, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
MessageBox(NULL, _T("Hello World"), _T("51asm"), MB_YESNO);
return 0;
}
这样也有缺点,很多函数无法使用,例如:strcpy对应ASNI,wtrcpy对应Unicode,需要用_mbscpy
方式四:使用系统库提供,并默认使用Unicode,建议使用
#include <Windows.h>
int WINAPI WinMain(HINSTANCE hInstace, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
TCHAR szBuf[] = { TEXT("HELLO 你好") };
MessageBox(NULL,szBuf , L"51asm", MB_OK);
return 0;
}
现在,普通公司都是直接Unicode
wchar_t szBuf[] = {L"Hello 你好"};
MessageBoxW(NULL,szBuf,L"51asm",MB_OK);
MessageBox()
int MessageBox(
[in, optional] HWND hWnd,
[in, optional] LPCTSTR lpText,
[in, optional] LPCTSTR lpCaption,
[in] UINT uType
);
第一个SDK程序
#include <Windows.h>
#include <tchar.h>
int WINAPI WinMain(HINSTANCE hInstace, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
TCHAR szBuf[] = { TEXT("请点击") };
TCHAR szTitle[] = { TEXT("51 asm") };
int nType = MB_YESNO;
int nRet = MessageBox((HWND)1, szBuf, szTitle, nType);
DWORD dwError = GetLastError();
if (dwError != ERROR_SUCCESS) {
MessageBox(NULL, TEXT("MessageBox调用错误" ), szTitle, nType);
return 0;
}
if (nRet == IDYES) {
MessageBox(NULL, TEXT("确定"), szTitle, MB_OK);
}
else if (nRet == IDNO) {
MessageBox(NULL, TEXT("取消"), szTitle, MB_OK);
}
return 0;
}
结果如下:
MessageBox函数
ASNI版本
WINUSERAPI
int WINAPI MessageBoxA(
_In_opt_ HWND hWnd,
_In_opt_ LPCSTR lpText,
_In_opt_ LPCSTR lpCaption,
_In_ UINT uType);
Unicode版本
WINUSERAPI int WINAPI
MessageBoxW(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType);
_In_opt_
是一个预处理宏,表示该参数是可选的,可以传入NULL
或默认值。在函数调用时,可以选择是否提供该参数的值。如果不提供该参数或将其设置为NULL
,函数将使用默认行为或忽略该参数。HWND
是 Windows 平台上的一个数据类型,它代表了一个窗口句柄(Handle to Window)。窗口句柄是用来标识和操作窗口的唯一标识符。在 Windows 操作系统中,每个窗口都有一个对应的窗口句柄。通过使用窗口句柄,程序可以与指定的窗口进行交互,如改变窗口的属性、发送消息给窗口、获取窗口的信息等等。窗口句柄通常是由系统分配和管理的,开发者不能直接创建或销毁窗口句柄。可以通过调用系统提供的函数来获取窗口句柄,如CreateWindow
、FindWindow
等。typedef const CHAR *LPCSTR;,LPCWSTR
类型的变量实际上是一个指向 Unicode 字符串常量的指针,而且指向的内容不可修改。如果需要修改字符串内容,则需要使用另一个类型LPWSTR
,其中W
表示宽字符。- typedef unsigned int UINT;