![深入探讨MFC消息循环和消息泵_第1页](http://file4.renrendoc.com/view/0dc68f6eca2c76c70787d6215010e207/0dc68f6eca2c76c70787d6215010e2071.gif)
![深入探讨MFC消息循环和消息泵_第2页](http://file4.renrendoc.com/view/0dc68f6eca2c76c70787d6215010e207/0dc68f6eca2c76c70787d6215010e2072.gif)
![深入探讨MFC消息循环和消息泵_第3页](http://file4.renrendoc.com/view/0dc68f6eca2c76c70787d6215010e207/0dc68f6eca2c76c70787d6215010e2073.gif)
![深入探讨MFC消息循环和消息泵_第4页](http://file4.renrendoc.com/view/0dc68f6eca2c76c70787d6215010e207/0dc68f6eca2c76c70787d6215010e2074.gif)
![深入探讨MFC消息循环和消息泵_第5页](http://file4.renrendoc.com/view/0dc68f6eca2c76c70787d6215010e207/0dc68f6eca2c76c70787d6215010e2075.gif)
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
深入探讨MFC消息循环和消息泵作者:周焱首先,应该清楚MFC的消息循环(::GetMessage,::PeekMessage),消息泵(CWinThread::PumpMessage)和MFC的消息在窗口之间的路由是两件不同的事情。在MFC的应用程序中(应用程序类基于CWinThread继承),必须要有一个消息循环,他的作用是从应用程序的消息队列中读取消息,并把它派送出去(::DispatchMessage)。而消息路由是指消息派送出去之后,系统(USER32.DLL)把消息投递到哪个窗口,以及以后消息在窗口之间的传递是怎样的。消息分为队列消息(进入线程的消息队列)和非队列消息(不进入线程的消息队列)。对于队列消息,最常见的是鼠标和键盘触发的消息,例如WM_MOUSERMOVE,WM_CHAR等消息;还有例女如WM_PAINT、WM_TIMER和WM_QUITO当鼠标、键盘事件被触发后,相应的鼠标或键盘驱动程序就会把这些事件转换成相应的消息,然后输送到系统消息队列,由Windows系统负责把消息加入到相应线程的消息队列中,于是就有了消息循环(从消息队列中读取并派送消息)。还有一种是非队列消息,他绕过系统队列和消息队列,直接将消息发送到窗口过程。例如,当用户激活一个窗口系统发送WM_ACTIVATE,WM_SETFOCUS,andWM_SETCURSOR。创建窗口时发送WM_CREATE消息。在后面你将看到,MS这么设计是很有道理的,以及他的整套实现机制。这里讲述MFC的消息循环,消息泵。先看看程序启动时,怎么进入消息循环的:_tWinMain->AfxWinMain->AfxWinInit->CWinThread::InitApplication->CWinThread::InitInstance->CWinThread::Run非对话框程序的消息循环的事情都从这CWinThread的一Run开始...第一部分:非对话框程序的消息循环机制。//thrdcore.cpp//mainrunningroutineuntilthreadexitsintCWinThread::Run(){ASSERT_VALID(this);//fortrackingtheidletimestateBOOLbIdle=TRUE;LONGlIdleCount=0;//acquireanddispatchmessagesuntilaWM_QUITmessageisreceived.for(;;){//phase1:checktoseeifwecandoidleworkwhile(bIdle&&!::PeekMessage(&m_msgCur,NULL,NULL,NULL,PM_NOREMOVE)){//callOnIdlewhileinbIdlestateif(!OnIdle(lIdleCount++))bIdle=FALSE;//assume"noidle"state}//phase2:pumpmessageswhileavailabledo{//pumpmessage,butquitonWM_QUITif(!PumpMessage())returnExitInstance();//reset"noidle"stateafterpumping"normal"messageif(IsIdleMessage(&m_msgCur)){bIdle=TRUE;lIdleCount=0;}}while(::PeekMessage(&m_msgCur,NULL,NULL,NULL,PM_NOREMOVE));} //无限循环,退出条件是收到WM_QUIT消息。ASSERT(FALSE);//notreachable}这是一个无限循环,他的退出条件是收到WM_QUIT消息:if(!PumpMessage())returnExitInstance();在PumpMessage中,如果收到WM_QUIT消息,那么返回FALSE,所以ExitInstance()函数执行,跳出循环,返回程序的退出代码。所以,一个程序要退出,只用在代码中调用函数VOIDPostQuitMessage(intnExitCode)。指定退出代码nExitCode就可以退出程序。下面讨论一下这个函数Run的流程,分两步:1,第一个内循环phasel。bldle代表程序是否空闲。他的意思就是,如果程序是空闲并且消息队列中没有要处理的消息,那么调用虚函数Onldle进行空闲处理。在这个处理中将更新UI界面(比如工具栏按钮的enable和disable状态),删除临时对象(比如用FromHandle得到的对象指针。由于这个原因,在函数之间传递由FromHandle得到的对象指针是不安全的,因为他没有持久性)。Onldle是可以重载的,你可以重载他并返回TRUE使消息循环继续处于空闲状态。NOTE:MS用临时对象是出于效率上的考虑,使内存有效利用,并能够在空闲时自动撤销资源。关于由句柄转换成对象,可以有若干种方法。一般是先申明一个对象obj,然后使用obj.Attatch来和一个句柄绑定。这样产生的对象是永久的,你必须用obj.Detach来释放对象。2,第二个内循环phase2。在这个循环内先启动消息泵(PumpMessage),如果不是WM_QUIT消息,消息泵将消息发送出去(::DispatchMessage)。消息的目的地是消息结构中的hwnd字段所对应的窗口。//thrdcore.cppBOOLCWinThread::PumpMessage(){ASSERT_VALID(this);//如果是WM_QUIT就退出函数(returnFALSE),这将导致程序结束.if(!::GetMessage(&m_msgCur,NULL,NULL,NULL)){#ifdef_DEBUGif(afxTraceFlags&traceAppMsg)TRACE0("CWinThread::PumpMessage-ReceivedWM_QUIT.\n");m_nDisablePumpCount++;//applicationmustdie//Note:preventscallingmessageloopthingsin'ExitInstance'//willneverbedecremented#endifreturnFALSE;}#ifdef_DEBUGif(m_nDisablePumpCount!=0){TRACE0("Error:CWinThread::PumpMessagecalledwhennotpermitted.\n");ASSERT(FALSE);}#endif#ifdef_DEBUGif(afxTraceFlags&traceAppMsg)_AfxTraceMsg(_T("PumpMessage"),&m_msgCur);#endif//processthismessageif(m_msgCur.message!=WM_KICKIDLE&&!PreTranslateMessage(&m_msgCur)){::TranslateMessage(&m_msgCur);//键转换::DispatchMessage(&m_msgCur);//派送消息}returnTRUE;}在这一步有一个特别重要的函数大家一定认识:PreTranslateMessage。这个函数在::DispatchMessage发送消息到窗口之前,进行对消息的预处理。PreTranslateMessage函数是CWinThread的成员函数,大家重载的时候都是在View类或者主窗口类中,那么,它是怎么进入别的类的呢?代码如下://thrdcore.cppBOOLCWinThread::PreTranslateMessage(MSG*pMsg){ASSERT_VALID(this);//如果是线程消息,那么将会调用线程消息的处理函数if(pMsg->hwnd==NULL&&DispatchThreadMessageEx(pMsg))returnTRUE;//walkfromtargettomainwindowCWnd*pMainWnd=AfxGetMainWnd();if(CWnd::WalkPreTranslateTree(pMainWnd->GetSafeHwnd(),pMsg))returnTRUE;//incaseofmodelessdialogs,lastchanceroutethroughmain//window'sacceleratortableif(pMainWnd!=NULL){CWnd*pWnd=CWnd::FromHandle(pMsg->hwnd);if(pWnd->GetTopLevelParent()!=pMainWnd)returnpMainWnd->PreTranslateMessage(pMsg);}returnFALSE;//nospecialprocessing}由上面这个函数可以看出:第一,如果(pMsg->hwnd==NULL),说明这是一个线程消息。调用CWinThread::DispatchThreadMessageEx到消息映射表找到消息入口,然后调用消息处理函数。NOTE:一般用PostThreadMessage函数发送线程之间的消息,他和窗口消息不同,需要指定线程id,消息激被系统放入到目标线程的消息队列中;用ON_THREAD_MESSAGE(message,memberFxn)宏可以映射线程消息和他的处理函数。这个宏必须在应用程序类(从CWinThread继承)中,因为只有应用程序类才处理线程消息。如果你在别的类(比如视图类)中用这个宏,线程消息的消息处理函数将得不到线程消息。第二,消息的目标窗口的PreTranslateMessage函数首先得到消息处理权,如果函数返回FALSE,那么他的父窗口将得到消息的处理权,直到主窗口;如果函数返回TRUE(表示消息已经被处理了),那么就不需要调用父类的PreTranslateMessage函数。这样,保证了消息的目标窗口以及他的父窗口都可以有机会调用PreTranslateMessage--在消息发送到窗口之前进行预处理(如果自己处理完然后返回FALSE的话-_-b),如果你想要消息不传递给父类进行处理的话,返回TRUE就行了。第三,如果消息的目标窗口和主窗口没有父子关系,那么再调用主窗口的PreTranslateMessage函数。为什么这样?由第二步知道,一个窗口的父窗口不是主窗口的话,尽管它的PreTranslateMessage返回FALSE,主窗口也没有机会调用PreTranslateMessage函数。我们知道,加速键的转换一般在框架窗口的PreTranslateMessage函数中。我找遍了MFC中关于加速键转换的处理,只有CFrameWnd,CMDIFrameWnd,CMDIChildWnd等窗口类有。所以,第三步的意思是,如果消息的目标窗口(他的父窗口不是主窗口,比如一个这样的非模式对话框)使消息的预处理继续漫游的话(他的PreTranslateMessage返回FALSE),那么给一次机会给主窗口调用PreTranslateMessage(万一他是某个加速键消息呢?),这样能够保证在有非模式对话框的情况下还能保证主窗口的加速键好使。我做了一个小例子,在对话框类的PreTranslateMessage中,返回FALSE。在主窗口显示这个非模式对话框,在对话框拥有焦点的时候,仍然能够激活主窗口的快捷键。总之,整个框架就是让每个消息的目标窗口(包括他的父窗口)都有机会参与消息到来之前的处理。呵呵~至此,非对话框的消息循环和消息泵的机制就差不多了。这个机制在一个无限循环中,不断地从消息队列中获取消息,并且保证了程序的线程消息能够得到机会处理,窗口消息在预处理之后被发送到相应的窗口处理过程。那么,还有一点疑问,为什么要一会儿调用::PeekMessage,—会儿调用::GetMessage呢,他们有什么区别?NOTE:一般来说,GetMessage被设计用来高效地从消息队列获取消息。如果队列中没有消息,那么函数GetMessage将导致线程休眠(让出CPU时间)。而PeekMessage是判断消息队列中如果没有消息,它马上返回0,不会导致线程处于睡眠状态。在上面的phasel第一个内循环中用到了PeekMessage,它的参数PM_NOREMOVE表示并不从消息队列中移走消息,而是一个检测查询,如果消息队列中没有消息他立刻返回0,如果这时线程空闲的话将会引起消息循环调用OnIdle处理过程(上面讲到了这个函数的重要性)。如果将::PeekMessage改成::GetMessage(***),那么如果消息队列中没有消息,线程将休眠,直到线程下一次获得CPU时间并且有消息出现才可能继续执行,这样,消息循环的空闲时间没有得到应用,OnIdle也将得不到执行。这就是为什么既要用::PeekMessage(查询),又要用::GetMessage(做实际的工作)的缘故。第二部分:对话框程序的消息循环机制基于对话框的MFC工程和上面的消息循环机制不一样。实际上MFC的对话框工程程序就是模式对话框。他和上面讲到的非对话框程序的不同之处,主要在于应用程序对象的Initinstance()不一样。//dlg_5Dlg.cppBOOLCDlg_5App::InitInstance(){AfxEnableControlContainer();#ifdef_AFXDLLEnable3dControls();//CallthiswhenusingMFCinasharedDLL#elseEnable3dControlsStatic();//CallthiswhenlinkingtoMFCstatically#endifCDlg_5Dlgdlg;//定义一个对话框对象m_pMainWnd=&dlg;intnResponse=dlg.DoModal();//对话框的消息循环在这里面开始if(nResponse==IDOK){//TODO:Placecodeheretohandlewhenthedialogis//dismissedwithOK}elseif(nResponse==IDCANCEL){//TODO:Placecodeheretohandlewhenthedialogis//dismissedwithCancel}//Sincethedialoghasbeenclosed,returnFALSEsothatweexitthe//application,ratherthanstarttheapplication'smessagepump.returnFALSE;}NOTE:Initinstance函数返回FALSE,由最上面程序启动流程可以看出,CWinThread::Run是不会得到执行的。也就是说,上面第一部分说的消息循环在对话框中是不能执行的。实际上,对话框也有消息循环,她的消息循环在CDialog::DoModal()虚函数中的一个RunModalLoop函数中。这个函数的实现体在CWnd类中:intCWnd::RunModalLoop(DWORDdwFlags){ASSERT(::IsWindow(m_hWnd));//windowmustbecreatedASSERT(!(m_nFlags&WF_MODALLOOP));//windowmustnotalreadybeinmodalstate//fortrackingtheidletimestateBOOLbIdle=TRUE;LONGlIdleCount=0;BOOLbShowIdle=(dwFlags&MLF_SHOWONIDLE)&&!(GetStyle()&WS_VISIBLE);HWNDhWndParent=::GetParent(m_hWnd);m_nFlags|=(WF_MODALLOOP|WF_CONTINUEMODAL);MSG*pMsg=&AfxGetThread()->m_msgCur;//acquireanddispatchmessagesuntilthemodalstateisdonefor(;;){ASSERT(ContinueModal());//phase1:checktoseeifwecandoidleworkwhile(bIdle&&!::PeekMessage(pMsg,NULL,NULL,NULL,PM_NOREMOVE)){ASSERT(ContinueModal());//showthedialogwhenthemessagequeuegoesidleif(bShowIdle){ShowWindow(SW_SHOWNORMAL);UpdateWindow();bShowIdle=FALSE;}//callOnIdlewhileinbIdlestateif(!(dwFlags&MLF_NOIDLEMSG)&&hWndParent!=NULL&&lIdleCount==0){//sendWM_ENTERIDLEtotheparent::SendMessage(hWndParent,WM_ENTERIDLE,MSGF_DIALOGBOX,(LPARAM)m_hWnd);}if((dwFlags&MLF_NOKICKIDLE)||!SendMessage(WM_KICKIDLE,MSGF_DIALOGBOX,lIdleCount++)){//stopidleprocessingnexttimebIdle=FALSE;}}//phase2:pumpmessageswhileavailabledo{ASSERT(ContinueModal());//pumpmessage,butquitonWM_QUIT//PumpMessage(消息泵)的实现和上面讲的差不多。都是派送消息到窗口。if(!AfxGetThread()->PumpMessage()){AfxPostQuitMessage(0);return-1;}//showthewindowwhencertainspecialmessagesrec'dif(bShowIdle&&(pMsg->message==0x118||pMsg->message==WM_SYSKEYDOWN)){ShowWindow(SW_SHOWNORMAL);UpdateWindow();bShowIdle=FALSE;}if(!ContinueModal())gotoExitModal;//reset"noidle"stateafterpumping"normal"messageif(AfxGetThread()->IsIdleMessage(pMsg)){bIdle=TRUE;lIdleCount=0;}}while(::PeekMessage(pMsg,NULL,NULL,NULL,PM_NOREMOVE));}//无限循环ExitModal:m_nFlags&=~(WF_MODALLOOP|WF_CONTINUEMODAL);returnm_nModalResult;}先说说怎么退出这个无限循环,在代码中:if(!ContinueModal())gotoExitModal;决定是否退出循环,消息循环函数返回也就是快要结束结束程序了。BOOLCWnd::ContinueModal(){returnm_nFlags&WF_CONTINUEMODAL;}NOTE:CWnd::ContinueModal()函数检查对话框是否继续模式。返回TRUE,表示现在是模式的;返回FALSE,表示对话框已经不是模式(将要结束)。如果要结束对话框,在内部最终会调用函数CWnd::EndModalLoop,它取消m_nFlags的模式标志(消息循环中的ContinueModal函数将返回FALSE,消息循环将结束,程序将退出);然后激发消息循环读取消息。也就是说,结束模式对话框是一个标志,改变这个标志就可以了。他的代码是://wincore.cppvoidCWnd::EndModalLoop(intnResult){ASSERT(::IsWindow(m_hWnd));//thisresultwillbereturnedfromCWnd::RunModalLoopm_nModalResult=nResult;//makesureamessagegoesthroughtoexitthemodalloopif(m_nFlags&WF_CONTINUEMODAL){m_nFlags&=~WF_CONTINUEMODAL;PostMessage(WM_NULL);}}NOTE:PostMessage(NULL)是有用的。如果消息队列中没有消息的话,可能消息循环中的ContinueModalO不会马上执行,发送一个空消息是激发消息循环马上工作。下面说一下CWnd::RunModalLoop函数中的消息循环究竟干了些什么事情:1,第一个内循环。首先从消息队列中查询消息,如果对话框空闲,而且消息队列中没有消息,他做三件事情,大家应到都能从字面上明白什么意思。最重要的是发送WM_KICKIDLE消息。为什么呢?第一部分讲到了,非对话框程序用OnIdle来更新用户界面(UI),比如工具栏,状态栏。那么,如果对话框中也有工具栏和状态栏呢,在哪里更新(网上有很多这样的程序)?可以处理WM_KICKIDLE消息:LRESULTCDlg_5Dlg::OnKickIdle(WPARAMw,LPARAMl){//调用CWnd::UpdateDialogControls更新用户界面UpdateDialogControls(this,TRUE);return0;}NOTE:CWnd::UpdateDialog函数发送CN_UPDATE_COMMAND_UI消息给所有的用户界面对话框控件。2,第二个内循环。最重要的还是PumpMessage派送消息到目标窗口。其他的,像第二个if语句,0x118消息好像是WM_SYSTIMER消息(系统用来通知光标跳动的一个消息)。也就是说,如果消息为WM_SYSTIMER或者WM_SYSKEYDOWN,并且空闲显示标志为真的话,就显示窗口并通知窗口立刻重绘。总之,对话框的消息循环机制和非对话框(比如SDI,MDI)还是类似的,仅仅侧重点不同。模式对话框是模式显示,自然有他的特点。下面部分讨论一下模式对话框和非模式对话框的区别。因为模式对话框有自己的特殊消息循环;而非模式对话框,共用程序的消息循环,和普通的窗口已经没有什么大的区别了。第三部分:模式对话框和非模式对话框的区别这个话题已经有很多人讨论,我说说我所理解的意思。在MFC框架中,一个对话框对象DoModal一下就能产生一个模式对话框,Create一下就能产生一个非模式对话框。实际上,无论是模式对话框还是非模式对话框,在MFC内部都是调用::CreateDialoglndirect(***)函数来创建非模式对话框。只是模式对话框作了更多的工作,包括使父窗口无效,然后进入自己的消息循环等等。::CreateDialoglndirect(***)函数最终调用CreateWindowEx函数通知系统创建窗体并返回句柄,他内部没有实现自己的消息循环。非模式对话框创建之后立即返回,并且和主程序共用一个消息循环。非模式对话框要等对话框结束之后才返回,自己有消息循环。比如下面的代码:CMyDlg*pdlg=newCMyDlg;pdlg->Create(IDD_DIALOG1);pdlg->ShowWindow(SW_SHOW);MessageBox("abc");非模式对话框和消息框MessageBox几乎是同时弹出来。而如果将Create改成DoModal,那么,只能弹出模式对话框,在关闭了对话框之后(模式对话框自己的消息循环结束),消息框才弹出来。NOTE:可以在模式对话框中调用GetParent()->EnableWindow(true);这样,主窗口的菜单,工具栏又激活了,能用了。MFC使用非模式对话框来模拟模式对话框,而在Win32SDK程序中,模式对话框激发他的父窗口Enable操作是没有效果的。关于消息循环总结:1,我们站在一个什么高度看消息循环?消息循环其实没有什么深奥的道理。如果一个邮递员要不断在一个城市中送信,我们要求他做什么?要求他来回跑,但他一次只能在一个地方出现。如果我们的应用程序只有一个线程的话,我们要他不断地为窗口传递消息,我们怎么做?在一个循环中不断的检测消息,并将他发送到适当的窗口。窗口可以有很多个,但消息循环只有一个,而且每时每刻最多只有一个地方在执行代码。为什么?看第二点。2,因为是单线程的(程序进程启动的时候,只有而且有一个线程,我们称他为主线程),所以就像邮递员一样,每次只能在某一个地方干活。什么意思呢?举个例子,用::DiapatchMessage派送消息,在窗口处理过程(WinProc,窗口函数)返回之前,他是阻塞的,不会立即返回,也就是消息循环此时不能再从消息队列中读取消息,直到::DispatchMessage返回。如果你在窗口函数中执行一个死循环操作,就算你用PostQuitMessage函数退出,程序也会down掉。while(1){PostQuitMessage(O);//程序照样down.}所以,当窗口函数处理没有返回的时候,消息循环是不会从消息队列中读取消息的。这也是为什么在模式对话框中要自己用无限循环来继续消息循环,因为这个无限循环阻塞了原来的消息循环,所以,在这个无限循环中要用GetMessage,PeekMessage,DispatchMessage来从消息队列中读取消息并派送消息了。要不然程序就不会响应了,这不是我们所希望的。所以说,消息循环放在程序的什么的地方都基本上是过的去的,比如放在DLL里面。但是,最好在任何时候,只有一个消息循环在工作(其他的都被阻塞了)。然后,我们要作好的一件事情,就是怎么从消息循环中退出!当然用WM_QUIT是可以拉~(PostThreadMessage也是个好主意),这个消息循环退出后,可能程序退出,也可能会激活另外一个被阻塞的消息循环,程序继续运行。这要看你怎么想,怎么去做。最后一个消息循环结束的时候,也许就是程序快结束的时候,因为主线程的执行代码也快要完了(除非BT的再作个死循环)。NOTE:让windows系统知道创建一个线程的唯一方法是调用APICreatThread函数(__beginthreadex之类的都要在内部调用他创建新线程)。好像windows核心编程说,在win2000下,系统用CreateRemoteThread函数来创建线程,CreateThread在内部调用CreateRemoteThread。不过这不是争论的焦点,至少win98下CreateRemoteThread并不能正常工作,还是CreateThread主持大局。3,在整个消息循环的机制中,还必须谈到窗口函数的可重入性。什么意思?就是窗口函数(他是个回调函数)的代码什么时候都可以被系统(调用者一般是user32模块)调用。比如在窗口过程中,向自己的窗口SendMessage(***);那么执行过程是怎样的?我们知道,SendMessage是要等到消息发送并被目标窗口执行完之后才返回的。那么窗口在处理消息,然后又等待刚才发送到本窗口的消息被处理后之后(SendMessage返回)才继续往下执行,程序不就互相死锁了吗?其实是不会的。windows设计一套适合SendMessage的算法,他判断如果发送的消息是属于本线程创建的窗口的,那么直接由user32模块调用窗口函数(可能就有窗口重入),并将消息的处理结果结果返回。这样做体现了窗口重入。上面的例子,我们调用SendMessage(***)发送消息到本窗口,那么窗口过程再次被调用,处理完消息之后将结果返回,然后SendMessage之后的程序接着执行。对于非队列消息,如果没有窗口重入,不知道会是什么样子。NOTE:由于窗口的可重入
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 《钱塘湖春行》教案15篇
- 2025年度房地产经纪公司品牌合作合同
- 2025年度数字经济产业发展基金投资管理合同
- 生物乙醇在农业循环经济中的价值挖掘
- 生产车间一线员工的激励性绩效考核培训
- 电力应急救援队伍的装备与配置
- 生态城市规划中可再生能源的应用
- 环境保护背景下的绿色供应链管理
- 2025年度融资租赁担保协议范本含担保人
- 现代企业制度下的税务风险管理
- 北京三甲中医疼痛科合作方案
- QCT957-2023洗扫车技术规范
- 新外研版高中英语选择性必修1单词正序英汉互译默写本
- 自愿断绝父子关系协议书电子版
- 2023年4月自考00504艺术概论试题及答案含解析
- 美丽的大自然(教案)2023-2024学年美术一年级下册
- 2024年低压电工考试题库(试题含答案)
- 成都特色民俗课件
- 花城版音乐四下-第四课-认知音乐节奏(教案)
- 统编版语文五年级下册 《古诗三首》公开课一等奖创新教学设计及反思
- 宠物医院员工手册
评论
0/150
提交评论