一、Windows窗口的创建流程
注册一个窗口类
RegisterClass
1.1、提供一个消息处理函数(窗口回调函数)
1.2、提供一个窗口类的名字
创建一个窗口
GreateWindow 能够创建一个窗口,得到一个窗口句柄,是系统找到串口的一个标识
更新显示窗口
UpdateWindow(更新)
Show Window(显示)
编写一个消息循环
While(GetMessage)(…))
{
TranslateMessage();
DispatchMessage(…);
}
编写一个消息处理函数(窗口回调函数)
DWORD WINAPI WndProc(HWND hWnd,int nMessage,WPARAM wParam,LPARAM lParam)
{
Switch(message)
{
}
}
问题1:怎么理解这一些代码,这个过程这段代码,是定形式,是微软规定好的流程
问题2:消息是怎么获取到的
只有窗口才能够收消息,窗口收的息,需要通过 GetMessage来从程序的消息队列中获取到。你需要创建一个窗口,然后在创建窗口之后就可以使用 GetMessage获得创建的窗口收到的消息
问题3:消息队列在哪里,我为什么看不到??
因为大部分的事情都是操作系统帮你完成了比如说消息队列,比如往消息队列添加消息,从消息队列中得到消息,删除已经得到的问题4:什么是回调函数?谁调用了回调函数?
回调函数就不是你调用的函数你提供了一个函数给操作系统,操作系统在合适的时机去调用你提供的函数。因为只有操作系统才能知道什么时候得到了消息,只有我才知道我的程序怎么处理消息
问题5:为什么创建窗口要先注册窗口类?为什么创建完窗口要写更新显示窗口?为什么消息循环要写 tMessage DispatchMessage并放在一个循环里面??需要先有一个板(窗口共有一些特性,放在窗口类里面),然后创建窗口的时候根据模板去创建
刚创建出来的窗口是隐藏起来的,比如 ShowWindow给显示出来, UpdateWindow能够让窗口产生一次自绘(WMPAINT)
的作用就是从滴息队列中获取消息
DispatchMessage的作用就是调用口相对应的回调函数
1 |
|
二、练习题
题:钢琴
描述:使用所提供的音乐资源写出可以根据按键弹奏播放对应的曲调
进阶:本地提供一个钢琴谱,根据琴谱自动弹奏
main.cpp//主函数
1 |
|
Data.h//数据类头文件,
用于本地数据的读取及函数的封装
1 |
|
Data.cpp//数据类函数定义
1 |
|
题:计算器
描述:自己编写一个计算器能够控制windows系统中的计算器进行操作
main.cpp//主函数
1 |
|
题:拦截控件消息
控件的窗口类,是系统自己注册的,所以,控件的消息处理函数,也是系统自己的。我们如果想要得到控件消息,就需要截获到控件的消息处理函数。
GetClassLong 能够获取一个已经注册的窗口的类中的属性
SetClassLong 能够设置一个窗口,对应的窗口类中的属性
示例:我们使用SetClassLong实现修改按钮的光标
//修改按钮的cursor属性
1 | HCURSOR hCursor = LoadCursor(g_hInstance, MAKEINTRESOURCE(IDC_CURSOR1)); |
//修改控件回调
1 |
|
题:校园学生登录
里面有图片控件是直接在可视化界面导入的
1 |
|
三、SDK详解
常用字符串处理函数
ASCLL版 | UNICODE版 | T版 | |
---|---|---|---|
获取长度 | strlen | wcsnlen | _tcslen |
字符串拷贝 | strcpy_s | wcscpy_s | _tcscpy_s |
字符转数字 | atoi | _wtoi | _tstoi |
字符转数字 | sscanf_s | swscanf_s | _stscanf_s |
数字转字符 | sprintf_s | swprintf_s | _stprintf_s |
调试输出
//变参函数,用于在输出款输出信息
1 | bool _trace(const TCHAR* format, ...) |
//第一天所用例题
1 |
|
数据类型
常用数据类型
Windows 数据类型 | 描述信息 |
---|---|
UINT | 无符号32位整数 |
DWORD | 整数 |
PDWORD | 指向32位值的指针 |
BOOL | 布尔(true/false) |
SHORT | 带符号16位整数 |
LPSTR | 字符串指针 |
LPCSTR | 字符串常量指针 |
WPARAM | 32位的消息参数 |
LPARAM | 32位的消息参数 |
LRESULT | 32位函数返回值 |
HWND | 窗口句柄 |
常用控件
标准控件
响应消息:WM_COMMAND
控件时常见的窗口上的交互元素
当控件的特定功能被触发后,会主动发消息通知父窗口
父窗口可以通过发消息给控件
例如:控件的创建(button时按钮控件)
1 | CreateWindow(L"button", L"控件名", WS_CHILD | WS_VISIBLE, 280, 440, 80, 50, hwnd, (HMENU)0x10016, hInstance, NULL); |
标准控件时windows提供的基本控件,使用方式也比较简单,一下时常用的标准控件
窗口类名 | 控件名 | 英文 |
---|---|---|
“button” | 按钮 | Button |
“button” | 复选框 | CheckBox |
“button” | 单选框 | RadioButton |
“static” | 静态文本 | Static Text |
“static” | 图片 | Picture Control |
“combobox” | 复合框 | ComBox |
“edit” | 编辑 | Edit |
“listbox” | 列表框 | ListBox |
滚动条 Slider Control控件
1 | case WM_HSCROLL://当水平的滑块滑动时,会产生这个消息 |
进度条
响应的还是WM_COMMAND消息,这个消息的WPARAM的低位时控件ID
1 | static int nPos = 0; |
通用控件
窗口类名 | 控件 |
---|---|
WC_LISTVIEW | 列表框控件 |
WC_TREEVIEW | 树控件 |
WC_TABCONTROL | Tab控件 |
HOTLEY_CLASS | 热键控件 |
列表框控件的创建
1 | //根据ID获取列表框控件的句柄 |
窗口风格
响应消息:WM_NOTIFY
窗口标准风格之
三大窗口风格
重叠窗口 | 弹出窗口 | 子窗口 |
---|---|---|
WS_OVERLAPPEDWINODW | WS_POPUPWINDOW | WS_CHILDWINDOW |
常用函数
函数名 | 说明 |
---|---|
CreateWindow | 使用窗口类,创建窗口,创建控件 |
RegisterClass | 注册窗口类 |
DialogBox | 使用资源创建一个模态对话框 |
MoveWindow | 移动窗口到指定位置 |
ShowWIndow | 隐藏(SW_HWID)或显示(SW_SHOW)窗口 |
GetWindowText | 获取窗口标题 |
SetWindowText | 设置窗口标题 |
SetParent | 更改指定窗口的父窗口 |
TrackPopupMeun | 弹出菜单(坐标需要转换,需要获取子菜单的句柄) |
GetSubMenu | 获取指定子菜单的句柄 |
GetDlgItem | 根据控件的ID获取子控件句柄 |
GetParent | 获取父窗口句柄 |
FindWindow | 找到一个窗口,获取其句柄 |
常用资源
想要操作资源需先获得资源的句柄
图标 Icon
需要有一个图标资源
LoadIcon根据资源ID得到句柄
将句柄填充到窗口类上
1
2
3
4
HBITMAP hBit_map = LoadBitmap(g_hInstance, MAKEINTRESOURCE(IDB_BITMAP1));
HWND hPic=GetDlgItem(hMain, IDC_STATIC1);//获取图片框资源句柄
SendMessage(hPic, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBit_map);
光标 Cursor
- 需要有一个光标资源
- LoadCursor根据ID得到句柄
- 将句柄填充到窗口类上
位图
- 配合picture控件使用
菜单
下拉菜单
- 有一个菜单资源
- 填充到窗口类中 直接填
- 填充到创建窗口的参数上,需要LoadMenu获取到句柄
- 有一个菜单资源
弹出菜单
TrackPopupMenu 提供坐标以及菜单的句柄
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27case WM_RBUTTONDOWN:
{
//这里得到的是窗口坐标
DWORD dwX = LOWORD(lParam);
DWORD dwY = HIWORD(lParam);
//我们需要将窗口坐标转换为屏幕坐标
POINT pt = { dwX ,dwY };
ClientToScreen(hWnd, &pt);
HMENU hMenu = LoadMenu(g_hInstance, MAKEINTRESOURCE(IDR_MENU2));
//获取子菜单的句柄
HMENU hSubMenu = GetSubMenu(
hMenu, //父级菜单句柄
1);
TrackPopupMenu(
hSubMenu,
TPM_LEFTALIGN,
pt.x,
pt.y,
0,
hWnd,
NULL
);
}
break;
对话框资源
可视化编程
- DialogBox 模态对话框 会阻塞住父窗口
- CreateDialog 非模态对话框 不会阻塞父窗口
模态对话框
1 |
|
非模态对话框
CreateDialog
1 | #include<Windows.h> |
对话框控件的使用
单选框
有一个重要操作,需要记住:分组
ctrl+D之后将同一组的radio都编为连续的序号。
将每组的第一个radio设置group为true
静态文本
一个提示语直接拖拽即可
静态图片
能够显示一个bitmap的图片
需要一个picture的控件,然后去设置两个属性
一个bmp格式的图片资源
1 | //1,初始化图片 |
四、SDK问题
问答1
1、什么是windows 错误码?应该在什么时候获取? 使用什么函数获取? 获取到之后如何
查看其所代表的内容?
答:windows 错误码是不同的API有不同类型的错误返回值,在API结束之后获取,可以使用SetLastError函数获取,使用错误查找工具或者在VS监视栏中输入“err,hr”查看。
2、如何为自己的函数定义windows 错误码,使用哪一个函数?
答:SetLastError函数。
3、在Virtual Stodio 中,在监视栏中输入什么字符,能够方便的查看错误码?
答:err,hr字符。
4、什么是句柄? 几个字节? 有什么用?
答:在程序设计中,句柄是一种特殊的智能指针。当一个应用程序要引用其他系统(如数据库、操作系统)所管理的内存块或对象时,就要使用句柄。在windows编程中,一个句柄是指使用的一个唯一的整数值,即一个4字节(64位程序中为8字节)长的数值,来标识应用程序中的不同对象和同类中的不同的实例,诸如,一个窗口,按钮,图标,滚动条,输出设备,控件或者文件等。
5、什么是实例? WinMain 函数的四个参数分别代表什么意思?
答:实例诸如一个窗口,按钮,图标,滚动条等,hInstance是程序实例的句柄,它是程序的加载地址,hPrevInstance这个参数已经废弃,IpCmdLine是由调用者传入的命令,nCmdShow是由调用者传入的int型参数。
6、请问TCHAR,_tcslen 本质是什么?由谁控制? 使用通用字符需要包含什么头文件?
答:T版的数据或者函数,本质上还是ascii编码或者unicode的编码,主要靠#indefine和typedef实现的,它只由编译环境自动选择,tchar.h头文件。
7、窗口回调函数的原型是什么?
答:原型如下:
LRESULT CALLBACK WindowProc(
HWND hwnd, //窗口句柄
UINT uMsg, //消息ID
WPARAM wParam, //消息参数1
LPARAM lParam //消息参数2
);
8、使用SPY++查看QQ 登录框类名是什么? 聊天窗口类名是什么?
答: TXGuiFoundation TXGuiFoundation
9、窗口类结构体中hbrBackground 有什么用? 请尽量详细的说明。
答:hbrBackground 可以刷新背景所用的画刷的句柄。Windows定义六种现有画刷:WHITE_BRUSH、LTGRAY_BRUSH、GRAY_BRUSH、DKGRAY_BRUSH、BLACK_BRUSH和NULL_BRUSH (也叫HOLLOW_BRUSH)。您可以将任何一种现有画刷选入您的装置内容中,就和您选择一种画笔一样。Windbws将HBRUSH定义为画刷的代号,所以可以先定义一个画刷代号变数:
HBRUSH hBrush ;
您可以通过呼叫GetStockObject来取得GRAY_BRUSH的代号:
hBrush = GetStockObject (GRAY_BRUSH) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (GRAY_BRUSH);//设置窗口背景画刷的句柄
10、什么是队列消息? 什么是非队列消息? 请各举出三个例子。
答:队列消息包括系统和线程的消息队列,队列送到系统消息队列,然后送到线程消息队列,而非队列消息是直接送给目的窗口的过程。队列消息如WM_MOUSERMOVE,WM_CHAR,WM_PAINT。非队列消息有WM_ACTIVATE,应用系统发送WM_WINDOWPOSCHANGED,SendMessage函数也会发送非队列消息。
11、PostMessage 和SendMessage 有什么区别?
答:PostMessage只是把消息放入队列,不管其他程序是否处理都返回,然后继续执行;而SendMessage必须等待其他程序处理消息后才返回,继续执行。PostMessage的返回值表示PostMessage函数执行是否正确;而SendMessage的返回值表示其他程序处理消息后的返回值。 PostMessage会造成消息的滞后性,而SendMessage则不会,但如果SendMessage消息处理失败,则会造成程序停止。
12、下面这个结构体是窗口类结构体,请分别说明,各个字段是什么含义。
答:各字段含义见注释。
typedef struct tagWNDCLASSW {
UINT style;//窗口类的风格(无符号32位整数)
WNDPROC lpfnWndProc;//指向窗口过程的指针
int cbClsExtra;//分派给窗口类的扩展的字节数
int cbWndExtra;//分派给窗口实例的扩展的字节数
HINSTANCE hInstance;.//实例句柄
HICON hIcon;//类图标的句柄
HCURSOR hCursor;//类鼠标指针的句柄
HBRUSH hbrBackground;//刷新背景所用的画刷的句柄
LPCWSTR lpszMenuName;//窗口类包含的菜单的名称
LPCWSTR lpszClassName;//窗口类名
}
WNDCLASSW
13、以下消息分别在什么时候触发
WM_LBUTTONDOWN 点击鼠标左键
WM_PAINT 绘制对象时
WM_CLOSE 点击关闭程序按钮
==================
1、窗口风格有三大风格,分别是什么? 各有什么特点?
答:窗口三大风格是重叠窗口(Overlapped Window),弹出窗口(Popup Window),子窗口(Child Window),重叠窗口是顶级窗口,是缺省类型,它有边框,标题栏,客户区等,还有其他组件;弹出窗口也是顶级窗口,通常用于对话框或者Message对话框,它具有WS_POPUP窗口风格,隐含带有WS_CLIPSIBLINGS窗口风格。
2、WM_COMMAND 作为控件的通知消息,wParam 与lParam 分别有什么含义?
答:wParam是指32位的消息参数,是一个消息有关的常量值,也可能是窗口或控件的句柄, lParam 也是指32位的消息参数,通常是一个指向内存中数据的指针。
3、什么是模态对话框? 什么是非模态对话框? 用什么创建?,
答:模态对话框创建后一定要在用户关闭对话框后,其父窗口才能响应用户操作,否则父窗口便无法响应任何用户的操作,模态对话框函数自带消息循环;非模态对话框则是创建完后其父窗口不需要像模态窗口那样等到对话框关闭才能响应用户的操作,一样可以在对话框未关闭前响应用户操作,非模态对话框函数需要自己写消息循环。模态对话框使用DialogBox创建,非模态对话框使用CreateDialog完成。
4、控件既可以用代码创建,也可以在对话框资源上拖拽,创建控件的函数是什么?。
答:创建控件的函数是CreateWindow。
5、WM_NOTIFY 和WM_COMMAND 的区别。
答:WM_COMMAND和WM_NOTIFY都是控件通知消息,WM_NOTIFY和WM_COMMAND相比,是一种更灵活的消息格式,lParam中放的是一个称为NMHDR结构的指针。在wParam中放的则是控件的ID。最初Windows 3.x就有的控件,如Edit,Combo,List,Button等,发送的控件通知消息的格式是WM_COMMAND;而后期的Win32通用控件,如List View,Image List,IP Address,Tree View,Toolbar等,发送的都是WM_NOTIFY控件通知消息。
6、使用通用(扩展)控件的准备工作都有哪些?
答:在15版本以下的VS使用通用控件需要先包含<CommCtrl.h>,引入 #pragma comment(lib,”comct132.lib”),调用InitCommonControls初始化通用控件,使用CreateWindowEx函数创建通用控件。
==================
1、什么是API? 什么是动态链接库? 写出常见的动态链接库及其大体功能。
答:API是指API(Application Programming Interface,应用程序编程接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组历程的能力,而又无需访问源码,或理解内部工作机制的细节。
动态链接库的英文名称叫DynamicLinkableLibrary,它是microsoft在windows操作系统中实现共享函数库概念的一种实现方式。大多数与windows相关的磁盘文件如果不是程序模块,就是动态链接程序。
常见的动态链接库及其大体功能:
kernel32.dll Windows9x/Me中非常重要的32位动态链接库文件,属于内核级文件。它控制着系统的内存管理、数据的输入输出操作和中断处理
user32.dll Windows用户界面相关应用程序接口,用于包括Windows处理,基本用户界面等特性,如创建窗口和发送消息。
gdi32.dll Windows GDI图形用户界面相关程序,包含的函数用来绘制图像和显示文字
winspool.dll 一款文本编辑软件的库文件
comdlg32.dll Windows应用程序公用对话框模块,用于例如打开文件对话框
都包括什么?
2、你所知道的文件操作
答:文件操作包括新建打开文件CreateFile,删除指定文件DeleteFile,复制文件CopyFile,ReadFile读取文件,WriteFile写文件,GetFileAttribute获取文件属性,SetFileAttribute设置文件或目录的属性。
3、什么是进程? 什么是线程?
答:进程是资源的分配和调度的一个独立单元,而线程是CPU调度的基本单元,同一个进程中可以包括多个线程,并且线程共享整个进程的资源(寄存器、堆栈、上下文),一个进程至少包括一个线程。
4、Dll 可以导出什么?
答:Dll可以导出类和函数。
5、在编写dll 时,当我们想要导出一个东西时,有哪些方式? 分别如何实现?
答:一、可以使用_declspec(dllexport),在函数声明前加_declspec(dllexport)。二、使用模块定义文件(扩展名为.def)导出,文件里面写
LIBRARY
DESCRIPTION “DLL”
EXPORTS
函数名
6、我们使用dll 的时候,通常有两种方式。分别如何实现?
答:见上一题。
7、什么叫做对象库? 什么叫做导入库?
答;对象库是一个扩展名为.LIB的文件,在这个文件的代码在运行链接器进行静态链接时被添加到程序的.EXE文件中。导入库是一种特殊的形式的对象库文件,后缀名和对象库一样,链接器用它来解析源代码中的函数调用。但是导入库不包含任何代码,它们只给链接器提供信息来建立EXE文件中用于动态链接的重定位表格。
8、什么叫做名称粉碎? 有什么用处?
答:名称粉碎是函数名称相同的函数在C中出现重定义,然而在C++中用于函数重载,相同的名称会粉碎成不同的函数内存地址,从而区别调用的是什么函数,由此可以实现多态。
问答2
怎么将”123”转换成123? 反过来呢?
使用sscanf_s函数将字符转换位数字,宽版使用stscanf_s
反过来就是先转换为数字后,反向遍历
(老师批注:stscanf_s是通用版本,根据项目默认编码方式自动匹配,wsprintf是宽版,用到通用版本才需要tchar,单纯用宽版函数是不需要tchar.h的)
“哈哈”和L”哈哈”一样吗? 哪里不一样? 怎么转换?
答:不一样,窄版和宽版,如果需要使用宽板只需在字符前加上L即可,但需包含头文件tchar.h
创建窗口的流程是什么?
1,使用WNDCLASS结构体定义窗口
2,将定义的窗口注册
3,将注册的窗口创建出来
4,更新并显示窗口
5,创建消息循环
6,回调
怎么响应窗口消息?
答:不同的消息有不同的响应方式,例如:当窗口创建的时候会产生WM_CREATE消息,点击按钮(button)时会产生WM_COMMAND消息,
wParam和lParam保存了什么信息?
答:如在响应WM_COMMAND消息时WPARAM的高位是通知码,低位是ID,LPARAM保存的是句柄
怎么响应控件的单击消息?
答:单击控件时会产生WM_COMMAND消息,在这个消息中他的WPARAM的低位是被单机控件的ID,把这个ID找出来后,通过这个ID使用GetDlgItem这个函数获取该控件的句柄从而进行想要的消息响应
如何创建单选框?
在工具栏中拖拽,然后通过WPARAM的低位ID响应,然后通过WPARAM的高位通知码获取单选框是否被点击
怎么将单选框设置为选中状态?
答:给单选框发送一个SendMessage消息第二参数BM_SETCHECK第三个参数就是设置选中的状态码1
如何获取复选框是否被选中?
使用BM_GETCHECK参数给复选框发送SendMessage消息,用BOOL值接收
如何给窗口添加主菜单?
答:资源创建,通过ID获取句柄,创建窗口时加上这个句柄
怎么在程序中加载图片资源?
答:
//加载图片
HBITMAP hBit_map = LoadBitmap(g_hInstance, MAKEINTRESOURCE(IDB_BITMAP1));
//获取图片框资源句柄
HWND hPic=GetDlgItem(hMain, IDC_STATIC1);
//设置图片
SendMessage(hPic, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBit_map);
怎么在窗口中弹出右键菜单
在WM_RBUTTONDOWN消息中响应右击,在这个消息中获取右击的坐标,使用这个坐标弹出窗口(TrackPopupMeun)
怎么设置和获取编辑框的文本
获取编辑框句柄GetDlgItem
创建接收文本的缓冲区WCHAR 数组
获取编辑款文本GetWindowText
下拉框怎么添加文本?
获取句柄在使用ComboBox_AddString函数添加文本
怎么结束一个对话框?
EndDiaLog
怎么获取或修改窗口的窗口风格?
获取需要修改控件的句柄,然后使用SetClassLong修改
怎么替换一个控件的消息回调函数?
控件句柄 修改的属性 回调函数
SetWindowLong(hButton1, GWL_WNDPROC, (LONG)ButtonProc);
怎么创建DLL工程?
在新建的时候选择dll,
怎么导出函数?
两种导出方法
在头文件中声明导出_declspec(dllexport)
模块定义导出,添加一个def文件,在文件中写入EXPORTS(后跟需要导出的函数,且函数后标号)
之后再属性配置里–>连接器–>输入,中有一个模块定义文件,把这个选项值写成.def文件全称(xxx.def)
怎么调用从DLL导出的函数?
两种调用方式
隐式调用
包含头文件,头文件中含有导出函数的声明。
载入lib文件 #pragma comment(lib,“”); 直接调用函数即可
显示调用
使用LoadLibrary()将目标dll强行加载到进程中。
在 GetProcAddress()获得函数的地址。然后使用返回的函数指针调用(老师批注:dll显式链接方式不要忘了FreeLibrary)
怎么调试DLL
(老师批注:可以通过编写调用dll函数的程序进行调试)
exe加载DLL时(使用相对路径),被加载的DLL可以放在任何目录吗,如果不能,那么都能放到哪些目录?
只能放在相对exe的相对路径中
怎么使用静态库? 和动态库相比有和区别?
使用方法是一样的,只是使用静态库编译后的程序会比动态库的程序占用空间大,因为静态库是直接把库文件载入到了程序中,而不是需要从外部调用
五、磁盘信息
//获取盘符的字符串,
GetLogicalDriveStrings( 100,buf);
// 把语言设置为中文
setlocale(LC_ALL, “chs”);
// 获取驱动器类型
,由于这里需要的是一个指针,所以需要在定义一个TCHAR*指针类型指向数组(缓冲区)
用整形接收返回值,
2,代表可移动设备 DRIVE_REMOVABLE
3,硬盘 DRIVE_FIXED
4,远程设备DRIVE_REMOTE
5,光驱 DRIVE_CDROM
GetDriveType(pTemp);
//计算磁盘空间
1 | GetDiskFreeSpace(pTemp,//盘符 |
//查看磁盘的实例代码
1 | // 02_获取磁盘信息.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 |
六、动态链接库
定义 DLL 应用程序的入口点
1 | // dllmain.cpp : |
声明导出
在需要导出函数的头文件中声明
extern “C” _declspec(dllexport) void pr();//需要声明的函数即返回类型
注意一定要在cpp文件中包含头文件,不然找不到该函数的定义
模块定义文件导出
添加一个.def文件 ,
1 | EXPORTS |
Fun1和Fun2是函数名,@1表示导出函数的序号
在属性配置里–>连接器–>输入,中有一个模块定义文件,把这个选项值写成.def文件全称(xxx.def)
调用dll中函数的方式,有两种:
隐式调用
包含头文件,头文件中含有导出函数的声明。
载入lib文件 #pragma comment(lib,“”);
直接调用函数即可
1 |
|
显示调用
使用显示调用不需要头文件,但是你需要知道函数的原型(返回值类型,各个参数的类型)。
LoadLibrary()将目标dll强行加载到本进程中。
GetProcAddress()获得函数的地址。
自然就可以调用此函数。
1 | typedef void(*FUN)(); |
lib文件的差别:
随着dll一起生成的lib文件,称之为导入库,里面是没有代码和数据,是链接信息,供编译器再exe文件与dll文件做链接的。
还有一种是静态对象库,编译的时候会将静态库中的使用到的代码数据编译到exe中,exe发布就不需要携带很多文件。
七、进程
1 内核对象的概念
2 进程,模块,线程
概念
基本操作:
进程 模块 线程的遍历
进程的终止 强制结束 进程间的通讯
线程挂起 恢复 终止 强制结束
3 文件操作
难点:递归遍历文件
4 线程同步:原子操作 临界区 互斥体 事件 信号量
5 同步IO和异步IO
6 基本的网络编程 TCP UDP
7 IOCP模型
概念
一、 如何理解内核对象的概念?
a. windows操作系统的设计是面向对象的,很多的组件都是以对象的方式去管理的。将整个操作系统的内核看成是一个封装体,API函数就是对外的接口。然后通过给API传递句柄去实现操作具体的某一个对象,句柄就是对象的标识。windows管理的对象也有很多类型,不同类型的对象的管理方式是不同的。
b. 所有的API函数都是隶属于不同的动态链接库(DLL文件)的。有三大DLL文件:
User32.dll GDI32.dll kernel32.dll
其中User32.dll和窗口,控件等有关,GDI32和绘制图形有关,Kernel32和操作系统的界面无关的很多机制有关。
这三种DLL创建出来的对象就可以分为3大类。管理方式就不一样。那么我们的内核对象有自己的管理方式。
c. 一个进程,是一个运行中的程序。
一个进程包含了一个运行中的程序所用到的所有资源:一个内存空间 内存空间中有很多的模块 内存空间被分成了两大部分—用户层和内核层
我们正常的普通程序只能访问用户层空间(000000007FFFFFFF)。大部分的对象(结构体变量)都是放在内核空间的(80000000FFFFFFFF)。
所以我们自然不能直接访问到对象,而是通过句柄。
所有的进程内核层是共享的,不同的进程用户层空间是不一样。
2 内核对象有什么特点??
特点1—内核对象是跨进程的:所有的内核对象存在于内核层的。既然内核层是大家共享的,所以内核对象也是可以跨进程的。
特点2—跨进程机制怎么管理呢??:通过引用计数和内核对象句柄表管理的。
进程A的句柄表
1 | 内核对象A的地址 | |
---|---|---|
2 | 内核对象B的地址 |
进程B的句柄表
1 | 内核对象B的地址 | |
---|---|---|
进程C的句柄表
1 | 内核对象M的地址 | |
---|---|---|
2 | 内核对象N的地址 | |
3 | 内核对象L的地址 |
4 | 内核对象B的地址 | |
---|---|---|
如果一个内核对象被n个进程使用,引用计数就是n。没有被进程使用,引用计数就是0,没被任何进程使用,内核对象就会自动销毁。
特点3:每一个内核对象都有安全描述属性,我们通过创建对象的API是否包含这样一个参数,就能确定它是不是内核对象
3 通过我们怎么获得内核对象的句柄的
a. 自己创建的
b. 自己打开的(这个对象是已经创建好的)
c. 从父进程继承过来的。
d. 别的进程复制过来的。DuplicateHandle
进程概念:
进程,简单来说,就是一个运行中的程序,包含了:
a. 一个虚拟的内存空间 所有程序的代码和数据都在这片内存空间中。
b. 内存空间中排布了很多的模块
c. 至少有一个线程
在进程的虚拟内存中,一般会加载一个exe,很多的dll。他们都称之为模块。
进程本身是一个综合了各种资源的东西,是不能执行代码,能够执行代码的是归属于进程的线程。
每一个线程都是一个独立的执行单元:
1 每一个线程有自己的一块堆栈。
2 每一个线程有自己的执行环境。
CPU在执行代码的时候,主要是依赖于一套寄存器:
通用寄存器:eax ebx ecx edx esi edi esp ebp
指令指针寄存器:eip 存储着下一条要执行的指令
段寄存器:cs ss ds es fs gs
…..
所有的线程都是操作系统统一去管理调度的,每一个线程都有自己的优先级。根据优先级决定先调用谁后调用谁。
线程发生切换,实际就是切换线程的执行环境,比如现在有A,B,C三个线程,此时线程A在执行,线程A的时间片用完了,就保存A的执行环境,看B和C的优先级谁高,假如是C高,那么就把C的线程环境加载到CPU中。
基本操作:
进程 模块 线程的遍历(非常重要的操作)
方法有很多 我们这里使用的是创建快照的方式:CreateToolHelp32Snapshot。
需要知道的几点:
a. 进程是操作系统管理的,遍历的时候,能够遍历出系统中的所有进程的信息:进程名,路径,进程ID
遍历进程的用处:通常来说我们都是知道进程名,然后去找到ID(ID每一次程序运行的时候都是不一样的),我们如果要操作进程,就需要使用OpenProcess函数得到它的句柄,OpenProcess这个函数,就是根据进程ID得到句柄的。
b. 模块是属于某一个进程的,所以我们遍历模块的时候,需要指定遍历的是哪一个进程的模块。
能够遍历出模块的信息为:模块名,模块的起始虚拟地址(加载基址)
遍历模块的用处:a 可以知道一个程序都加载了哪些DLL,监测DLL注入 b 分析DLL中的PE文件信息,可以为我们分析一个程序提供依据。
c. 线程虽然也是属于某一个进程的,但是其管理是操作系统统一管理的,所以我们遍历的时候,也是遍历出操作系统中的所有线程。需要自己去过滤然后,得到某一个进程的线程。
线程遍历,能够得到的信息有:线程ID,所属进程的ID。
遍历线程的用处:可以得到进程中每一个线程的信息。可以操作这些线程,比如挂起,终止等等。
进程控制
WinExec | 可以创建进程 |
---|---|
ShellExecute | 可以创建一个进程,打印一个文件,浏览一个文件夹 |
system | 也能够以控制台命令的方式打开一个进程 |
CreateProcess | 创建进程 |
OpenProcess | 打开进程 |
ExitProcess | 退出本进程 |
TerminateProcess | 结束其它进程 |
进程的创建
1使用宏加载想要的进程#define PATH2 L”D:\Program Files (x86)\Tencent\QQ\Bin\QQScLauncher.exe”
2使用结构体PROCESS_INFORMATION定义加载进程信息的缓冲区
3使用CreateProcess函数创建进程
1 |
|
结束一个进程
使用该进程PID打开进程从而获取到进程句柄
使用TerminateProcess函数结束进程
1 |
|
进程与模块
CreateToolhelp32Snapshot | 可以分别创建进程,线程,进程模块进程堆的快照 |
---|---|
Process32First | 用来首次调用,获得第一个进程信息 |
Process32Next | 以后的调用由它来完成,不断的获取进程信息 |
Module32First | 用来首次调用,获得第一个模块信息 |
Module32Next | 以后的调用由它来完成,不断的获取模块信息 |
Thread32First | 用来首次调用,获得第一个线程信息 |
Thread32Next | 以后的调用由它来完成,不断地获取线程信息 |
创建快照
WIndows提供了一组快照的API,使用前需要包含TIHelp32.h头文件,这一组API
能够给当前系统中所有进程拍一个快照,能够获取所有进程的一些基本信息
能够当前系统中的线程拍一个快照
能够给某一个进程拍模块快照
能够给某一个进程拍堆快照
进程的遍历
1 |
|
模块的遍历
1 |
|
进程间通讯
发送消息
COPYDATASTRUCT发送
1 |
|
邮槽发送
1 |
|
接收消息
COPYDATA接收只需在回调函数的WM_COPYDATA消息响应定义接收的结构体
1 | case WM_COPYDATA: |
1 |
|
八,线程
线程创建
1 |
|
线程信号
WaitForSingleObject(hThread,-1 ); //该函数表示信号,参数一为被创建的线程名,参数二为一直等到有信号的状态,意思是等待被创建线程结束
1 | // 02_关于线程的信号.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 |
线程遍历
1 |
|
理解伪句柄
1 |
|
获取线程环境
1 |
|
线程同步
概念
1 线程同步是一个什么样的问题???
如果你编写的是多线程程序,那么多个线程是并发执行,可以认为他们是同时在执行代码。但是线程和线程之间并非是完全的没有关系。很多时候会有以下两种关系:
第一种情况,线程A的继续执行,要以线程B完成了某一个操作之后为前提。 这种需求称之为同步
第二种情况,多个线程在争抢一个资源,比如:全局变量,可以是文件,可以是一个数据结构,可以是一个对象。 这种需求称之为同步互斥。
2 以下的这些机制怎么解决的这个问题??各自又什么区别,分别用于什么样的场景呢???
a. 以下这些机制都能够比较方便的解决互斥问题。
原子操作:
原子操作适合去解决共享资源是全局变量的互斥问题。
作用就是对于一个变量的基本算术运算保证是原子性的。
1 |
|
临界区解决互斥问题:
被保护的代码(代码访问了共享资源)放置在
EnterCriticalSection
LeaveCriticalSection 之间即可
临界区具有线程所有权这个概念,必须进入临界区的线程,调用离开临界区,临界区才会被打开。假如加锁的线程崩溃了,其他线程就锁死了。
介绍另外3种机制前,先说两个重要概念
1:
激发态(有信号) 非激发态(没有信号)
WaitForSignaleObject(内核对象,时间);函数的作用,当内核对象处于非激发态的时候,就阻塞住,内核对象处于激发态了,就立即返回。
2:WaitForSignaleObject的副作用,WaitForSignaleObject对于被等待的内核对象有副作用。
1 |
|
知道了以上两个概念后,我们看其他3个机制
互斥体解决互斥问题:
被保护的代码(代码访问了共享资源)放置在
WaitForSignalObject
ReleaseMutex
互斥体也具有线程所有权的概念,得到互斥体的线程,需要自己去释放互斥体。谁加锁,谁开锁。如果得到互斥体的线程崩溃了,互斥体会立即变为激发态。所有等待互斥体的线程中会立即有线程得到互斥体。不会造成死锁的问题。
1 |
|
事件解决互斥问题:
需要是自动模式的事件
被保护的代码(代码访问了共享资源)放置在
WaitForSignalObject
SetEvent
事件,没有线程所有权的概念,任何线程都可以释放事件。
1 |
|
事件决定顺序
1 |
|
信号量:
有信号数这么一个概念,只要信号数不为0,那么就处于激发态。WaitForSignaleObject函数对它的副作用是将信号数减1。
最大信号数为1 的信号量,可以认为是一个事件,可以解决互斥问题。
被保护的代码(代码访问了共享资源)放置在
WaitForSignalObject
ReleaseSemaphore
1 | HANDLE CreateSemaphore( |
b. 对于共享资源有序的访问,也可以更关注于要有序。
事件和信号量更适合解决有序的问题。因为他们不要求谁上锁,谁开锁。
用代码实现一个读文件线程,一个写文件线程,请实现先写后读,两个线程都结束之后,主线程结束。
这种没有过多线程同时访问的有顺序的问题,比较适合用事件来解决。
1 |
|
多个信号数的信号量,比较适合解决多个线程之间有顺序需要协调的问题,最为经典的就是生产者消费者问题。
关键点有两个:
1 必须有一个队列,可以有数量限制,也可以没有数量限制。我们考虑的是有数量限制的问题
2 每一个生产者是一个线程,每一个消费者也是一个线程。队列满了,生产者需要等待。队列空了,消费者需要等待。
整个的问题是多线程并发时的协调问题。
生产者消费者问题,有两个操作
P:生产
V:消耗
1 |
|
总结:
原子操作,只能保证对于基本算数操作是原子性的。
临界区和互斥体从词语的含义上看,他们主要就是为了解决互斥问题。
临界区的优点是快。互斥体的优点是能够跨进程访问,崩溃不死锁。
事件 从词语的含义上看,更适合做通知(产生了一个事件)。比较适合解决有先后顺序的多线程问题。
事件和互斥体的最大区别,就是线程所有权。互斥体谁上锁,谁开锁。事件没有这个要求。
信号量,由于存在信号数的问题,比较适合解决多线程的协调问题。典型问题,就是生产者消费者问题。
常用函数
原子操作:
函数 | 作用 | 备注 |
---|---|---|
InterlockedIncrement | 自增 | InterlockedIncrement(&g_count) |
InterlockedDecrement | 自减 | InterlockedDecrement(&g_count); |
InterlockedExchangeAdd | 加法/减法 | InterlockedExchangeAdd(&g_count, 256L); |
InterlockedExchange | 赋值 | InterlockedExchange(&g_count, 256L); |
临界区
函数 | 作用 | 备注 |
---|---|---|
InitializeCriticalSection | 初始化 | |
DeleteCriticalSection | 销毁 | |
EnterCriticalSection | 进入临界区 | |
LeaveCriticalSection | 离开临界区 |
互斥体
函数 | 作用 | 备注 |
---|---|---|
CreateMutex | 创建互斥体 | 可以给互斥体起名字 |
OpenMutex | 打开互斥体,得到句柄 | 根据名字才能打开互斥体 |
ReleaseMutex | 释放互斥体 | 会使得互斥体处于激发态 |
CloseHandle | 关闭句柄 | 使用完后关闭 |
WaitForSignalObject | 等待互斥体处于激发态 | 等到激发态后,会使得互斥体再次处于非激发态 |
事件
函数 | 作用 | 备注 |
---|---|---|
CreateEvent | 创建事件 | 可以给事件起名字 可以设置两种模式:手工 自动 |
OpenEvent | 打开事件,得到句柄 | 根据名字才能打开事件 |
SetEvent | 释放事件 | 会使得事件处于激发态 |
ResetEvent | 重置事件 | 会使得事件处于非激发态,对手工模式的事件有效 |
WaitForSignalObject | 等待事件处于激发态 | 等到激发态后,对于自动模式的事件会使其再次处于非激发态 |
信号量
函数 | 作用 | 备注 |
---|---|---|
CreateSemaphore | 创建信号量 | 可以给信号量起名字 可以指定最大信号数和当前信号数 |
OpenSemaphore | 打开信号量 | 根据名字才能打开信号量 |
ReleaseSemaphore | 释放信号量 | 会增加信号量的信号数,但是不会超过最大信号数 |
WaitForSignalObject | 等待信号量处于激发态 | 若处于激发态,则会减少1个信号数,信号数位0,将其置为非激发态 |
九、文件操作
DeleteFile | 删除文件 |
---|---|
CopyFile | 拷贝文件 |
MoveFile | 移动文件 |
CreateFile | 打开文件 |
GetFileSize | 获取文件大小 |
ReadFile | 读取文件 |
CloseHandle | 关闭文件 |
FindFirstFile | 查找第一个文件 |
FindNextFile | 查找下一个文件 |
WriteFile | 写入数据 |
文件属性
1 |
|
遍历盘符
1 | //1. 获取驱动器名称 |
文件遍历
1 | //文件遍历 |