BLOG

【Win32】初识Win32编程

Windows编程应用程序分类 控制台程序Console

DOS程序,本身没有窗口,通过Windows DOS窗口执行。(DOS是操作系统预留的) 窗口程序

拥有自己的窗口,可以与用户交互。 库程序

存放代码、数据的程序、执行文件可以从中取出代码执行和获取数据静态库程序:扩展名LIB,在编译链接程序时,将代码放入到执行文件中。动态库程序:扩展名DLL,在执行文件时从中获取代码 。 静态库中的代码是直接嵌入到你的项目中,而动态库中的内容是通过地址来找到。

静态库程序无法执行,也就是说它最终生成的文件无法进入内存。动态库程序有入口函数,可以执行。**但是它不能独立运行。**谁调动态库里面的东西,它就依附于谁。应用程序对比入口函数 控制台程序-main窗口程序-WinMain动态库程序-DllMain静态库程序-无入口函数文件存在方式 控制台程序、窗口程序-EXE文件动态库程序-DLL文件静态库程序-LIB文件编译工具编译器CL.EXE,将源代码编译成目标代码.obj。链接器LINK.EXE,将目标代码、库链接生成最终文件。资源编译器RC.EXE,(.rc)将资源编译,最终通过链接器存入最终文件库文件和头文件库文件

kernel32.dll-提供核心的API,消息进程,线程,内存管理等。user32.dll-提供了窗口、消息等API。gdi32.dll-绘图相关的API头文件

windows.h——所有windows头文件的集合windef.h——windows数据类型winbase.h——kernel32的APIwingdi.h——user32的APIwinnt.h——UNICODE字符集支持相关函数代码语言:javascript复制int WINAPI wWinMain(

HINSTANCE hInstance,//当前程序的实例句柄,找到你当前进程所占据的那块内存

HINSTANCE hPrevInstance,//当前程序前一个示例句柄,废弃了

PWSTR pCmdLine, //命令行参数字符串

int nCmdShow//窗口的显示方式

);暂时可以将句柄理解成,句柄是用来找到内存的东西,但绝对不是指针。

代码语言:javascript复制int MessageBox(

[in, optional] HWND hWnd,//父窗口句柄

[in, optional] LPCTSTR lpText,//显示在消息框中的文字

[in, optional] LPCTSTR lpCaption,//显示在标题栏中的文字

[in] UINT uType//消息框中的按钮、图标显示了类型

);//返回点击的按钮ID能够将程序暂停在这里,说明它是个阻塞函数。它执行,可能不会立即返回。

如何理解分析一个阻塞函数?

1.这个函数什么情况下阻塞。

2.这个函数什么情况下解除阻塞返回。

看到数据类型以H开头,多半就是个句柄。

程序编译过程 可以直接用vs集成好的环境。 编译程序-CL,CL.EXE -C xxx.c,生成obj文件

链接程序-LINK,LINK.EXE xxx.obj xxx.lib

执行生成的exe文件

编写资源的文件,.rc资源脚本文件编译rc文件,RC.EXE将资源链接到程序中,LINK.EXE.res文件和.obj文件统称为目标文件(中间文件,因为不是最终文件)

编译链接过程:将代码转换为机器语言,将生成的res文件和obj文件加上使用的库链接到一起,整合出一个exe文件,这是用编译器所感受不到的。

窗口创建过程定义WinMain函数定义窗口的处理函数(自定义,消息处理)注册窗口类(向操作系统中写入一些数据)创建窗口(内存中创建窗口)显示窗口(绘制窗口的图像)消息循环(获取/翻译/派发消息)消息处理第一个windows窗口代码语言:javascript复制#include

//窗口处理函数(自定义、处理消息)

LRESULT CALLBACK WndProc(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam)

{

return DefWindowProc(hWnd, msgID, wParam, lParam);

}

//入口函数

int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPerIns, LPSTR lpCmdLine, int nCmdShow)

{

//注册窗口类

WNDCLASS wc = { 0 };

//申请两种不用的缓冲区

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

wc.hCursor = NULL;

wc.hIcon = NULL;

wc.hInstance = hIns;

wc.lpfnWndProc = WndProc;

wc.lpszClassName = "myWindow";

wc.lpszMenuName = NULL;

wc.style = CS_HREDRAW | CS_VREDRAW;

//将上面赋的这些值全部写入操作系统

RegisterClass(&wc);

//在内存中创建窗口

HWND hWnd = CreateWindow("myWindow", "menu", WS_OVERLAPPEDWINDOW, 100, 100, 500, 500, NULL, NULL, hIns, NULL);

//显示窗口

ShowWindow(hWnd, SW_SHOW);

//再画一遍(刷新窗口)

UpdateWindow(hWnd);

//消息循环

MSG nMsg = { 0 };

while (GetMessage(&nMsg,NULL,0,0))

{

TranslateMessage(&nMsg);

DispatchMessage(&nMsg);//将消息交给窗口处理函数来处理

}

return 0;

}窗口有无与进程退不退没有关系。

Unit04字符编码编码历史背景

ASC——7位代表一个字符ASCII——8位代表一个字符DBCS——单双字节混合编码,没有同一标准,存在解析问题。UNICODE——字符集,有多种编码方式,一般windows只的是utf-16(所有的字符无论中文汉子还是英文字母,都按两个字符编码。),linux utf-8,有统一标准,不存在解析问题。宽字节数据类型这个数据类型下的所有字符, 都占2个字节。

wchar_t每个字符占2个字节,wchar_t实际上是unsigned short类型,定义时,需要增加‘L’,通知编译器按照双字节编译字符串,采用UNICODE编码。

需要使用支持wchar_t函数操作宽字节字符串,例如:

wchar_t* s1= L"123456";

wprintf(L"%s\n",s1);

有操作char类型字符串的函数,肯定就有对应操作宽字节字符串的函数。

代码语言:javascript复制#include

#include

int main(void)

{

const wchar_t* s1 = L"hello";

printf("%d\n", wcslen(s1));//5,有效字符个数

return 0;

}TCHAR如果定义了unicode宏,该字符串为wchar*类型,反之为char *类型。

打印UNICODE字符UNICODE字符打印,wprintf对UNICODE字符打印支持不完善。

在windows下使用WriteConsole打印UNICODE字符。

GetStdHandle获取标准句柄(标准输入句柄、标准输出句柄、标准错误句柄)。

代码语言:javascript复制WriteConsole(标准输出句柄,pszText,wcslen(pszText),NULL,NULL);下面的使用案例

代码语言:javascript复制 short width = LOWORD(lParam);

short height = HIWORD(lParam);

char szText[256] = { 0 };

sprintf_s(szText, "WM_SIZE:宽度%d,高度:%d\n", width, height);

WriteConsole(g_HOUTPUT, szText, strlen(szText), NULL, NULL);创建项目时候为什么要改成多字节字符集?

如果项目的属性选择是UNICODE字符集,编译器会自动给你增加一个UNICODE的宏定义。反之,则不会。

系统调用函数的参数类型:

(对已有的类型进行重命名)

代码语言:javascript复制LPSTR => char*

LPSTR => const char*

LPWSTR => wchar_t*

LPCWSTR => const wchar_t*

用的多的是这两个

LPTSTR => TCHAR

LPCTSTR => const TCHAR*注册窗口类窗口类的概念

窗口类是包含了窗口的各种参数信息的数据结构。每个窗口都具有窗口类,基于窗口类创建窗口。每个窗口类都具有一个名称,使用前必须注册到系统。在操作系统内核里存着就叫窗口类,在程序里存着就叫窗口类。

窗口类的分类:

系统窗口类 系统已经定义好的窗口类,所有应用程序都可以直接使用。不需要注册,直接使用窗口类即可。系统已经注册好了。例如:按钮-BUTTON,编辑框-EDIT应用程序全局窗口类 由用户自己定义,当前应用程序所有模块都可以使用。应用程序局部窗口类 由用户自己定义,当前应用程序中本模块可以直接使用。全局及局部窗口类 :

注册窗口类的函数

(ATOM——unsigned short)

ATOM RegisterClass(

​ CONST WNDCLASS *lpWndClass//窗口类的数据);

//注册成功后 ,返回一个数字标识。(0失败,非0成功。)

style窗口类风格

应用程序全局窗口类的注册,需要在窗口类的风格中添加CS_GLOBALCLASS。

应用程序局部类窗口类注册,无需添加如上风格。

不建议使用全局窗口类——因为局部窗口类能完成全局窗口类的功能,并且全局窗口类可能会产生冗余。

代码语言:javascript复制CS_HREDRAW ——当窗口水平变化时,窗口重新绘制

CS_VREDRAW ——当窗口垂直变化时,窗口重新绘制

CS_DBLCLKS ——允许窗口接收鼠标双击

CS_NOCLOSE ——窗口没有关闭按钮窗口创建窗口创建:

CreateWindow / CreateWindowEx

CreateWindow内部是如何实现的

系统(CreateWindows函数内部)根据传入的窗口类名称,在应用程序局部窗口类中查找,如果找到执行2 ,没找到执行3。比较局部窗口与创建窗口时传入的HINSTANCE变量。如果有发现相等。创建和注册类在同一模块,创建窗口返回。如果不相等,继续执行3。在应用程序全局窗口类,如果找到,执行4, 没找到执行5。使用找到的窗口类信息,创建窗口返回。在系统窗口类中查找,如果找到创建窗口返回,否则创建窗口失败。代码语言:javascript复制#include

//窗口处理函数(自定义、处理消息)

LRESULT CALLBACK WndProc(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam)

{

switch (msgID)

{

case WM_DESTROY:

PostQuitMessage(0);

break;

default:

break;

}

return DefWindowProc(hWnd, msgID, wParam, lParam);

}

//入口函数

int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPerIns, LPSTR lpCmdLine, int nCmdShow)

{

//注册窗口类

WNDCLASS wc = { 0 };

//申请两种不用的缓冲区

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

wc.hCursor = NULL;

wc.hIcon = NULL;

wc.hInstance = hIns;

wc.lpfnWndProc = WndProc;

wc.lpszClassName = "myWindow";

wc.lpszMenuName = NULL;

wc.style = CS_HREDRAW | CS_VREDRAW;

//将上面赋的这些值全部写入操作系统

RegisterClass(&wc);

//在内存中创建窗口

HWND hWnd = CreateWindow("myWindow", "menu", WS_OVERLAPPEDWINDOW, 100, 100, 500, 500, NULL, NULL, hIns, NULL);

//显示窗口

ShowWindow(hWnd, SW_SHOW);

//再画一遍(刷新窗口)

UpdateWindow(hWnd);

//消息循环

MSG nMsg = { 0 };

while (GetMessage(&nMsg, NULL, 0, 0))

{

TranslateMessage(&nMsg);

DispatchMessage(&nMsg);//将消息交给窗口处理函数来处理

}

return 0;

}子窗口创建过程

创建时要设置父窗口句柄创建风格要增加WS_CHILD | WS_VISBLE(根据注册的窗口类,来创建多个窗口。)

(个人理解发现:注册的窗口可以复用(覆盖创建一个新的))。

代码语言:javascript复制HWND hChild1 = CreateWindowEx(0, "Child", "C1", WS_CHILD | WS_VISIBLE | WS_OVERLAPPEDWINDOW, 0, 0, 200, 200, hWnd, NULL, hIns, NULL);

HWND hChild2 = CreateWindowEx(0, "Child", "C2", WS_CHILD | WS_VISIBLE | WS_OVERLAPPEDWINDOW, 200, 0, 200, 200, hWnd, NULL, hIns, NULL);消息基础消息的概念和作用消息组成(windows平台下) 窗口句柄消息ID消息的两个参数(两个附带信息)消息产生的时间消息产生时的鼠标位置消息的作用 当系统通知窗口工作时,就采用消息的方式(DispatchMessage)派发给(调用)窗口的窗口处理函数(将MSG的前四个信息传递给消息处理函数)。每一个窗口都有窗口处理函数MSG结构体接收消息

结构体定义如下:

代码语言:javascript复制//对应解释同上消息组成

typedef struct tagMSG {

HWND hwnd;

UINT message;

WPARAM wParam;

LPARAM lParam;

DWORD time;

POINT pt;

#ifdef _MAC

DWORD lPrivate;

#endif

} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;DispatchMessage如何找到窗口处理函数

代码语言:javascript复制nMsg.hwnd->保存窗口数据的内存->找到对应的窗口处理函数->WndProc

回到你自己定义的消息处理函数->传递参数->处理消息

LRESULT CALLBACK WndProc(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam)

传递这四个参数,不用关系后两个窗口处理函数每个窗口都必需有窗口处理函数,只要基于窗口类创建窗口,就肯定要有个窗口处理函数。

窗口处理依照如下结构定义:

代码语言:javascript复制LRESULT CALLBACK WindowProc(

HWND hWnd;//窗口句柄

UINT uMsg;//消息ID

WPARAM wParam;//消息参数

LPARAM lParam;//消息参数

);当系统通知窗口时,(DispatchMessage)会调用窗口处理函数,同时将消息ID和消息参数传递给窗口处理函数。

在窗口处理函数中,不处理的消息,使用缺省窗口处理函数。

例如:DefWindowProc(可以给各种消息做默认处理)。

消息循环中的相关函数(浅谈)GetMessage-到系统的某个地方抓本进程的消息

函数原型如下:

代码语言:javascript复制BOOL GetMessage(

LPMSG lpMsg,//存放获取到消息的BUFF,

HWND hWndp;//窗口句柄,要是定为NULL,将会抓取本进程中所有窗口中的消息

UNIT wMsgFilterMin,//获取的最小ID

UNIT wMsgFilterMax//获取消息的最大ID

//最后两个参数都为0,就是不管ID有多大,只要是本进程的消息都抓过来

);其中后三个参数可以限制抓取消息的范围,如果设置为NULL,0,0那其实就是没有进行限制,只要是本进程的消息我都把它抓过来。

GetMessage的返回值

消息WM_QUIT会使GetMessage返回0,从而中终止消息接收。

PostQuitMessage(0);会在进程中扔出WM_QUIT这个消息,get后从而使得消息循环终止。

TranslateMessage-翻译消息——它可不是什么消息都翻译。

将按键(可见字符按键,a~z)消息翻译成字符消息。

所以进入到它的内部, 它所做的第一件事就是检查这个消息是否合法,是否是它要翻译的消息类型。

如果不是按键类型消息,不做任何处理,继续执行。

函数原型如下:

代码语言:javascript复制BOOL TranslateMessage

{

CONST MSG* lpMsg;//要翻译的消息地址

}DispatchMessage-派发消息(调用对应窗口的消息处理函数)

函数原型如下

代码语言:javascript复制LRESULT DispatchMessage(

CONST MSG* lpmsg//要派发的消息

);常见消息如何学习一个消息

1.掌握这个消息的产生时间。

分析每个消息附带的两个信息。这个消息可以用来干什么(一般用法)。WM_DESTORY产生时间:窗口被销毁时产生附带信息:wParam:为0,lParam:为0一般用法:常用于在窗口被销毁前,做相应的善后处理,例如资源、内存等(该回收回收,该释放释放。)。WM_SYSCOMMAND产生时间:当点击窗口最大化,最小化,关闭等。附带信息: wParam:具体点击的位置,例如关闭SC_CLOSE等,lParam:鼠标光标的位置(这个不重要,我们只需要知道点没点就行,具体在哪个位置其实无所谓(具体情况具体使用)),LOWORD(lParam);水平位置,HIWORD(lParam);垂直位置。(高两字节传纵坐标,低两字节传横坐标。)一般用法:常用在窗口关闭时,提示用户处理。WM_CREATE产生时间:在窗口创建成功但还没显示时。附带信息: wParam:为0lParam:为CREATESTRUCT类型的指针(强转成这个类型再用),通过这个指针可以获取CreatWindowEx中全部12个参数的信息。一般用法:常用于初始化窗口函数、资源等等,包括创建子窗口等。WM_SIZE产生时间:在窗口的大小发生变化后。附带信息: wParam:窗口大小变化的原因。lParam:窗口变化后的大小 LOWORD(lParam)变化后的宽度HIWORD(lParam)变化后的高度一般用法:常用于窗口大小发生变化后,调整窗口内各个部分的布局。WM_QUIT产生时间:程序员发送。附带信息: wPram:PostQuitmessage函数传递的参数。lParam:0。一般用法:用于结束消息循环,当GetMessage收到这个消息后,会返回FALSE,结束while处理,退出消息循环。这个消息不用我们去处理,进不去我们定义的窗口处理函数,GetMessage()返回了0,无法进入循环获取消息。

WM_PAINT产生时间:当窗口需要绘制的时候 or( GetMessage没消息可抓的时候,详情请看下面。)附带信息:(没用) wParam: 0lParam:0专职用法:就是用于绘图。第一个WM_PAINT是系统发送的,当第一次创建窗口。

也就是说产生WM_SIZE消息的同时肯定回产生WM_PAINT消息,重新绘制。

相关函数

窗口无效区域:需要重新绘制的区域。调用这个函数,让窗口需要重新绘制,GetMessAge会发送WM_PAINT消息,注意,是谁发送消息。 (就可以理解说这个函数告诉GetMessage说,这个窗口需要绘制了,GetMessage发送消息到消息队列,然后转发到对应进程的消息队列中,开始执行。)代码语言:javascript复制BOOL InvalidateRect(

[in] HWND hWnd,//窗口句柄

[in] const RECT *lpRect,//区域的矩形坐标,对窗口的哪一部分进行重新绘制

[in] BOOL bErase//重绘前是否先擦除

);调试技巧代码语言:javascript复制设置一个全局变量

HANDLE g_HOUTPUT = 0;//接收标准输出句柄

增加一个dos窗口

AllocConsole();

g_HOUTPUT = GetStdHandle(STD_OUTPUT_HANDLE);dos窗口只能接收字符串类型

sprintf转,writeconsole打印

消息循环的原理消息循环的阻塞GetMessage-从系统获取消息,将消息从系统中移除,阻塞函数。当系统无消息时,会等候下一条消息。 对人来说消息是一直存在的,但是对于CPU来说(速度接近光速),消息不是经常有的,所以会经常发生阻塞。这样程序的效率就不高,从而引出下面这个函数。

PeekMessage-以查看的方式从系统中获取消息,可以不将消息从系统出移除,非阻塞函数。当系统无消息时,返回FALSE,继续执行后续代码。函数原型如下:

(前四个参数同GetMessage)

最后一个参数是,是否赋予它抓取消息的能力,一般是不给它的,也就是填写

代码语言:javascript复制BOOL PeekMessageA(

[out] LPMSG lpMsg,

[in, optional] HWND hWnd,

[in] UINT wMsgFilterMin,

[in] UINT wMsgFilterMax,

[in] UINT wRemoveMsg

);也就是说,更好的流程是,先派PeekMessage去侦查是否有消息,有就告诉GetMessage让它来处 理。没有就不要派Get去了,因为它会一直在那里等着消息的出现。

例如:

代码语言:javascript复制while (1)

{

if (PeekMessage(&nMsg, NULL, 0, 0, PM_NOREMOVE))

{

//有消息-判断是否是WM_QUIT

if (GetMessage(&nMsg, NULL, 0, 0))

{

TranslateMessage(&nMsg);

DispatchMessage(&nMsg);

}

else

{

return 0;

}

}

else

{

//没有消息——空闲处理

WriteConsole(g_HOUTPUT, "空闲ing\n", strlen("空闲ing"), NULL, NULL);

}

}发送消息Windows平台上的消息,都是它们两个造出来的。

SendMessage-发送消息,会等候消息处理的结果。PostMessage-投递消息,消息发出后立刻返回,不等候消息执行结果。函数原型如下:

代码语言:javascript复制LRESULT SendMessage(

[in] HWND hWnd,//消息发送的目的创建

[in] UINT Msg,//消息ID

[in] WPARAM wParam,//消息参数

[in] LPARAM lParam//消息参数

);这四个参数就是一个消息的前四个参数,剩下的两个参数函数内部以某种手段自加来获取。

(**其中,**sendmessage的消息扔到哪去,我们不知道,在之后的课程中会补充。postmessage的消息会扔到getmessage能接收到的区域)

消息分类系统消息-ID范围0~0x03FF 由系统定义好的消息,可以在程序中直接使用。程序员只负责一头,要么发送不用处理,要么处理不用发送。用户自定义的消息-ID范围0x0400(WM_USER) - 0x7FFF(31743) 由用户自己定义,满足用户自己的需求。由用户自己发出消息,并响应处理。由程序员,自己定制,自己发送,自己处理。自定义消息宏:WM_USER(叫什么都行)例如:

定义消息名称

代码语言:javascript复制#define WM_MYMESSAGE WM_USER+1001发送,在哪发都可以,附加消息,你自己的,附加什么都行。

代码语言:javascript复制PostMessage/SendMessage(hWnd, WM_MYMESSAGE, 1, 2);消息队列消息队列的概念消息队列是用于存放消息的队列。消息在队列中先进先出。所有窗口都具有消息队列。程序(GetMessage())可以从队列中获取消息。消息队列的分类系统消息队列——由系统维护的消息队列。存放系统产生的消息,例如鼠标、键盘等。程序消息队列——属于每个应用程序的(线程)的消息队列。由应用程序(线程)维护。 每个进程都有一个消息队列,都有GetMessage(),在本进程的消息队列中抓取消息。

细节解释:

所有进程产生的消息都先进系统消息队列,操作系统会每个一段时间,将消息转发到各个进程中去。所以才能在本进程中抓到本进程的消息。

如何做到正确转发:

消息的第一个参数是窗口句柄,保存窗口数据的内存,通过它可以找到当前程序实例句柄,找到本进程对应内存,从而正确转发。

SendMessage既没有扔到系统消息队列里,也没有扔到进程消息队列中。

消息和消息队列的关系 消息和消息队列的关系

当鼠标、键盘产生消息时,会将消息存放到消息队列中。系统会根据存放的消息,找到对应程序的消息队列。将消息投递到程序的消息队列中 根据消息和消息队列之间使用关系,将消息分成两类

队列消息-消息的发送和获取,都是通过消息队列完成。 消息发送后,首先放入队列,然后通过消息循环,从队列中获取。GetMessage-从消息队列中获取消息PostMessage-将消息投递到消息队列常见的消息队列:WM_PAINT、键盘、鼠标、定时器非队列消息-消息的发送和获取,是直接调用消息的窗口处理函数完成。 消息发送时,首先找到消息接收窗口的窗口处理函数,直接调用处理函数,完成消息。 SendMessage-直接将消息发送窗口的处理函数,并等待处理结果。常见消息:WM_CREATE(它是必须不能进队列的,否则就没办法把它抓出来了)、WM_SIZE等。GetMessage在程序(线程)消息队列查找消息,如果队列有消息,检查消息是否满足指定条件(HWND,ID范围),不满足条件就不会取出消息,否则从队列取出消息返回。如果程序(线程)消息队列没有消息,会向系统消息队列获取(找系统要,发起请求)属于班本程序的消息(之前说系统会定时分发属于对应进程的消息,如果这个时间没到,但是有的进程没消息了,会打破这个时间,提前分发。)。如果系统队列的当前消息属于本程序,系统会将消息转发到程序消息队列中。如果系统消息也没有消息(那也不闲着),检查当前进程的所有窗口需要重新绘制的区域,如果发现有需要绘制的区域,产生WM_PAINT消息扔到系统的消息队列,取得消息返回处理。如果没有重新绘制区域,检查定时器,如果有到时的定时器,产生WM_TIMER,返回处理执行。如果没有到时的定时器,整理程序的资源、内存等等。GetMessage会继续等候下一条消息。PeekMessage会返回FASLE,交出程序的控制权。注意:GetMessage如果获取到的是WM_QUIT,函数会返回FALSE。总结:总结起来就是,GetMessage非常的死心眼,没消息就找系统要,要不来就自己造,造不来就整理下资源(干点打扫卫生的活儿),还没有消息来,那就阻塞,等待消息的传来,但是依然不返回。

阻塞了,就说明GetMessage已经尽力了。

PeekMessage也会干上面的事儿,但是它最后不会傻等。

键盘消息键盘消息分类WM_KEYDOWN-按键被按下时产生WM_KEYUP-按键被放开时产生WM_SYSKEYDOWN-系统键按下时产生,比如ALT、F10WM_SYSKEYUP-系统键放开时产生附带信息:

.WPARAM-按键的Virtual Key(每个这个对应一个按键-这个虚拟键码值无法区分大小写,所以我们才需要去翻译消息)

LPARAM-按键的参数,例如按下的次数

字符消息(WM_CHAR)TranslateMessage在转换WM_KEYDOWN消息时,对于可见字符可以产生WM_CHAR,不可见字符无此消息。附带信息: WMPARAM-输入的字符的ASCII字符编码值LPARAM-按键的相关参数翻译消息的内部大致流程,如下图所示:

文字解释:

先检查是否有按键被按下, 没有直接return,有则判断是否是可见字符消息(刚才的按键消息再一次被GET过来时,已经变成了字符消息,不是按键消息,在这部就直接return了),不是直接return,都通过了,那就判断大写键是否打开,根据大小写发出相应的消息。

鼠标消息鼠标消息的分类基本鼠标消息 WM_LBUTTONDOWN-鼠标左键按下WM_LBUTTONUP-鼠标左键抬起WM_RBUTTONDOWN-鼠标右键按下WM_RBUTTONUO-鼠标右键抬起WM_MOUSEMOVE-鼠标移动消息双击消息 WM_ LBUTTONDBLCLK-鼠标左键双击WM_RBUTTONDBLCLK-鼠标右键双击滚轮消息 WM_MOUSEWHEEL-鼠标滚轮消息鼠标基本消息附带信息 WPARAM:其他按键的状态,例如Ctrl/Shift等。 LPARAM:鼠标的位置,窗口客户区坐标系。 LOWWORD X坐标位置HIWORD Y坐标位置一般情况鼠标按下/抬起成对出现。在鼠标移动过程中,会根据移动速度产生一系列的WM_ MOUSEMOVE消息。鼠标双击消息附带消息: WPARAM-其它按键的状态,例如CTRL/SHIFT。LPARAM-鼠标的位置,窗口客户区坐标系。 LOWORD(lParam)-X坐标位置HIWORD(lParam)-Y坐标位置消息产生顺序 一左键双击为例: WM_LBUTTONDOWNWM_LBUTTONUPWM_LBUTTONDBLCLKWM_LBUTTONU使用时需要在注册窗口类的时候添加CS_DBLCLKS风格。鼠标滚轮消息附带消息 WPARAM LOWORD-其他按键的状态HIWORD-滚轮的偏移量(120的倍数 ),通过正负值表示滚轮的方向正-向前滚动,负-向后滚动LPARAM:鼠标当前的位置,屏幕坐标系 LOWORD-X坐标HIWORD-Y坐标使用:通过偏移量,获取滚轮的方向和距离定时器消息定时器消息介绍产生时间:

在程序中创建定时器,当到达时间间隔时,定时器会向程序发送一个WM_TIMER消息。定时器的精度是毫秒,但是准确度很低。例如时间间隔为1000ms,但是会在非1000毫秒到达消息。附带信息: WPARAM:定时器ID(到时间的)LPARAM:定时器处理函数的指针GetMessage肯定有没事干的时候,所以WM_TIMER消息肯定会产生。

按照一定周期去执行,时间要求不严格,都可以用这个定时器。

创建销毁定时器创建定时器函数原型如下

代码语言:javascript复制UINT_PTR SetTimer(

[in, optional] HWND hWnd,//定时器窗口句柄

[in] UINT_PTR nIDEvent,//定时器ID

[in] UINT uElapse,//时间间隔(毫秒为单位)

[in, optional] TIMERPROC lpTimerFunc;//定时器处理函数指针(一般不使用,为NULL)创建成功,返回非0。

);关闭定时器函数原型如下

代码语言:javascript复制BOOL KillTimer(

[in, optional] HWND hWnd,//窗口句柄

[in] UINT_PTR uIDEvent//定时器ID

);菜单资源菜单分类窗口的顶层菜单(不需要也不能设置ID,因为它的唯一作用的就是弹出下拉次菜单)弹出式菜单系统菜单HMENU(菜单句柄)类型表示菜单,ID表示菜单项。

资源相关资源脚本文件:.rc文件编译器:RC.EXE菜单资源的使用添加菜单资源-添加文件.rc文件加载菜单资源 注册窗口类时设置菜单 (wc.lpszMenuName = (char*)IDR_MENU1;//菜单设置)创建窗口传参设置菜单 CreateWindow的导数第三个参数在主窗口WM_CREATE消息中利用SetMenu函数设置菜单LoadMenuW函数原型:

代码语言:javascript复制HMENU LoadMenuW(

[in, optional] HINSTANCE hInstance,

[in] LPCWSTR lpMenuName

);SetMenu函数原型:

代码语言:javascript复制BOOL SetMenu(

[in] HWND hWnd,

[in, optional] HMENU hMenu

);命令消息(WM_COMMAND)处理附带消息: WPARAM: HIWORD-对于菜单为0(没用)LOWRD-被鼠标点击的菜单项的IDLPARAM 对于菜单项为0(没用)Windows资源图标资源指的就是.ico为后缀的图片

添加资源 注意图标的大小, 一个图标文件中,可以有多个不同大小的图标。加载函数原型如下:

代码语言:javascript复制到本进程的内存中找图标的数据同loadMenu

HICON LoadIconA(

[in, optional] HINSTANCE hInstance,

[in] LPCSTR lpIconName

);//成功返回HICON句柄

例:使用

wc.hIcon = LoadIcon(hIns,(char*)IDI_ICON1);设置 注册窗口类光标资源添加光标的资源 光标的大小默认是32X32像素,每个光标有HotSpot,是当前鼠标的热点(点击图标生效的那个点)。加载资源函数原型如下:

代码语言:javascript复制HCURSOR LoadCursorW(

[in, optional] HINSTANCE hInstance,//可以为NULL,获取系统默认的Cursor

[in] LPCWSTR lpCursorName

);设置资源

在注册窗口时,设置光标。代码语言:javascript复制wc.hCursor = LoadCursor(hIns,(char*)IDC_CURSOR1);使用SetCursor设置光标。代码语言:javascript复制HCURSOR SetCursor(

HCURSOR hCursor

);

//必须放在处理下面这个消息的时候调用WM_SETCURSOR消息参数 WPARAM-当前使用的光标句柄LPARAM-LOWORD-当前区域代码(Hit-Test code)HICLIENT/HTCAPTION…HIWORD-当前鼠标消息ID字符串资源添加字符串资源 添加字符串资源,在表中增加字符串。字符串资源的使用 函数原型如下代码语言:javascript复制int LoadStringW(

[in, optional] HINSTANCE hInstance,

[in] UINT uID,//字符串ID

[out] LPWSTR lpBuffer,//存放字符串

[in] int cchBufferMax

);//成功返回字符串长度,失败0使用:例如实现中英文两版的软件。使用这个就非常方便。

字符串能写就写在这张字符串表中,容易修改。

用loadstring去读,然后在字符串表中改。

代码语言:javascript复制char szTitle[256] = { };

LoadString(hIns, IDS_WIND, szTitle, 256);加速键资源什么ctrl+c,v之类的,就是快捷键呗。

能使用加速键(组合键)的功能,在菜单栏中也对应有一个,一般是绑定使用。

添加,资源添加加速键表,增加命令ID对应的加速键。使用:函数原型如下:

加载加速键表

代码语言:javascript复制HACCEL LoadAcceleratorsA(

[in, optional] HINSTANCE hInstance,

[in] LPCSTR lpTableName//加速键表资源ID

);//返回加速键表句柄翻译加速键

代码语言:javascript复制int TranslateAcceleratorA(

[in] HWND hWnd,//处理消息的句柄

[in] HACCEL hAccTable,//加速键句柄

[in] LPMSG lpMsg//消息

);//如果是加速键,返回非0

位置:一定是放在GetMessage的后面,因为它不抓哪来的消息。

并且放在TranslateMessage的后面,假如我按了ctrl+m,我是想让他区分按的是大M还是小m吗?不是,我为的是实现对应的功能(产生对应的消息WM_COMMAND。)

如果按的是加速键,在内部会发送出WM_COMMAND消息。代码示例:

代码语言:javascript复制 HACCEL hAcc = LoadAccelerators(hIns, (char*)IDR_ACCELERATOR1);//加载加速键表

while (GetMessage(&nMsg, NULL, 0, 0))

{

if (!TranslateAccelerator(hWnd, hAcc, &nMsg))//不是加速键

{

TranslateMessage(&nMsg);

DispatchMessage(&nMsg);//将消息交给窗口处理函数来处理

}

}注意:

由TranslateAccelerator发出的WM_COMMMAND消息,HIWORD(WPARAM)为1。

而鼠标点击菜单资源的HIWORD(WPARAM)为0。

LOWORD(WPARAM)都代表命令ID。

LPARAM为0。

绘图编程绘图基础绘图设备(上下文/描述表)DC(Device Context)绘图上下文/绘图描述表HDC - DC句柄,表示绘图设备,绘图设备句柄。GDI-Windows graphics device interface(Win32提供的绘图API)颜色 计算机使用红、绿、蓝(RGB)每个点颜色是3个字节24位保存0~2^24 - 1种颜色16位:5,5,632位:8,8,8,8绘图或透明度颜色的使用 COLORREF-实际DWORD 例如:COLORREF nColor = 0;赋值使用RGB宏 例如:nColor = RGB(0,0,255);获取RGB值 GetRBValue/GetGValue/GetBValue例如:BYTE nRed =GetRValue(nColor);基本图形绘制在绘图消息中绘图

绘制点

SetPixel设置指定点的颜色函数原型如下:

代码语言:javascript复制COLORREF SetPixel(

[in] HDC hdc,//绘图设备句柄

[in] int x,//X坐标

[in] int y,//Y坐标

[in] COLORREF color//设置的颜色

);//返回点原来的颜色例如:

代码语言:javascript复制 PAINTSTRUCT PS = { };//抓到画家

HDC hdc = BeginPaint(hwnd, &PS);//开始绘图

SetPixel(hdc,100,100,RGB(255,0,0));

EndPaint(hwnd,&PS);//放掉画家线的使用(直线、弧线)

MoveToEx-指名窗口当前点(更改窗口当前点)

代码语言:javascript复制BOOL MoveToEx(

[in] HDC hdc,

[in] int x,

[in] int y,

[out] LPPOINT lppt//为空则不返回上一个点

);LineTo-从窗口当前点到指定点绘制一条直线(并且致命窗口当前点我)代码语言:javascript复制BOOL LineTo(

[in] HDC hdc,

[in] int x,

[in] int y

);当前点:上一次绘图时的最后一点,初始为(0,0)点封闭图形:能够使用画刷填充的图形(反之则不是)。

Rectangle/Ellipse——(圆形/矩形)内切圆,参数相同。

GDI绘图对象画笔画笔的作用 线的颜色、线型、线粗。HPAN-画笔句柄画笔的使用1.创建画笔,函数原型如下

代码语言:javascript复制HPEN CreatePen(

[in] int iStyle,//画笔的样式

[in] int cWidth,//画笔的粗细

[in] COLORREF color//画笔的颜色

);//创建成功画笔句柄PS_SOILD-实心线,可以支持多个像素宽,其它线型只能是一个像素宽。

2.将画笔应用到DC中

代码语言:javascript复制HGDIOBJ SelectObject(

[in] HDC hdc,//绘图设备句柄

[in] HGDIOBJ h//GDI绘图对象句柄,(包括)兼容画笔句柄

);//返回原来的GDI绘图对象句柄3.绘图

4.取出DC中的画笔

将原来的画笔,使用SelectObject函数,放入到设备DC中,就会将我们创建的画笔取出。

5.释放画笔,函数原型如下

代码语言:javascript复制BOOL DeleteObject(

[in] HGDIOBJ ho//GDI绘图对象句柄,画笔句柄。

);只能删除不被DC使用的画笔,所以在释放前,必须从将画笔从DC中取出。

大致过程就是,创建-交换-绘画-取出-释放

示例:

代码语言:javascript复制 PAINTSTRUCT PS = { };//抓到画家

HDC hdc = BeginPaint(hwnd, &PS);

HPEN hPen = CreatePen(PS_SOLID,20,RGB(255,0,0));//创建

HGDIOBJ nOldPen = SelectObject(hdc, hPen);//交换

Ellipse(hdc, 100, 100, 300, 300);

SelectObject(hdc, nOldPen);//交换,这里可以不用接收,上面创建时的句柄可以找到这跟笔,让画家松手就行了,我们就能销毁了。

DeleteObject(hPen);

EndPaint(hwnd,&PS);画刷画刷相关 画刷-封闭图形的填充颜色、图案HBRUSH-画刷句柄画刷的使用套路就跟画笔一样默认是画刷颜色是白色的画刷的使用1.创建画刷

代码语言:javascript复制创建实心画刷

HBRUSH CreateSolidBrush(

[in] COLORREF color

); 代码语言:javascript复制创建纹理画刷

HBRUSH CreateHatchBrush(

[in] int iHatch,

[in] COLORREF color

);2.将画刷应用到DC中

SelectObject

3.绘图

4.将画刷从DC中取出

SelectObject

5.删除画刷

DeleteObject

其他可以使用GetStockObject函数获取系统维护的画刷,画笔等。

如果不使用画刷填充,需要使用NULL_BRUSH参数,获取不填充的画刷。

GetStockObject返回的画刷不需要DeleteObject。

示例:

代码语言:javascript复制//向操作系统借一把透明刷子

HGDIOBJ hBrush = GetStockObject(NULL_BRUSH);位图位图绘制位图相关

光栅图形-记录图像中每一点的颜色等信息矢量图形-记录图像算法、绘图指令等。HBITMAP-位图句柄位图的使用

1.在资源中添加位图资源

2.从资源中加载位图——loadBitMap

3.创建一个与当前DC相匹配的DC(内存DC)(在内存的一个虚拟的区域画)

代码语言:javascript复制HDC CreateCompatibleDC(

[in] HDC hdc//当前DC句柄,可以为NULL(使用屏幕DC)

);//返回创建好的DC句柄兼具资源的步骤和GDI绘图对象的步骤。

4.将位图放入匹配的DC中SelectObject

5.成像(1:1)

代码语言:javascript复制BOOL BitBlt(

[in] HDC hdc,//目的DC

[in] int x,//目的左上X坐标

[in] int y,//目的左上Y坐标

[in] int cx,//目的宽度

[in] int cy,//目的高度

[in] HDC hdcSrc,//源DC

[in] int x1,//源左上X坐标

[in] int y1,//源左上Y坐标

[in] DWORD rop//成像方法SRCCOPY

);缩放成像

代码语言:javascript复制BOOL StretchBlt(

[in] HDC hdcDest,

[in] int xDest,

[in] int yDest,

[in] int wDest,

[in] int hDest,

[in] HDC hdcSrc,

[in] int xSrc,

[in] int ySrc,

[in] int wSrc,

[in] int hSrc,

[in] DWORD rop

);6.取出位图

SelectObject

7.释放位图

DeleteObject

8.释放匹配的DC

DeleteDC

代码示例:

代码语言:javascript复制PAINTSTRUCT ps = { };

HDC hdc = BeginPaint(hwnd, &ps);

HBITMAP hBmp = LoadBitmap(g_HINSTANCE, (char*)IDB_BITMAP1);

HDC hMemdc = CreateCompatibleDC(hdc);//创建一个内存DC,并构建一个虚拟区域,并且内存DC在虚拟区域中绘图

HGDIOBJ nOldBmp = SelectObject(hMemdc,hBmp);//将位图数据送给内存DC,内存DC在虚拟区域中将位图绘制出来,返回旧位图(原来并没有,这是个假的,只是为了我们将来能够换回来)

BitBlt(hdc, 100, 100, 48, 48, hMemdc, 0, 0, SRCCOPY);//将虚拟区域绘制好的图像成像到窗口中

StretchBlt(hdc, 200, 200, 96, 96,hMemdc,0,0,48,48,SRCCOPY);//缩放成像

//开辟的区域比要成像的图像大,就是放大图像。

SelectObject(hMemdc, nOldBmp);//换回来

DeleteObject(hBmp);//释放画的位图

DeleteDC(hMemdc);//释放内存DC文本绘制文字的绘制TextOut-将文字绘制在指定坐标位置代码语言:javascript复制BOOL TextOutW(

[in] HDC hdc,

[in] int x,

[in] int y,

[in] LPCWSTR lpString,

[in] int c

);DrawText代码语言:javascript复制int DrawText(

[in] HDC hdc,//DC句柄

[in, out] LPCTSTR lpchText,//字符串

[in] int cchText,//字符数量

[in, out] LPRECT lprc,//绘制文字的矩形框

[in] UINT format//绘制的方式

);文字颜色和背景

文字颜色:SetTextColor代码语言:javascript复制COLORREF SetTextColor(

[in] HDC hdc,

[in] COLORREF color

);文字背景色:SetBkColor——只适用在不透明文字背景模式:SetBkMode(OPAQUE/TRANSPARENT)(透明(默认)/非透明)字体字体相关:window常用的字体为TrueType格式的字体文件 字体名-标识字体类型HFONT-字体句柄(保存字体的数据信息——外观形状)字体的使用1.创建字体CreateFont

代码语言:javascript复制HFONT CreateFontA(

[in] int cHeight,//字体高度

[in] int cWidth,//字体宽度

[in] int cEscapement,//字符串倾斜角度

[in] int cOrientation,//字体的旋转角度

[in] int cWeight,//字体的粗细

[in] DWORD bItalic,//斜体

[in] DWORD bUnderline,//字符下划线

[in] DWORD bStrikeOut,//删除线

[in] DWORD iCharSet,//字符集-GB2312_CHARSET

//下面的4参数全写0即可

[in] DWORD iOutPrecision,//输出精度

[in] DWORD iClipPrecision,//剪切精度

[in] DWORD iQuality,//输出质量

[in] DWORD iPitchAndFamily,//匹配字体

//这个得写

[in] LPCSTR pszFaceName//字体名称

);代码示例:

代码语言:javascript复制PAINTSTRUCT ps = { };

HDC hdc = BeginPaint(hwnd,&ps);

SetTextColor(hdc, RGB(255, 0, 0));

SetBkColor(hdc, RGB(0, 255, 0));

HFONT hFront = CreateFont(30,0,45,0,900,1,1,1, GB2312_CHARSET,0,0,0,0,"黑体");

HGDIOBJ nOldFron = SelectObject(hdc, hFront);//将创建的字体给它

const char* szText = "我是TEXTOUT";

TextOut(hdc,100,100,szText,strlen(szText));

//DrawText在矩形的范围内画,所以首先要确定一个范围

RECT rc;

rc.left = 100;

rc.top = 150;

rc.right = 200;

rc.bottom = 200;

//DrawText有矩形作为限制边界(可以打破)

DrawText(hdc, szText, strlen(szText),&rc,DT_LEFT | DT_TOP);

//DT_VCENTER和DT_BOTTOM只适用于DT_SINGLELINE和DT_WORDBREAK冲突

SelectObject(hdc, nOldFron);//让它放开

DeleteObject(hFront);//字体更要记得释放,因为字体占的内存较大

EndPaint(hwnd,&ps);对话框处理消息的方式与普通窗口相反,缺省的函数调用自定义函数。

对话框原理对话框的分类 模式对话框-当前对话框显示时,会禁止本进程其它窗口和用户交互操作。无模式对话框-在对话框显示后,本进程其它窗口仍然可以和用户进行操作。对话框基本使用 对话框窗口处理函数注册窗口函数(不使用)创建对话框对话框的关闭谁注册窗口类,窗口处理函数就由谁来实现。

对话框处理函数(并非真正的对话框处理函数,真正的系统内部。)代码语言:javascript复制DLGPROC Dlgproc;

INT_PTR Dlgproc(

HWND unnamedParam1,//窗口句柄

UINT unnamedParam2,//消息ID

WPARAM unnamedParam3,//消息参数

LPARAM unnamedParam4//消息参数

);返回TRUE——缺省处理函数不需要处理。

返回False——交给缺省处理函数处理。不需要调用缺省对话框处理函数。

模式对话框代码语言:javascript复制void DialogBoxA(

[in, optional] hInstance,//当前程序实例句柄

[in] lpTemplate,//对话框资源ID

[in, optional] hWndParent,//对话框父窗口

[in, optional] lpDialogFunc//自定义函数

);DialogBox是一个阻塞函数,只有当对话框关闭后,才会返回,继续执行后续代码。

返回值是通过EndDialog设置。

对话框的关闭代码语言:javascript复制BOOL EndDialog(

[in] HWND hDlg,//关闭的对话框窗口

[in] INT_PTR nResult//关闭的返回值,能指定返回值就说明能接触阻塞

);关闭模式对话框,只能使用EndDialog,不能使用DestoryWindow等函数。

nRsult是DiglogBox函数退出时的返回值。

对话框的消息WM_INITDIALOG-对话框创建之后在显示之前,通知对话框窗口处理函数,可以完成自己的初始化相关的操作。

EndDialog销毁对话框,并且解除阻塞。

而DestroyWindow只能销毁对话框,并不能解除阻塞。

无模式对话框创建对话框代码语言:javascript复制HWND CreateDialog(

HINSTANCE hInstance,//应用程序实例句柄

LPCTSTR lpName,//模板资源ID

HWND hWndParent hWndParent,//父窗口

DLGPROC lpDialogFunc//自定义函数

);非阻塞函数,创建成功后返回窗口句柄,需要使用ShowWindow函数显示对话框。

对话框关闭关闭时使用DestroyWindow销毁窗口,不能使用EndDiaglog关闭对话框。

代码示例:

代码语言:javascript复制HWND noModel = CreateDialog(g_Hinstance, (char*)IDD_DIALOG1,hwnd, DlgProc);

ShowWindow(noModel, SW_SHOW);

xxx

DestroyWindow(hwnd);静态库Windows上静态库和Linux上的静态库在原理上没有任何区别,都是封装一堆东西等着别人去掉。

静态库的特点运行不存在。 没有如何,不能执行,生成的文件无法形成静态影像,无法进内存。静态库源码被链接到调用程序中。目标程序的归档。C语言静态库C静态库的创建 创建一个静态库程序。添加库程序,源文件使用C文件。C静态库的使用 库路径设置:可以使用#pragma关键字设置 #pragma comment(lib,“…/lib/clib.lib”)C++静态库C++静态库的创建 创建一个静态库项目添加库程序,源文件使用CPP文件。C++静态库的使用 库路径设置:可以使用pragma关键字设置#pragma comment(lib,“…/xx/xxx.lib”)示例:

代码语言:javascript复制#include

using namespace std;

//给编译器看

int CLIB_add(int add1, int add2);

int CLIB_sub(int add1, int add2);

//给链接器看

#pragma comment(lib,"../Debug/CPPLIB.lib")

int main(void)

{

cout << CLIB_add(5, 2) << endl;

cout << CLIB_sub(5, 2) << endl;

return 0;

}C++调库中函数需要在前面声明。告诉编译器到底换不换名。

C++编译器编译时会更改函数名。

解决:

代码语言:javascript复制extern"C" 函数声明;告诉编译器以C语言方式编译(不要给我改名)。动态库动态库的特点运行时独立存在(依附着其它程序运行起来的之后)源码不会链接到执行程序使用时加载(使用动态库必须使动态库执行)与静态库进行比较

由于静态库是将代码嵌入到使用程序中,多个程序使用时,会有多分代码,所以代码体积会增大。动态库的代码只需要存一份,其他程序通过函数地址使用,所以代码体积较小。静态库发生变化后,新的代码需要重新链接嵌入到执行程序中。动态库发生变化后,如果库中函数的定义(或地址)未变化(仅仅是函数的实现发生了变化),其他使用DLL的程序不需要重新链接。动态库的创建创建动态库项目

添加库程序

库程序导出-提供给使用库者库中的函数等信息。

声明导出:使用_declspec(dllexport)导出函数。将函数的地址存放在动态库的文件头中。(换名之后的函数)

注意:动态库编译链接后,也会有LIB文件(与该DLL配套生成的),是作为动态库函数映射使用,与静态库不完全相同。LIB中存放每个函数的名字和它对应的标号,还有与它配套的DLL文件名——xxx.dll

模块定义文件.def

代码语言:javascript复制例如:

LIBRARY DLLFunc //库

EXPORTS //库导出表

DLL_Mul //导出的函数动态库的使用隐式链接(操作系统负责使动态库执行,系统偷偷的把动态库扔到内存中。) 头文件和函数原型,在函数原型的声明前,增加_declspec(dllimport)导入动态库的LIB文件在程序中使用函数 隐式的链接的情况,dll文件可以存放的路径(编译器可以找到的位置):与执行文件中同一个目录下当前工作目录Windows目录,(三个window…不建议使用,因为是系统路径, 公共场所,容易出现问题。)Windows/System32目录Windows/System环境变量PATH指定目录显式链接(程序员自己负责使动态库执行)1.定义函数指针类型 typedef

2.加载动态库

代码语言:javascript复制HMODULE LoadLibrary(

LPCTSTR lpFileName//动态库文件名或全路径

);//返回DLL的实例局句柄(HINSTANCE)3.获取函数(绝对/真实)地址

代码语言:javascript复制FARPROC GetProcAddress(

HMODULE hModule,//DLL句柄

LPCSTR lpProcName//函数名称

);//成功返回函数地址4.使用函数

5.卸载动态库(释放那块内存,结束动态库执行。)

代码语言:javascript复制BOOL FreeLibrary(

HMODULE hModule//DLL的实例句柄

);代码示例:

代码语言:javascript复制LIBRARY Dll3

EXPORTS

Print @1代码语言:javascript复制#include

#include

using namespace std;

typedef void(*PRINT)();

int main(void)

{

HINSTANCE hDll = LoadLibrary("Dll3.dll");

PRINT MYPRINT = (PRINT)GetProcAddress(hDll, "Print");

MYPRINT();

FreeLibrary(hDll);

return 0;

}动态库中封装类类导出的其实也是函数地址。

在类名称前增加_declspec(dllexport)定义,例如:代码语言:javascript复制class _declspec(dllexport)CMath

{

...

}通常使用预编译开关切换类的导入导出定义,例如:

代码示例:

(如果生成错误就改一下代码顺序)

动态库中封装类.h

代码语言:javascript复制#ifndef _DLLCLASS_H

#define _DLLCLASS_H

#ifdef DLLCLASS_EXPORTS

#define EXT_CLASS _declspec(dllexport)//导出

#else

#define EXT_CLASS _declspec(dllimport)//导入

#endif

class EXT_CLASS Print

{

public:

void PRINT();

};

#endif动态库中实现.cpp

代码语言:javascript复制#include "pch.h"

#include

#include"CLASSDLL.h"

using namespace std;

#define DLLCLASS_EXPORTS

void Print::PRINT()

{

cout << "123" << endl;

}使用该动态库

代码语言:javascript复制#include

using namespace std;

#include"../CLASSDLL/CLASSDLL.h"

#pragma comment(lib,"../Debug/CLASSDLL.lib")

int main(void)

{

Print p1;

p1.PRINT();

return 0;

}线程线程基础Windows的线程是可以执行代码的实例。系统是以线程为单位调度程序。一个程序当中可以有多个线程,实现多任务的处理。进程开启意味着分内存,线程开辟意味着程序的执行。Windows线程的特点: 线程都具有1个ID每个线程都具有自己的内存栈同一进程中的线程使用同一个地址空间线程的调度:操作系统将CPU的执行时间划分成时间片,依次根据时间片执行不同的线程

线程轮询:线程A->线程B->线程A…

在一个时间点上,CPU只能执行一个线程。

创建线程创建线程函数原型如下:

代码语言:javascript复制HANDLE CreateThread(

[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,//安全属性

[in] SIZE_T dwStackSize,//线程栈的大小

[in] LPTHREAD_START_ROUTINE lpStartAddress,//线程处理函数的函数地址

[in, optional] __drv_aliasesMem LPVOID lpParameter,//传递给线程处理函数的参数

[in] DWORD dwCreationFlags,//线程的创建方式

[out, optional] LPDWORD lpThreadId//创建成功,返回线程的ID

);//创建成功,返回线程句柄。只要看到安全属性这个参数就通通置空,这个参数已经被废弃了。

线程栈大小,按照1MB对齐。

看到处理函数,就说明这个函数由我们程序员去定义,由系统调用。

线程的创建方式有两种,立即执行方式,挂起方式。

多线程,宏观上同时开始(并行),真实是,一个时间点只能执行一个线程(串行)。

定义线程处理函数代码语言:javascript复制DWORD WINAPI ThreadProc(

LPVOID lpParameter//创建线程时,传递给线程的参数。

);代码示例:

代码语言:javascript复制#include

#include

using namespace std;

DWORD CALLBACK TestProc(LPVOID pParam)

{

while (1)

{

cout << (char*)pParam << endl;

Sleep(1000);

}

return 0;

}

DWORD CALLBACK TestProc1(LPVOID pParam)

{

while (1)

{

cout << (char*)pParam << endl;

Sleep(1000);

}

return 0;

}

int main(void)

{

DWORD nID = 0;

const char* text = "123";

HANDLE hTread = CreateThread(NULL, 0, TestProc, (void*)text, 0, &nID);

const char* text1 = "abc";

HANDLE hTread1 = CreateThread(NULL, 0, TestProc1, (void*)text1, 0, &nID);

getchar();

return 0;

}线程挂起/销毁挂起:

代码语言:javascript复制 DWORD SuspendTread(

HANDLE hThread //线程句柄

);唤醒:

代码语言:javascript复制DWORD ResumeTread(

HANDLE hThread//线程句柄

);结束指定线程:

代码语言:javascript复制BOOL Terminate Thread(

HANDLE hThread,//线程句柄

DWORD dwExitCode//退出码,没有实际意义

);结束函数所在的线程:

代码语言:javascript复制VOID ExitThread(

DWORD dwExitCode//退出码

);线程相关操作获取当前线程ID

代码语言:javascript复制GetCurrentThreadId();获取当前线程的句柄

代码语言:javascript复制GetCurrentThread();等候(可等候)单个句柄有信号——线程句柄

代码语言:javascript复制VOID WaitForSingleObject(

HANDLE handle,//句柄BUFF的地址

DOWRD dwMilliseconds//等候时间INFINITE(无限大)

);一个可等候的句柄,一定有有信号和无信号的两种状态。

等候的时间已经到了,该句柄还是无信号那就返回了。

同时等候多个句柄有信号

代码语言:javascript复制DWORD WaitForMultipleObjects(

DOWRD nCount,//要等候的句柄数量

CONST HANDLE*lpHandle,//句柄BUFF的地址

BOOL bWaitAll,//等候方式

//TRUE-所有事件都有信号才返回

//FALSE-有一个事件有信号就返回

DWORD dwMilliseconds//等候时间INFINITE

);bWaitAll-等候方式

TRUE-表示所有句柄都有信号,才结束等候。FALSE-表示句柄中只要1个有信号,就结束等候。线程句柄执行的时候无信号,结束的时候有信号。

线程同步原子锁相关问题多个线程对同一个数据进行原子操作,会产生结果丢失。比如执行++运算时。

错误代码分析当线程A执行g_value++时,如果线程切换时间正好是在线程A再次被切换回来之后,会将原来线程A保存的值保存到g_values上,线程B进行的加法操作被覆盖。(汇编角度)

当CPU在执行某个线程的时候,一旦时间到了它要离开,它会将这个线程已经执行到的位置保护起来,一般是压栈保护。将位置信息压到本线程的栈中。再来,先弹栈读取,恢复战场。

图示解释/错误分析:

第一个线程刚要将+1后的值赋给g_value,cpu给它的时间就到了,这时切换到线程2,线程2执行完毕,g_value == 1,切换回线程1,先弹栈恢复,执行完毕得到g_value == 1,这就丢失了一次数据。

解决:使用原子锁函数,来++

代码语言:javascript复制DWORD CALLBACK TestProc1(LPVOID pParam)

{

for (int i = 0; i < 1000000; i++)

{

//g_value++;

InterlockedIncrement(&g_value);

}

return 0;

}

DWORD CALLBACK TestProc2(LPVOID pParam)

{

for (int i = 0; i < 1000000; i++)

{

//g_value++;

InterlockedIncrement(&g_value);

}

return 0;

}使用原子锁函数代码语言:javascript复制InterlockedIncrement

......

每一个操作符都有对应的一个原子锁函数原子锁的实现:直接对数据所在的内存进行操作,并且在任何一个瞬间只能有一个线程访问这块内存。(++并不是直接对内存进行操作,而是对寄存器进行操作,然后再赋值给变量。)

锁的是数据所在的这块内存,并不是CPU。

解释:

先执行线程1,进来直接给g_value(内存)上锁,如果这时候CPU给的时间到了,那就压栈保护,然后转去执行线程2,线程2一看已经g_value已经被锁了,不会重复锁,发生阻塞,等到时间的耗尽,又转去执行线程1,弹栈恢复,完成执行完后,给g_value解锁,再去执行线程2,线程2就能给g_value上锁了…重复上述过程。

简单来说就是,上了锁之后可以保证一个线程对该变量,完成一次完整的操作后,另一个线程再进行一次完整的操作。

使用原子锁之后执行效率变慢,因为这两个线程老是对着睡觉。

重要的是保证结果的正确性,如果保证不了正确性,宁可牺牲效率也要保证结果正确。

局限性&优点只能对运算符进行原子锁。而且有大量函数。但是在所有的枷锁机制中,它的效率最高。

互斥相关的问题多线程下代码或资源的共享使用

互斥的使用一.创建互斥

代码语言:javascript复制HANDLE CreateMutex(

LPSECURITY_ATTRIBUTES lpMutexAttributes,//安全属性

BOOL bInitialOwner,//初识的拥有者TRUE/FALSE

LPCTSTR lpName//命名

);//创建成功返回互斥句柄互斥不光能对操作符进行枷锁,也能对其它东西进行枷锁,比如说锁定一段或一行代码。

原子锁能解决的问题,互斥都能解决,但是互斥能解决的问题,原子锁不一定能解决。

互斥句柄也是可等候句柄。

特点:

1.任何一个时间点上只能有一个线程拥有互斥。其它线程只能等待这个线程把互斥扔掉才能拥有。独占性和排他性、

当任何一个线程都不拥有互斥,互斥句柄有信号。一旦某个线程有互斥,互斥句柄无信号。谁先等待互斥,谁先得到互斥。二.等到互斥

代码语言:javascript复制WaitForSingleObject/WaitForMultipleObjects...互斥的等候遵循谁先等候谁先获取三.释放互斥

代码语言:javascript复制BOOL ReleaseMutex(

HANDLE hMutex//互斥句柄

);四.关闭互斥句柄

代码语言:javascript复制CloseHandle代码示例:

代码语言:javascript复制#include

#include

using namespace std;

HANDLE g_hMutex = 0;//接收互斥句柄

DWORD CALLBACK TestProc(LPVOID pParam)

{

char* text = (char*)pParam;

while (1)

{

WaitForSingleObject(g_hMutex, INFINITE);

for (int i = 0; i < strlen(text); i++)

{

cout << text[i];

Sleep(333);

}

cout << endl;

ReleaseMutex(g_hMutex);

}

return 0;

}

DWORD CALLBACK TestProc1(LPVOID pParam)

{

char* text = (char*)pParam;

while (1)

{

WaitForSingleObject(g_hMutex, INFINITE);

for (int i = 0; i < strlen(text); i++)

{

cout << text[i];

Sleep(333);

}

cout << endl;

ReleaseMutex(g_hMutex);

}

return 0;

}

int main(void)

{

//创建互斥的这个主线程也不拥有它,

g_hMutex = CreateMutex(NULL,FALSE,NULL);

DWORD nID = 0;

const char* text = "123";

HANDLE hTread = CreateThread(NULL, 0, TestProc, (void*)text, 0, &nID);

const char* text1 = "abc";

HANDLE hTread1 = CreateThread(NULL, 0, TestProc1, (void*)text1, 0, &nID);

getchar();

CloseHandle(g_hMutex);

return 0;

}解释:互斥句柄在主函数中创建出来,设备FALSE,谁都不拥有,互斥句柄有信号,等待抢占,假设CPU先执行线程1, 拿到互斥句柄,互斥句柄变为无信号,执行打印,到时间后,压栈保存,转去执行线程2,此时WaitForSingObject就无法通过,阻塞,等待时间结束,又执行线程1,恢复现场,假设执行完毕,线程1丢掉互斥,互斥句再次变为有信号,线程2WaitForSingObject通过,拿到互斥…。

原子锁和互斥都是枷锁机制,实现的都是多个线程之间有排斥关系。 多个线程之间去竞争临界资源。

下面这两个同步技术,事件和信号量,实现的是线程之间的协调工作关系。

事件相关问题程序(线程)之间的通知的问题。

两个线程要协调工作,它们两个就得通信。

事件的使用1.创建事件

代码语言:javascript复制HANDLE CreateEvent(

LPSECURITY_ATTRIBUTES lpEventAttributes,//安全属性

BOOL bManualReset,//事件重置(复位(将事件变为无信号))方式,TRUE手动,FALSE自动

BOOL bInitialState,//事件初识状态,TRUE有信号

LPCTSTR lpName//事件命名

);//创建成功返回事件句柄2.等候事件

代码语言:javascript复制WaitForSingleObject/WaitForMultipleObjects3.触发事件(将事件设置成有信号状态)

代码语言:javascript复制BOOL SetEvent(

HANDLE hEvent //事件句柄

)4.复位事件(将事件设置成无信号状态)

代码语言:javascript复制BOOL ResetEvent(

HANDLE hEvent//事件句柄

);5.关闭事件

代码语言:javascript复制CloseHandle代码示例:

代码语言:javascript复制#include

#include

using namespace std;

HANDLE g_hEvent = 0;//接收事件句柄

DWORD CALLBACK PrintProc(LPVOID pParam)

{

while (1)

{

WaitForSingleObject(g_hEvent,INFINITE);

//ResetEvent(g_hEvent);//将事件设置成无信号

cout << "*************" << endl;

}

return 0;

}

//通过事件进行通信

DWORD CALLBACK CtrlProc(LPVOID pParam)

{

while (1)

{

Sleep(1000);

SetEvent(g_hEvent);//解救线程1

}

}

int main(void)

{

//FALSE为自动复位,当经过WaitForSingleObject函数之后

//TRUE为手动复位,手动在WaitForSingleObject后ResetEvent

g_hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);

DWORD nID = 0;

HANDLE hThread[2] = { 0 };

hThread[0] = CreateThread(NULL,0,PrintProc,NULL,0,&nID);

hThread[1] = CreateThread(NULL,0,CtrlProc,NULL,0,&nID);

WaitForMultipleObjects(2,hThread,TRUE,INFINITE);

return 0;

}注意:小心事件死锁。

信号量相关的问题类似于事件(不是原理类似时间,是作用类似事件),解决通知的相关问题。但提供一个计数器,可以设置次数。

信号量的使用1.创建 信号量

代码语言:javascript复制HANDLE CreateSemaphoreW(

[in, optional] LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,//安全属性

[in] LONG lInitialCount,//初始化信号量数量(初识计数值)

[in] LONG lMaximumCount,//信号量(计数器)的最大值

[in, optional] LPCWSTR lpName//命名

);//创建成功返回信号量句柄信号量的计数器不为0时,信号量句柄有信号。

2.等候信号量

代码语言:javascript复制WaitForSingleObject/WaitForMultipleObjects每等候一次,信号量减1,直到为0阻塞。

3.给信号量指定计数值

代码语言:javascript复制BOOL ReleaseSemaphore(

[in] HANDLE hSemaphore,//信号量句柄

[in] LONG lReleaseCount,//释放数量

[out, optional] LPLONG lpPreviousCount//释放前原来信号量的数量,可以为NULL

);4.关闭句柄

代码语言:javascript复制CloseHandle 代码示例:

代码语言:javascript复制#include

#include

using namespace std;

HANDLE h_handle = 0;//信号量句柄

DWORD CALLBACK TestProc(LPVOID pParam)

{

while (1)

{

WaitForSingleObject(h_handle, INFINITE);

cout << "******" << endl;

}

return 0;

}

int main(void)

{

h_handle = CreateSemaphore(NULL, 3, 10, NULL);

DWORD nID = 0;

HANDLE hThread = CreateThread(NULL, 0, TestProc, NULL, 0, &nID);

getchar();//回车之后信号量数量改为5

ReleaseSemaphore(h_handle, 5, NULL);

WaitForSingleObject(hThread, INFINITE);

CloseHandle(h_handle);

return 0;

}