目录
SDK 概念
SDK下载
创建桌面应用程序
API
用户模式与内核模式
Windows内核对象
句柄 - 内核对象的 "ID" (每个对象对应的唯一标识符)
消息机制
Windows 多任务实现(多线程)
虚拟内存
进程隔离 - 多程序运行
控制台程序 与 Windows程序
处理流程区别
程序入口区别
代码编写区别
wWinMain 入口函数
Messagew 消息对话框
Hello World
ANSI 与 Unicode
TCHAR char wchat_t
解决编码的影响
SDK —— software development kit 软件开发包(套件)
Windows SDK 提供了大量的 API 接口,用于开发 Windows 平台上的应用程序。这些 API 接口涵盖了各个方面,包括窗口和用户界面、文件和 I/O 操作、网络通信、系统管理等等。
通常情况下,SDK会包含以下内容:
库文件:SDK提供一组现成的库文件,包含了各种功能模块和工具,例如网络通信、图形处理、数据存储等。开发人员可以直接调用这些库文件,加快开发速度,并且可以避免从头开始编写复杂的功能模块。
API文档:SDK通常会提供详细的API文档,其中包含了所有可用的函数、类和方法的说明。开发人员可以通过查阅API文档了解每个接口的用法、参数说明以及返回值,从而正确地使用SDK提供的功能。
示例代码:SDK通常提供一些示例代码,展示如何使用SDK进行开发。这些示例代码可以作为开发人员学习和参考的范例,帮助他们快速上手并理解各种功能的实现方式。
工具软件:一些SDK还会提供一些辅助性的工具软件,用于调试、测试、模拟等目的。这些工具可以简化开发过程中的一些任务,提高开发效率。
SDK被集成在VS中,以VS2019为例:
安装位置:C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC
查看方式:项目属性 -- VC++目录 -- 库目录
创建Windows桌面应用程序,即可使用SDK开发
项目依赖的库
为了学习方便,可以关闭随机基址
api:application programming interface,应用程序接口
操作提供的一组功能性函数。使应用程序能够间接的使用操作系统接管的输入输出设备,所提供的接口。
为什么之前写的控制台程序不能直接操作鼠标键盘,屏幕?
因为windows系统中,操作系统接管了所有的输入输出设备,应用想要调用输入输出设备,就必须通过WINDOWS操作系统的函数或则代码返回给app
0环~3环 4个等级,给操作者分级,不同操作者使用不同的等级
windows系统为什么只有0环和3环?
目的:为了提升兼容。不想与CPU绑定,防止CPU更改权限使其不兼容。
在DOS时代不区分权限,只要程序有执行的能力,DOS能够修改操作系统的内容,所以那时的病毒非常的泛滥。
目前的病毒,要做的第一件事是提权,跟随电脑的启动而启动(服务),由于创建服务需要管理员权限,所以这种病毒只是三环的病毒,不会影响内核的东西。
内核对象 —— 受操作系统保护的对象,只允许通过该对象提供的API接口来修改或则访问对象。
内核对象的访问:
内核对象与一般数据结构的区别
内核对象与一般数据结构的最大区别是其内部数据结构是隐藏的,必须调用一个对象服务才能从此对象中得到数据。
内核对象的数据结构仅能够从内核模式访问,所以直接在内存中定位这些数据结构对应用程序来说是不可能的,只能通过API来访问它,在用户界别下用来表示内存对象的数据成为对象句柄。可以认为是另一种形式下的“指针”。既:访问内核对象 需要 API + 相应的句柄(哪个内核对象)
简单来说就是,如同类私有成员一样,WINDOWS也有保护的成员,这个就是内核。内核提供操作内核数据的接口API。
操作系统内部有非常多的内核对象,如果想要访问特定的内核对象,那么必须告诉操作系统,我们需要访问的对象是哪一个对象,也就是给对象一个标识符。方便与用户代码进行交互。
HINSTANCE
是一个 Win32 API 数据类型,表示当前实例句柄(instance handle)。实例是指操作系统为每个应用程序创建的一个运行时环境,该环境包括可执行文件、资源和数据等元素。对于任何 Windows 应用程序,操作系统都会分配一个唯一的实例句柄来标识该应用程序的当前实例。
HINSTANCE
类型的变量是一个句柄,用于表示当前应用程序实例的唯一标识符,通常用于资源加载和窗口创建等编程任务中。
windows 是消息驱动的操作系统。没消息的时候,什么也不干,当有消息的时候才开始干活。
当一个Windows程序执行时,系统都会为该程序创建一个消息队列,这个消息队列用来存放该程序创建窗口的消息。Windows将产生的消息依次放到消息队列中,应用程序通过一个消息循环不断地从消息队列中取出消息,并进行响应。
在Windows中,不仅用户程序可以调用系统的API函数,反过来,系统也会调用用户程序,这个调用是通过消息来进行的。将响应的事件包装成一个消息,投递到应用程序的消息队列中,然后应用程序从消息队列中取出消息并进行响应。这个处理的过程就是操作系统调用程序中一个专门负责处理消息的函数,这个函数就称为窗口过程函数。
例子:当按下记事本关于的时候,弹出一个框,显示其中的信息,其实,这个框是操作系统从鼠标获取响应,再通过封装APP的代码,跳转到APP开发者的代码部分。
实质:不停的输入,不停的封装消息,应用程序不停的处理。
本质:回调函数,开发者自己实现该动作的响应方法,然后把这个函数的地址传给操作系统,操作系统接收到响应动作的时候,通过函数指针回调开发者自定义的函数。
在DOS时代,一个CPU只能执行一个程序。
在WINDOWS中,可以同时执行很多程序,一个CPU在一个时间内只能执行一个应用的代码,在操作系统中,有很多程序同时运行,是怎么同时管理那么多程序的呢?
Windows操作系统上执行代码的基本单位 —— 线程,一个操作系统可以执行多个任务。
80386对多任务操作系统的支持原理:
来回切换应用程序,由于cpu执行代码的速度非常快,所以每个app执行一段代码后又跳过去执行另外一个app,肉眼是无法分辨出来的。
虚拟内存的目的是为了内存隔离,程序员编写程序,不需要考虑内存地址;虚拟内存地址到实际内存地址映射交给硬件完成。
实际上32位的系统上能够运行非常多的进程。原因在于对每个进程进行了内存隔离,每个进程的高2G空间都指向了物理内存的同一块位置(操作系统代码每个进程都一样)。低2G指向了物理内存条不同的地方,这是内存映射,且在进程开始的时候并不是运行开始就分配4G的空间,而用多少分配多少,程序运行时的内存也叫虚拟内存,它是映射到物理内存的,当我们需要访问程序的某个地址时,会通过转换 + 偏移,映射到物理内存的特定的位置。
控制台程序与Windows程序两者运行的机制根本就不一样
控制台程序:主要使用顺序的,过程驱动的程序设计方法。顺序的,过程驱动的程序有一个明显的开始,明显的过程及一个明显的结束,因此程序能直接控制程序事件或过程的顺序。虽然在顺序的过程驱动的程序中也有很多处理异常的方法,但这样的异常处理也仍然是顺序的,过程驱动的结构。
Windows程序:消息驱动,不由事件的顺序来控制,而是由事件的发生来控制,所有的事件都是无序的,所为一个程序员,在你编写程序时,你并不知道用户先按哪个按纽,也不知道程序先触发哪个消息。你的任务就是对正在开发的应用程序要发出或要接收的消息进行排序和管理。事件驱动程序设计是密切围绕消息的产生与处理而展开的,一条消息是关于发生的事件的消息。
SDK程序: 程序入口:wWinMain 链接选项:SUBSYSTEM:WINDOWS
Console程序: 程序入口:main 链接选项:SUBSYSTEM:console
Console程序:C语言编程至少有一个主程序,其名字是main()。
SDK程序:一个是WinMain(),另一个是窗口过程函数WindowProc
在dos里,程序能直接控制事件的发生顺序,结果等。而在Windows里,应用程序不直接调用任何窗口函数,而是等待Windows调用窗口函数,请求完成任务或返回信息。为保证Windows调用这个窗口函数,这个函数必须先向Windows登记,然后在Windows实施相应操作时回调,所以窗口函数又称为回调函数。WindowProc是一个主回调函数,Windows至少有一个回调函数。
函数原型如下:
int APIENTRY wWinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nShowCmd
)
APIENTRY:是一个宏,是调用约定
#define APIENTRY WINAPI
#define WINAPI __stdcall
_In_ 与 _In_opt_
_In_
是一个标记宏,表示该参数是输入参数。_In_opt_
是一个用于函数参数的标记宏,表示该参数是可选的(optional);NULLHINSTANCE
HINSTANCE
是一个 Win32 API 数据类型,表示当前实例句柄(instance handle)。实例是指操作系统为每个应用程序创建的一个运行时环境,该环境包括可执行文件、资源和数据等元素。对于任何 Windows 应用程序,操作系统都会分配一个唯一的实例句柄来标识该应用程序的当前实例。
HINSTANCE
类型的变量是一个句柄,用于表示当前应用程序实例的唯一标识符,通常用于资源加载和窗口创建等编程任务中。
LPWSTR: typedef WCHAR *LPWSTR;
LPWSTR
是在 Windows 平台上使用的数据类型之一,用于表示一个指向宽字符(Unicode)字符串的指针。LPWSTR
实际上是一个指向 WCHAR
类型的指针,而 WCHAR
是宽字符类型,通常占用两个字节(16 位)。
随机基址已经关闭,看一下参数1
指向操作系统为当前程序创建的实例结构(结构体或者对象),其中包含了程序所需的资源、数据和状态信息。该句柄可以用于获取程序所需的系统资源,例如图标、菜单、对话框等。这个句柄的存储地址是指向当前程序实例的内存地址。
Windows API 函数,用于显示一个消息框(Message Box)并等待用户响应。
NULL
,则消息框将没有父窗口。int WINAPI MessageBoxW(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType)
LPCWSTR:typedef const WCHAR *LPCWSTR;
常量的指针,指针指向的地址不可修改,而存储的值却可以
示例程序:
#include
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
int nRet = MessageBox(NULL, L"hello world", L"提示", MB_YESNO);
if (nRet == IDYES)
{
MessageBox(NULL, L"YES", L"提示", MB_YESNO);
}
else if (nRet == IDNO)
{
MessageBox(NULL, L"NO", L"提示", MB_YESNO);
}
return 0;
}
结果如下:
ANSI(American National Standards Institute)编码是指使用ASCII码表的字符编码方式,ANSI编码的字符范围是0-127,共计128个字符。其中包括了大写和小写字母、数字、标点符号以及一些控制字符。ANSI编码是单字节编码,每个字符只占用一个字节的存储空间。这意味着ANSI编码只能表示有限的字符集,并且不支持多国语言的字符。
Unicode编码是一种用于表示文字字符的标准编码系统。它旨在统一世界上所有的字符,包括各种语言的字母、符号、标点符号和特殊字符等。Unicode编码使用了固定的位数来表示每个字符,常见的有UTF-8、UTF-16和UTF-32等不同的编码方案。
ASNI对比Unicode:
VS2019配置项目编码:两者的区别就是编译器会自动加上宏定义
THAR:就是当你的字符设置为什么就是什么
如果在程序中既包括ANSI又包括Unicode编码,需要包括头文件tchar.h。TCHAR是定义在该头文件中的宏,它视你是否定义了_UNICODE宏而定义成:
#ifdef UNICODE
typedef char TCHAR;
#else
typede wchar_t TCHAR;
#endif
_T( )也是定义在该头文件中的宏,视是否定义了_UNICODE宏而定义成:
注意:如果在程序中使用了TCHAR,那么就不应该使用ANSI的strXXX函数或者Unicode的wcsXXX函数了,而必须使用tchar.h中定义的_tcsXXX函数
char :单字节变量类型,最多表示256个字符,
wchar_t :宽字节变量类型,用于表示Unicode字符,它实际定义在
为了让编译器识别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)字符处理方式
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
int WINAPI WinMain(HINSTANCE hInstace, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
MessageBox(NULL, "Hello World", "51asm", MB_YESNO);
return 0;
}
#define UNICODE
#include
int WINAPI WinMain(HINSTANCE hInstace, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
MessageBox(NULL, L"Hello World", L"51asm", MB_YESNO);
return 0;
}
项目依然很难维护
方式三:使用TCHAR,需包含
ASNI版本
#include
#include
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
#include
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
int WINAPI WinMain(HINSTANCE hInstace, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
TCHAR szBuf[] = { TEXT("HELLO 你好") };
MessageBox(NULL,szBuf , L"51asm", MB_OK);
return 0;
}