首页
归档
友情链接
关于
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结构
页面
归档
友情链接
关于
搜索到
77
篇与
的结果
2015-11-04
Win32 API 三态按钮的实现
Windows平台提供了丰富的控件,但是在使用中我们不会使用它提供的默认风格,有时候需要对控件进行改写,让它展现出更友好的一面,这次主要是说明三态按钮的实现。三态按钮指的是按钮在鼠标移到按钮上时显示一种状态,鼠标在按下时展现一种状态,在鼠标移开时又展现出另外一种状态,总共三种。当然鼠标按下和移出按钮展示的状态系统自己提供的有,这个时候在处理这两种状态只需要贴相应的图片就行了,三态按钮的实现关键在于如何判断鼠标已经移动到按钮上以及鼠标移出按钮,然后根据鼠标的位置将按钮做相应的调整。判断鼠标在按钮的相应位置,系统提供了一个函数_TrackMouseEvent用户处理鼠标移出、移入按钮。函数原型如下:BOOL _TrackMouseEvent( LPTRACKMOUSEEVENT lpEventTrack );函数需要传入一个TRACKMOUSEEVENT类型的指针,该结构的原型如下:typedef struct tagTRACKMOUSEEVENT { 2 DWORD cbSize;//该结构体所占空间大小 3 DWORD dwFlags;//指定服务的请求(指定它需要侦听的事件),这次主要用到的是TME_HOVER和TME_LEAVE(侦听鼠标移开和移入事件) 4 HWND hwndTrack;//指定我们需要侦听的控件的句柄 5 DWORD dwHoverTime;//HOVER消耗的时间,可以用系统提供的一个常量HOVER_DEFAULT由系统默认给出,也可以自己填写,单位是毫秒 6 } TRACKMOUSEEVENT, *LPTRACKMOUSEEVENT;在使用该函数时需要包含头文件commctrl.h和lib文件comctl32.lib解决了鼠标行为的检测之后,就是针对不同的鼠标行为重绘相应的按钮。重绘按钮需要在消息WM_DRAWITEM中,这个消息的处理是在相应控件的父窗口中实现的,而在一般情况下父窗口不会收到该消息,需要我们手工指定控件资源的属性为的OWNERDRAW为真,或者在创建相应的按钮窗口时将样式设置为BS_OWNERDRAW 。设置完成后就可以在对应的父窗口处理函数中接收并处理WM_DRAWITEM,在该消息中重绘按钮该消息中主要使用的参数是lpParam它里面包含的是一个指向DRAWITEMSTRUCT的结构体:typedef struct tagDRAWITEMSTRUCT { UINT CtlType; //控件类型 UINT CtlID; //控件ID UINT itemID; //子菜单项的ID主要用于菜单 UINT itemAction; //控件发出的动作,如ODA_SELECT表示控件被选中 UINT itemState; //控件状态,这次需要用到的状态为ODS_SELECTED表示按钮被按下 HWND hwndItem; //控件句柄 HDC hDC; RECT rcItem;//控件的矩形区域 ULONG_PTR itemData; } DRAWITEMSTRUCT;该结构体中的一些成员需要根据控件类型赋值,同时结构体中的itemAction、itemState是可以由多个值通过位或组成在判断是否具有某种状态时需要使用位与运算而绘制控件时我们可以使用函数DrawFrameControl,该函数可以根据指定的控件类型、控件所处的状态来绘制控件的样式,绘制出来的任然是系统的之前的标准样式,处理WM_DRAWITEN消息的具体代码如下:LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT)lParam; char szBuf[50]; GetWindowText(lpdis->hwndItem,szBuf,50); if (ODT_BUTTON ==lpdis->CtlType)<BR>{ UINTuState = DFCS_BUTTONPUSH; if(lpdis->itemState & ODS_SELECTED) { uState |= DFCS_PUSHED; } DrawFrameControl(lpdis->hDC,&(lpdis->rcItem),DFC_BUTTON,uState); SetTextColor(lpdis->hDC,RGB(255,0,0)); DrawText(lpdis->hDC,szBuf,strlen(szBuf) + 1,&(lpdis->rcItem),DT_CENTER | DT_VCENTER | DT_SINGLELINE); }函数_TrackMouseEvent根据其检测的鼠标状态不同可以返回不同的消息,这次主要用的是WM_MOUSEHOVER(表示鼠标移动到按钮上)、WM_MOUSELEAVE(鼠标移出按钮),还需要注意的是这个函数每次检测完成返回后不会再次检测,需要我们自己主动调用函数检测鼠标状态,由于要多次调用,而每次调用都需要初始化所需要的结构体指针,所以我们封装一个函数专门用于调用_TrackMouseEvent:void Track(HWND hWnd) { TRACKMOUSEEVENT tme; tme.cbSize =sizeof(TRACKMOUSEEVENT); tme.dwFlags = TME_HOVER | TME_LEAVE; tme.dwHoverTime = 10; tme.hwndTrack = hWnd; _TrackMouseEvent(&tme); }消息WM_MOUSEHOVER和消息WM_MOUSELEAVE的处理是在对应的窗口过程中处理的,而按钮的窗口过程由系统提供我们并不知道,所以只有使用子类化的方法在我们的窗口过程中处理这两个消息。在按钮创建后立马要检测鼠标所以可以按钮对应的父窗口完成创建后子类化,对于窗口可以在它的WM_CREATE消息中处理,对于对话框可以在WM_INITDIALOG消息中处理,子类化调用函数SetWindowLong:g_OldProc = (LRESULT*)SetWindowLong(GetDlgItem(hDlg,IDC_BUTTON1),GWL_WNDPROC,(LONG)BtnProc); return0; 在新的窗口过程中处理消息,完成三态按钮:switch(uMsg) { caseWM_MOUSEMOVE: Track(hBtn);//当鼠标移动时检测 break; caseWM_MOUSEHOVER: { charszBuf[50]; RECT rtBtn;<BR> GetClientRect(hBtn,&rtBtn); HDChDc = GetDC(hBtn); DrawFrameControl(hDc,&(rtBtn),DFC_BUTTON,DFCS_BUTTONPUSH); HBRUSHhBr = CreateSolidBrush(RGB(255,255,255)); FillRect(hDc,&rtBtn,hBr); GetWindowText(hBtn,szBuf,50); SetBkMode(hDc,TRANSPARENT); DrawText(hDc,szBuf,strlen(szBuf),&rtBtn,DT_CENTER | DT_VCENTER | DT_SINGLELINE); ReleaseDC(hBtn,hDc); } break; caseWM_MOUSELEAVE: { charszBuf[50]; RECT rtBtn; GetClientRect(hBtn,&rtBtn); HDChDc = GetDC(hBtn); DrawFrameControl(hDc,&(rtBtn),DFC_BUTTON,DFCS_BUTTONPUSH); GetWindowText(hBtn,szBuf,50); SetBkMode(hDc,TRANSPARENT);//设置字体背景为透明 DrawText(hDc,szBuf,strlen(szBuf),&rtBtn,DT_CENTER | DT_VCENTER | DT_SINGLELINE); ReleaseDC(hBtn,hDc); } break; default: returnCallWindowProc((WNDPROC)g_OldProc,hBtn,uMsg,wParam, lParam);//在处理完我们感兴趣的消息后一定要记得将按钮的窗口过程还原 } return0;到这个地方为止,已经实现了三态按钮的基本样式,通过检测鼠标的位置设置按钮样式,上述代码只是改变了按钮的背景颜色和文字颜色,可能效果不好看。
2015年11月04日
4 阅读
0 评论
0 点赞
2015-10-05
程序隐藏到任务栏的实现
我们在使用软件的时候,有的软件允许最小化到任务栏,然后双击任务栏的图标时又会显示出来,这篇文章主要说明如何实现这种功能;实现这种功能主要分为两步,一是将程序窗口隐藏,二是将程序图标添加到任务栏,再次显示也是分为两步:第一步是将任务栏上的图标删除,第二步是将窗口显示出来。窗口的隐藏与显示我们用API函数ShowWindow,而添加和删除任务栏中的程序图标用的是Shell_NotifyIcon函数,ShowWindow函数平时用的比较多,而且也比较简单,这里就不在阐述,下面主要说明Shell_NotifyIcon的用法:BOOL Shell_NotifyIcon( DWORD dwMessage, PNOTIFYICONDATA lpdata ); 该函数有两个参数,第一个表示你希望对图标做何种操作主要有这几个值:NIM_ADD、NIM_DELETE、NIM_MODIFY、NIM_SETFOCUS、NIM_SETVERSION;常用的是前面3个主要是向任务栏添加图标、删除图标、修改图标;第二个参数是一个结构体该结构体的定义如下:typedef struct _NOTIFYICONDATA { DWORD cbSize;//该结构的大小 HWND hWnd; //表明当对任务栏图标进行操作是将消息发送给那个窗口 UINT uID; //应用程序的ID UINT uFlags; //一个标志 UINT uCallbackMessage;//对任务栏图标操作时向窗口发送的一个消息 HICON hIcon; //放到任务栏中的图标句柄 WCHAR szTip[64]; //当鼠标停在图标上时显示的提示信息 } NOTIFYICONDATA, *PNOTIFYICONDATA;UINT uID 参数是应用程序的ID,这个ID并不是必须的可以任意给值UINT uFlags 参数是一个标志,主要用于控制图标的行为:NIF_ICON:有这个标志hIcon才是有效值NIF_MESSAGE:有这个标志uCallbackMessage才有效,也就是说有这个标志,当我们进行操作时才有消息产生NIF_TIP:当有这个标志时szTip,才有效,才会出现提示信息;UINT uCallbackMessage:当我们对任务栏图标进行操作时会发送一条消息这个消息由用户自己定义,并且在窗口过程中处理。函数介绍完了,接下来就是实现的代码://这里是将移出图标与添加图标放到一个函数中,根据第二个参数判断是需要移出或是添加 BOOL TrackIcon(HWND hWnd, BOOL bTrak) { NOTIFYICONDATA nid = {0}; nid.cbSize = sizeof(NOTIFYICONDATA); nid.hWnd = hWnd; nid.uID = 0; if (bTrak) { nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; nid.uCallbackMessage = WM_TRAKICON_MSG; nid.hIcon = LoadIcon(NULL, IDI_APPLICATION); _tcscpy_s(nid.szTip, sizeof(nid.szTip), _T("提示信息")); ShowWindow(hWnd, SW_MINIMIZE); ShowWindow(hWnd, SW_HIDE); return Shell_NotifyIcon(NIM_ADD, &nid); }else { //当需要移出图标时,窗口也应该完全显示,因此不需要对图标操作,后面的几个值就是无效值,这里可以不用管它们 ShowWindow(hWnd, SW_SHOWNA); return Shell_NotifyIcon(NIM_DELETE, &nid); } } //这是对我们自定义的消息进行处理,这个消息中的lParam保存了相应的消息ID case WM_TRAKICON_MSG: { switch (lParam) { case WM_LBUTTONDBLCLK: TrackIcon(hwndDlg,FALSE); ShowWindow(hwndDlg,SW_SHOWNORMAL); break; } } break;最后程序的运行结果如下:
2015年10月05日
5 阅读
0 评论
0 点赞
2015-09-23
MFC中属性表单和向导对话框的使用
每次在使用MFC创建一个框架时,需要一步步选择自己的程序的外观,基本功能等选项,最后MFC会生成一个基本的程序框架,这个就是向导对话框;而属性表单则是另外一种对话框,表单上有多个属性页,每点击某一页,会显示该页的内容,最好的例子是Visual C++6.0中的Option对话框;如图:属性表单的创建:属性表单上由许多属性页组成,每个属性页都可以在可视化的编辑环境中编辑,需要添加的资源名称是对话框下面的IDD_PROPPAGE_LARGE、IDD_PROPPAGE_MEDIUM, IDD_PROPPAGE_SMALL,如下图:创建资源时也可以直接创建对话框,在属性中将Style属性选为Child、Border选为Thin、勾上Disable选项;创建了资源,下面就是关联MFC的类,属性页的类是CPropertyPage,该类是继承于CDialog类,在使用时需要从CPropertyPage中派生。创建了多个属性页就需要派生多个新类;创建了属性页,下面就需要创建属性表单,属性表单不需要编辑资源,可以从类CproppertyPage中派生一个新类,用来表示新表单类;为了将属性页加到属性表单上需要在对应的构造函数中调用AddPage函数,最后需要调用该类的DoModal或者Create函数创建一个模态或者非模态的属性表单;在一下代码中有三个对应的属性页的类(CProp1、CProp2、CProp3)和一个属性表单的类(CPropSheet);//在CPropSheet中创建三个属性页的对象 public: CProp1 m_Prop1; Cprop2 m_Prop2; CProp3 m_Prop3;//在构造函数中添加属性页 AddPage(&m_Prop1); AddPage(&m_Prop2); AddPage(&m_Prop3);至于它的使用则是于普通的对话框类似向导的创建与使用:向导所使用的类与属性表单相同,这里就不在说明,为了创建向导,需要在调用DoModal或者Create之前调用SetWizardMode()函数,这样之前的属性表单就变为了向导程序,向导程序上通过下一步来转到下一个属性页,每个页面上都有“下一步”、“上一步”、“取消”按钮,这个特性不便于用户的操作,我们一般习惯于将第一个向导页的“上一步”隐藏,最后一页的“下一步”变为“完成”,为了实现这个需要使用函数SetWizardButtons(),这个函数只有一个参数表示的是页面上按钮的特性,它的取值可以是PSWIZB_BACK、PSWIZB_NEXT、PSWIZB_FINISH、PSWIZB_DISABLEDFINISH中的一个或者几个,分别用来设置该页上的一个“上一步”按钮、“下一步”按钮、“完成”按钮、和一个禁用的“完成”按钮,一般来说在属性页中的OnSetActive函数中调用,当属性页被选中,从而被激活时程序会响应WM_ACTIVATE,而该消息在函数OnSetActive中处理,由于在基类中有了这个函数,所以我们需要重写这个函数,下面是一个例子:BOOL CProp1::OnSetActive() { ((CPropertySheet*)GetParent())>SetWizardButtons(PSWIZB_NEXT); return CPropertyPage::OnSetActive(); }有时候需要实现这样的功能:属性页上有一些信息需要用户填写或选择,当用户没有选择或填写完整时不允许进入下一个页面。每次需要进入下个页面时用户会单击“下一步”按钮,而这个时候程序会调用OnWizardNext函数进入下一个页面(根据页面按钮的不同,点击不同的按钮程序会调用OnWizardNext、OnWizardBack、OnWizardFinish),当该函数返回-1时会禁止属性页发生变更,返回0时会正常进入下一页,下面是一个例子代码:LRESULT CProp1::OnWizardNext() { // TODO: 在此添加专用代码和/或调用基类 UpdateData(TRUE); if (-1 == m_occupation) { MessageBox(TEXT("请选择你的职业")); return -1; } if (TEXT("") == m_workAddr) { MessageBox(TEXT("请选择你的工作地点")); return -1; } return CPropertyPage::OnWizardNext(); }注意:将变量与控件相关联时为了获取控件返回的值需要调用UpdateData()函数,当该函数参数为TRUE时会调用DoDataExchange,该函数会根据控件返回的值,动态更新变量的值;一般情况下只有当用户点击完成时才保存用户输入的信息当用户点击取消时应该取消信息的保存;一般情况下CPropertySheet类的DoModal函数返回值是IDOK或者IDCANCLE。但是当属性表单被创建为向导时会返回ID_WIZFINISH和IDCANCLE这个时候我们可以根据返回值来判断是否保存;
2015年09月23日
7 阅读
0 评论
0 点赞
2015-09-21
对话框伸缩功能的实现
对话框的伸缩功能是指当触发某一操作时只显示部分对话框的内容,再次触发时显示全部的对话框范围,就比如画图软件上的选择颜色对话框,我们可以使用系统预定义的颜色,也可以选择自定义颜色,当点击自定义的按钮时,对话框出现原先隐藏的另一边,让用户填写颜色的RGB值。为了实现这个功能,我们需要记录两个矩形范围:全部对话框的大小和我们希望显示的部分对话框的大小,利用函数SetWindowPos来设置显示的对话框的大小,该函数的原型如下:BOOL SetWindowPos( HWND hWnd, // 需要设置的窗口的句柄 HWND hWndInsertAfter, // Z序中下一个窗口的句柄 int X, // int Y, // 窗口所在矩形的顶点坐标(x, y) int cx, // 矩形宽 int cy, // 矩形高 UINT uFlags // 显示属性 ); 下面是对该函数的补充:hWndInsertAfter:除了给出具体的窗口句柄外还可以是这几个值:HWND_BOTTOM、HWND_NOTOPMOST、HWND_TOP、HWND_TOPMOST;uFlags主要的一些标志: SWP_NOMOVE:调用该函数不改变窗口之前的顶点位置,当设置这个这个值的时候,x、y参数将被忽略; SWP_NOZORDER:忽略Z序,这个标志被设置时将忽略hWndInsertAfter参数;具体的信息可以在MSDN中查找;以下是具体的实现代码://按钮的WM_COMMAND消息处理 case WM_COMMAND: { if (LOWORD(wParam) == IDC_BUTTON) { if (HIWORD(wParam) == BN_CLICKED) { TCHAR szBuf[255] = TEXT(""); GetWindowText(GetDlgItem(hDlg, IDC_BUTTON), szBuf, 255); if (0 == _tcscmp(szBuf, TEXT("收缩>>"))) { SetWindowText(GetDlgItem(hDlg, IDC_BUTTON), TEXT("扩张<<")); }else { SetWindowText(GetDlgItem(hDlg, IDC_BUTTON), TEXT("收缩>>")); } Extern(hDlg, szBuf); } } }//改变对话框大小的函数 void Extern(HWND hWnd, const TCHAR *pszStr) { //保存对话框在扩张和收缩状态下的矩形大小 static RECT rtSmall; static RECT rtLarge; //当两个量不是有效值时,获取这两种状态下的矩形大小 if (IsRectEmpty(&rtLarge)) { RECT rtSpecrator; GetWindowRect(GetDlgItem(hWnd, IDC_SPERATOR), &rtSpecrator); rtSmall.left = rtLarge.left; rtSmall.top = rtLarge.top; rtSmall.right = rtSpecrator.right; rtSmall.bottom = rtSpecrator.bottom; } if (0 == _tcscmp(pszStr, TEXT("收缩>>"))) { SetWindowPos(hWnd, NULL, 0, 0, rtSmall.right - rtSmall.left, rtSmall.bottom - rtSmall.top, SWP_NOZORDER | SWP_NOMOVE); } else { SetWindowPos(hWnd, NULL, 0, 0, rtLarge.right - rtLarge.left, rtLarge.bottom - rtLarge.top, SWP_NOZORDER | SWP_NOMOVE); } }IDC_SPERATOR是一个分割线的ID,分割线我们采用的是一个图片控件,将这个控件的高度尽量缩小,这个控件本身也是一个矩形,可以用GetWindowRect函数获取它的矩形大小,缩小时只保存控件之上的部分;
2015年09月21日
5 阅读
0 评论
0 点赞
2015-09-12
socket模型处理多个客户端
最近学完了简单的socket编程,发现其实socket的网络编程其实并没有什么难度,只是简单的函数调用,记住客户端与服务端的步骤,写起来基本没有什么问题。在服务器程序的设计中,一个服务器不可能只相应一个客户端的链接,为了响应多个客户端的链接,需要使用多线程的方式,每当有一个客户端连接进来,我们就开辟一个线程,用来处理双方的交互(主要是利用recv或者recvfrom用于收发信息),由于但是在网络中可能出现这样一种情况:由于处理比较复杂,下一条信息到来之后,上一条信息的处理还没有完成,这样信息太多了之后系统的缓冲占满之后可能会发生丢包的现象,所以为了解决这个问题,需要另外再开一个线程,专门用来处理接收到的数据,这样总共至少有3个线程,主线程,收发信息的线程,处理线程;这样可能也不完整,处理的操作种类多了的话可能需要根据不同的请求来开辟不同的线程用来处理这一类请求,下面是实现这一思路的部分代码:全局变量:DWORD WINAPI AcceptThread(LPVOID lpParameter); DWORD WINAPI RecvThread(LPVOID lpParameter); DWORD g_nAcceptID = 123; DWORD g_nRecvID = 234; HANDLE g_hAccpetThread; HANDLE g_hRecvThread;主线程函数:int _tmain(int argc, _TCHAR* argv[]) { WSADATA wd; WSAStartup(MAKEWORD(2, 2), &wd); SOCKET sockListen = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); if (INVALID_SOCKET == sockListen) { cout << "创建侦听套接字失败,错误码为:" << WSAGetLastError() << endl; return -1; } SOCKADDR_IN srvAddr = { 0 }; srvAddr.sin_family = AF_INET; srvAddr.sin_port = htons(6666); srvAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (SOCKET_ERROR == bind(sockListen, (SOCKADDR*)&srvAddr, sizeof(SOCKADDR))) { cout << "绑定失败,错误码为:" << WSAGetLastError() << endl; WSACleanup(); return -1; } if (SOCKET_ERROR == listen(sockListen, 5)) { cout << "侦听失败,错误码为:" << WSAGetLastError() << endl; WSACleanup(); return -1; } while (true) { SOCKET sockConn = accept(sockListen, NULL, 0); if (INVALID_SOCKET == sockConn) { cout << "本次连接失败,即将进入下一次连接,错误码为:" << WSAGetLastError() << endl; closesocket(sockConn); closesocket(sockListen); WSACleanup(); continue; } g_hAccpetThread = CreateThread(NULL, 0, AcceptThread, &sockConn, 0, &g_nAcceptID); } WaitForSingleObject(g_hAccpetThread, INFINITE); WSACleanup(); return 0; }收发数据函数:DWORD WINAPI AcceptThread(LPVOID lpParameter) { cout << "有客户端连接进来" << endl; SOCKET sockConn = *(SOCKET*)lpParameter; while (true) { char *pszBuf = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY | HEAP_NO_SERIALIZE, 255); if (SOCKET_ERROR == recv(sockConn, pszBuf, 255, 0)) { cout << "接受数据失败,错误码为:" << WSAGetLastError() << endl; cout << "准备进行下一次接受数据....." << endl; continue; } g_hRecvThread = CreateThread(NULL, 0, RecvThread, pszBuf, 0, &g_nRecvID); WaitForSingleObject(g_hRecvThread, INFINITE); if (0 == strcmp("exit", pszBuf)) { cout << "正在断开与该客户端的连接" << endl; HeapFree(GetProcessHeap(), 255, pszBuf); return 0; } } return 0; }信息处理子线程:DWORD WINAPI RecvThread(LPVOID lpParameter) { cout << "接受到客户端的数据:" << (char*)lpParameter << endl; return 0; }虽说这个解决了多个客户端与服务器通信的问题,但是这样写确定也很明显:所有的与客户端通信的socket都有程序员自己管理,无疑加重了程序员的负担;每有一个连接都需要创建一个线程,当有大量的客户端连接进来开辟的线程数是非常多的,线程是非常耗资源的,所以为了解决这些问题就提出了异步的I/O模型,它们解决了这些问题,由系统管理套接字,不要要人为的一个个管理,同时不需要开辟多个线程来处理与客户端的连接,我们可以将线程主要用于处理客户端的请求上;
2015年09月12日
5 阅读
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
...
7
8