Win32——学习笔记

一、应用程序分类:
控制台程序 Console :Dos程序,本身没有界面,通过WindowsDos界面执行
窗口程序:拥有自己的窗口,可以与用户交互
库程序:存放代码,数据的程序,执行文件可以从中取出代码执行和获取数据 :
 1、动态库程序:拓展名DLL,在执行文件是获 取代码
 2、静态库程序:拓展名LIB,在连接程序时,将代码放到执行文件中    
 二、编译工具和库
1、编译工具 (存在路径:C : ProgramFiles(x86)\Microsoft Visual Studio 10.0 \VC \bin)
编译器CL.EXE  将源代码编译成目标代码.obj    最终生成c / cpp 文件
链接器LINK.EXE  将目标代码、库 链接生成最终文件
资源编辑器RC.EXE (.rc)将资源编译,通过连接器存入最终文件  最终生成rc文件
2、Windows库文件(函数)和头文件(声明)  
动态库(存在路径: C:\Windows\System32)
kernel32.dll -提供核心的API、比如进程、线程、内存管理等。
user32.dll -提供了窗口、消息等API
gdi32.dll -提供绘图相关的API
头文件(存在路径: C:\ProgramFiles(X86)\Miscrosoft SDKs\ Windows\v7.0A\Include)
window.h -所有的windows头文件的集合
windef.h -windows数据类型
winbase.h - kernel32的API
wingdi.h - gdi32的API
winuser.h -user32的API
winnt.h-UNICODE的字符集支持
相关函数:大写H出现的可以大胆猜测这是句柄
int WINAPI WinMain(1、HINSTANCE hInstance  //当前句柄先把它用来找到内存的东西 找到你当前进程的地址.
          2、HINSTANCE hPreinstance  //当前程序前一个实例句柄已经被弃用
           3、LPSTR lpCmdLine  // 命令行参数字符串   LPSTR 实际时char* 只能存一个参数      就像命令行参数 mian(cha* [])
          4、int  nCmdShow  //窗口显示方式  1、窗口最大化  2、窗口的最小化 3、原样显示)
提示框
int MessageBox(1、HWND hWnd  //父窗口句柄   用处:hWnd 保存的是窗口属性 大少 颜色 风格 位置... 父窗口句柄 提示框 窗口他爹是谁,提示框依附的是谁
               2、LPCTSTR lpText //显示消息框中的文字  
               3、LPCTSTR lpCaption //显示标题栏中的文字
               4、UINT uType //消息框中的按钮、图标显示类型  UINT无符号整型
) ; //返回点击的按钮ID
二、字符编码
1、编码的历史背景
ASC    7位   128个字节       原因:内存昂贵 26大小字母 控制符号 够用 技术限制
ASCII  8位    256                原因:美国推广欧洲外国  
2、DBCS和UNICODE码
DBSC :        单(英文)双(汉字、日、韩)字节混合编码      缺陷:没有统一的标准      原因:美国推广亚洲
UNICODE :   可变长的<liunx(utf-8)Windows(utf-16:特点无论中文或者英文都按两个字节编码英文占位补0有浪费)>   万国码(每一种语言的每一种字符都分配了唯一的编码)
3、宽字节字符字--符串类型
wchar_t 占两个字节 实际上是unsigned short (占两个字符)类型,定义时需要增加“L”,通知编译器按照双字节编译字符串,采用UNICODE
wchat_t* pwszText = L"Hell_wchar"; // L 告诉编译器使用 双字节编译字符串,采用utf-16编码。
wprintf(L"%s\n",pwszText);

char 占一个字节


4、TCHAR  编程的时候有时候会是char 有时候会wchar_t  微软提供一个 TCHAER类型解决这个编码问题 在头文件WINNT.h中处理
伪代码:
如果定义了: UNICODE
              那么 TCHAR 的值为 wchar_t
             __TEXT(quote) L ## quote 拼接 引号里的字符串
如果没有定义:UNICODE
    那么TCHAR的值为 char
        __TEXT(quote) quote   
# ifdef UNICODE               
typedef wchat_t TCHAR;
 #define_TEXT(quote) L ##quote
#else
typedef char TCHAR;
#define _TEXT(quote)quote
#endif

#define UNICODE // 需要定义在头文件上方

void PintUNICODE(){
   TCHAR* pszText = __TEXT("Hello TCHAR"); // char* pszText = "Hello TCHAR ";  wchar_t* pszText = L"Hello TCHAR";
  #ifdef UNICODE
     wprintf(L"%s\n", pszText);
  #else
    printf("单%s\n",pszText);
#endif
}

void c_char(){
   char* pszText = "hello wchar";
        int len = wcslen(pszText);
         wprintf(L"%s %d\n",pszText,len);
}
void w_char(){
    wchar_t* pszText = L"hello wchar";
    printf("%s\n",pszText);
}


5、打印UNICODE字符

6、系统调用函数的参数类型
LPSTR = char*                LPCSTR = const char*
LPWSTR = w_char_t       LPCWSTR = const w_char*_t
LPTSTR = TCHAR*          LPCTSTR = const TCHAR*

三、注册窗口类
1、窗口的概念
1.1窗口类包含窗口的各种参数信息的数据结构     窗口类(在windows叫法)就是WINSDCLASS 结构体(在程序中的叫法)
WINSDCLASS{
wc.cbClsExtra = 0; //申请缓存区
    wc.cbWndExtra = 0; //申请缓冲区 0个字节
    wc.hbrBackground = (HBRUSH)COLOR_WINDOW + 3; //白色
    wc.hCursor = NULL;  //默认光标
    wc.hIcon = NULL; //默认图标
    wc.hInstance = histance; //当前实例句柄
    wc.lpfnWndProc = WndProc; //自定义的处理函数
    wc.lpszClassName = "Main";
    wc.lpszMenuName = NULL; //不要菜单
    wc.style = CS_HREDRAW | CS_VREDRAW; //当水平或者 垂直方向有变化是重新画一遍
    RegisterClass(&wc); //将以上所有赋值全部写入操作系统内核中
}

typedef struct _WNDCLASSA {
    UINT        style; //窗口类的风格
    WNDPROC     lpfnWndProc; // 窗口处理函数
    int         cbClsExtra;  // 窗口类的附加数据buff的大少
    int         cbWndExtra; // 窗口的附加数据buff的大少
    HINSTANCE   hInstance; // 当前模块的实例句柄
    HICON       hIcon; // 窗口图标句柄
    HCURSOR     hCursor; //鼠标句柄
    HBRUSH      hbrBackground;// 绘制窗口背景的画涮句柄
    LPCSTR      lpszMenuName; // 窗口菜单的资源ID字符串
    LPCSTR      lpszClassName; // 窗口类的名称
} WNDCLASSA, *PWNDCLASSA, NEAR *NPWNDCLASSA, FAR *LPWNDCLASSA;

1.2 每个窗口都具有窗口类,基于窗口类创建窗口
1.3 每一个窗口类都具有一个名称,使用前必须注册到系统

2、窗口的分类
2.1、系统窗口类:系统已经定义好的窗口类,所有应用程序都可以直接使用
特点:不需要注册:直接可以使用窗口类即可。系统已经注册好了
如:
BUTTON   EDIT
2.2、(应用程序)局部窗口类:用户自己定义,当前应用程序中本模块可以使用
注册窗口类的函数
ATOM unsigned int 返回非零值
ATOM RegisterClass(
        CONST WNDCLASS * lpWndClass // 窗口类的数据
                               ) ;// 注册成功,返回一个数字标记
2.3、(应用程序)全局窗口类:用户自己定义,当前应用程序所有模块都可以使用
ATOM RegisterClass(
        CONST WNDCLASS * lpWndClass // 窗口类的数据
                               ) ;// 注册成功,返回一个数字标记

四、窗口创建
1、
CreateWindow / CreateWindowEx(加强版)

HWND CreateWindowEx
(
    _In_ DWORD dwExStyle,  //窗口的扩展风格(没有啥用)
    _In_opt_ LPCSTR lpClassName,注册窗口类 的 名称
    _In_opt_ LPCSTR lpWindowName,窗口标题栏的名字
    _In_ DWORD dwStyle,窗口基本风格
    _In_ int X, 窗口左上角水平坐标位置
    _In_ int Y, 窗口左上角垂直坐标位置
    _In_ int nWidth,窗口的宽度
    _In_ int nHeight,窗口的高度
    _In_opt_ HWND hWndParent,窗口的父窗口句柄
    _In_opt_ HMENU hMenu,窗口菜单句柄
    _In_opt_ HINSTANCE hInstance, 当前应用程序实例句柄
    _In_opt_ LPVOID lpParam 窗口创建时依附参数
); 创建成功返回窗口句柄

2、窗口创建过程
2.1系统根据传入的窗口类名称,在应用程序局部窗口类中查找,如果找执行2、如果没有找到执行3
2.2比较局部窗口类与创建时窗口传入的HINSTACE变量。如果发现相等,创建和注册的窗口类在同一模块,窗口返回如果
2.3在全局窗口类使用找到的窗口类的信息,创建窗口返回
2.3在系统类中查找,如果找到创建窗口返回否则创建窗口失败

if(找到窗口类){

   申请一大块内存,将窗口的数据信息(找到的窗口类信息和自身函数的参数信息)存入这块内存
        return 这块内存的句柄
       }else{
    return NULL
    }

3子窗口的创建过程
1、创建时要设置父窗口句柄
2、创建风格要增加 WS_CHILD | WS_VISIBLE

五、消息基础
1、消息的概率和作用
消息的组成(windows平台下)
/*
typedef struct tagMSG {
    HWND        hwnd;窗口句柄
    UINT        message;消息ID
    WPARAM      wParam;消息的两个参数(两个附带信息)
    LPARAM      lParam;消息的两个参数(两个附带信息)
    DWORD       time;消息产生的时间
    POINT       pt;消息产生时的鼠标位置
#ifdef _MAC
    DWORD       lPrivate;
#endif
} MSG

*/

消息的作用:
当系统通知窗口工作时,采用消息的方式派发(DispatchMessage(&msg))给每个窗口的窗口处理函数()
2、窗口处理函数(函数必须这样定义)
LRESULT CALLBACK WindowProc(
    HWND hwnd, //窗口句柄
    UINT uMsg, //消息ID
    WPARAM wParam,// 消息参数
    LPARAM lParam,消息参数
     )
当系统 通知窗口时,会调用窗口处理函数,同时将消息ID和消息参数传递给窗口处理函数
在窗口处理函数中,不处理的消息,使用缺省窗口处理函数。例如:DefWindowProc(微软提供的默认处理函数).
 
3、浅谈消息相关的函数
GetMessage(&msg); 获取本进程的消息
tMessageW(
    _Out_ LPMSG lpMsg, // 存放获取到的消息BUFF(获取到消息后,将消息的参数存放到MASG结构体中)
    _In_opt_ HWND hWnd,//窗口句柄(获取到hWnd所指定窗口消息)
    _In_ UINT wMsgFilterMin,//获取消息的最小ID (只能获取到有他们指定的消息范围内的消息,结果为0,表示没有限制范围)
    _In_ UINT wMsgFilterMax);//获取消息最大的ID

#ifdef UNICODE
#define GetMessage  GetMessageW
#else
#define GetMessage  GetMessageA
#endif // !UNICODE

TranslateMessage(&msg); 翻译消息,将按键消息,翻译成字符消息。
BOOL TranslateMessage(
   CONST MSG *lpMsg //要翻译的消息地址


检查消息是否是按键的消息,如果不是按键消息,不做任何处理,继续执行。
六、常见消息:消息ID
WM_DESTROY
产生时间:窗口被销毁时的消息
附带信息:wParam:为0;
     lParam:为0;
    一般用法:常用于在窗口被销毁之前。做相应的善后处理,例如资源、内存等
WM_SYSCOMMAND
产生时间:当点击窗口的最大化,最少和关闭等
附带信息:wParam:具体点击的位置,比如关闭SC_CLOSE等

    lParam:鼠标光标的位置 最终基本类型为long  4个字节    
               LOWORO(lParam) 水平位置 通过低两个字节做区分
    HIWOERD (lParam)//垂直位置 通过高两个字节做区分

一般用法:常在窗口关闭时,提示用户处理(弹出提示框)

WM_CREATE
产生时间:窗口创建成功但还没有未显示时
附带信息:wParam:为 0。
     lParam :为 CREATESTRUCT(的结构体)类型的指针    :这里传过来的时 指针(4个字节,lParam也是四个字节)但是 强转CreateWindowEx一一对应的形信息

    通过这个指针可以获取CeateWindowEX中全部的 12个参数的信息
    一般用法:常用于初始化窗口的参数、资源等等,包括创建子窗口
    
WM_SIZE
产生时间:在窗口的大少发生变化后

附带信息:wParam:窗口大少变化的原因
    lParam: 窗口变化后的大少
    LOWORD(lParam)//变化后的宽度
    HIWORD(lParam)//变化后的高度
一般用法:常用于窗口大少变化后,调整窗口内部各个部分的布局

WM_QUIT
产生时间:程序员发送 (不需要程序员处理的消息,不会进入自定义消息处理函数)
附带信息:wParam:PostQuitMessage函数传递的参数
    lParm:0
一般用法:用于结束消息循环,当GetMessage收到这个消息后返回FALSE,结束while处理,退出消息循环(当出现这个消息不会进入DispatchMessager()也就不会进入自定义的消息处理函数了)。
七 消息循环的原理
1、消息循环的阻塞
GetMessage -从系统获取消息,将消息 从系统中移除,阻塞函数,当系统无消息时,会等候下一条消息(对于CPU来说没有消息的时间时非常多的所以该方法效率低<光速查询>)

PeekMessage- 一查看的方式从系统获取消息(只是查询不拿取该方法会效率会比较高),可以不将消息从系统移除,非阻塞函数。当系统无消息时,返回FALSE,继续执行后续代码
PeekMessageW(
    _Out_ LPMSG lpMsg, //message information
    _In_opt_ HWND hWnd,//handle to window
    _In_ UINT wMsgFilterMin, //fist message
    _In_ UINT wMsgFilterMax, //last message
    _In_ UINT wRemoveMsg); // 移除标识
#ifdef UNICODE
#define PeekMessage  PeekMessageW
#else
#define PeekMessage
2、发送消息()
系统发消息 (系统时怎么造消息的)->PeekMessage()或者GetMessage()->TranlateMessage()->DispatchMessage() ->WndProc()
SendMessage() 发送消息,会等候消息处理的结果。 (打电话,接不接都可以看到结果)
PostMessage() 投递消息,消息发出后立即返回,不等候执行结果。 (类比邮政邮箱 投递了就走)

// 消息的制造工厂
BOOL SendMessage/ PostMessage(    //四个参数可以组成一个消息的前四个参数
            HWND hWnd, // 消息发送的目的窗口
            UINT Msg, // 消息ID
    WPARAM wParam, // 消息参数
    LPARAM lParam, // 消息参数

      )

3、消息的分类
系统消息:ID范围 0-0X03FF  (1024)
    系统定义好的消息(只负责一端 发送 或者 处理),可以在程序中直接使用。
用户自定义消息: 0X0400-0X7FFF(31743)
 由用户自定义,满足用户自己的需求,有用户自己发出消息,并响应处理

自定义消息宏:WM_USER + n   //WM_USER== 0X0400

八、Windows消息
1、消息队列概率
用于存放消息的队列
消息在队列中为先进先出
所有的窗口程序都具有消息队列
程序可以从队列中获取消息
2、消息队列分类(只要消息进队列肯定先进系统队列 )
系统消息队列-由 系统维护 的消息队列。存放系统产生的消息、比如鼠标、键盘等、、、
程序消息队列-属于每一个应用程序(线程<一般指主线程>)的消息队列。由应用程序(线程)维护。
为什么系统能发送个线程消息?
1、系统有一个机制:每一段时间会把消息转发给每个进程
系统为什么能找到每个程序给它发过去?
操作系统可以拿到nMsg.hwnd->保存窗口数据的内存-> hIns
PostMessage扔到系统队列。 PostMessage
3、消息和消息队列关系
1、当鼠标、键盘产生消息时,会将消息存放在系统消息队列
2、系统会根据存放的消息,找到对应程序消息队列
3、将消息投递到消息队列中
根据消息和消息队列之间使用关系,将消息分为两类:
队列消息- 消息的发送和获取,都是通过消息队列完成。
非队列消息-消息的发送和获取,是直接调用消息的窗口处理函数完成的

队列消息-消息发送后,首先放入消息队列,然后通过消息循环,从队列当中获取,在转发给程序队列
GetMessage 从消息队列中获取消息
PostMessage(进系统队列) 将消息投递到消息队列中去
常见队列消息:WM_PAIANT、 键盘、鼠标、定时器 (WM_QUIT必须进队列)
非队列消息-消息发送时,首先查找消息接收窗口的窗口处理函数,直接调用处理函数,完成消息
 SendMessage(不进入系统队列)-将直接发送给窗口处理函数,并等候处理结果
常见消息:WM_CTREATE(必须不能进队列)、WM_SIZE
4、深谈GetMessage原理
1、到本进程去的消息队列中捉消息,本进程没有会去跟系统要,系统没有会去找WM_PAINT这个消息看有没有窗口可以重新绘制的
5、WM_PAINT消息
产生时间:1、当窗口需要绘制的时候 2、当getMessage捉消息没有捉到且向系统中要也没有的时候才会产生这个消息
第一个WM_PAINT 消息是ShowWindow发的,后面WM_PAINT都是getMessage发的(就是空闲的时候)
附带消息:wParam:0
    lParam:0;
专职位用法:用于绘图

声明窗口无效区域:需要重新绘制的区域(当调用这个函数时候,显示屏消失了一瞬间,马上又重新画出来了,肉眼无法捕捉)
 BOOL InvalidateRect(<该函数还是只是重新绘制区域,但是不发WM_PAINT消息还是GetMessage发消息>
    HWND hWnd, //窗口句柄  
    CONST RECT* lpRect //区域的矩形坐标  RECT是结构体 NULL的话,整个窗口都需要重新画一遍,
    BOOL bErase // 重绘前是否先擦除
    )

绘图编程过程
如果你想绘制一个图形:消息处理步骤 要在WM_PAINT消息下画;
1> 开始绘制
HDC BeginPaint(
    HWND hwnd, //要绘制的窗口
    LPPAINTSTRUCT lpPaint // 绘制参数的BUFF(声明的结构图)    
   );返回绘图设备句柄HDC

2>正式绘图
如画字体
TextOut(hdc,100,100,"hello",5);  //(绘图设备句柄,X位置,y位置,需要画的图像,要画的长度)

3>结束绘图
BOOL EndPaint(
  HWND hWnd, //绘图窗口
  CONST PAINTSTRUCT *lpPaint // 绘图参数的指针BeginPaint返回
 )

/*
以上绘图的代码,必须放在处理WM_PAINT 消息调用
画图的代码不能在其他消息中写

*/


键盘消息
WM_KEYDOWN - 按键按下时产生
WM_KEYUP - 按键被放开时产生
WM_SYSKEYDOWN - 系统按下时产生 比如 ALT 、F10
WM_SYSKEYUP -系统键放开产生

附带信息:
 WPARAM - 按键Virtual key(取到键码值就能知道那个键盘放开和按下了)
LPARAM - 按键的参数,例如按下的次数
字符消息
WM_CHAR与键盘息息相关
TranslateMessage在在转换WM_KEYDOWN消息时,对于可见字符可以产生WM_CHAR,不可见字符无此消息
TranslateMessage(&sMsg){  //只翻译部分消息,翻译可见字符,绝大部分是不翻译的
if(nMsg.message != WM_KEYDOWN
    return ....;
 根据nMsg.wParam(键码值)可以获知那个按键被按下
if(不可见字符的按键)
    retrun ...; 不翻译
查看CapsLock大小写是否打开
if(打开){
    PostMessage(nMsg.hwnd,WM_CHAR,65,..); //A
}else{
PostMessage(nMsg.hwnd,WM_CHAR,97,..); //a
}

}
附带信息:
WPARAM -输入的字符的ASCII字符编码值
LPARAM-按键相关的参数
十、鼠标消息
1、鼠表消息分类
2、鼠标基本消息
wPARAM:其他按键的状态,例如 Ctrl/Shit等
lPARAM:鼠标的位置,窗口客户区坐标系
     LOWORD X 坐标位置
     HIWORD Y坐标位置
一般情况下鼠标按下/ 抬起成对出现,在鼠标移动过程中,会根据移动速度产生一系列的WM_MOUSEMOVE
 


WM_LBUTTONDOWN-鼠标左键按下


WM_LBUTTONUP-鼠标左键抬起
WM_RBUTTONDOWN -鼠标右键按下
WM_RBUTTONUP-鼠标右键抬起
WM_MOUSEMOVE -鼠标移动消息
3、鼠标双击消息
wPARAM:其他按键的状态,例如 Ctrl/Shit等
lPARAM:鼠标的位置,窗口客户区坐标系
     LOWORD X 坐标位置
     HIWORD Y坐标位置

WM_LBUTTONDBLCLK-鼠标左键双击
消息产生的顺序
以左键双击为例子:
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDBLCLK
WM_LBUTTONUP
使用时需要在注册窗口类的时候添加CS_DBLCLKS风格


WM_RBUTTONDBLCLK-鼠标右件双击
4、鼠标滚轮消息
WM_MOUSEWHEEL 鼠标滚轮消息
wPARAM:
  LOWORD-其他按键的状态
  HIWORD-滚轮的偏移量,通过正负值表示滚轮方向
    正:向前滚动  负:向后滚动

lPARAM:鼠标的位置,屏幕坐标系
     LOWORD X 坐标位置
     HIWORD Y坐标位置
使用通过偏移量,获取滚动的方向和距离

十、
定时器消息
定时器消息的介绍
产生时间:
在程序中创建定时器,当到达时间间隔时,定时器(时钟时间到了就会给)会向程序(GetMessage)发送一个WM_TIME消息。
定时器的精度是毫秒(比如枪的精度很高),但是精确度很低(你用的时候确是打不准,因为GetMessage非常忙碌的所以会定时器并不准不准),例如设置时间间隔为1000ms,但是会在非1000ms到达消息

如果GetMessage一直有消息可以捉那么定时器估计不会发出WM_TIMER理论上是这样,但是cpu的速度很快所以还是能捉取到的但是会影响到准度,不准归不准误差不会很大
(不会达到一秒,只是几毫米的误差)
附带消息:
wPARAM:定时器ID(到时间的定时器ID)
lPARAM:定时器处理函数的指针(通常不用)
使用:只要你要干的事情是周期性的并且对时间大要求并不是很高的可以用定时器

创建销毁定时器:
埋定时器函数
 UINT_PTR SetTimer(
       HWND hWnd, // 定时器窗口句柄
       UINT_PTR nIDEvent, //定时器ID
       UINT uElapse, // 时间间隔
       TIMERPROC lpTiemrFunc, // 定时器处理函数指针(一般不适用,为NULL)
   ) ; 创建成功,返回非0。

销毁定时器:
 BOOL KilTimer(
     HWND hWnd,// 定时器窗口句柄
     UINT_PTR uIDEvent, //定时器ID
    );

十一、菜单资源
1、菜单分类 (容器的作用) 里面的有菜单项目

1.窗口的顶层菜单(文件,编辑,格式,查看)
2.弹出式菜单(右键菜单)
3.系统菜单(如,最大化、最小化 在顶部 )
HMENU类型表示菜单句柄作用是找到一块内存,存放菜单的数据信息,ID表示菜单项目

2、资源相关
资源脚本文件:*.rc文件 ,描述资源文件
编译器:RC.EXE
CL.EXE
.c/.cpp ---->.obj | LINK>EXE
RC.EXE  | ------>.exe
.rc------>.res |
3、资源使用
如何添加资源文件夹: 选中项目 右键  添加   新建项  资源 选择 资源文件.rc  就可以添加了 资源文件夹了
如何添加菜单? 在资源视图中,右键  添加资源 选择你要 的资源类型  
添加菜单资源:Menu菜单资源文件夹    菜单选项中的分割线如何做? 命名:无法命名 直接空格键(当作命名),那么右键就可以属性 选择 分割线 设置为TURE  
菜单资源只有3项  第一种:点击后 弹出下拉菜单(不需要设置菜单ID),第二种:分割线 (不需要ID) 第三种:能干具体的事情
产生资源ID  资源ID都在 resource.h中  有需要看到可以直接到头文件看
加载菜单资源(三种方法)
 1> 注册窗口类时设置菜单
wc.lpszMenuName = (char *)IDR_MENU1; // 不能直接写ID名  把数字型强转成为字符串类型
  2>创建窗口传参设置菜单
加载菜单资源:该函数在本进程中
HMENU LoadMenu(
    HINSTANCE  hInstance, //handle to module(当前程序实例句柄)能找到本进程所在的内存
    LPCTSTR lpMenuName; // menu name or resource identifier
   );
HWND hWnd = CreateWindowEx(0, "Main", "物质阵", WS_OVERLAPPEDWINDOW, 300, 200, 800, 600, NULL, 需要菜单句柄(LoadMenu), histance, NULL);

    ShowWindow(hWnd, SW_SHOW);
 3>在主窗口WM_CREATE消息中利用SetMeun函数设置菜单
 HINSTANCE g_histance ; 拿到当前实例句柄
    HMENU hMenu = LoadMenu(g_histance,(char*)IDR_MENU1);
    SetMenu(hWnd, hMenu);

4、
产生时间:点击了有ID的菜单项会发出消息 :命令消息处理(点击了有ID的菜单项会发出消息)
WM_COMMAND
附带信息:
wPARAM:
 HIWORD - 对于菜单为0;为0就是没有啥用
 LOWORD - 菜单项的ID; 被鼠标点击的那个参数

lPARAM- 对于菜单为0; 为0就是没有啥用
5、菜单项状态
6、上下文菜单

十一、
一、图标资源
.ico为后缀的图片
1、加载资源 (这个不需要写代码,操作可视化界面) 步骤:1、选择项目 右键 选择.rc文件  建资源文件夹 2、在.rc文件中添加资源(在资源视图中选择)3.产生画布
 注意图标的大少,一个图标文件中,可以有多个不同的图标 (图标的数据需要在硬盘上)
2、加载
HICON LoadIcon(

    HINSTANCE hinstance, //handle to application instance 当前实例句柄
    LPCTSTR lpIconName , //name string or resouce identifier

 ) ;成功返回HICON句柄
3、设置
在注册窗口类
wc.hIcon = LoadIcon(histance,(char*)ID_ICON);
二、光标资源
添加光标的资源
 光标的大少是默认 32 X 32 像素,每个光标有HotSpot,是当前鼠标的热点 ,在资源视图中添加作用点
加载资源
 HCUSOR LoadCursor(
    HINSTANCE hInstance, //handle to applicate instance
    LPCTSTR lPCurosName , //name or resoure identifier
   ); hInstance -可以为NULL ,获取系统默认Cursor
设置资源
 在注册窗口时,设置光标
wc.hCursor = LoadCursor(histance,(char*)IDC_CURSOR1);//NULL  //默认光标 仅在客户区起作用,标题栏是不起作用的,一旦设置只能按照这个来了
使用SetCursors设置光标(能灵活设置光标动态设置光标)

HCURSOR SetCursor(  // 必须放在 消息处理函数中 WM_SETCURSOR:
    HCURSOR hCursor // handle to cursor
          );

WM_SETCURSOR 消息参数 要与WM_MOUSEMOVE区分开来,他们可以可能同时出现
产生时间:只要光标有移动的情况
 wPARAM -当前使用的光标句柄
lPARAM - LOWORD 当前区域的代码 (Hit Test code)低亮字节可以告诉你当前鼠标在哪里动
  HTCLIENT (htclient客户区)/ HTCAPTION(htcaption标题栏区)...
 HIWORD 当前鼠标消息ID

使用:没有一般用法,只有专职用法就是改光标

三、字符串资源(实现中英文俩版本)
使用步骤:
1、添加字符串资源
 添加字符串表(为什么是表,因为应该不止一个字符串),在表中增加字符串
2、字符串资源的使用
 int LoadString(
   HINSTANCE hInstance , // handle to resource module
   UINT uID, // 字符串ID
   LPTSR lPBuff ,字符串BUFF (接收字符串的缓冲区)
   int nBufferMax, // 字符串BUFF长度  (接受字符串的长度)
  );成功返回字符串长度,失败 0;

char StringBuff[256] = { 0 };
LoadString(histance, IDS_STRING_WND, StringBuff,256);
HWND hWnd = CreateWindowEx(0, "Main", StringBuff, WS_OVERLAPPEDWINDOW, 300, 200, 800, 600, NULL, NULL, histance, NULL);
四:加速键资源 (Ctrl v,Ctrl c,这些都是加速键(怎么会成为可视化资源(可视化界面)呢?)
但凡可以加速键或者快捷键的一般在菜单里都会有对应的菜单项(一般绑定使用但是原理上并没有关系)
使用步骤:
1、添加资源加速键表(Acclerator),增加命令ID对应的加速度。(一个ID,应以一个加速度键,让加速键的ID与菜单项(ID_NEW)的ID相同就可以绑定加速键(ID_NEW)了)

ID               Modifier (选择搭配的按键)                               Key (键盘当中的所有按键可以选择或者自己写)         Type
ID_NEW       Alt/Ctrl/Shift/Ctrl + Alt  / Ctrl+ Alt+Shift         M/N /P                                                                VIRTKEY
ID_NEW       Alt/Ctrl/Shift/Ctrl + Alt  / Ctrl+ Alt+Shift         M/N /P                                                                VIRTKEY
3、使用

 加载加速度  表  (用表因为不止一个)

HACCL LoadAccelerators(
    HINSTANCE hInstance, // handle to moudule
    LPCSTR lpTableName, //accelerator table name

  );返回的加速键表句柄

翻译()加速度表
int TranslateAcclerator(
    
    HWND hWnd, // 处理消息的窗口句柄
    HACCEL  hAccTable , // 加速键表句柄
     LPMSG   lpMsg // 消息

);如果是加速键,返回非零
int TranslateAcclerator(hWnd,hAccel,&nMsg){
   第一步判断消息 时候键盘按下
 if(nMsg.message != WM_KEYDOWN)
    return 0;
     如果有键盘按下
  根据nMsg.wParam(键码值),知道那些按键按下(CTRL + M)
  拿着(CTRL + M)到hAccel(加速表)比对 查找  Modifier (选择搭配的按键)                               Key (键盘当中的所有按键可以选择或者自己写)        
                                                                       Alt/Ctrl/Shift/Ctrl + Alt  / Ctrl+ Alt+Shift         M/N /P                                                               
                                                                       Alt/Ctrl/Shift/Ctrl + Alt  / Ctrl+ Alt+Shift         M/N /P                         

if(没有找到)
retrun 0;
   if(找到){
  要翻译才能返回非零
 SendMessage(hWnd,WM_COMMAND,LOWROD(wParam)<ID_NEW > | HIWORD(wParam),lPAram(无用))    

 
   return 非零(需要翻译)
   }
}

按加速键和菜单项都会产生WM_COMMAND ,

放在消息循环中
GetMessage的后面 (TranslateAccelerator)放在TranslateMessage之前

十二、绘图编程(必须在WM_PAINT消息处理一个画图)
1、绘图基础
绘图设备 DC(device Context)(类比画家),绘图上下文/绘图描述表
HDC hdc;
HDC hdc = BeginPaint(hWnd,...) //捉住绘图设备
画字符串:
TextOut(hdc,100,100,"hello",....);画字符串函数(画家,在x方向100,在y方向100,“画hello”,....);
. . . . .调用各种各样的函数帮你去画

画完后结束绘图
EndPaint(hWnd,...));// 参数跟 BeginPaint()一样

HDC - DC句柄,表示绘图设备 Windows graphics device interface(Win32提供的绘图API)
颜色
 计算机使用 红、绿、蓝色
    R- 0~ 255
    G- 0~255
    B ~0~255
每一个点颜色是2个字节保存0 - 2^24-1  16M
  16位:5、5、6 (过去)
  32位:8、8、8(跟颜色是没有关系的) 绘图或者透明度(二维绘图不涉及透明度)
2、基本绘图绘制
颜色的使用
新的颜色数据类型
COLORREF - 实际上 DWORD(无符号长整形)4个字节
例如:COLORREF nColor = 0;(这样就能保存一个颜色值了,赋值为零,那么红绿蓝都没有,是黑色)
赋值使用 RGB 宏
如:
    nColor = RGB(0,0,255);
   获取RGB值
  GetRValue/ GetGValue/GetBValue
例如:BYTE nRed = GetRValue(nColor);


绘制一个点
SetPixel 设置指定点的颜色 (将某个点设置颜色相当于画一个点)
  COLORREF  SetPixel(
     HDC hdc , // DC 句柄
     int X , ///X轴坐标
     int Y , // Y轴坐标
   COLORREF  crColor // 设置颜色
    );返回点原来的颜色

PAINTSTRUCT ps = { 0 };  //画笔结构体
    HDC hdc = BeginPaint(hWnd, &ps);
    // SetPixel(hdc,100,100,RGB(255,0,0));
    2.//for(int i = 0; i < 256 ; i++){
    //SetPixel(hdc,100,100,RGB(255,0,0));
                }

        for (int i = 0; i < 256; i++)
    {
        for (int j = 0; j < 256;j++) {
                //SetPixel(hdc, i, j, RGB(255, 0, 0));  
            SetPixel(hdc, i, j, RGB(255, i, j)); //这样画太慢了,肉眼能看到画的过程
        }
    }
    EndPaint(hWnd,&ps);
线的使用 :画线(画直线、画弧线)
MoveToEx-指名   窗口当前点(每一个窗口都会有,默认在(0,0)点MoveToEx的作用就是更改(0,0点),让它(MoveToEx指定的当前点)变成窗口当前点)
LineTo-从窗口当前点(MoveToEx点)到(LineTo指定点)指定点绘制一条直线  (两个函数搭配一起用才能画一条线 )
当前点:上次绘制图时的最后一点,初始点为(0,0)点 但是如果调用过LineTo(x,y)那么下一LineTo的起始点就(x,y)
如果想接着上一个点再画直线那么直接LineTo()即可,不需要调用MoveToEx()指定当前点

void  DrawLine(hWnd){

PAINTSTRUCT ps1 = { 0 };
    HDC hdc = BeginPaint(hwnd,&ps1);
    MoveToEx(hdc,200,200,NULL); //第三个参数是 之前的位置  MoveTo()是指定当前点是100,100
    LineTo(hdc,400,400);                                                          LineTo(); 是指名从(100,100点)到(300,300)LineTo()也有指定窗口的功能,而且会调用最后后调用MoveToEx()
    EndPaint(hwnd, &ps1);
}

封闭图形(第一能封闭,第二还能用画刷填充):能够用画刷填充的图型
Rectangle(直角矩形) / Ellipse(椭圆)

Void DrawRect(HWND hwnd){
    PAINTSTRUCT ps = { 0 };
HDC hdc =  BeginPaint(hwnd,&ps);    // HDC 是很穷的画家(只有黑色的铅笔)
  //  Rectangle(hdc,100,100,300,300);
    Ellipse(hdc,100,100,300,300);//虽然是画圆但是圆形也是从矩形(内切圆)切割过来的 ,这些绘图都很粗糙,边框会有锯齿状,GDI能够把这些处理好
    EndPaint(hwnd,&ps);

}

3、GDI(Graphics Device Inteface)绘图对象  GDI是比较有钱的画家(能解决毛刺那些)
GDI还有画笔,画刷、字体、位图、调色板等
1、画笔(画图形)
画笔的作用:
线的颜色、线型(实际生活中就是实线,但是计算机中还有虚线画笔、点线画笔、)、线粗;
HPEN -画笔句柄

1、画笔的使用
1.创建画笔
HEPN CreatePen(
         int fnPenStyle, //画笔的样式(实心笔、虚线笔)参数:PS_SOILD- 实心画笔,可以支持多个像素宽其他线型只能是一个像素宽
         int nWidth  ,// 画笔的粗细
         COLORREF crColor // 画笔的颜色
    );创建成功返回句柄

PS_SOILD- 实心画笔,可以支持多个像素宽其他线型只能是一个像素宽,画笔粗细可以随意但是其他的不允许
2.将HPEN创建的画笔应用到DC中用HGDIOBJ()
HGDIOBJ SelectObject(  // 败家的函数(实际是交换把之前DC的铅笔换成现在的DC(HPEN)),创建出来会给到DC
      HDC hdc, //画家的句柄,绘图设备句柄
       HGDIOBJ hgdiobj // GDI绘图 对象句柄,画笔句柄,注意HGDIOBJ 不是HPEN类型但是 包含HPEN
   ); 返回原来的GDI绘图对象(当我们把新创建的HPEN给到DC后,会返回原来DC的黑色铅笔)句柄

2.1注意保存原来DC(铅笔)当中画笔(要接收一下)
3.绘图(拿着彩色的铅笔去画画了)
4.取出DC(彩色的铅笔<HPEN>)中的画笔
将原来的画笔,使用SelectObject函数,放入到设备DC(原来那个黑色铅笔还回去,所以前面为什么要接收HPEN)中,就会将我们创建的画笔取出来。
5.释放画笔(彩色那个铅笔)
BOOL DeletObject(
     HGDIOBJ hObject  // GDI绘图对象句柄,画笔句柄
  );
只能删除不被DC使用的画笔,所以在释放前,必须将画笔从DC中取出

2、画刷(填充颜色东西)但是只给封闭图像填充东西
画刷相关-给封闭图型的填充的颜色、图案
   HBRUSH - 画刷句柄
 画刷的使用
2.1 创建画刷
   CreateSolidBrush - 创建实心画刷(给封闭图像填充单一的颜色)
  HBRUSH   CreateSolidBrush(GRB( , , ));
 
   CreateHatchBrush(HS_CROSS)- 创建纹理画刷经维线(HS_CROSS,RGB(255,0,0))
2.2 将画刷应用到DC中
   SelectObject
2.3 绘图
2.4将画刷从DC中取出来
      SelectObject
2.5删除画刷
      DeleteObject

  /*
     1、创建画刷
     2、把画笔给画家,并接受收画家原来的黑色铅笔
     3、画画
     4、(画完后)把红色画刷的铅笔要回来,把黑色的铅笔还给画家
     5、释放灰色画笔
     */
    HBRUSH hBrush =  CreateSolidBrush(RGB(255,0,0));
     HGDIOBJ hOldBrush =  SelectObject(hdc,hBrush); //原来也有画刷,为什么看不到 ? 有一把白颜色的画刷 可以在wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+3)把背景设置成黑色的那么画刷默认是黑色的
     DrawEllipse(hdc);
     DeleteObject(hBrush);
     EndPaint(hWnd,&ps);
GDI绘图对象- 画刷(如:透明画刷)
其他
  可以使用GetStockObject()函数获取系统维护的画刷、画笔等
  如果不使用画刷填充,需要使用NULL_BRUSH(透明画刷)参数,获取不填充的画刷。
  GetStockObject返回的画刷不需要DeleteObject

3、GDI绘图对象-位图
位图相关
   光栅图形 (bmp纯真的光栅图像)- 记录图像中每一点的颜色等信息(在计算机平时看到的)。
   矢量图形 - 记录图像算法(一边用于科学计算,实验室分析这些光谱特色领域)、绘图指令等
    HBITMAP - 位图句柄(保存的是位图图像的每一个颜色值)
位图的使用(位图也是GDI的一种,同时也是资源的一种,位图是兼顾资源和绘图对象的一种)
1、在资源中添加位图资源(不需要写代码,可以画界面)
2、在资源中加载位图LoadBitmap
3、创建一个于当前DC(BeginPaint捉回来的那个<在当前窗口画>)相匹配的DC(内存DC<在内存虚拟中画>)
  HDC CreateCompatibleDC(
       HDC hdc // 当前DC句柄,可以为NULL(使用屏幕DC)
     );返回创建好的内存DC句柄
CreateCompatibleDC:两个作用1、创建内存DC  2、内存中构建虚拟DC
4、将位图放入匹配的DC中 SelectObject
5、
成像:
1:1   好比单反相机画在底板上
BOOL bitBlt(
    HDC hdcDest, // 目的DC
    int nXDest,       // 目的左上X坐标
               int  nYDest,   // 目的左上角Y坐标
    int nHeight,   //目的高度
               int nWidth,       // 目的宽度
    HDC hdcSrc      // 源DC
    int nXSrc,         // 源左上X坐标
    int nYSrc,        // 源左上Y坐标
    DWORD dwRop  // 成像方法  SRCCOYPY(作用:在成像过程中不更改任何点颜色)
                    )

缩放成像
BOOL  StretchBIt (
               HDC  hdcDest         //目的DC
    int nXOriginDest    // 目的左上X坐标
    int nYOriginDest    // 目的左上角Y坐标
    int nWidthDest     //  // 目的宽度
    int nHeight,   //目的高度
    HDC hdcSrc    // 源DC
    int nXOriginSrc    // 源左上X坐标
    int nYOriginSrc //源左上Y坐标
               int nWithSrc   //源DC宽
    int nHeightSrc  //源DC 高
                DWORD dwRop
         )

6、取出位图(要回位图)
SelectObject
7、释放位图
DeleteObject
8释放匹配的DC
DeleteDC
十三、文本绘制-绘制字符串
 文字的绘制
TextOut 将文字绘制在* 指定*的坐标位置,win32中绘图能力最差的一个(不能换行,没有对其方式)

 int DrawText(  //比较强大,不是在指定位置画,而是在矩形位置画(在矩形位置画的话可以换行可以对齐,左右靠上下不同位置画)
         HDC hDc, // DC句柄
         LPCTSTR lpString // 字符串
         int nCount , //字符串数量
        LPRECT lpRect ,绘制文字的矩形框(指定范围,DrawText只能在这里画)LP通常是指针 Rect(叫矩形范围结构体<成员有上下左右>)是一个结构体
        UINT uFormat, //绘制的方式 很多方式
      )
文字
颜色和背景
文字颜色:SetTextColor
文字背景色:SetBkColor
文字背景模式:SetBkMode(OPAUE/ TRANSPARENT)
字体:
GDI的一种
字体相关:Window常用的字体为TrueType(机器当中得有这个字体C盘Window Fonts)格式的字体文件
字体名 - 标识字体类  C盘 Fonts双击打开第一行才能看到正确的:字体名字
HFONT -字体句柄可以找到一块内存那块内存存放这字体的信息(每一个字的外观信息)

字体使用
1、创建字体
HFONT CreateFont(
        int nHeight ;
       int nWidth;
       int nEscapement ; 字符串的倾斜角度(以0.1度为单位)(不是斜体)  倾斜角度:是站在都很直如楼梯成角度,斜体:是不在同一水平线上,同在一个水平线上斜体倾斜   如这样 / / / / 同一水平线上
       int nOrientation; 字符旋转角度 :写不写看不到任何现象 以X轴为轴心向内或者向外旋转 三维
       int fnWeitght; //字体的粗细
       DWORD fdwItalic // 斜体  斜体为 1
        DWORD bUnderline,   下划线为 1
       DWORD bStrikeOut,  删除线为1  不要为0
      DWORD iCharSet,   GB323涵盖所有的字符集
      DWORD iOutPrecision,  输出精度
      DWORD iClipPrecision,  剪切精度
       DWORD iQuality,  输出质量
       DWORD iPitchAndFamily,  匹配字体
        LPCWSTR pszFaceName,字体名字 看机器当中得有这个字体C盘Window Fonts C盘 Fonts双击打开第一行才能看到正确的:字体名字
     )
2、应用字体到DC
3、绘制文字
 DrawText / TextOut
4、取出字体
SelectObject
5、删除字体
DeleteObject

  十四、Windows的内核开发
对话框也是一种窗口(大到窗口界面,少到一个弹框都可以叫窗口)对话框是一种特殊(特殊在原理不一样)窗口
处理消息的原理不一样 :
普通窗口:自定义函数调用缺省处理函数也可以叫
WndProc(...)   // 这是自定义函数
{
.......
 DefWindowProc(...);//  只是缺省函数(或者默认函数)  我们不想处理的函数都交给它

}

对话框处理消息:和普通窗口刚好相反 缺省函数(不是DefWindowsProc而是其他的系统提供的)调用 自定义函数
缺省函数(. . .){
 .  . . .
 自定义函数(. . . .);
}

对话框: 普通窗口是自己注册的(自己实现窗口处理函数WndProc),对话框窗口是系统注册的(系统提供缺省处理函数(这个产生为了我们能参与进来会调用自定义的函数)), 谁注册谁实现窗口处理函数,那如何定义这个函数呢?

对话框窗口处理函数(并非真正的对话框窗口处理函数这时自定义的),系统提供的缺省函数才是默认的窗口处理函数,缺省函数要调用自定义的函数
函数原型 :
INT CALLBACK DialogProc(
                  HWND hwndDlg, //窗口句柄
                 UINT uMsg,//消息ID
     WPARAM wParam,  //消息参数
                 LPARAM lParam // 消息参数
                );
返回TRUE -缺省函数不需要处理(自己处理)
返回FALSE-  交给缺省函数处理(正常的对话框处理函数处理)
不需要调用缺省对话框处理函数    

分类:
模式对话框-当对话框显示时,会禁止其他窗口和用户交换操作。
无(非)模式对话框-当对话框显示后,其他窗口还可以于用户交换操作
对话框基本使用
1、对话框窗口处理函数
2、注册窗口类(不使用)
3、创建对话框
4、对话框的关闭
一·、对话框原理
对话框时操作系统注册好的
二、模式对话框
创建模式对话框
1、添加对话框资源(注意是资源所以不用代码  选中项目 >右键>添加 >新建项> (侧面的)资源 > 选择资源文件 新建了xxx.rc 文件夹  如何选中 xxx.rc文件(在资源视图那边)> 添加资源 )

(创建)显示一个模式对话框
INT DialogBox
    (
     HINSTANCE hInstance, //应用程序实例句柄
      LPCTSTR lpTemplate, // 对话框资源ID(也是资源的一种)
       HWND  hWndParant, // 对话框父窗口
       DLGPROC lpDialogFunc ,// 自定义函数
    );

销毁模式对话框
 DialogBox 是一个阻塞函数(对话框一出现就会阻塞),只有当对话框关闭后,才会返回,继续再继续执行后面的代码

返回值是通过EndDialog设置

模式对话框的关闭:
BOOL EndDialog(HWND  hDlg, // 关闭的对话框窗口句柄
                              INT_PTR nResult //关闭的返回值
                                   );
关闭模式对话框,只能使用EndDialog,不能使用DestryWindow等函数(因为只能把对话框销毁,但是没有解除阻塞)
nResult是DialogBox函数退出时的返回值

对话框消息跟普通消息都一样,只有这个不一样
WM_INITDIALOG- 对话框创建之后显示之前,通知对话框处理函数,可以完成自己的初始相关的操作

三、无模式对话框的创建
HWND CreateDialog(
    HINSTANCE hInstance, // 应用程序实例句柄
    LPCTSR lpTemplate, //对话框资源ID
                HWND hWndParant // 父窗口
                DLGPORC lpDialogFunc //自定义函数
       )  ;
非阻塞函数,创建成功返回窗口句柄,需要使用ShowWindow()函数显示对话框

无模式对话框的关闭

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

四、对话框总结

十四、静态库(之前讲的是窗口程序)

1、静态库的特点
运行不存在(没有入口,无法执行,最终生成的文件是无法进入内存的,死在硬盘里面,等在调用程序调用它)

静态库的源码被链接到调用程序中(哪一个程序调用静态库捉取一份源代码)

目标程序的归档(静态库里封装了一堆的函数,封装好了归档好了,谁想调用到我这里捉,归档的作用)

2、C语言静态库
C语言做静态库
C静态库的创建
1)创建静态库
项目  (选择解决方案右键  添加项目 选择静态库)
2)添加库程序(封装函数一定要写在.C文件里),源文件使用C文件。(封装C函数,写在.C文件里)(添加文件跟平时一样添加只是把后缀名要写.c)
C静态库的使用
1、要使用它,因为不是启动项 使用要先编译链接但是不是直接 按启动
编译链接的方法: 选中要编译链接的 项目(Clib项目)右键  选择Bulid(中文:生成)完成的是编译和链接的工作没有运行
注:编译完如何看编译链接生成的项目呢?  在解决方案的Debug中 (一个解决方案下的所有项目的最终文件都在解决方案的Debug下)
注: 通常说的源码时二进制的,源代码才是说高级语言比如C 、C++

C语言调用静态库
2、新建一个控制台或者 桌面程序 让(C静态库)有启动的项目(入口)设置为启动项
启动项:启动项要也是C文件,C文件调用C动态库。在Windows下的C语言,如果调用函数没有函数的声明的话,编译器是不会给你报错的,
也就是说用C语言调用C库不需要声明 。
 试验一下调用项目能不能编译通过(只编译不运行<不添加头文件情况下>)  点击编译器上工具栏上的 Build(生成)  > Complie(编译)编译能通过,
C语言调用C库头文件都不用包含(为什么C语言调用不用声明函数头文件,而C++就要头文件呢? 答:因为C没有换名一说,而C++在编译的情况下会换名)
 试验一下能不能链接能不能通过(只编译不运行<不添加头文件情况下>)但是没有链接的步骤 但是如果链接不报错那么就能跑起来,否则跑不起来就是链接问题,因为前面已经试验编译能通过了  >所以C语言调用C库<如果不添加头文件的话>链接器不知道去哪里找 源码  
如何解决链接器找不到C库的?                                                                               
答:库路径设置:可以使用pragma关键字设置(告诉连接器去哪里找源码)
#pragma comment(lib,"../Debug/clib.lib");   点点 . . 是退一个目录的意思    那为什么是进入Debug中呢? 因为解决方案中的项目生成的项目都在解决方案的Debug中(C库生成的源码也在Debug)
这里要注意 在调用C静态库的时候要要找的是项目生成在Debug中生成的xxx.lib ,而不是要找xxx.c源文件项目 (用C语言和C++语言做的静态库本身没有什么区别)


3、C++静态库
C++语言做静态库
选择解决方案  右键  选择 添加  > 选择新建项目 > 选择静态库lib
选中项目 右键  添加C++文件  写C++函数 写完后 选择项目 静态库项目 右键 (或者Complie)bulid (生成) 可以在Debug中看到到生成的源码(二进制)xxx.lib

C++语言调用静态库(静态库没有入口需要别人调用)
新建一个控制台或者桌面程序(项目)调用C++静态库

调用 CPPlib_add(4,5);   // 但是C++调用编译器会报错,编译也不通过,需要函数声明(一般声明函数都会包含头文件)声明函数还是会报错证明链接有问题
          CPPlib_add(5,4);

如何解决链接器找不到C++库的?                                                                               
答:库路径设置:可以使用pragma关键字设置(告诉连接器去哪里找源码)
#pragma comment(lib,"../Debug/clib.lib");   点点 . . 是退一个目录的意思    那为什么是进入Debug中呢? 因为解决方案中的项目生成的项目都在解决方案的Debug中(C库生成的源码也在Debug)
这里要注意 在调用C静态库的时候要要找的是项目生成在Debug中生成的xxx.lib ,而不是要找xxx.c源文件项目 (用C语言和C++语言做的静态库本身没有什么区别)

C语言库和C++语言库有什么区别?

答:本身没有什么区别,只是在调用的时候 C++库需要函数声明
C语言调用C++库肯定没有问题
但是C++语言调用C库有没有问题?  需要声明C库的函数
用关键字:extern "C" 加上 函数声明
extern “C”int Clib_add(int add1,int add2);
extern “C”int Clib_sub(int add1,int add2);
#pragma comment(lib,"../Debug/Clib.lib")

十五、动态库
1、动态库的特点:(自己不能单独进入内存,依附别人进入内存,进入之后就独立运行了<沾上进去后抛弃>)
1)运行时独立存在(意思时:依附这别的程序运行起来之后,它就独立了):动态库能独立运行 有入口函数,那么就是说动态库dll文件能进内存。
要依附这你的程序,只要你调用它会依附这你的代码进入内存,但是要注意当动态库依附你的代码运行起来了,进入内存,然后它有独立了
有自己的内存空间
2)源码不会链接到执行程序(如果别的程序调用动态库里的一个函数的话,别的程序不会从动态库中捉取源码,只会记录调用动态库中的地址)
3)使用时加载(想调用使用动态库的函数必须使用动态库执行(进入内存))如何让动态库进入内存运行?
与静态库的比较:
1)由于静态库是  将 代码嵌入 到使用程序中,多个程序使用时,会有多份代码、所以代码体积会增大。动态库的代码只需要存在一份,所以代码体积少
2)静态库发生变化后,新的代码需要重新链接嵌入到执行程序中。
动态库发生变化后,如果函数的定义(或地址)未发生变化。
其他使用DLL的程序不需要重新链接

2、动态库创建(制作一个动态库)
1 创建一个动态库项目
2 添加库程序  (写完动态库函数后记得编译一下)
3 库程序导出 - 提供给使用者库中的函数等信息。(不倒出来无法使用)
如何导出两种方式导出:(如果导出来成功后会有一个动态库对应的 同名的xxx.lib但是导出函数 它和静态库,是不同的东西)
  1种导出)声明导出:使用 _declspec(dllexport)你想把那个函数导出来酒吧那个函数前面加速 _declspec(dllexport)
注意:动态库编译链接后把函数导(严谨来说不是把函数导出来而是把函数的地址导出来,那把地址导到哪里去呢?会把地址导到xxx.dll的头文件中xxx.dll有两部分一部分是文件头(函数地址)一部分是正文(就是封装好的函数))出后,xxx.dll会配套生成xxx.lib 也会有LIB文件,是作为动态库函数映射使用,与静态库不完全相同。
dll.存了地址(头文件)和函数(正文),那么配套生成的xxx.lib存放的是什么?答:存放的是 xxx.dll的函数名和对应的标号,和静态库存放的源码不一样只是文件的后缀一样
用声明导出导出来的都是换名之后的函数名对应的相对地址

  2种导出)模板定义文件.def(做动态库的时候一定要把动态库的地址导出来)选择 项目 右键  添加 新建项目 在代码(VS2019版本) -xxx.def(模块定义文件)
如何做? 在项目里增加一个.def文件在def里书写你要导出来的函数信息
def的写法:
  例如:LIBRARY DLLFunc(就写文件名不要写后缀,只写dll文件名就好) // 库
             LIBRARY CPPdll;  // LIBRARY创建时生成,自己添加动态库名即可。

            EXPORTS  // 库导出表(关键字)后面不用写东西

          DLL_Mul(函数名) @1 //导出的函数
          CPPdll_Sub @1 要隔开一个空格不然编译不过来  函数名和 @要隔开一个空格
           DLL_Mul(函数名)  @2 //导出的函数
          CPPdll_Mu1 @2
           DLL_Add(函数名) @3 //导出的函数
           DLL_xxx(函数名)  @ n //导出的函数
   //以上这种方式导出来的是没有换名的函数名和它对应的地址
  SUB mySub = (SUB)GetProcAddress(hDll, "CPPdll_sub"); //这种漂亮的函数名可以用,是因为用来模块定义文件.def导出函数名所以这样写
    cout << "mySub:" << mySub << endl;
    int sub = mySub(5,4);
    cout << "sub:" << sub <<endl;
    MUL myMul = (MUL)GetProcAddress(hDll,"CPPdll_mul");
    cout << "myMul:" << myMul << endl;
    int mul = myMul(5, 4);
    cout << "myMul:" << mul << endl;
}
3、动态库的使用
如何使用动态库?

第一种是隐式链接(怎么来的?为什么叫隐方链接 因为操作系统自己偷偷第负责使动态库执行)
  1)头文件和函数原型
       可以在函数原型的声明前,增加调用时导入用_declspec(dllimport)    导出时用_declspec(dellexport)
  例子:
#include <iostream>
using namespace std;
_declspec(dllimport) int CPPdll_add(int a, int b);
_declspec(dllimport) int CPPdll_sub(int a, int b);
_declspec(dllimport) int CPPdll_mul(int a, int b);

  2) 导入动态库的LIB文件
#pragma comment(lib,"../Debug/CPPdll.lib")

// 通知链接器(编译器进入xxx.lib<无论xxx.dll生成的xxx.lib还是静态库的xxx.lib>的时候它都不知道捉什么,只是看有源码就会捉源码,
有地址就捉地址,有编号就捉编号编译器进入lib文件是有什么捉什么)
到哪里 捉 编号<动态库是捉xxx.lib的编号,在静态库中是捉xxx.lib的源码只是写法一样而已>
(链接器不能到xxx.dll抓函数,只能在xxx.lib上捉)和捉CPPdll.dll文件名
(不加的话找不到动态库的的函数地址(因为动态库函数导出来的地址在生成的xx.lib文件中))

  3)在程序中使用函数
int sum =  CPPdll_add(5,4);
int sub = CPPdll_sub(5, 4);
int mul =  CPPdll_mul(5, 4);
 std:: cout << "sum = " << sum <<" sub = "<<sub<<" mul = "<<mul << endl;

  4)隐式链接的情况 ,dll 文件可以存放在路径:
    (1)与执行文件(exe文件)同一个目录下
    (2)当前工作目录(项目的里面不是解决方案里面)
    (3)Windows目录
    (4)Windows/System32目录
    (5)Windows/System
    (6) 环境变量PATH指定目录

第2种是 显示链接(程序自己负责写代码使动态库执行,所以明明白白的知道在调用所以叫显方链接)
1)定义 函数指针类型 typedef(C语言)只要定义了一个指针的函数类型  这个类型就可以定义变量
这个变量就能就接收一个函数的指针了。

#include <iostream>
using namespace std;
typedef int(*ADD)(int m, int n);
typedef int(*SUB)(int m, int n);
typedef int(*SUB)(int m, int n);


2) 加载动态库  (让动态库进入内存)
函数原型:
    HMODULE LoadLibary(
             LPCTSTR lpFileName                  // 动态库文件名或者全路径 。
        );返回Dll 实例句柄(HINSTANCE ==HMODULE 可以找到动态库内存和首地址)
                             // 什么时候写文件名? 答:当你的dll文件,和你的.exe文件在同一路径下,或者dell在某个系统目录下(Windows目录, Windows  / System32目录 ,Windows  / System)
                                                                  // 什么时候写路径名?  答:其他都要写路径名
      
例子:
HINSTANCE hDll= LoadLibrary("CPPdll.dll");
cout << "hDll:" << hDll << endl;


3)获取函数地址(绝对/真实地址)
     FARPROC  GetProcAddress(
    HMOUDLE hModule, // DLL句柄
    LPCSTR lpProcName, // 函数名称
        );成功返回函数地址

例子:
ADD myadd =(ADD) GetProcAddress(hDll,"?CPPdll_add@@YAHHH@Z");
ADD myadd =(ADD) GetProcAddress(hDll,"?CPPdll_add@@YAHHH@Z");
//因为导出都是用_declspec(dllexport)这种方式导出来的使用导致写函数名的时候要写换名后的奇怪的函数名
cout << "myAdd:" << myadd << endl;

4)使用函数
int sum = myadd(5,4);
cout << "sum:" <<sum<< endl;


5)卸载动态库
Bool FreeLibary(
    HMOUDLE hModule // DLL的实例句柄
 
   );

4、动态库封装类   (必须要把类导出去,是把动态库封装的各个类封装的函数相对导出去,一般不用 文件定义模块 的方式导出来,通常用声明导出)
在类名称前面增加_declspec(dllexport)定义,
例如:class  _declspec(dllexprot)   CMath{
             .....
      };  
但是这样定义不好因为这个动态库需要给两个角色使用的 对于使用动态库的人来说需要的是class _declspec(dllimport)定义,
_declspec(dllexprot)这是输出做动态库的人来说的这样定义

如果我想要做动态库的人和使用动态库的人就共用一个头文件怎么办

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

头文件的定义:
#pragma once
#ifndef _DLLCLASS_H   //if not define 的缩写 是宏定义的一种,
#define _DLLCLASS_H


#ifdef DLLCLASS_EXPORTS
#define EXT_CLASS _declspec(dllexprot) //DLL
#else
#define EXT_CLASS _declspec(dllimport) //使用者
#endif

//_declspec(dllexport) 加这个关键字导出来的是类里面两个成员函数的相对地址
class EXT_CLASS CMath {
public:

    int Add(int add1,int add2);
    int Sub(int sub1,int sub2);

};

#endif // !1

源文件的实现:
# include "ClassDll.h" //不能只写这个头文件,如果只是写这个相当于只是吧头文件拷贝过来那么头文件就是有这个#ifdef DLLCLASS_EXPORTS 那么动态库是输出
#define DLLCLASS_EXPORTS // 有这个定义那么宏定义开关就会切换到导出的状态


int CMath::Add(int add1, int add2) {
    return add1 + add2;
}
int CMath::Sub(int sub1, int sub2) {

    return sub1 - sub2;
}
写好之后 bulid一下 在解决方案的Debug会生成xxx.dll和配套的xxx.lib

那么怎么使用这个动态库呢:

#include <iostream>
using namespace std;
#include "../ClassDll/ClassDll.h"  // 让动态库进内存
#pragma comment (lib,"../Debug/ClassDll.lib") //连接器找编号

int main()
{
    CMath math;
    int sum = math.Add(1,2);
    int sub = math.Sub(1,2);
    cout << "sum = " << sum << "sub" <<sub << endl;
}

补知识:
#ifndef 1   

#endif // !1
   //if not define 的缩写  是宏定义的一种,实际上这是预处理的三种功能中的一种(宏定义、文件包含、条件编译)--条件编译
 在C语言中,对同一个 变量 或者 函数进行多次声明是不会报错的。
所以h文件只是进行了声明工作,即使不使用 #ifndef 宏定义。
多个C文件包含同一个h文件也不会报错

但是在C++语言中,#ifdef的作用域只是在单个文件。
所以如果h文件里定义了全局变量。即使采用#ifdef宏定义,多个c文件包含同一个h文件还是会出现全局变量重复定义的报错。
那么使用#ifndef可以避免下面这种错误:如果在h文件中定义了全局变量,一个C文件包含同一个文件多次,如果不加#ifndef宏定义,会出现变量重复定义的错误,如果加了#ifndef,则不会出现这种错误
#ifndef x  //先测试x 是否被宏定义过
#define x
    程序段1blabla ~ // 如果x没有被宏定义过,定义x,并编译程序段1
#endif
    程序段2blabla~ // 如果x已经定义过了则编译程序段2的语句,跳过程序段1

  条件指示符#ifndef的最主要的目的是 防止头文件的 重复包含和 编译。如果 条件编译也可以用条件语句来实现。但是用条件语句将整个源程序进行编译,
生成的目标程序很长,而采用条件编译,则根据条件只编译其中的程序段1或程序段2,生成的目标程序比较短。如果条件选择的程序段很长,采用条件编译的方法是十分必要的
#ifndef 和 #endif要一起使用,如果丢失了#endif,可能会报错。总结再c语言中,对同一个 或者函数进行多次声明是不会报错的,
使用#ifndef可以避免:h文件中定义了,一个c文件包含同一个多次,如果不加#ifndef,会出现重复定义错误


多种表达方式:
#ifdef :与ifndef类似,ifdef顾名思意,就是if define

# ifdef x
  程序1bababa~
#endif

意思是:如果定义了 x ,则执行程序1.

还有例子:
#ifndef x
#define x
   程序段1
#else
程序段2
#endif
意思是:当x没有由#define定义过,则编译“程序段1”,    否则编译“程序2”。

#if 表达式
 程序段1
#else
程序段2
#endif


#define c或C++中允许用一个来表示一个字符串,成为宏“宏”."define"为命令。
被定义为“宏”的标识符称为“宏名”。都用定义好的字符串去代换,这种称为“宏代换”或者“宏代替”
宏代换是由预处理程序自动完成的
优点:
方便修改
提高程序运行效率。使用带参数的宏定义可以完成的功能,

十五、Windows线程开发
Windows建议使用线程比较多(多用于客户端),Unix支持进程比较多,(因为Unix多做服务器开进程)
线程
1、线程基础:Widows线程是可以执行(进程只是分内存)的代码的实例
(CPU以线程为单位来调度程序的,程序真正跑起来的是线程)。系统是以线程为单位的调度程序(CPU真正被执行的是线程)
                                       进程开启意味这分内存,并不意味着线程执行,线程开启才意味这程序的开启
一个程序当中可以有多个线程,实现多任务的处理(主线程只能有一个)。
Windows的线程的特点:
1)线程都具有一个ID
2)每一个线程都具有自己的内存栈
3)进同一个线程中的线程使用同一个地址空间

线程的调度:
(操作系统)将CPU的执行时间划分为时间片(时间很短),依次根据时间片执行不同的线程
线程轮询:线程A->线程B->线程A
2、创建线程
需要用到API函数
HANDLE CreateThread(
                             LPSECURITY_ATTRIBUTES lpThreadAttributes, //安全属性 Windows开发看到安全属性这个参数,统统为NULL,是一个废弃的参数
                  SIZE_T dwStackSize,   //线程栈的大少 (创建线程的时候就要指明栈空间多大,写0没有用,这里是按照1M对齐的,如1.1M就按照2M,所以这个栈空间至少是1M)
                 LPTHREAD_START_ROUTINE lpStartAddress, //线程处理函数地址(ThreadProc())
                             LPVOID lpParameter, // 传递给线程处理函数的参数
                 DWORD dwCreatetionFlags, // 线程的创建方式,只有两种 一种(0)是立即执行,(CREATE_SUSPENDED)一种是挂起(也叫休眠)唤醒了才能让CPU去执行
                LPSDWORD lpThreadId  //创建成功,返回线程的ID
                       );创建成功,返回线程句柄
例子:
int main() {
    //栈空间写 0 实际上(有1M)系统会以 1M对齐
    DWORD nId = 0;
    char szText[] = "hello";
    HANDLE hThread = CreateThread(NULL,0,TestProc, &szText,0, &nId);
    // getchar(); //程序执行到这里只要你不回车,你的程序会阻塞在这里
    char pszText2[] = "--------";
    DWORD nId1 = 0;
    HANDLE hThread2 = CreateThread(NULL,0,TestProc1,&pszText2,0, &nId1);
    getchar();
    return 0;
}


定义线程处理函数
   DWORD CALLBACK WINAPI ThreadProc(
        LPVOID lpParamenter // 创建线程时,传递给线程的参数
                      );
例如:

#include<Windows.h>
#include<stdio.h>


LPVOID m_lpParamenter = 0;

DWORD CALLBACK WINAPI TestProc(LPVOID lpParamenter) {
    char* pszText = (char*)lpParamenter;
    while (1) {
        printf("%s\n",pszText);
        Sleep(1000);
    }
    return 0;
}

DWORD CALLBACK WINAPI TestProc1(LPVOID lpParamenter) {
    char* pszText = (char*)lpParamenter;
    while (1) {
        printf("%s\n", pszText);
        Sleep(1000);
    }
    return 0;

}
                      

3、线程挂起(休眠)/ 销毁
挂起:
DWORD SuspendThread(
               HANDLE hThread //handle to thread

             );

例子:
SuspendThread(hThread); //挂起线程1

唤醒
DWORD ResumeThread(
    HANDLE hThread //handle to thread
  )

例子:
ResumeThread(hThread2); //唤醒线程2


结束指定线程 (杀掉别的线程,拿到别的线程句柄,就能干掉该线程,杀别人的)
BOOL TerminateThread(
                        HANDLE  hThread, // handle to thread
           DWORD   dwExitCode //exit code  没有什么意义,喜欢添什么就什么 1 2,3都可
                          )
结束函数所在的线程(只能销毁调用它的那个线程,哪一个线程调用就销毁哪一个线程,自杀线程)
 VOID ExitThread(
        DWORD dwExitCode //exit code for thread  //没有什么意义,喜欢添什么就什么 1 2,3都可以

                       )

4、线程相关操作
1、获取当前线程的ID
GetCurrentThreadId    //没有参数,返回的是当前线程的ID

2、获取当前线程的句柄
GetCurrentThread  //没有参数,返回的是当前线程的句柄

3、等候单个句柄有信号   // 什么意思?线程句柄(互斥句柄,事件句柄,信号量句柄)才具备有信号无信号这两种状态称为  可等候的句柄   必须具备有信号和无信号两种句号的句柄,才能称之为可等候句柄
  VOID WaitForSingleObject(
   HANDLE handle , // 句柄BUFF的地址  //这里是线程句柄填在这里必须是可等候的句柄,当这个句柄有信号的时候,这个函数就会马上返回,但是如果这个句柄一直无信号的状态WaitForSingleObject这个函数就会阻塞不返回,你的程序就会停在这里等,一种等这个句柄有信号才会返回
  DWORD dwMilliseconds //等候时间 INFINITE(infinte等候时间无限大)(最大等候时间)毫秒级别为单位,如果我设置 3000毫秒,如果等到三秒还是没有信号那么等候函数将不会阻塞了,如果到了3秒不管你有没有信号我都要返回了
   )

那么线程什么时候有信号呢? 当线程处于执行状态的时候 线程句柄无信号,当线程结束的时候线程句柄有信号

4、同时等候多个句柄有信号
 DWORD WaitForMultipleObject(
               DWORD nCount , //句柄数量  
               CONST HANDLE *lpHandles, //句柄BUFF的地址,要一个数组的名字
               BOOL bWaitAll, // 等候方式
               DWORD dwMilliseconds // 等候时间
         )

    bWaitAll - 等候方式
          TRUE - 表示所有句柄都有信号,才结束等候。
         FASLE  -表示句柄只有1个信号,就结束等候。


十六、线程同步(一)或者说同步技术或说加锁机制  为什么要用加锁机制呢? 无非就是当多个线程同时操作一个资源的时候,一般把这个资源称之为临界资源(可以是任何东西可以是个变量,可以是内存,也可以是文件,也可以是窗口,反正多个线程同时操作这个东西(临界资源)必须使用加锁机制,不然的话必然会出错)
加锁机制或者叫同步技术
四个锁每一个都有使用场景
1、原子锁 ---解决的是什么问题呢?
相关问题
 多个线程对同一个数据(例如变量)进行原子操作(就是运算符操作),会产生结果丢失。比如执行++运算时

错误代码分析:因为CPU在运行时在运行的时候,时间到了会离开正在执行的线程(线程如果不是够时间就走那么如果写了死循环那么永远都没有结束的那一天,当CPU分给某个线程时间片一旦时间片一到那么CPU会离开你这个线程,转而执行其他线程)
 但是有一个问题就是如果代码没有执行完但是线程时间到了,那么怎么办 答 :应该在离开之前把线程执行到的地方保护起来,等过一段时间再回来执行,CPU会保护起来,保存再哪里呢? 会以压栈(每个线程有自己的栈空间,如果时间到了而没有执行完会把位置信息压到本线程的栈里保存,当下一次回到来的时候就弹栈把位置恢复出来接着往下执行)的方式保护起来
 当线程A执行g_value++时,
如果线程切换时间正好是在线程A将保值到g_value之前线程
B继续执行g_value++,那么当线程A再次别切换回回来之后,
会将原来线程A保存的值保存到g_value上,
线程B进行的加法操作被覆盖

解释: g_value++是三条汇编指令

解决方案
使用原子锁函数
InterlockedIncremnet  ++ ;


DWORD CALLBACK TestProc1(LPVOID pParam) {
    for (int i = 0; i < 100000; i++) {
        //g_value++;
        //原子锁函数
InterlockedIncrement(&g_value);
//用原子锁函数不会丢结果?
//为什么用++操作符就会丢结果,用原子锁就不会丢结果
//因为这个函数会锁死这个变量的内存地址,要注意的是对变量加锁//
// 而不是对线程加锁,线程时间到了依旧会离开
    }
    return 0;
}


DWORD CALLBACK TestProc2(LPVOID pParam) {  
//当运行时间来的 2时候想对g_value加1 但是这是候会发现&g_value变量已经上锁了那么就不能再赋值了,
不在赋值那么线2就会停止在这里什么都没有干,
时间片到了就会执行其他的线程,比如回到线程1把线程弹栈恢复线程把加1运算做完(线程是你阻碍你一次,我阻碍你依次,都要等一次做完)所以慢但是正确率高
 
    for (int i = 0; i < 100000; i++) {
        //g_value++;
        //原子锁函数
        InterlockedIncrement(&g_value);  
    }
    return 0;
}


InterlockedDecrement
InterlockedCoparaExchange
InterlockedExchange
. . . .
原子锁的实现:直接对数据所在的内存操作(不会引入第三方的寄存器),并且在任何一个瞬间只能有一个线程访问
原子锁难用:因为要就记很多函数:

InterlockedDecrement  减减
InterlockedCoparaExchange  //三目运算符的原子锁
InterlockedExchange   //等于的原子锁
原子锁的局限性很大:只能多运算符加锁, 比如如果写了10代码如果想给他们加锁是做不到的,但是所以的加锁机制中原子锁的加锁效率是最高的

线程同步(二) 互斥
作用:多线程下代码或资源的共享使用(也是解决多个线程同时操作同一个资源的问题,原子锁能解决的问题互斥都能解决但是互斥能解决的原子锁不一定能解决,互斥能力更强大些)
 互斥不仅仅能多操作符加锁,也能对一段代码加锁
互斥的使用:
1、创建互斥  HANDLE 互斥句柄:学习到的第二个:可等候的句柄,就是有(有无信号)两种状态
Mutex-互斥体
HANDLE CreateMutex (
       LPSECURITY_ATTRIBUTES lpMutexAttributes, //安全属性   没有用直接 NULL
       BOOL bInitalOwner,// 初始拥有者TRUE (谁创建了互斥就拥有了互斥)/FALSE
       LPCTSTR lpName // 命名  ,可以起一个名字,不想起的话,可以NULL
      ); 创建成功返回互斥句柄
互斥的特性1:如何时间时间点下只能有一个线程拥有互斥,如果一个线程拥有了其他线程不能拥有,只能等拥有互斥的线程把它放弃了能得到线程
所以互斥是具有独占性和排他性的
互斥的特性2:当所有的线程都不拥有的时候互斥有信号,当某一个线程用于互斥时,互斥线程无信号
互斥的特性3:

2、等候互斥
WaitFor...  互斥的等候遵循谁先等候谁先获取(先执行到WaitFor那么就可以等到互斥)跟买火车票像

3释放互斥,一旦运行那么线程的互斥不在拥有,线程变成有信号
BOOL ReleaseMutex(
        HANDLE hMutex //handle to mutex
    )
4、关闭互斥句柄
CloseHandle(hMutex );
例子:

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

DWORD CALLBACK  TestProc(LPVOID lpParamenter) {
    char* pszText = (char*)lpParamenter;
    while (1) {
        //printf("%s\n", pszText);  
        //在这里等信号,因为互斥有信号所以 WaitFor...就通过了,通过阻塞的情况下呢,线程就拥有了互斥,互斥句柄就没有信号了,就能往下走
        WaitForSingleObject(g_hMutex,INFINITE);
        for (int i = 0; i < strlen(pszText);i++) { //时间复杂度增加,打印的东西会乱,原因时多线程争夺同一个资源导致的
        printf("%c",pszText[i]);  
        Sleep(125);    //那么如何解决呢?给这段代码加锁即可
        }
        printf("\n");
        ReleaseMutex(g_hMutex);
    }
    
    return 0;
}

创建互斥  //填FALSE,说明主线程不拥有互斥不和子线程互斥,
    // NULL说明现在是如何线程都不拥有互斥,说明互斥句柄是有信号的状态
g_hMutex = CreateMutex(NULL,FALSE,NULL); //有信号状态
    .........
    代码.....

    CloseHandle(g_hMutex);  用完后关闭互斥句柄
那为什么不用原子锁呢? 原子锁的效率会比较高

线程同步(三)
事件和信号量,他们之间是实现的是协调关系,他们不但不排除,反而共同工作
原子锁和互斥是加锁机制,多个线程之间有排斥的情况
事件:解决的问题是程序(就是线程)之间的通知问题:如果两个或多个协调工作,必须之间有通信
事件的使用
1、创建事件
HANDLE CreateEvent(
     LPSECRITY_ATTRIBUTES  lpEventAttributes, //安全属性  NULL
    BOOL bMannualReset,  //事件重置(复位)<复位的意思是将有信号变为无信号的情况,把无信号变为有信号叫触发>方式,TURE 手动复位,FALSE自动复位
  BOOL bInitialState, // 事件初始状态,TRUE有信号
   LPCTSTR lpName , 事件命名
      );创建成功返回句柄 ,也是可等候句柄就是说可以用WaitForSinlgeObject()/ 或者WaitForMultiplepleObjects函数等候信息,有信号就能通过,没有信号呢,就不能通过
事件的说明时候又信号,什么时候无信号可以自己控制(用的多)

2、等候事件
 WaitForSingleObject / WaitMultipleObjects
3、触发事件(将事件设置成无信号状态)
 BOOL ResetEvent(

                       HANDLE hEvent // handle to event
      );
5 关闭事件
 CloseHnalde

小心事件的死锁:线程多了可能会出现这种情况
线程1:                                                         线程2:
WaitForSingleObject(事件1,....) ;                  WaitForSingleObject(事件2,....);
SetEvent(事件2);                                            SetEvent(事件1);
 

线程同步(四)
相关的问题:信号量:不解决多个线程的排斥,解决的是多个线程协调工作(使用想多个线程协调工作那么可以用事件按,可以用信号量(比事件多了一个功能,多了一个计数器,可以设置次数))
类似于事件(作用类似事件(事件什么作用?协调多个线程协调工作),不是说原理类似事件),解决通知的相关问题。但提供一个计数器,可以设置次数
信号量的使用
1、信号量的使用
1、创建信号量
HANDLE CreateSemaphore(
           LPSECURITY_ATTRIBUTRS   lpSemmaphoreAttributes, // 安全属性 ,NULL
          LONG lInitialCount, // 初始化信号数量   就是信号量计数器值不为零的时候能通过不阻塞,当为0那一刻就(无信号)会阻塞
       (这个参数有什么用呢?答:比如初始设置为3,当WaitForSingleObject的时候,会直接通过但是初始值会变为2,
        下一次WFSO()时候也会直接通过,2会变成1,在遇到WFSO的时候不会阻塞,直接通过 1变 0,之后再遇到WaitForSingleObject就会阻塞)

         LONG lMaximumCount , //信号量数量  
         LPCTSTR lpName  //命名
       );创建成功返回信号量句柄,也是一个可以等候(有信号和无信号两种状态)的句柄
2等候信号量
WaitFor.. 每等候通过一次,信号量的信号减1,直到为0阻塞


3 给信号量指定计数值
    BOOL ReleaseSemaphore(
                       HANLDE hSemaphore , // 信号量句柄
          LONG lReleaeCount , // 释放数量 设置5之后那么新的计数值为 5
                     LPLONG lpPreviousCount   //释放前原来信号量的数量,可以为NULL,目前剩余的值
       )
4 关闭句柄
CloseHandle()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值