1.所有Windows程序都必须载入windows.h。
2.每一个Windows程序都应该有一个如下的循环:
MSG msg;
while(GetMessage(&msg,NULL,NULL,NULL)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
3.Windows消息分为两类(按输入):由硬件装置所产生的消息(如鼠标按下),放在系统队列(System Queue)中,以及由Windows系统或其它Windows程序传送过来的消息,放在程序队列(application queue)中。以应用程序的眼光来看,消息就是消息,来自哪里或放在哪里其实没有太大的区别,反正程序通过调用GetMessage API来取得消息,程序的生命靠它来推动。接受并处理消息的主角是窗口,每一个窗口都应当有一个窗口函数来负责处理消息。
4.一开始,Windows程序必须做些初始化工作,如产生窗口。CreateWindow可以做到,但是在窗口产生之前、其属性必须先设定好。所谓属性包括窗口的“外貌”和“行为”,一个窗口的边框、颜色、标题、位置等等就是外貌,而窗口接受消息后的反应就是其行为(具体地说就是指窗口函数本身)。程序必须在产生窗口之前先利用RegisterClass设定属性(注册窗口类)。RegisterClass需要一个大型数据结构WNDCLASS作为参数,CreateWindow则另需要11个参数。
其中wc.lpfnWndProc所指定的函数就是窗口的行为中枢,也就是所谓的窗口函数。CreateWindow只产生窗口,并不显示窗口,所以稍后我们必须再利用ShowWindow将它显示在屏幕上。我们又希望先传送一个WM_PAINT给窗口,以驱动窗口的绘图操作,所以调用UpdateWindow。
5.消息循环:初始化工作完成后,WinMain进入所谓的消息循环:
while(GetMessage(&msg,NULL,NULL,NULL)){
TranslateMessage(&msg); //转换键盘消息
DispatchMessage(&msg); //分派消息
}
6.消息映射(Message Map)的雏形
首先定义一个MSGMAP_ENTRY结构和一个dim宏:
struct MSGMAP_ENTRY{
UINT nMessage;
LONG (*pfn)(HWND, UINT, WPARAM, LPARAM); //函数指针,指向函数处理nMessage消息
};
#define dim(x) (sizeof(x) / sizeof(x[0]))
接下来,组织两个数组_messageEntries[]和_commandEntries[],把程序中欲处理的消息以及消息处理例程的关联性建立起来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // 消息与处理例程之对照表格 struct MSGMAP_ENTRY _messageEntries[] = { WM_CREATE, OnCreate, WM_PAINT, OnPaint, WM_SIZE, OnSize, WM_COMMAND, OnCommand, WM_SETFOCUS, OnSetFocus, WM_CLOSE, OnClose, WM_DESTROY, OnDestroy, }; 消息 , 消息处理例程 // Command-ID与处理例程之对照表格 struct MSGMAP_ENTRY _commandEntries = { IDM_ABOUT, OnAbout, IDM_FILEOPEN, OnFileOpen, IDM_SAVEAS, OnSaveAs, }; WM_COMMAND命令项 , 命令处理例程 |
于是窗口函数可以这么设计:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | LRESULT CALLBACK WndProc(HWND hWnd, UINT message,WPARAM wParam, LPARAM lParam) { int i; 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)); } LONG OnCommand(HWND hWnd, UINT message,WPARAM wParam, LPARAM lParam) { int i; 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)); } |
这么一来,WinProc和OnCommand永远不必改变,每当有新的要处理的消息,只要在_messageEntries[]和_commandEntries[]两个数组中加上新元素,并针对新消息撰写新的处理例程即可。
7.对话框的运行
常用的对话框分为模态(令其父窗口无效,直到对话框结束)的和非模态(父窗口与对话框共同运行)的对话框。
为了创建一个对话框,需要两样东西:
1:对话框模板,它是在RC文件中定义的,用以表明对话框的外观。
2:对话框函数。很类似与窗口函数,但是只处理WM_INITDIALOG和WM_COMMAND两个消息。对话框中的每个控件都有自己的窗口函数,它们以消息的方式与父窗口沟通。所有的控件传来的消息都是WM_COMMAND消息,有所属控件ID区分。
8.窗口的生命周期
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.程序对WM_DESTROY的标准反应是调用PostQuitMessage。
8.PostQuitMessage没什么其他操作,就只送出WM_QUIT消息,准备让消息循环中的GetMessage取得,如步骤2,结束消息循环。
9.GetMessage和PeekMessage
①:GetMessage将等到有合适的消息时才返回,而PeekMessage只是撇一下消息队列。
②:GetMessage会将消息从队列中删除,而PeekMessage可以设置最后一个参数wRemoveMsg来决定是否将消息保留在队列中。
当消息队列中没有消息时,GetMessage会一直等待(挂起),直到出现下一个消息时返回。而PeekMessage会在没有取得消息后,立即返回,这使得程序得以继续执行。
10.CreateThread、_beginthread和beginthreadex
CreateThread 是一个Win 32API 函数,_beginthread 是一个CRT(C Run-Time)函数,他们都是实现多线城的创建的函数。
CreateThread、_beginthread和_beginthreadex都是用来启动线程的,但大家看到oldworm没有提供_beginthread的方式,原因简单,_beginthread是_beginthreadex的功能子集,虽然_beginthread内部是调用_beginthreadex但他屏蔽了象安全特性这样的功能,所以_beginthread与CreateThread不是同等级别,_beginthreadex和CreateThread在功能上完全可替代,我们就来比较一下_beginthreadex与CreateThread!
一般来说,从使用角度是没有多大的区别的,CRT函数中除了signal()函数不能在CreateThread创建的线城中使用外,其他的CRT函数都可一正常使用,但是如果在CreateThread创建的线城中使用CRT函数的话,会产生一些Memory Leak.
用CreateThread 创建的线城能否被CRT函数 _endthreadex() 关闭?
首先应当尽量避免使用CreateThread(),事实上,_beginthreadex()在内部先为线程创建一个线程特有的tiddata结构,然后调用CreateThread()。在某些非线程安全的CRT函数中会请求这个结构。如果直接使用CreateThread()的话,那些函数发现请求的tiddata为NULL,就会在现场为该线程创建该结构,此后调用EndThread()时会引起内存泄漏。_endthreadex()可以释放由CreateThread()创建的线程,实际上,在它的内部会先释放由_beginthreadex()创建的tiddata结构,然后调用EndThread()。
如果我使用_beginthreadex函数创建了线程,它会为我创建好CRT函数需要的一切,并且最后无需我操心,就可以把清除工作做得很好,可能唯一需要注意的就是,如果需要提前终止线程,最好是调用_endthreadex或者是返回,而不要调用ExitThread,因为这可能造成内存释放不完全。同时我们也可以看出,如果我们用CreateThread函数创建了线程,并且不对C运行库进行调用(包括任何间接调用),就不必担心什么问题了。
若要使多线程C和C++程序能够正确地运行,必须创建一个数据结构,并将它与使用C/C++运行期库函数的每个线程关联起来。当你调用C/C++运行期库时,这些函数必须知道查看调用线程的数据块,这样就不会对别的线程产生不良影响。
1.每个线程均获得由C/C++运行期库的堆栈分配的自己的tiddata内存结构。
2.传递给_beginthreadex的线程函数的地址保存在tiddata内存块中。传递给该函数的参数也保存在该数据块中。
3._beginthreadex确实从内部调用CreateThread,因为这是操作系统了解如何创建新线程的唯一方法。
4.当调用CreatetThread时,它被告知通过调用_threadstartex而不是pfnStartAddr来启动执行新线程。 还有,传递给线程函数的参数是tiddata结构而不是pvParam的地址。
5.如果一切顺利,就会像CreateThread那样返回线程句柄。如果任何操作失败了,便返回NULL。
_beginthreadex和_beginthread函数的区别。_beginthread函数的参数比较少,因此比特性全面的_beginthreadex函数受到更大的限制。
例如,如果使用_beginthread,就无法创建带有安全属性的新线程,无法创建暂停的线程,也无法获得线程的ID值。
转载请标注来源:http://www.alonemonkey.com
By:AloneMonkey