首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
80 阅读
2
nvim番外之将配置的插件管理器更新为lazy
58 阅读
3
2018总结与2019规划
54 阅读
4
PDF标准详解(五)——图形状态
33 阅读
5
为 MariaDB 配置远程访问权限
30 阅读
心灵鸡汤
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
菜谱
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
登录
Search
标签搜索
c++
c
学习笔记
windows
文本操作术
编辑器
NeoVim
Vim
win32
VimScript
Java
emacs
linux
文本编辑器
elisp
反汇编
OLEDB
数据库编程
数据结构
内核编程
Masimaro
累计撰写
308
篇文章
累计收到
27
条评论
首页
栏目
心灵鸡汤
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
菜谱
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
页面
归档
友情链接
关于
搜索到
308
篇与
的结果
2015-08-05
菜单的使用
# Windows菜单的基本知识:1)顶级菜单:紧贴在标题栏下面的菜单称为顶级菜单,也可以叫做程序的主菜单;2)弹出式菜单:一般在顶级菜单上都有很多菜单项,单击这些菜单项时会弹出一个下拉式的菜单项,我们点击的这个菜单称为弹出式菜单3)菜单项:每一个可选菜单项被赋予一个唯一的ID,当用户单击某个菜单项时Windows会将该菜单项的ID发送给父窗口,父窗口通过WM_COMMAND消息处理菜单的单击消息,但是弹出式菜单没有ID,WM_COMMAND消息也不处理弹出式菜单的点击信息4)菜单加速键:主要是多个键的组合,当同时按下这些键的时候相当于点击了菜单的某个菜单项5)菜单项一般具有“可用”(Enabled)、“不可用”(disabled)、“变灰”(gray)几种选项,其中变灰选项将菜单项变成不可用的同时也会将菜单项变成灰色,所以当我们需要禁用某个菜单项的时候最好将它变灰,以便提示用户;6)菜单句柄:每一种菜单都有一个菜单句柄,包括弹出式菜单的菜单项,顶级菜单,弹出式菜单;菜单的创建:Windows中菜单有两种方式,一种是通过资源的方式通过可视化或者编写rc文件来创建一个菜单资源,并在代码中显示的加载,另一种是通过调用CreateMenu、AppendMenu、InsertMenu等函数创建菜单并插入相应的菜单项,下面对这两种方式一一进行说明:1)采用rc文件的方式:可以在visual studio中利用可视化的方式编辑菜单,在这里就不在说明,而需要手工编写rc文件请参考我的另外一篇博文当我们编辑好了rc文件之后有三种方法添加菜单:通过在创建窗口类的时候在lpszMenuName项的后面添加一个用于标示菜单的字符串,若菜单使用的是ID号作为标示那么可以使用宏MAKEINTRESOURCE;在函数CreateWindow或者CreateWindowEx中的相应参数中填入菜单句柄,为了获取这个句柄需要提前使用LoadMenu函数加载菜单,这个函数的功能是将资源文件中的菜单加载到内存,并返回一个菜单句柄,函数的原型如下:HMENU LoadMenu( HINSTANCE hInstance, // 当前应用程序的实例句柄 LPCTSTR lpMenuName // 菜单唯一标示,可以是字符串或者用MAKEINTRESOURCE转化而来的字符串 );第三种方式是先通过LoadMenu函数获取菜单句柄后在窗口创建后通过SetMenu函数设置菜单,该函数用于为指定窗口加载一个顶级菜单、该函数原型如下:BOOL SetMenu( HWND hWnd, // 需加载菜单的窗口句柄 HMENU hMenu // 菜单句柄 );各个方式的源代码如下:WNDCLASS wd = {0}; wd.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wd.hCursor = LoadCursor(NULL, IDC_ARROW); wd.hIcon = LoadIcon(NULL, IDI_APPLICATION); wd.hInstance = hInstance; wd.lpfnWndProc = WindowProc; wd.lpszClassName = "MenuClass"; //第一种方式 //wd.lpszMenuName = MAKEINTRESOURCE(IDM_MENU); wd.style = CS_HREDRAW | CS_VREDRAW; HMENU hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDM_MENU)); //加载加速键 HACCEL hAccelerator = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDA_MAIN)); if (!RegisterClass(&wd)) { int nErr = GetLastError(); return nErr; } //第二种方式 //HWND hWnd = CreateWindow("MenuClass", "Menu", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, hMenu, hInstance, NULL); //第三种方式 HWND hWnd = CreateWindow("MenuClass", "Menu", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); SetMenu(hWnd, hMenu);如果采用函数动态创建的方式,需要如下几个步骤:1)通过函数CreateMenu()创建一个顶级菜单;2)通过CreateMenu()创建一个弹出式菜单;3)利用AppendMenu()或者InsertMenu()向弹出式菜单中插入菜单项;4)利用AppendMenu()将弹出式菜单插入到顶级菜单中;5)用SetMenu函数将创建好的菜单加到程序下面分别说明这些函数的功能和用法:CreateMenu()用于创建一个菜单(函数会将菜单初始化为空菜单),并返回一个菜单句柄,函数原型如下:HMENU CreateMenu(VOID) AppendMenu()用于在顶级菜单、弹出式菜单的最后面的菜单项后查入新菜单项,函数原型如下: BOOL AppendMenu( HMENU hMenu, // 菜单项的句柄 UINT uFlags, // 新菜单项的类型,主要使用的是MF_STRING、MF_POUP(弹出式菜单) UINT uIDNewItem, // 新菜单项的ID,如果是弹出式菜单、则使用菜单的句柄 LPCTSTR lpNewItem //该值取决于第二个参数,若为MF_STRING则应该是一个以0结尾的字符串 ); InsterMenu()函数作用与AppendMenu相同,函数原型如下: BOOL InsertMenu( HMENU hMenu, // 菜单项的句柄 UINT uPosition, // 新菜单项的识别方式,主要有两种MF_BYCOMMAND和MF_BYPOSITION,在以后我们取菜单项的句柄或者对菜单项做其他操作,需要辨认时会有一定的作用,主要表明是靠ID号辨别还是靠在菜单中的相对位置(以0为第一个菜单项) UINT uFlags, // 新菜单项的类型,主要使用的是MF_STRING、MF_POUP(弹出式菜单) UINT uIDNewItem, // 新菜单项的ID,如果是弹出式菜单、则使用菜单的句柄 LPCTSTR lpNewItem //该值取决于第三个个参数,若为MF_STRING则应该是一个以0结尾的字符串 );下面是一个使用这种方式的例子#include <Windows.h> LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); #define IDM_FILE 100 #define IDM_ABOUT 200 #define IDM_CLOSE 300 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { WNDCLASS wd = {0}; wd.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wd.hCursor = LoadCursor(NULL, IDC_ARROW); wd.hIcon = LoadIcon(NULL, IDI_APPLICATION); wd.hInstance = hInstance; wd.lpfnWndProc = WindowProc; wd.lpszClassName = "MenuClass"; wd.style = CS_HREDRAW | CS_VREDRAW; if (!RegisterClass(&wd)) { int nErr = GetLastError(); return nErr; } HWND hWnd = CreateWindow("MenuClass", "Menu", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); //创建主菜单 HMENU hMenu = CreateMenu(); //创建弹出式菜单 HMENU hPopup = CreateMenu(); //向弹出式菜单中插入菜单项 AppendMenu(hPopup, MF_STRING, IDM_FILE, TEXT("文件")); AppendMenu(hPopup, MF_STRING, IDM_ABOUT, TEXT("关于")); InsertMenu(hPopup, MF_BYCOMMAND, MF_STRING, IDM_CLOSE, TEXT("关闭")); //将弹出式菜单插入到主菜单中 AppendMenu(hMenu, MF_POPUP,(UINT_PTR)hPopup, TEXT("系统")); SetMenu(hWnd,hMenu); if (NULL == hWnd) { int nErr = GetLastError(); return nErr; } ShowWindow(hWnd, nShowCmd); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_COMMAND: { if (IDM_ABOUT == LOWORD(wParam)) { MessageBox(hWnd, TEXT("About"), TEXT("TEST"), MB_OK); } } break; case WM_CLOSE: DestroyWindow(hWnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, uMsg, wParam, lParam); } return 0; }键菜单的使用:创建一个右键菜单有如下步骤(在WM_RBUTTONDOWN消息下处理):1)创建一个可用的菜单(一般是主菜单);2)根据主菜单获取弹出式菜单的句柄,使用函数GetSubMenu()2)加载菜单项3)获取鼠标点击的位置4)将客户区坐标转化为屏幕坐标(这一步千万别忘了)5)调用TrackPopupMenu函数,该函数用来显示一个快捷菜单,这个函数中需要填入菜单显示的位置,这个位置值为屏幕坐标,这也就是我们为什么需要转化坐标的原因;该函数的原型为:BOOL TrackPopupMenu( HMENU hMenu, // 快捷菜单的句柄 UINT uFlags, // 快捷菜单显示的类型 int x, // int y, //菜单显示点的坐标,根据第二个参数确定如何显示,一般有左对齐(最左边顶点为该坐标)、右对齐(右上角坐标为该坐标)、中间对齐(上边线的中点坐标为该坐标); int nReserved, // 该参数必须给0 HWND hWnd, // 显示快捷菜单的窗口句柄 CONST RECT *prcRect // 该参数被忽略,一般给NNULL );下面是一段例子代码:HMENU hMenu = LoadMenu(GetModuleHandle(NULL), MAKEINTRESOURCE(IDM_MENU)); hMenu = GetSubMenu(hMenu, 0); POINT ptChick = {LOWORD(lParam), HIWORD(lParam)}; ClientToScreen(hWnd, &ptChick); TrackPopupMenu(hMenu, TPM_LEFTALIGN, ptChick.x, ptChick.y, 0, hWnd, NULL);其他菜单操作的函数主要有:GetSystemMenu()获取系统菜单句柄;Deletemenu()从菜单中删除某一菜单项并销毁它RemoveMenu()从菜单中移出某一菜单项但不销毁它InsertMenu()在菜单中插入一个菜单项NodifyMenu()修改一个已存在的菜单项
2015年08月05日
6 阅读
0 评论
0 点赞
2015-07-24
从唯一实例谈静态成员
在实际的项目中我们可能需要这样一种类,它的对象在整个项目中只能有一个,在整个程序中只能创建一个类的对象,比如说,最常用的mp3播放软件Winamp,由于它需要独占计算机中的音频设备,因此该程序只允许自身运行唯一的一个例程。c++并没有提供这一特性,我们可以自己封装一个唯一实例的类,为了完成这个工作,我们需要一个静态的整型数据成员用于记录当前程序中类的实例个数,还需要一个指向自身的静态指针,指向新创建的类的对象。实现的代码如下:class CSingle { private: CSingle(){}; CSingle(CSingle &r){}; <span style="white-space:pre"> </span>void operator = (CSingle &r){}; ~CSingle(){}; public: static CSingle* Create(); static void Release(); private: static int m_nCount; static CSingle *m_pInstance; }; int CSingle::m_nCount = 0; CSingle *CSingle::m_pInstance = NULL; CSingle* CSingle::Create() { if (0 == m_nCount) { m_nCount++; m_pInstance = new CSingle; }else { return NULL; } return m_pInstance; } void CSingle::Release() { if (0 != m_nCount) { delete m_pInstance; m_nCount--; } return; }利用一下代码测试:CSingle *pObj = CSingle::Create(); CSingle *pObj1 = CSingle::Create(); cout<<pObj<<endl; cout<<pObj1<<endl; CSingle::Release();上述代码使用的是类的静态成员,静态成员用static关键字进行说明,类的静态成员可以是静态数据成员,也可以是静态函数成员,下面分别来说明这两个内容:类的成员函数:我们知道一般程序用来存储变量的内存分为静态存储区,和堆栈段,静态存储区用于存储静态变量和全局变量,而堆栈段用来存储函数中定义的局部变量,也就是说静态变量和全局变量在同一段内存空间中,也可以相当于一个全局变量,关键字static除了在定义静态变量的时候使用外,还可以作为限定符,限定定义的变量只能在某一区域内可见,在函数中定义的只在该函数中可见,在文件中定义的只在该文件中可见。所以说静态变量是一种限定了可见区域的全局变量,在类中定义的静态数据变量被限定只在类中可见,与类的普通成员间最大的区别在于普通成员只有在类存在时才会在内存中真正存在,而静态成员一旦定义就已经存在,且在整个内存中只有一份,这也就是说所有类共享这一个静态变量,任何一个类修改了变量的值,都会影响到其他类。因为它们有类的生命周期无关,所以不论类是否创建了对象,都可以使用它们,只是需要加上类域,因为static已经限定了它们只在类中可见。类的成员函数:静态函数成员与静态数据成员类似,也是相当于一个全局函数,所有类公用一份,它与类函数的不同在于,普通的类函数都会由编译器隐式的传入一个this 指针,而静态成员没有传入这样一个值,也就意味着这个函数并不知道是哪个类的对象在调用它,自然也就不知道该如何使用哪个类对象的普通成员,所以说c++规定静态函数成员不能访问非静态数据成员,而静态数据成员,所有类共享,所以只能访问静态数据成员。静态函数成员也受类的保护限制,即不能通过对象打点调用对象的私有成员。接下来分析这个程序,首先唯一实例必须用静态数据成员计算当前有几个类的实例,不能用普通的内成员,否则每个类都有一个计数器,而每个类的计数器可能都为1,这样达不到唯一的效果。如果用构造函数初始化的话,每个类对象都共享这块内存,所以我们如果用构造函数初始化,每个类的构造函数都有可能会修改这个值,导致错误的结果,所以我们不用类的构造函数,只能将它私有化,不让系统调用构造函数,系统提供了3中默认的构造函数,分别是不带参的构造函数、拷贝构造函数、赋值运算符重载的构造函数,所以为了不让系统调用任何默认构造,我们将这3个构造函数私有化,并且在构造函数中不做任何事情。不能用构造函数创建对象,也就是说我们需要利用其它方法创建对象,既然没有构造函数,那么通过对象打点调用其它函数创建肯定也是不可能的,我们需要一种函数即使类对象没有它们也存在,静态函数成员正是这种函数,所以我们需要创建的静态函数成员,并且通过动态内存创建一个类的对象,并用静态数据成员保存该指针。至此我们已经完成了一个唯一实例的类。
2015年07月24日
6 阅读
0 评论
0 点赞
2015-07-15
Windows程序设计学习笔记(五)——菜单资源和加速键的使用
菜单可能是Windows提供的统一用户界面中最重要的一种方式,菜单通常在标题栏的下一行显示,这一栏叫做菜单栏,菜单栏中的每一项称之为菜单项,菜单栏中的每一个菜单项在激活时会显现一个下拉菜单(也可以说是它的子菜单),下拉菜单中也可以有多个菜单项,每个菜单项又可以有子菜单,每个菜单项都有一个唯一的数字标示,称为菜单项的ID,但是有子菜单的菜单项没有ID。用户点击某项后,会产生一个WM_COMMAND消息发送到其父窗口,该消息中包含了这个菜单项的ID。菜单的创建可以通过可视化的方法创建,也可以通过编写资源脚本的方式创建菜单资源,在这里重点说明如何通过脚本编写的方式创建菜单//Menu IDM_MENU MENU BEGIN POPUP "文件(&F)" BEGIN MENUITEM "打开(&O)", IDM_OPEN MENUITEM "关闭(&C)", IDM_OPTION MENUITEM SEPARATOR MENUITEM "关闭(&X)", IDM_EXIT END POPUP "查看(&V)" BEGIN MENUITEM "字体(&V)...\tAlt + F", IDM_SETFONT MENUITEM "背景色(&B)...\tCtrl + Alt + B", 40009 MENUITEM SEPARATOR MENUITEM "被禁用的菜单项", ID_40010, INACTIVE MENUITEM "变绘的菜单项", ID_40011, GRAYED MENUITEM "大图标(&G)", 40012 MENUITEM "小图标(&M)", IDM_SMALL MENUITEM "列表(&L)", 40015 MENUITEM SEPARATOR MENUITEM "详细信息(&D)", IDM_DETAIL POPUP "工具栏" BEGIN MENUITEM "标准按钮(&S)", 40019 MENUITEM "文字标签(&C)", 40020 MENUITEM "命令栏(&I)", 40021 END MENUITEM "状态栏(&U)", 40022 END POPUP "帮助(&H)", HELP BEGIN MENUITEM "帮助主题(&H)\tF1", IDM_HELP MENUITEM "关于本程序(&A)...", 40025 END END IDA_MAIN ACCELERATORS BEGIN VK_F1, IDM_HELP, VIRTKEY, NOINVERT "B", IDM_SETCOLOR, VIRTKEY, CONTROL, ALT, NOINVERT "F", IDM_SETFONT, VIRTKEY, ALT, NOINVERT END下面来分析这段代码:首先是通过一些列的宏定义来定义各种菜单项的ID,菜单ID用于唯一标识一个菜单项,不同的菜单项所用的ID号应该不同除非这些菜单项完成相同的工作,菜单项的ID可以是16位的整数,同时菜单项也可以用字符串来表示,在调用相应的API函数的时候检测到这个值大于10000h的时候将它作为字符串指针,这个时候用字符串唯一标示菜单项,当这个数小于10000h时表示的是一个数字,这个时候用数字唯一标示。菜单在脚本中的定义格式为:菜单ID MENU [DISCARDABLE] BEGIN 菜单项的定义 END菜单ID:每个菜单都有的一个唯一的标示,可以是字符串,可以是数字。DISCARDABLE:菜单的内存属性,标示菜单在不再使用的时候可以暂时从内存中释放以节省内存菜单项的定义方法有3种分别对应不同类型的菜单项:MENUITEM 菜单文字,命令ID, [选项列表](用法1) MENUITEM SEPARATOR (用法2) popup 菜单文字 [,选项] BEGIN MENUITEM 菜单文字,命令ID, [选项列表] ......................... END (用法3)用法1:用于创建一个菜单项;用法2:用于创建一个分割符;用法3:用于创建一个菜单项的子菜单项;菜单文字:显示在菜单项上的文字,需要字符串中某个字母带下划线的话,可以在字母前面加上一个&符号,比如上面的“状态栏(&U)”,带下划线的字母被系统当做快捷键,比如我们点击查看菜单项,打开它的子菜单,在按下字母U就相当于直接点击菜单中的状态栏一项;命令ID:上述我们定义的菜单ID项,父窗口的WM_COMMAND消息的参数中带有这个值,通过这个值判断是哪个菜单项被点击;选项列表:用来定义菜单项的各种属性,他可以是下面的值:CHECKED——表示打上选定标志(菜单项前有一个钩)GRAYED——菜单项变灰INAVTIVE——菜单项不可用MENUBREAK或者MENUBARBREAK——表示这个菜单项和以后的菜单项在新的一列显示;对于popup后面的选项可以是下面值的一个:GRAYED——菜单项变灰INAVTIVE——菜单项不可用HELP——菜单项靠右边显示快捷键的定义格式为:快捷键ID ACCELERATORSBEGIN 键名, 命令 [, 类型] [,选项] END键名:表示加速键对应的按键,可以有3中方式定义:“^字母” :表示Ctrl加上字母”字母“:表示字母,这时类型必须指明为VIRTKEY数值:表示ASCii码为该数值的字母,这个时候类型必须指明为ASCii命令ID:按下加速键以后Windows向程序发送的命令ID,如果想把加速键和菜单项关联起来,这里就是相应的菜单项的ID类型:用以指定键的定义方式,可以是ASCii或者VIRTKEY选项:可以是Alt、control、shift中的一个或多个,表示这些键和键名定义的键一起组成一个快捷键菜单项的消息响应:菜单项的处理一般由菜单父窗口处理,菜单被选中中时会向其父窗口发送一条WM_COMMAND的消息,将该项的相关信息告诉给其父窗口,该消息的说明如下:WM_COMMAND wNotifyCode = HIWORD(wParam); // 通知码 若对应的资源为加速键该值为1,若为菜单项则为0 wID = LOWORD(wParam); // 菜单项、加速键、控件的ID hwndCtl = (HWND) lParam; // 控件句柄我们可以在WM_COMMAND消息的处理中添加如下的内容,让其显示我们选中的是那一项:if (IDM_HELP == LOWORD(wParam)) { MessageBox(hWnd, "您选中了帮助主题菜单项","提示", MB_OK); }当选择“帮助主题”的时候,会弹出一个消息框,如果按下F1键也会显示这样一个消息框,因为我们已经将加速键绑定到对应的菜单项上面。
2015年07月15日
6 阅读
0 评论
0 点赞
2015-07-12
8086cpu中的标志寄存器与比较指令
标志寄存器在8086CPU中有一个特殊的寄存器——标志寄存器,该寄存器不同于其他寄存器,普通寄存器是用来存放数据的读取整个寄存器具有一定的含义,但是标志寄存器是每一位都有固定的含义,记录在运算中产生的信息,标志寄存器的机构如下图:寄存器中的第1、3、5、 12、 13、 14 、15位在8086CPU中没有使用,其他位置代表不同的含义,各个位置的意思如下(该表截自百度知百科中的标志寄存器):一般我们常用到的是如下几个标志CF:CF标志表示进位,我们知道对于8086CPU来说,寄存器只能存储16位二进制数,但是有些指令产生的结果可能大于16位,比如:mov ax,200h add ax, 0fffffh产生的结果已经超过16位,由于ax寄存器只能保存16位数据,因此高位产生的数据必然被丢弃,但是也不是简单的丢弃,这个时候CF标志位会变成1,表示结果产生了进位;PF:表示标志,这个奇偶不是数字本身是奇数或者是偶数,表示的是某个数据中有奇数个1或者是偶数个1;ZF:0标志:表示计算结果是否为0;SF:符号标志记录相关计算结束后得到的结果是否为负,若为负则标志位为1,否则标志位为0;方向标志:方向标志用于内存单元的拷贝,我们在将一段内存单元拷贝到另一段内存中去时使用循环一个字节一个字节的拷贝,但是8086CPU提供了一个指令movsb 、movsw分别是按字节拷贝和按字拷贝,这两个指令所对应的源内存地址只能用ds:[si],目的地址只能用es:[di]表示,其中DF标志指明我们是从低到高字节拷贝还是从高到低字节拷贝,当df = 1时,地址递减, = 0时地址递增;OF:溢出标志,它的作用与CF相同,当得到的操作数大于16位的时候,该标志置为1,但是OF用于有符号数,而CF用于无符号数。IF:在DEBUG中使用,当我们启动DEBUG模式的时候,一条指令执行完后,该寄存器被置为1,这个时候会调用相应的中断程序,使我们的代码在该位置停止执行,以便我们查看相应的结果;CMP指令CNP指令使用的格式为CMP 操作数1,操作数2;cmp指令的作用是将两个操作数相减,并根据结果改变标志寄存器的值,但是并不保存计算结果,当两个数都为正时,如果ZF = 0则说明两个数相等,这个指令一般用于判断两个数据的大小关系,如果我们只是使用它,那么在判断两个数字的大小关系上,可能还会判断其他内容,假设我们使用了cmp n1, n2这样的指令的话,那么可能出现三种情况:n1 = n2:要判断是不是出现这种情况只需要判断ZF是否为0,当ZF为0时两数相等;n1 >n2:我们知道大数减去小数结果一定为正,是不是只需要判断SF呢?不是!在数学上大数减去小数结果一定为正这是肯定的,但是在计算机中确并不一定是这样的,我们需要考虑到是否溢出的问题比如“ffffh - (-2)”这个结果在数学上肯定是负数,但是在计算机上结果却为正,,这个时候除了要校验SF还需要校验OF,当溢出产生的时候结果正好与我们使用SF校验的相反;n1 < n2:这个结果的校验与上述的校验类似;然而幸运的是,在我们实际比较两个数据大小的时候我们并不需要这样,80886CPU为我们提供了一系列指令用来做这个工作:指令含义检测的相关标志位je(jmp equal)当两数相等时跳转ZF = 1jne(jmp not equal)当两数不相等的时候跳转ZF = 0jb(jmp blow)小于时跳转CF = 1jnb(jmp not blow)不小于时跳转CF = 0ja(jmp above)大于时跳转CF= 0且ZF = 0jna(jmp not above)不大于时跳转CF = 1或ZF = 0以上指令指示检测标志寄存器中相应位置的值来判断,至于在它的前面是否使用了cmp指令CPU并不关心,在执行这些指令的时候只要CPU检测到相关的标志满足条件则会自动跳转,比如执行下面的指令:mov ax,0 add ax,ax je s1 inc ax s1: inc axCPU执行到je的时候检测到ZF寄存器为0,这个时候会自动跳转到s1处的代码中执行,不会执行je的下一行代码。为了实现比较功能最好将cmp与这些指令配套使用。高级语言中的if语句正是用着一套指令实现的一般在破解时可能需要修改某些标识,以达到跳转或者不跳转的目的,下面是我从小甲鱼网站上找到的图片,记录了各个跳转指令实现所需要的条件,根据这个表中的内容,修改相应标识,就可以控制程序执行流程
2015年07月12日
3 阅读
0 评论
0 点赞
2015-06-26
windows编程学习笔记(三)ListBox的使用方法
ListBox是Windows中的一种控件,一般被当做子窗口使用,Windows中所有子窗口都是通过发送一个通知码到父窗口父窗口通过WM_COMMAND消息接收,并在此消息中处理,并控制子窗口,ListBox自然也不例外,ListBox中有它独有的消息,通知消息,风格,查看MSDN可以看到风格主要有:LBS_EXTENDEDSEL 用户可以通过SHIFT + 鼠标或者其他组合键进行多选(只能通过SHIFT + 鼠标或者其他组合键)LBS_HASSTRINGS 指定一个自绘的列表框中包含有字符串项,这些字符串的指针由应用程序管理,我们可以利用GetText函数得到相应的字符串LBS_MULTICOLUMN 列表框可以有多列,默认情况是只有一列即一行只有一个字符串,我们可以使用 SetColumnWidth设置列宽LBS_MULTIPLESEL 用户可以同时选择多项(用户单击一项时这项被选中,单击另一项时,这两项都被选中,选择多项时只需要点击不同的项,不需要用组合键的方式,同一项第一次单击时选中,第二次单击时取消选中)LBS_NOINTEGRALHEIGHT 列表框的大小由系统在创建这个列表框的时候决定。一般不会只显示部分列表项LBS_NOREDRAW 列表框的大小在显示后不会改变,但是可以通过发 WM_SETREDRAW消息来取消这一风格LBS_NOTIFY 当用户单击或双击时会发送一条消息到父窗口,风格,父窗口将接收不到用户选择的项LBS_OWNERDRAWFIXED 父窗口负责绘制列表框,这个时候列表框中的项的大小都一样LBS_OWNERDRAWVARIABLE 列表项的大小可以不一样LBS_SORT 字符串会以首字母排序LBS_STANDARD 系统会将字符串排序,同时父窗口会收到用户单机或者双击鼠标的消息LBS_USETABSTOPS 允许用户使用TAB键在各项中切换LBS_WANTKEYBOARDINPUT 当列表框通过键盘获得焦点时会向父窗口发送 WM_VKEYTOITEM 或 WM_CHARTOITEM 消息,以便程序处理特殊的键盘消息LBS_DISABLENOSCROLL 列表框会拥有一个垂直滚动条 ,在列表框不能够显示所有项时显示。一般父窗口通过向列表框发送消息来控制列表框的行为,而发送的消息一般有以下几种:LB_ADDFILE 添加文件LB_ADDSTRING 添加字符串LB_DELETESTRING 删除字符串LB_DIR 添加文件名列表LB_FINDSTRING 返回列表框中的一个字符的索引LB_FINDSTRINGEXACT 在列表框查找第一个与特定字符匹配的字符并返回它的索引LB_GETANCHORINDEX 获取锚点的索引,锚点就是在多选模式下选中的第一项LB_GETCARETINDEX 在多选模式下返回具有焦点条目的索引LB_GETCOUNT 获取列表框中子项的总数LB_GETCURSEL 获取被选中的子项的索引,只在单选模式下有效LB_GETHORIZONTALEXTENT 获取水平滚动条的宽度LB_GETITEMDATA 获取与指定列表项相关的程序的自定义值(长度为32位)LB_GETITEMHEIGHT 获取列表项的高LB_GETITEMRECT 获取列表项边界矩形的大小LB_GETLOCALE 获得当前列表的区域,可以通过该区域决定正确的排序规则或者显示排序后的文本LB_GETSEL 获得列表项的选择状态,被选中时大于0,未被选中时为0,发生错误时小于0LB_GETSELCOUNT 在多选模式下获取当前被选中的项总数LB_GETSELITEMS 在多选模式下,获取选项的值,需要提供一个相应的数组的首地址用来保存返回结果LB_GETTEXT 获取指定项的字符串LB_GETTEXTLEN 获得指定项字符串的长度LB_GETTOPINDEX 获取列表框中显示的第一列的索引,当使用滚动条使显示内容发生变化时,这个索引也会发生改变LB_INITSTORAGE 需要加入大量列表项时使用LB_INSERTSTRING 添加列表项,但是与LB_ADDSTRING不同的是,加入后新字符串不参加排序LB_RESETCONTENT 清除所有列表项LB_SELECTSTRING 从指定位置向后查找我们指定的字符串项,找到后将该项设置为选中状态LB_SELITEMRANGE 在多选模式下,将某一区域内一个或多个项设置为选中状态LB_SETCARETINDEX 在多选模式下,设置给定索引值的矩形设置为焦点矩形,如果该值没有显示,那么滚动条将会自动滚动到相应行LB_SETCOLUMNWIDTH 在多列模式下设置所有项的的列宽,使用这个消息必须保证列表框有LBS_MULTICOLUMN风给LB_SETCOUNT 设置列表项的总数,用于具有LBS_NODATA风给但是不具有LBS_HASSTRINGS风格的列表框LB_SETCURSEL 设置某项处于被选中状态,并将该项加亮显示LB_SETHORIZONTALEXTENT 设置水平滚动条的宽度,当列表框的宽度不足以显示所有项的时候,滚动条出现,否则隐藏LB_SETITEMDATA 设置特定项的值LB_SETITEMHEIGHT 设置列表项的宽。LB_SETLOCALE 设置列表框的当前区域LB_SETSEL 在多选模式下选中某一字符串LB_SETTABSTOPS 设置TAB键停止的位置LB_SETTOPINDEX 设置列表框中的某一项处于可见位置列表框向其父窗口发送的通知码为:LBN_DBLCLK 当某一项被单击时发送LBN_ERRSPACE 当系统不能分配足够的内存来进项相应的处理时发送该通知码LBN_KILLFOCUS 当列表框中某一项失去焦点时发送LBN_SELCANCEL当用户取消选择时发送LBN_SELCHANGE 当用户选择改变时发送LBN_SETFOCUS 当某一项获得焦点时发送下面是一个小例子:(在窗口程序中创建列表框,框中选择人物姓名,可以得到人物的相应信息)利用到的结构体的定义如下:struct PERSON { const char *pszName; int nAge; const char *pszPhoneNum; };首先在WM_CREATE中创建:HWND hList = CreateWindow("LISTBOX", "", WS_CHILD | WS_BORDER | WS_VISIBLE | LBS_HASSTRINGS | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT, 0,0,200,800,hWnd, (HMENU)123, g_hInst, NULL); for(int i = 0; i < 3; i++) { SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)(g_Person[i].pszName)); } SendMessage(hList, LB_SETCURSEL, (WPARAM)0, 0);关于列表框的显示与行为控制都在WM_COMMAND中处理:if (123 == LOWORD(wParam)) { if (LBN_DBLCLK == HIWORD(wParam)) { int nIndex = SendMessage((HWND)lParam, LB_GETCURSEL, 0, 0); sprintf(szBuf, "姓名:%s 年龄:%d 电话:%s", g_Person[nIndex].pszName, g_Person[nIndex].nAge,g_Person[nIndex].pszPhoneNum); InvalidateRect(hWnd, NULL, TRUE); } }当点击某一项后需要在窗口中显示,显示的工作可以在WM_PAINT中完成PAINTSTRUCT ps; RECT rtClient; GetClientRect(hWnd, &rtClient); HDC hDc = BeginPaint(hWnd, &ps); DrawText(hDc, szBuf, strlen(szBuf), &rtClient, DT_CENTER | DT_VCENTER | DT_SINGLELINE); EndPaint(hWnd, &ps);
2015年06月26日
4 阅读
0 评论
0 点赞
2015-06-26
Windows程序设计学习笔记(四)自绘控件与贴图的实现
Windows系统提供大量的控件供我们使用,但是系统提供的控件样式都是统一的,不管什么东西看久了自然会厌烦,为了使界面更加美观,添加一些新的东西我们需要自己绘制控件。控件在默认情况下并不进行自绘,如果是在窗口中利用CreateWindow创建的话要在风格中加入一个对应的自绘风格,这个一般在MSDN中都可以查到比如按钮的自绘风格是BS_OWNERDRAW、列表框是 LBS_OWNERDRAWFIXED (列表项的高度一致)、LBS_OWNERDRAWVARIABLE(列表项的高度可以不一致),如果我们是在对话框下通过资源的方式创建的可以在其属性上将自绘风格选上。控件被改为自绘时,每当需要自画时控件都会向其父窗口发送一条WM_DRAWITEM消息,该消息中两个参数的如下:WM_DRAWITEM idCtl = (UINT) wParam; // 控件ID lpdis = (LPDRAWITEMSTRUCT) lParam; // 一个指向DRAWITEMSTRUCT的结构体结构体DRAWITEMSTRUCT的定义如下:typedef struct tagDRAWITEMSTRUCT { UINT CtlType; //控件类型 UINT CtlID; //控件ID UINT itemID; //控件子项的ID只用于菜单项、组合框、列表框 UINT itemAction; //控件行为,一般在一个动态的行为发生时产生 UINT itemState; //控件状态,在处于某个静态时产生 HWND hwndItem; //控件句柄 HDC hDC; //绘制控件的设备上下文句柄 RECT rcItem; //控件项的矩形范围 DWORD itemData; //程序为菜单项、列表项、组合框中的列表项指定的32值 } DRAWITEMSTRUCT; 对于列表框和组合框,在重绘时会发送一条消息:WM_MEASUREITEM,该消息用于设置列表项的大小信息。可以在该消息中利用下面的代码设置行高:LPMEASUREITEMSTRUCT lpmis = (LPMEASUREITEMSTRUCT) lParam; lpmis->itemHeight = 32;下面来说说贴图。贴图的一般步骤为:1)使用LoadBitmap加载一幅图片,该函数的原型为:HBITMAP LoadBitmap(//函数返回一个对应位图的对象句柄 HINSTANCE hInstance, //实例句柄,系统通过这个值找到对应的位图 LPCTSTR lpBitmapName //位图名称,这个值可以通过MAKEINTRESOURCE宏获得 );2)用CreateCompatiableDC函数创建一个与指定DC兼容的内存设备描述符。3)利用SelectObject函数将对应位图选入到对应的HDC中,该函数返回一个原来未被替代的对象句柄,一般我们需要保存这个变量以便以后用于恢复。4)使用BitBlt贴图函数BitBlt,该函数的原型如下:BOOL BitBlt( HDC hdcDest, // 目的控件的设备上下文句柄 int nXDest, // int nYDest, // 这两个参数表示需要贴在目的设备对应矩形中的哪个位置,分别是客户坐标的横坐标和纵坐标 int nWidth, int nHeight, //图片的大小和宽度 HDC hdcSrc, // 源图片所在的DC的句柄 int nXSrc, int nYSrc, //表示从原图片的哪个像素点开始,这两个值表示开始位置的横纵坐标 DWORD dwRop // 贴图的方式,它规定了原图片颜色如何与目标控件颜色组合已形成最终的颜色 );对于第二步的操作并不是必要的,在贴图时我们可以使用同一个句柄作为原和目的句柄,但是当我们需要贴的图片过多,使用同一个句柄会造成客户区的闪烁,所以可以另外定义一个句柄,保存我们所需要的所有图片,然后一次性通过源DC贴到目的DC,这样可以一次完成,避免了客户区的闪烁。下面的例子采用的是ListBox控件:HWND hList = CreateWindow("LISTBOX", "", WS_CHILD | WS_BORDER | WS_CLIPSIBLINGS |WS_VISIBLE | LBS_HASSTRINGS | LBS_NOTIFY | LBS_OWNERDRAWFIXED , 0,0,200,800,hWnd, (HMENU)123, g_hInst, NULL);//在创建ListBox时定义为自画风格,同时WS_CLIPSIBLINGS风格指明在重绘子窗口时不重绘整个客户区在WM_DRAWITEM消息中编写重绘的代码:LPDRAWITEMSTRUCT lpDis = (LPDRAWITEMSTRUCT)lParam; RECT rtListItem = lpDis->rcItem; if (ODT_LISTBOX == lpDis->CtlType) { if (ODS_SELECTED & lpDis->itemState)//当某项被选中时设置虚线框并使背景为蓝色 { rtListItem.right -= 2; rtListItem.bottom -= 2; HBRUSH hBlue = CreateSolidBrush(RGB(0,0,255)); HGDIOBJ hOld = SelectObject(lpDis->hDC, hBlue); FillRect(lpDis->hDC, &rtListItem, hBlue); DrawFocusRect(lpDis->hDC, &rtListItem); } //贴图,并将图片背景色设置为所在矩形框的背景色 <span style="white-space:pre"> </span>HBITMAP hBitMap = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP1)); HDC hMerDc= CreateCompatibleDC(lpDis->hDC); SelectObject(hMerDc, hBitMap); BitBlt(lpDis->hDC, lpDis->rcItem.left, lpDis->rcItem.top, lpDis->rcItem.right - lpDis->rcItem.left, lpDis->rcItem.bottom - lpDis->rcItem.top, hMerDc, 0, 0, SRCAND); SelectObject(lpDis->hDC,hBitMap); DeleteObject(hMerDc); <span style="white-space:pre"> </span>//将文字设置为透明、并显示文字 SetBkMode(lpDis->hDC, TRANSPARENT); DrawText(lpDis->hDC, g_Person[lpDis->itemID].pszName,strlen(g_Person[lpDis->itemID].pszName), &(lpDis->rcItem),DT_CENTER | DT_VCENTER | DT_SINGLELINE); }
2015年06月26日
0 阅读
0 评论
0 点赞
2015-06-19
Windows程序设计笔记(二) 关于编写简单窗口程序中的几点疑惑
在编写窗口程序时主要是5个步骤,创建窗口类、注册窗口类、创建窗口、显示窗口、消息环的编写。对于这5个步骤为何要这样写,当初我不是太理解,学习到现在有些问题我基本上已经找到了答案,同时对于Windows对于窗口的管理机制有了更深的认识,下面我通过问答的方式,一一写出自己之前的疑惑。问题一、窗口类与窗口之间有何关系?答:窗口类与窗口就好像C++中类与对象的关系,窗口是窗口类的具体表现,在注册窗口类成功后,系统并没有创建窗口,只是分配的相应的存储空间存储了我们为窗口类填写的一些信息。只有调用CreateWindow后系统才会创建窗口。窗口类中的成员变量定义的是这一类窗口的共性,比如定义窗口类风格为子窗口,那么用这个窗口类创建的窗口就都是子窗口。而创建窗口时传入的参数是具体窗口显示形式,比如大小、长宽等;既然窗口类是窗口的共性,那么窗口过程自然是所有用该类创建的窗口都公用这个窗口过程,窗口过程根据窗口句柄来判断处理那个窗口,而Windows中提供了获取并修改窗口过程的方法(超类化),所以只要掌握了窗口句柄那么就可以控制该类的所有窗口。问题二、为何需要注册窗口类,而不是根据我们填写的窗口类结构体来直接创建?答:在程序中为窗口类定义了一个变量,填写好各个成员变量后,这个只是我们自己知道我们定义了一个新的窗口过程但是系统并不知道我们,系统中有一个专门的表用来存储系统中各个窗口类的信息,注册窗口类实际上是将我们填写的窗口类的信息添加到系统的这个表中,以后创建时系统会在这个表中查找相应的窗口类。问题三、创建窗口时使用的是窗口类名而不是我们定义的窗口类的变量?答:上面说过,系统中有一个专门用于管理各个窗口类的表,在调用CreateWindow函数时会首先在表中查找是否有这个类,没有的话就返回出错,并不会在我们所定义的窗口类结构体变量的内存中查找,通过这一点我们可以知道其实对于所有的窗口类只需要使用一个结构体变量来创建所有的窗口类,只要注册后系统将相关信息存储到窗口类表中,改变这个变量并不会对前面创建的窗口类产生影响。窗口类表中采用类名作为主码(不知道是不是真的采用数据库的相关方法存储,但是系统是根据类名来唯一确定一条窗口类的信息),并不是保存类结构体变量的地址,所以注册后这个窗口类就与这个变量没有关系。问题四、为何需要一个窗口句柄、为何系统不直接利用窗口类生成一个窗口,用窗口类名表示窗口窗口?答:在实际使用中,可能很多窗口具有相同或者相似的性质,因此为了代码的重用,系统抽象出一个窗口类用来管理具有共性的窗口,这样就表示一个窗口类可以产生多个窗口,这样用窗口类的信息管理窗口自然就不现实,而系统采用句柄的方式,而窗口句柄又是什么呢?系统对每个窗口也有一张表,而这个句柄就是相应的表项的一个索引。问题五、在消息环中GetMessage和Dispatchmessage各有什么作用,为什么一个应用程序只需要一个消息环而不是每个窗口一个消息环?答:这就涉及到系统的消息机制,Windows采用的是消息机制,每一个应用程序都有一个消息队列,系统有一个总的消息队列用来存储所有的产生的消息,在我们产生相应的操作时,首先由硬件捕捉到再由驱动程序做简单的翻译,再由系统根据传来的信息,组织生成一个MSG结构体,然后由系统根据MSG 中的第一参数发送到相应应用程序的消息队列中,这个是由PostMessage或者是SendMessage来完成,应用程序会不断的从自己的消息队列中取出消息,这是由GetMessage完成,取出后根据MSG中的HWND参数确定是哪个窗口的消息,从而发送到相依的窗口过程中。每个应用程序只有一个消息环,而取出消息和将消息分配到对应的窗口过程都争对的这一个消息队列自然没有必要写多个消息环问题六、系统是如何根据窗口句柄找到相应的窗口过程的?答:系统中有两个表分别管理窗口类和窗口,窗口类中最重要的信息是窗口类名和窗口过程地址,有了类名就可以在定义窗口时找到类的相关信息,有了窗口过程地址就可以处理消息,毕竟对于程序而言最重要的还是对于信息的处理,窗口什么的都只是用于与用户更好的交互。而系统在处理消息时是如何知道该调用哪个窗口过程的呢,有一种思路是根据消息中的HWND找到窗口表项,根据表项找到相应的窗口类,最后根据窗口类找到对应的窗口过程,但是实际上系统并不是这样做的,当要处理大量的消息时这样查找效率太低,所以系统的做法是在窗口表项中增加一些空间,用来存储从窗口类中拷贝的信息,在创建窗口时系统将窗口过程等重要信息拷贝一份放到相应的窗口信息表项中,在查找时只要找到窗口就可以找到窗口过程,所有在子类化时我们只是修改窗口表中的窗口过程,这样只改变对应窗口的窗口过程,而用该类创建的其他窗口的窗口过程并不受影响,而改变窗口类的窗口过程,则所有用该类创建的窗口的窗口过程都被修改。
2015年06月19日
5 阅读
0 评论
0 点赞
2015-06-15
Windows程序设计学习笔记(一)Windows内存管理初步
学习Windows程序设计也有一些时间了,为了记录自己的学习成果,以便以后查看,我希望自己能够坚持写下一系列的学习心得,对自己学习的内容进行总结,同时与大家交流。因为刚学习所以可能有的地方写不不正确,希望大家能够指出。在学习了一定的Windows API后我决定进入到一些基础的学习,希望能够学习一些原理性的知识,能够做到知其然的同时知其所以然。为了达到这个目的,这段时间我学习了一些计算机的基础知识,在这写下这篇博客,总结一下。在早期的16位8086CPU中我们使用段与段内的偏移偏移的方式寻址,得到的是真实的物理地址,当时的寄存器是16位而地址总线是20位,为了充分利用这些地址总线,Intel的工程师采用的是分段的方式,将段寄存器与通用寄存器中的数值通过地址加法器合成20位的地址,具体的合成方式为段地址 * 16 + 偏移地址,利用这种方式得到的都是真实的物理地址。8086系列的CPU之所以成为一个划时代的产物就是因为这种分段的思想,而这种思想一直沿用至今。但是8因为086CPU得到的都是真实的物理地址,所以在早期的程序设计中不得不详细考虑内存段的划分,有可能出现后一个程序将前一个程序的内存占用,这种方式非常不安全。到32位CPU的诞生产生了一种新的寻址方式,80386CPU有32位地址总线,寻址区间为2的32次方为4GB。所以用32位的通用寄存器都可以访问4GB的内存空间,这个时候段寄存器不在存储段的首地址段,寄存器DS、CS、ES等寄存器中存储的是段选择子的索引。段选择子的概念出现在32位CPU中的保护模式中,在保护模式下,每个内存段都有一个64位的结构体用来描述这段内存的基本信息叫做段描述符,段描述符包括安全级别,段的基地址等信息,系统将所有内存段的64位描述符存储在一个段描述符表中,将段寄存器中的16位数据作为段描述符表的索引,称为选择子。为了管理段描述符表,80386引入两个寄存器一个是48位的GDTR(全局段描述符表寄存器)另一个是16位的LDTR(局部段描述符表寄存器)分别指向GDT(全局段描述符表)和LDT(局部段描述符表)。GDT(Global Descriptor Table)包含系统中所有任务都可用的段描述符,通常包含描述操作系统所使用的代码段、数据段和堆栈段的描述符及各任务的LDT段等;全局描述符表只有一个。而LDTR(Local Descriptor Table)80386处理器设计成每个任务都有一个独立的LDT。它包含有每个任务私有的代码段、数据段和堆栈段的描述符,也包含该任务所使用的一些门描述符,如任务门和调用门描述符等。系统根据不同的任务切换不同的LDT。这样便于实现不同进程间内存的隔离。为了确定所在段描述符的位置,段寄存器中的16位数据中只有13位表示段描述中的索引。第0位、第1位合起来表示程序的等级,第2位TI位用来表示在段描述符的位置;TI=0表示在GDT中,TI=1表示在LDT中。对于一个虚拟地址XXXX:YYYYYYYY首先判断XXXX中TI位的值,当TI = 0时表示的是全局段描述符表,这个时候首先利用GDTR中的值确定GDT的位置,然后直接取段寄存器中高13位的值作为索引在GDTR中查找得到相应的段描述符,由于段描述符包含段的基址、限长、优先级等各种属性,这就得到了段的起始地址;当TI等于1时表示的是局部的段描述符表,这个时候寻址变得相对比较复杂,第一步还是从GDTR获得GDT的位置,然后根据LDTR中的16位数值作为索引在GDT中查找LDT所在位置,然后才是根据XXXX中的高13位作为索引在LDT中查找得到相应的段描述符,由于段描述符包含段的基址、限长、优先级等各种属性,这就得到了段的起始地址。这两种方式的步骤如下图:12在这里我们只是说得到线性地址并没有说得到内存地址,那么线性地址是否就是内存地址呢?可以是也可以不是,这取决于80386的分页机制是否被使用。在早期的分段模式下,系统回收程序使用的内存可能会残留很小的内存碎片,导致任何程序都不能使用,为了解决这一问题,80386CPU提供了一种分页机制,系统将固定大小的内存块分为一页,在一页中在使用段分配的方式,每页的大小有系统决定,Windows系统的页大小为4KB。每页物理内存可以根据“页目录”和“页表”,随意映射到不同的线性地址上。这样,就可以将物理地址不连续的内存的映射连到一起,在线性地址上视为连续。而是否启用内存分页机制是由80386处理器新增的CR0寄存器中的位31(PG位)决定的。如果PG=0,则分页机制不启用,这时所有指令寻址的地址(线性地址)就是系统中实际的物理地址;当PG=1的时候,80386处理器进入内存分页管理模式,所有的线性地址要经过页表的映射才得到最后的物理地址。当每页大小为4KB时,我们得到的32位线性地址中高20位表示页号,低12位表示页中的偏移地址,这样通过页映射的方式我们可以将线性地址转化成对应的物理地址。到这里我们可引出几个重要的概念:1)32位机器中共有2中段描述符表,每个表中有2^13个表项,每个表项代表一个段首地址,而每一段的段内偏移最大为2^32B所以32位机器的理论寻址范围为2^13 2^1 2^32 = 2^(13 + 1 + 32) = 64TB;2)系统为每个应用程序分配4GB的虚拟内存,并且由于保护模式的相关机制,这4GB的内存为每个应用程序独享3)由于保护模式不同应用程序中相同的逻辑地址最终映射到不同的内存地址,所以在应用程序中我们不必过多操心程序所使用的内存被其他应用程序占用。在Windows的保护模式中,将应用程序分级分为RING0到RING3,其中RING0的级别最高、GING3的级别最低,虽说分为4个级别但是实际上只使用了两个,Windows为了与其他CPU兼容,只使用2个,RING0主要是内核代码、RING3主要是用户代码,那是不是说RING3级别的代码就不能访问RING0级别的内存呢?这个自然也不是,Windows我们都知道Windows提供了一系列的API ,其中我们可以调用相应的API访问内核所在的内存,只是不能直接访问内核代码,也就是说不能直接用jmp指令访问内核代码,但是可以使用call指令调用,API对于程序员来说是不透明的。Windows保护模式下主要机制有:1)Windows提供不同安全级别,不同安全级别的代码访问内存的权限也不一样2)不同进程的内存都是独立的,每个进程独享自己的4GB内存,不同进程即使在代码中使用相同的虚拟地址,系统也会映射到不同 的地址空间
2015年06月15日
6 阅读
0 评论
0 点赞
1
...
30
31