深入浅出MFC(第一章)

第一章    勿在浮沙筑高台

摘要:win32console、进程、线程、优先级

Windows SDK程序开发流程(32位):


Windows程序调用的函数可分为C Runtimes和Windows API两大部分。CRuntimes支持动态连接,LIBC.LIB、MSVCRT.LIB;Windows API由操作系统本身(主要是Windows三大模块GDI32.DLL和USER32.DLL和KERNEL32.DLL提供);

所有的Windows程序都必须包含WINDOWS.H头文件。WINDOWS.H只照顾三大模块所提供的API函数。


所有的GUI系统,都是以消息为基础的事件驱动系统。

Windows程序的本体与操作系统之间的关系:


Makefile,使某个文件和某个文件相比,比较其产生日期,只要右边的任一文件比左边的文件新,就执行下一行所指定的动作。例如:

Generic.res : generic.rc  generic.h

Rc     generic.rc

程序的进入点WinMain:当Windows的shell侦测到使用者欲执行一个Windows程序,于是调用加载器把该程序加载,然后调用C startup code,后者再调用WinMain,开始执行程序,WinMain的四个参数由操纵系统传递进来。

程序必须在产生窗口之前利用API函数RegisterClass设定属性:


CreateWindow只产生窗口,并不显示窗口,必须利用ShowWindow将之显示在屏幕上。

一般的编程习惯,RegisterClass包装在InitApplication函数中,CreateWindow则包装在InitInstance函数中。

消息循环:

While(GetMessage(&msg,…) {

     TranslateMessage(&msg);       //转换键盘消息

    DispatchMessage(&msg);      //把消息传给窗口函数去处理不用指定函数名称

}

DispatchMessage经过USER模块的协助,才能把消息交到窗口函数手中。

窗口函数:

通常使用switch/case方式判断消息种类。它是被Windows系统所调用的,是一种call back函数。函数虽然由你设计,但是永远不会也不该被你调用,它们是为Windows系统准备的,它能开放出一个接口给操作系统调用,窗口函数的形式,相当一致:

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

不论什么消息,都必须被处理,所以switch/case指令中的default:处必须调用DefWindowProc,这是Windows内部预设的消息处理函数,wParam和lParam的意义,因消息的不同而异。

消息映射(Message Map)的雏形

首先,定义一个MEGMAP_ENTRY结构和一个dim宏:

Struct MSGMAP_ENTRY {

                  UINT nMessage;

                  LONG(*pfn)(HWND,UINT,WPARAM,LPARAM);//指向处理消息的函数指针

                    }
#define  dim(x)  (sizeof(x)/sizeof(x[0]))
接着,组织两个数组_messageEntries[]和_commandEntries[],把程序中欲处理的消息以及消息处理例程的关联性建立起来:

//消息与处理例程之对照表格

Struct MSGMAP_ENTRY _messageEntries[]=

{

    WM_CREATE,OnCreate,                      //消息     消息处理例程

    WM_PAINT,OnPaint,

    WM_SIZE,OnSize,

    WM_COMMAND,OnCommad,

    WM_SETFOCUS,OnSetFocus,

    WM_CLOSE,OnClose,

    WM_DESTROY,OnDestroy,

};
//Command-ID与处理例程之对照表格
Struct MSGMAP_ENTRY _commandEntries=

{

         IDM_ABOUT,OnAbout,             //WM_COMMAND 命令处理例程

         IDM_FILEOPEN,OnFileOpen,

         IDM_SAVEAS,  OnSaveAs,

};

于是窗口函数可以这样设计:

LRESULT CALLBACK WndProc(HWND hwnd,UINTmessage,WPARAM wParam,LPARAM lParam)

{

         IntI;

                   For(i=0;i<dim()messageEntries);i++){

                            If(message==messageEntries[i].nMessage)

                                     Return((*messageEntries[i].pfn)(hwnd,message,wParam,lParam));

                   }

                   Return(DefWindowProc(hWnd,message,wParam,lParam));

}

//OnCommand专门处理WM_COMMAD

LONG OnCommand(HWND hWnd,UINTmessage,WPARAM wParam,LPARAM lParam)

{

         IntI;

                   For(i=0;i<dim(_commandEntries);i++){

                            If(LOWORD(wParam)==commandEntries[i].nMessage)

                                     Return((*commandEntries[i].pfn)(hWnd,message,wParam,lParam));

                   }

                   Return(DefWindowProc(hWnd,message,wParam,lParam));

}

LONG OnCreate(HWND hWnd,UINT wMsg,UINTwParam,LONG lParam)

{

         …………….

}

LONG OnAbout(HWND hWnd,UINT wMsg,UINTwParam,LONG lParam)

{

         ………

}
这样,WndProc和OnCommand永远不必改变,每当有新的要处理的消息,只要在_messageEntries[]和_commandEntries[]两个数组中加上新元素,并针对新消息撰写新的处理例程即可。

对话框的运作:

1、  modal对话框         令其父窗口除能,只到对话框结束。

2、  modeless对话框      父窗口与对话框共同运行。

为了做出对话框,需要做两方面准备:

1、  对话框模版(dialogtemplate)。RC文件中定义

2、  对话框函数(dialogprocedure)。对话框中的各个控制组件也都各有自己的窗口函数,它们与其管理者(父窗口)沟通,所有的控制组件传来的消息都是WM_COMMAND,再由其参数分辨哪一种控制组件以及哪一种通告(notification)。

Modal对话框的激活与结束,靠的是DialogBox和EndDialog两个API函数。

对话框的诞生、运作、结束:


模块定义文件(.DEF)

Windows程序需要一个模块定义文件,将模块名称、程序节区的内存特性、模块堆积(heap)大小、堆栈(stack)大小、所有callback函数名称….等等记录下来。

资源描述档(.RC)

RC文件是一个以文字描述资源的地方。常用的资源有九项之多,分别是ICON、CURSOR、BITMAP、FONT、DIALOG、MENU、ACCELERATOR、STRING、VERSIONINFO。还可能有新的资源不断加入。这些文字描述需经过RC编译器,才产生可使用的二进制代码。

Windows程序的生死:


1、  程序初始化过程中调用CreateWindow,为程序建立了一个窗口。CreateWindow产生窗口之后会送出WM_CREATE直接给窗口函数,后者可以做些初始化动作。

2、  程序运行中,不断用GetMessage从消息队列中抓取消息,如果这个消息是WM_QUIT,GetMessage会传回0而结束while循环,进而结束整个程序。

3、  DispatchMessage透过Windows USER模块的协助与监督,把消息分派至窗口函数。消息在该处被判别并处理。

4、  程序不断进行2和3。

5、  当使用者按下系统菜单中的close命令时,系统送出WM_CLOSE。通常程序的窗口函数不拦截此消息,于是DefWindowProc处理它。

6、  DefWindowProc收到WM_CLOSE后,调用DestroyWindow把窗口清除。DestroyWindow本身又会送出WM_DESTROY。

7、  PostQuitMessage没什么其它动作,就只送出WM_QUIT消息,准备让消息循环中的Getmessage取得,结束消息循环。

为什么结束一个程序复杂如斯?因为操作系统与应用程序职责不同,二者是互相合作的关系,所以必须各做各的份内事,并互以消息通知对方。

空闲时间的处理:OnIdle

所谓的空闲时间(idle time),是指系统中没有任何消息等待处理的时间。代码设计如下:

while(TRUE){

         if(PeekMessage(&msg,NULL,0,0,PM_REMOVE){

                   if(msg,message==WM_QUIT)

                                     break;

                            TranslateMessage(&msg);

                            DispatchMessage(&msg);

}

         else{

                   OnIdle();

                   }

}

Peekmessage和GetMessage的性质不同。操作系统第一次没抓到消息,第二次抓取时,又没抓到:

GetMessage会过门不入,于是操作系统再去照顾他人。

PeekMessage会取回控制权,使程序得以执行一段时间,于是消息循环进入OnIdle函数中。

Console程序:

Console程序可以调用部分的、不牵扯到图形使用者接口(GUI)的Win32API,甚至可以在console程序中使用部分的MFC类别(同样必须是与GUI没有关联的),例如处理数组、串行等数据结构的collection class(CArray、CList、CMap)、与文件有关的CFile、CStdioFile。

Console程序与DOS程序的差别:

制造方式:如果程序是以main为进入点,调用Cruntime函数和不牵扯GUI的Win32API函数,那么就是一个console程序,console窗口将成为其标准输入和输出装置。而过去在DOS环境下开发的程序,称为DOS程序,它也是以main为程序进入点,可以调用C runtime函数,但是不能调用win32 API函数。

可执行文档的格式:DOS程序是所谓的MZ格式。Console程序的格式则和所有的Win32程序一样,是所谓的PE格式,意思是它可以被拿到任何Win32平台上执行。

进程与执行线程(Process and Thread):

线程实际上是CPU的排程单位。

核心对象:

是系统的一种资源,系统对象一但产生,任何应用程序都可以开启并使用该对象,系统给予核心对象一个计数值作为管理之用。核心对象包括:

核心对象

产生方法

event      用于执行线程的同步化

createEvent

Mutex     用于执行线程的同步化

CreateMutex

Semaphore 用于执行线程的同步化

CreateSemaphore

File      

CreateFile

File-mapping 用于内存映射文件

CreateFileMapping

Process

CreateProcess

Thread

CreateThread

每使用一次,其对应的计数值就加1。核心对象的结束方式相当一致,调用CloseHandle即可。

一个进程的诞生与死亡:

1、  shell调用CreateProcess激活App.exe。

2、  系统产生一个进程核心对象,计数值为1.

3、  系统为此进程建立一个4GB地址空间。

4、  加载器将必要的码加载到上述地址空间中,包括App.exe的资料、程序以及所需的动态链接函数库(DLLs)。加载器如何知道要加载那些DLLs呢?它们被记录在可执行文件(PE文件格式)的idata section中。

5、  系统为此进程建立一个执行线程,称为主执行线程(primary thread)。执行线程才是CPU时间的分配对象。

6、  系统调用C runtime函数库的Startupcode。

7、  Startup code调用App程序的WinMain函数。

8、  App程序开始运作。

9、  使用者关闭App主窗口,使WinMain中的消息循环结束掉,于是WinMain结束。

10、回到Startup code。

11、回到系统,系统调用ExitProcess结束进程。

可以说,透过这种方式执行起来的所有Windows程序,都是shell的子进程。本来,母进程与子进程之间可以有某些关系存在,但shell在调用CreateProcess时已经把母子之间的脐带关系剪断了,因此它们事实上是独立实例。

 产生子进程:

BOOL WINAPICreateProcess(

  __in         LPCTSTRlpApplicationName//可执行文档的名字

  __in_out     LPTSTRlpCommandLine,      //传递的命令参数

  __in         LPSECURITY_ATTRIBUTESlpProcessAttributes,//进程的安全属性

  __in         LPSECURITY_ATTRIBUTESlpThreadAttributes,//线程的安全属性

  __in         BOOLbInheritHandles,

  __in         DWORDdwCreationFlags,

  __in         LPVOIDlpEnvironment,

  __in         LPCTSTRlpCurrentDirectory,

  __in         LPSTARTUPINFOlpStartupInfo,//设定窗口的标题、位置、大小

  __out        LPPROCESS_INFORMATIONlpProcessInformation//保存创建的核心对象&线程对象的句柄

);

贴上一段代码示例:

#include <iostream>

#include<windows.h>

using namespacestd;

int main()

{

     STARTUPINFO si;   //一些必备参数设置

     memset(&si,0,sizeof(STARTUPINFO));

     si.cb =sizeof(STARTUPINFO);

     si.dwFlags =STARTF_USESHOWWINDOW;

     si.wShowWindow=SW_SHOW;

     PROCESS_INFORMATION pi;//必备参数设置结束

     if(!CreateProcess(L"c:\\windows\\system32\\notepad.exe",NULL,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi))

     {

         cout<<"CreateFail!"<<endl;

         exit(1);

     }

     else

     {

         cout<<"Success!"<<endl;

     }

     return 0;

}
进程结束:

void ExitProcess(UINT fuExitCode);

结束另一个进程的生命:

BOOL TerminateProcess(HANDLEhProcess,UINT fuExitCode);

剪断脐带:

PROCESS _INFORMATION ProcInfo;

BOOL fSucess;

fSuccess=CreateProcess(…..,&ProcInfo);

if(fSucess){

     CloseHandle(ProcInfo.hThread);

     CloseHandle(ProcInfo.hProcess);

}

执行线程的诞生与死亡:

程序代码的执行,是执行线程的工作。当一个进程建立起来,主执行线程也产生。所以每一个Windows程序一开始就有了一个执行线程。我们可以调用CreateThread产生额外的执行线程。

HANDLE WINAPI CreateThread(
  __in          LPSECURITY_ATTRIBUTES lpThreadAttributes,//安全属性
  __in          SIZE_T dwStackSize,//堆栈大小
  __in          LPTHREAD_START_ROUTINE lpStartAddress,//执行线程函数
  __in          LPVOID lpParameter,//函数参数
  __in          DWORD dwCreationFlags,//0表示立即执行,CREATE_SUSPENDED表示暂停执行
  __out         LPDWORD lpThreadId//执行线程的ID
);

执行线程的结束有两种情况:一是执行线程函数正常退出,那么执行线程也就自然而然终结,这时系统会调用ExitThread(也可自己调用)做清理工作;二是别的执行线程强制以TerminateThread将它终结。

_beginthreadex取代CreateThread:

这不是一个标准的ANSIC runtime函数,不再由win32类别包装,_beginthreadex在内部调用了CreateThread,在调用之前_beginthreadex做了很多的工作,从而使得它比CreateThread更安全

示例代码:

#include <windows.h>

#include <process.h>

#include <stdio.h>

unsigned _stdcall myfunc(void *p);

void main()

{

     UINT thd;

     HANDLE tid;

     tid=(HANDLE)_beginthreadex(NULL,0,myfunc,0,0,&thd);

     if(tid!=NULL)

     {

         CloseHandle(tid);

     }

     Sleep(10);

}

unsigned _stdcall myfunc(void *p)

{

     printf("threadstart!\n");

     return 0;

}

执行线程优先权(Priority):

Win32 有所谓的优先权的概念,较高优先级的线程必然获得较多的 CPU 时间。优先权以一个数值表示,值为 0 ~ 31。

进程的优先级类型(Priority class)决定了进程的重要性,Win32 提供了 4 种优先级类型:

  1. HIGH_PRIORITY_CLASS — 权值 13
    任务管理器就是使用 HIGH_PRIORITY_CLASS,即使系统很忙碌的情况下,它常常会对操作有反应
  2. IDLE_PRIORITY_CLASS — 权值 4
    有时候我们期望程序在 CPU 空闲下来的时候才执行,这个时候使用 IDLE_PRIORITY_CLASS 就很合适
  3. NORMAL_PRIORITY_CLASS — 权值 7 or 8
    一般的应用程序都是使用这个类型
  4. REALTIME_PRIORITY_CLASS — 权值 24
    进程设置为此优先权类型,那么它将优于内核进程和设备驱动进程,不要对 GUI 程序或者典型的服务器程序使用此优先级类型

Windows 的相关 API 为 SetPriorityClass() 和GetPriorityClass()。

线程的优先级等级(Priority level)决定了同一个进程中的各个线程的相对重要性,实际上线程的优先级等级是对进程的优先级类型的一个修改,Win32 提供了 7 种优先级等级:

  1. THREAD_PRIORITY_HIGHEST — 权值 +2
  2. THREAD_PRIORITY_ABOVE_NORMAL — 权值 +1
  3. THREAD_PRIORITY_NORMAL — 权值 +0
  4. THREAD_PRIORITY_BELOW_NORMAL — 权值 -1
  5. THREAD_PRIORITY_LOWEST — 权值 -2
  6. THREAD_PRIORITY_IDEL — 权值设置为 1
  7. THREAD_PRIORITY_TIME_CRITICAL — 权值设置为 15

Windows 的相关 API 为 SetThreadPriority() 和GetThreadPriority()。如果某一个进程的优先级类别为 HIGH_PRIORITY_CLASS,其中一个线程的优先级等级为 THREAD_PRIORITY_LOWEST 那么它的优先级权值就是13 – 2 = 11。

上面的两个因素决定了线程的优先权,另外还有一个决定线程优先权的因素:动态提升(Dynamic boost)。
首先,我们可以调整系统的设置,使得 CPU 更加倾向于程序还是后台服务。
除了调整系统设置,键盘消息、鼠标消息、计时器消息都可能引发动态提升,例如,线程获得键盘输入时,就获得了一个 +5 的优先级调整值。
最后,当等待状态得到满足时,例如,Wait… 返回时,此线程的优先权就会获得动态提升。

 


补充一个程序示例:

示例程序:为了确保线程拥有对单个资源的互斥访问权,使用互斥对象(mutex)可以理解为钥匙

#include<windows.h>
#include<iostream>
using namespace std;

DWORD WINAPI FUN1(LPVOID lpParameter);
DWORD WINAPI FUN2(LPVOID lpParameter);
int index=0;
int tickets=100;
HANDLE hMutex1,hMutex2,hMutex3;//必须是全局变量
void main()     //主进程结束,子线程也会结束 CPU为线程分配时间片
{
	HANDLE thread1;
	HANDLE thread2;
	//HANDLE thread3;
	thread1=CreateThread(NULL,0,FUN1,0,0,NULL);
	thread2=CreateThread(NULL,0,FUN2,0,0,NULL);
	CloseHandle(thread1);//需要有
	CloseHandle(thread2);
	hMutex1=CreateMutex(NULL,FALSE,NULL);//默认的安全属性、主线程不拥有该对象、互斥对象的名称
	Sleep(3000);
	hMutex2=CreateMutex(NULL,true,L"main");//默认的安全属性、
	hMutex3=CreateMutex(NULL,true,L"main");///利用互斥对象 还可以保证只有一个程序的实例运行
	if(hMutex3)
	{
		if(ERROR_ALREADY_EXISTS==GetLastError())
		{
			cout<<"only one instance can run!"<<endl;
			return;
		}
	}
	ReleaseMutex(thread2);

	/*while(index++<100)
	cout<<"main thread is running"<<endl;*/
	Sleep(3000);
	
}
DWORD WINAPI FUN1(LPVOID lpParameter)
{
	cout<<"thread1 is running"<<endl;*/
	while(TRUE)
	{	
		WaitForSingleObject(hMutex1,INFINITE);//必须主动请求钥匙(互斥对象)使互斥对象无信号
		if(tickets>0)
			cout<<"thread1 sell ticket:"<<tickets--<<endl;
		else
			break;
		ReleaseMutex(hMutex1);//释放钥匙(互斥对象)是互斥对象有信号  如果没有该语句 则 线程执行完之后会自动释放
	}
	return 0;
}
DWORD WINAPI FUN2(LPVOID lpParameter)
{
	cout<<"thread2 is running"<<endl;*/
		while(TRUE)
	{
		WaitForSingleObject(hMutex1,INFINITE);
		if(tickets>0)
			cout<<"thread2 sell ticket:"<<tickets--<<endl;
		else
			break;
		ReleaseMutex(hMutex1);//互斥对象是谁拥有 谁释放
	}
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值