面向对象的思想难以适应q种分布式Y件模型,于是lg化程序设计思想得到了迅速的发展?/p>
按照lg化的E序设计的思想Q复杂的应用E序被设计成一些小的,功能单一的组件模块,q些lg模块可以q行在同一台机器上Q也可以q行在不同的机器上?/p>
Z实现q样的应用YӞl徏E序和组建程序之间需要一些极为细致的规范Q?只有lgE序遵守了这些共同的规范Q然间系l才能正常运行?/p>
为此QOMG和Microsoft分别提出了CORBA(Common Object Request Breaker Architecture)和COM(Component Object model)标准Q目前CORBA模型主要应用于UNIX操作pȝq_上,而COM 则主要应用于Microsoft Windows操作pȝq_上?
在COM标准中,一个组件程序也被称Z个模块,它可以是一个动态连接库(DLL), 被称E内lg(in-of-process component)也可以是一个可执行E序(EXE)Q被UCؓq程外组?out-of-process component).
COM对象是徏立在二进制可执行代码U的基础上,而C++{语a中的对象是徏立在源代码基础上的Q因此COM对象是语a无关的。这一Ҏ用不同编E语a开发的lg对象q行交互成ؓ可能?/p>
在Microsoft Windowspȝq_上,COM技术被应用于系l的各个层次Q从底层的COM对象理C层的应用E序交互都用CCOM标准?
概述
COM既提Zlg之间q行交互的规范,也提供了实现交互的环境, 因ؓlg对象之间交互的规范不依赖于Q何特定的语言Q所以COM也可以是不同语言协作开发的一U标准?
OLE技术以COM规范为基QOLE充分发挥了COM标准的优势,使Windows操作pȝ上的应用E序h极强的可交互性。如果没有OLE的支持,Windows操作pȝ则会逊色很多?
但是QCOM规范q不局限于OLE技术,实际上,OLE技术只是COM的一个应用而已Q这几年QOLE技术在q行|络互连是显C出了很大的局限性,而COM则表现出了极强的适应能力?
COM标准包括规范和实C大部分,规范部分定义了组件和lg之间通信的机Ӟq些规范不依赖于M特定的语a和操作系l,只要按照该规范,M语言都可以? COM标准的实现部分是COM库,COM库ؓCOM规范的具体实现提供了一些核心服务?
COM是面对对象的软g模型Q因而对象是他的基本要素之一。类gC++中对象的概念Q对象是某个c?class)的一个实?而类则是一l相关的数据和功能组和在一L一个定义。用对象的应用(或另一个对?成ؓ客户Q有时也成ؓ对象的用戗?
接口是一l逻辑上相关的函数集合Q其函数也被UCؓ接口成员函数。对象通过接口成员函数为客h供各UŞ式的服务?
在COM模型中,对象本n对于客户来说是不可见的,客户h服务Ӟ只能通过接口q行。每一个接口都׃?28位的全局唯一标识W?GUID,Globally Unique Identifier)来标识。客户通过GUID获得接口的指针,在通过接口指针Q客户就可以调用其相应的成员函数?
一般来_接口是不变的Q只要客h望的接口在组建对象中q存在,它就可以l箋使用该接口所提供的服务。对象可以支持多个接口,因此对组件对象的升可通过增加接口的办法实玎ͼq样得到的新接口可以不媄响老接口的使用?
客户如何来标识COM对象呢?与接口类|每个对象也用一?28位GUID来标识,UCؓCLSID(class identifier,cL识符或类ID)Q用CLSID标识对象可以保证(概率意义?在全球范围内的唯一性?
只要pȝ中含有这cCOM对象的信息,q包括COM对象所在的模块文g(DLL或EXE文g)以及COM对象在代码中的入口点Q客L序就可以由此CLSID来创建COM对象?
那么客户怎么使用COM对象所提供的服务呢Q客戯得的又是什么呢Q?/p>
实际上,客户成功创徏对象后,它得到的是一个指向对象某个接口的指针Q因为COM对象臛_实现一个接口,所以客户就可以调用该接口提供的所有服务?
但是COM对象可以有自q状态,正是q种状态才使客h觉到COM对象的存在。如果客户同时拥有两个相同CLSID的对象,则两个对象可以有不同的状态,客户完全不必兛_COM对象是怎么实现的,以及两个对象的状态数据之间有什么关p?数组或者链?。当ӞCOM对象也可以是无状态的Q这UCOM对象以提供功能服务ؓ主,可以用来代替传统的API函数接口Q得应用程序编E接口更为有序,l织层次性更强?
COM本n除了规范之外Q也有实现的部分Q其中包括一些核心的pȝU代码,也正是这部分核心代码Q才使得对象和客户之间可通过接口在二q制代码U进行交??/p>
在Microsoft Windows操作pȝ环境下,q些库以.dll文g的Ş势存在,其中包括以下内容:
(1) 提供了少量的API函数实现客户和服务端COM应用的创E。在客户端,主要是一些创建函?而在服务器端Q提供一些对象的讉K支持?/p>
(2) COM通过注册表查找本地服务器即EXEE序Q以及程序名与CLSID的{换等?/p>
(3) 提供了一些标准的内存控制ҎQ应用控制q程中内存的分配?/p>
COM库一般不在应用程序层实现Q而在操作pȝ层次上实玎ͼ因此一个操作系l只有一个COM库实现。而且COM库的实现必须依赖于具体的pȝq_Q尤其是pȝ底层的一些标准?/p>
COM库可以保证所有的lg按照l一的方式进行交互操作,而且它我们在编写COM应用Ӟ可以不用~写行COM通信而必需的大量基代码Q而是直接利用COM库提供的APIq行~程Q从而大大加快了开发的速度。例如,现在COM库的版本都支持远E组件即分布式COMQ我们不用编写Q何网l或者RPC(remote procedure call)的代码,可以实现在|络上进行程序之间的通信?
如果我们用面向对象语a来实现COM对象Q则很自然可以用cȝ定义对象。在C语言中,对象的概念可能变成一个逻辑概念Q如果两个对象同时存在,则在接口实现中必L知道所q行的操作是针对哪个对象的,q个q程可由COM接口的定义保证?
COM规范使用GUID来标识COM对象的思想源于OSF(Open Software Foundation)采用的UUID(Universallz Unique Identifier), UUID被定义ؓDCE(Distributed Computing Environment)的一部分Q主要用于表识RPC通信的双斏V?
除了装性和重用性,C++对象q有一个重要特性是多态性。正是C++对象的多态性,才体CC++语言用类描述事物的高度抽象的特征;COM对象?h多态性,但这U多态性需要通过COM对象所h的接口才能体现出来,像C++对象的多态性需要通过?virtual)函数才能体现一栗?
从API到COM接口
假如我们要实C个字处理应用pȝQ它需要一个查字典的功能,按照lg化程序设计的ҎQ自然应该把查字典的功能攑ֈ一个组?.dll)E序中实现。如果以后字典程序的查找法或者字典库改变了,只要应用E序和组件之间的接口不变Q则新的lgE序仍然可以被应用系l用。这是采用lgE序带来的灵zL?
Z把应用系l和lgE序q接hQ又能它们协同工作Q最单的做法是先定义一l查字典的函敎ͼ而且q组函数可能一般化Q不要加入特定的与字典库相关的知识?
函数
功能说明
Initialize
初始?br>
LoadLibrary
装入字典?br>
InsertWord
插入一个单?br>
DeleteWord
删除一个单?br>
LookupWord
查找单词
RestoreLibrary
把内存中的字典库存入指定的文件中
FreeLibrary
释放字典?br>
q面型的API接口层可以很好地把两个程序连接v来,但存在以下一些问题:
(1) 当API函数非常多时Q用会非常不方便,需要对函数q行l织?/p>
(2) API函数需要标准化Q按照统一的调用方式进行处理,以适应不同的语a~程实现。参数的传递顺序,参数cdQ寒暑返回处理都需要标准化?/p>
COM定义了一套完整的接口规范Q不仅可以I补以上API作ؓlg借口的不Iq充分发挥了lg对象的优势,q实Clg对象的多态性?/p>
接口定义和标?
从技术上Ԍ接口是包含了一l函数的数据l构Q通过q组数据l构Q客户代码可以调用组件对象的功能。接口定义了一l成员函敎ͼq组成员函数是组件对象暴露出来的所有信息,客户E序利用q些函数或的lg对象的服务?
客户E序用一个指向接口数据机构的指针来调用接口成员函数。接口指针实际上又指向另一个指针,q第二个指针指向一l函敎ͼUCؓ接口函数?虚函数表)Q接口函数表中每一ؓ4个字节长的函数指针,每个函数指针与对象的具体实现q接h。通过q种方式Q客户只要获得了接口指针Q就可以调用到对象的实际功能?
对于一个接口来_他的虚函数表vtable是确定的Q因此接口的成员函数个数是不变的Q而且成员函数的先后顺序也是不变的;对于每个成员函数来说Q其参数和返回g是确定的?
在一个接口的定义中,所有这些信息都必须在二q制一U确定,不管什么语aQ只要能支持q样的内存结构描qͼ也就是能够支?#8220;structure“?#8220;record“cdQƈ且这U类型能够包含双重的指向函数指针表的成员Q则它就可以支持接口的描qͼ从而可以用于编写COMlg或者用COMlg?/p>
接口描述语言IDL
COM规范在采用OSF的DCE规范描述q程调用接口IDL的基上,q行扩展形成了COM接口的描q语a?/p>
COM规范使用的IDL接口描述语言不仅可用于定义COM接口Q同时还定义了一些常用的数据cdQ也可以描述自定义的数据l构Q对于接口成员函敎ͼ我们可以指定每个参数的类型,输入输出Ҏ,甚至支持可变长度的数l的描述。IDL支持指针cdQ与C/C++很类伹{?/p>
Microsoft Visual C++提供了MIDL工具Q可以把IDL接口描述文g~译成C/C++兼容的接口描q头文g(.h)?/p>
IUnknown的定?IDL):
interface IUnknown
{
HRESULT QueryInterface([in] REFIID iid, [out] void **ppv);
ULONG AddRef(void);
ULONG Release(void);
}
IUnknown的定?C++):
class IUnknown
{
Public:
virtual HRESULT _stdcall QueryInterface([in] REFIID iid, [out] void **ppv)=0;
virtual ULONG _stdcall AddRef(void)=0;
virtual ULONG _stdcall Release(void)=0;
}
q程内组?/p>
因ؓq程内组件和客户E序q行在同一个进E地址I间中,所以一旦客L序与lgE序建立起通信关系之后Q客L序得到的接口指针指向lgE序中接口的vtableQ这个vtable包含了所有成员函数地址Q客户代码可以直接调用这些成员函敎ͼ所以其效率非常高?/p>
因ؓDLLE序是在q行时刻被客戯入到内存中的Q所以DLL模块本n也是独立的,它ƈ不依赖于客户E序?/p>
在C++语言中,Z使编制的DLLE序更ؓ通用Q一般指定DLL的引出函C用_stdcall调用习惯Q如果用了_cdecl调用习惯Q则有些~程语言环境׃能用这些DLLE序。C++~译器ؓDLLE序的每个引出函数生成了一个修饰名Q这些修饰名对于不同的编译器q不兼容Q因此,从通用性角度出发,我们在每个函数定义前加上extern ?C“ 说明W。在Visual C++ 开发环境中Q下面的说明语句可以很好的说明一个引出函敎ͼ
extern ? C“ int _stdcall MyFunction(int n);
Z~制DLLE序Q我们可以按照这L步骤Q?/p>
(1) 创徏一个DLL工程
(2) Ҏ个引出函敎ͼ使用extern ? C“说明W,以及_stdcall修饰W,如上面对MyFunction函数的说明?/p>
(3) 按照传统的编E方法,我们q应该编写一个DEF文gQ用来描qDLLE序的模块信息。在Win32q_上,我们可以不用DEF文gQ而是直接在函数说明时使用_declspec(dllexport)说明W,例如Q?/p>
extern ? C“_declspec(dllexport) int _stdcall MyFunction(int n);
按照q样的方法徏立v来的DLL模块可以被其他程序调用,因ؓC++q接器会把所有引出函数的信息q接到最l的目标代码中?
从客L序一Ҏ看,有三个系l函数可用于操作DLLE序QLoadLibrary, GetProcAddress, 和FreeLibrary?
一般地Q对于DLLE序的用过E按照这L步骤q行Q?/p>
首先Q客L序用LoadLibrary函数装入DLLQ该函数q回模块的实例句柄,供以后操作该模块使用?/p>
然后Q客L序可以调用GetProcAddress函数获得DLL中引出的函数的地址Q我们既可以按函数的序号(在DEF文g中指?也可以按函数的名字来获取引出函数的地址Q因为客L序和DLLE序在相同的内存地址I间中,所以客L序可以直接调用这些引出函数?/p>
最后FreeLibraryQ把DLLE序卸出内存Q以侉K放资源?
说明Q?/p>
(1) DLLE序不仅可以引出函数Q也可以引出全局变量Q因为客L序和DLLE序在同一个地址I间Q所以,把DLL中的全局变量引出到客L序中是有意义的。引用的Ҏq不复杂Q或者把变量名放到DEF文g的EXPORTS部分Qƈ加上DATA选项; 或者在变量说明前面加上_declspec(dllexport)说明W?/p>
(2) DumpBin 通过/Exports选项可以列出DLLE序中的所有被引出的信息?/p>
(3) 客户E序本n也可以是一个DLLE序Q但它一定先被装入到q程I间中,以便可以调用pȝ函数操作作ؓ服务E序的DLL模块?/p>
q程外组?
因ؓq程外组件程序和客户E序位于不同的进E空间之中,他们使用不同的地址I间Q所以组件和客户之间的通信必须跨越q程边界Q这涉及到以下一些问题:
(1) 一个进E如何调用另外一个进E中的函?/p>
(2) 参数如何从一个进E被传递到另外一个进E中
Windowsq_上,在不同进E之间进行通信的办法很多,包括DDE, named pipe,或者共享内存等{,COM采用了LPC(Local Procedure Call)和RPC(Remote Procedure Call)
RegEdit可检查CLSID子键下的COM对象(63?
Microsoft Visual C++提供OleView.exeQ可列出当前机器上的所有类别信息,以及每一U类别下的组件对象列表?/p>
RegSvr32 D:\DicComp\DictComp.dll
RegSvr32 /u D:\DicComp\DictComp.dll
DLLlg必须有DllRegisterServer和DllUnregisterServer两个用于注册的入口函敎ͼ才能用RegSvr32注册?/p>
COM规定Q支持自注册的进E外lg必须支持两个命o行参?RegServer?UnregServerQ以便完成注册或注销操作?
Class Factory
实际上,客户E序q不直接调用lgE序的引出函敎ͼ它调用COM库的函数q行lg对象的创建工作,COM库的创徏函数Ҏ注册表的信息调用lgE序的入口函数来创徏lg对象。组件程序需要提供一个标准的入口函数DLLGetObjectClassQ用于提供本l程序的lg信息?/p>
Class Factory和DLLGetObjectClass函数
cd是COM对象的生产基圎ͼCOM库通过cd创徏COM对象; 对应每一个COMc,有一个类厂专门用于该COMcȝ对象创操作。类厂本w也是一个COM对象Q它支持一个特D的接口QIClassFactoryQ其定义如下Q?/p>
Class IClassFactory : public IUnknown
{
virtual HRESULT _stdcall CreateInstance(IUnknown *pUnknownOuter, const IID& iid, void **ppv) = 0;
virtual HRESULT _stdcall LockServer(BOOL bLock) = 0;
};
接口IClassFactory有一个重要的成员函数CreateInstanceQ用于创建对应的COM对象。因为每个类厂之针对特定的COMcd象,所以CreateInstance成员函数知道该创Z么样的COM对象。在CreateInstance成员函数的参CQ第一个参数pUnknownOuter用于对象被聚合的情ŞQ没有聚合设成NULL。IClassFactory的另一个成员函数LockServer用于控制l徏的生存周期?/p>
因ؓcd本n也是个COM对象Q它被用于其它COM对象的创E,那么cd对象又由谁来创徏呢?{案是DLLGetClassObject引出函数。DLLGetClassObject函数q不是COM库的函数Q而是q件程序实现的引出函数Q我们先看一下DLLGetClassObject函数的原型:
HRESULT DLLGetClassObject(const CLSID& clsid,
Const IID& iid,
(void **) ppv
);
COM库在接到对象创徏的指令后Q它要调q程内组件的DLLGetClassObject函数Q由该函数创cd对象Qƈq回cd对象的接口指针,COM库或者客户一旦有了类厂的接口指针Q它们就可以通过cd接口IClassFactory的成员函数CreateInstance创徏相应的COM对象?/p>
COM库与cd的交互(67)
在COM库中Q有三个API函数可用于对象的创徏Q它们分别是CoGetClassObject, CoCreateInstance和CoCreateInstanceEx。通常情况下,客户E序调用其中之一完成对象的创建,q返回对象的初始接口指针。COM库与cd也通过q三个函数进行交互?br>
深入出ShellExecute
译者:徐景?/font>(原作:Nishant S)
ShellExecute(this->m_hWnd,"open","calc.exe","","", SW_SHOW );?
ShellExecute(this->m_hWnd,"open","notepad.exe", "c:\\MyLog.log","",SW_SHOW );正如您所看到的,我ƈ没有传递程序的完整路径?br>
ShellExecute(this->m_hWnd,"open", "c:\\abc.txt","","",SW_SHOW );
ShellExecute(this->m_hWnd,"open", "http://www.google.com","","", SW_SHOW );
ShellExecute(this->m_hWnd,"open", "mailto:nishinapp@yahoo.com","","", SW_SHOW );
ShellExecute(this->m_hWnd,"print", "c:\\abc.txt","","", SW_HIDE);
ShellExecute(m_hWnd,"find","d:\\nish", NULL,NULL,SW_SHOW);
SHELLEXECUTEINFO ShExecInfo = {0}; ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO); ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS; ShExecInfo.hwnd = NULL; ShExecInfo.lpVerb = NULL; ShExecInfo.lpFile = "c:\\MyProgram.exe"; ShExecInfo.lpParameters = ""; ShExecInfo.lpDirectory = NULL; ShExecInfo.nShow = SW_SHOW; ShExecInfo.hInstApp = NULL; ShellExecuteEx(&ShExecInfo); WaitForSingleObject(ShExecInfo.hProcess,INFINITE);或:
PROCESS_INFORMATION ProcessInfo; STARTUPINFO StartupInfo; //This is an [in] parameter ZeroMemory(&StartupInfo, sizeof(StartupInfo)); StartupInfo.cb = sizeof StartupInfo ; //Only compulsory field if(CreateProcess("c:\\winnt\\notepad.exe", NULL, NULL,NULL,FALSE,0,NULL, NULL,&StartupInfo,&ProcessInfo)) { WaitForSingleObject(ProcessInfo.hProcess,INFINITE); CloseHandle(ProcessInfo.hThread); CloseHandle(ProcessInfo.hProcess); } else { MessageBox("The process could not be started..."); }
SHELLEXECUTEINFO ShExecInfo ={0}; ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO); ShExecInfo.fMask = SEE_MASK_INVOKEIDLIST ; ShExecInfo.hwnd = NULL; ShExecInfo.lpVerb = "properties"; ShExecInfo.lpFile = "c:\\"; //can be a file as well ShExecInfo.lpParameters = ""; ShExecInfo.lpDirectory = NULL; ShExecInfo.nShow = SW_SHOW; ShExecInfo.hInstApp = NULL; ShellExecuteEx(&ShExecInfo);
首先声明, q里的工作线E与UIU程是相对的,x有Q何窗口的. 如果需要与ȝE或其它辅助U程通讯,有几U方法如事g,消息,信号{?也可以是以上几种Ҏ的综合运?下面列Z下两U通讯Ҏ的代码框?/span>
Q?1Q?nbsp;只用消息通讯
Q?Q用消息和事仉讯
上面用到了GetMessage和PeekMessage 函数, q两者都是从消息队列取出消息, 不同的是GetMessage从消息队列删除消?q且d调用U程. PeekMessage则是查询消息队列,如果有消息就取出,没有消息也立卌? 是否从消息队列删除消息由最后一个参数决?PM_REMOVE表示删除,PM_NOREMOVE表示不删?可以单地认ؓ,GetMessage是同步的,PeekMessage是异步的.
收获最大的是对q两个函数的理解:GetMessage和PeekMessage 函数,
q两者都是从消息队列取出消息, 不同的是GetMessage从消息队列删除消?q且d调用U程. PeekMessage则是查询消息队列,如果有消息就取出,没有消息也立卌? 是否从消息队列删除消息由最后一个参数决?PM_REMOVE表示删除,PM_NOREMOVE表示不删?可以单地认ؓ,GetMessage是同步的,PeekMessage是异步的.
首先声明, q里的工作线E与UIU程是相对的,x有Q何窗口的. 如果需要与ȝE或其它辅助U程通讯,有几U方法如事g,消息,信号{?也可以是以上几种Ҏ的综合运?下面列Z下两U通讯Ҏ的代码框?/span>
Q?1Q?nbsp;只用消息通讯
Q?Q用消息和事仉讯
上面用到了GetMessage和PeekMessage 函数, q两者都是从消息队列取出消息, 不同的是GetMessage从消息队列删除消?q且d调用U程. PeekMessage则是查询消息队列,如果有消息就取出,没有消息也立卌? 是否从消息队列删除消息由最后一个参数决?PM_REMOVE表示删除,PM_NOREMOVE表示不删?可以单地认ؓ,GetMessage是同步的,PeekMessage是异步的.
收获最大的是对q两个函数的理解:GetMessage和PeekMessage 函数,
q两者都是从消息队列取出消息, 不同的是GetMessage从消息队列删除消?q且d调用U程. PeekMessage则是查询消息队列,如果有消息就取出,没有消息也立卌? 是否从消息队列删除消息由最后一个参数决?PM_REMOVE表示删除,PM_NOREMOVE表示不删?可以单地认ؓ,GetMessage是同步的,PeekMessage是异步的.
~译Ӟ~译器需要的是语法的正确Q函C变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位|(头文件中应该只是声明Q而定义应该放在C/C++文g中)Q只要所有的语法正确Q编译器可以编译出中间目标文g。一般来_每个源文仉应该对应于一个中间目标文ӞO文g或是OBJ文gQ?
链接Ӟ主要是链接函数和全局变量Q所以,我们可以使用q些中间目标文gQO文g或是OBJ文gQ来链接我们的应用程序。链接器q不函数所在的源文Ӟ只管函数的中间目标文ӞObject FileQ,在大多数时候,׃源文件太多,~译生成的中间目标文件太多,而在链接旉要明昑֜指出中间目标文g名,q对于编译很不方便,所以,我们要给中间目标文g打个包,在Windows下这U包?#8220;库文?#8221;QLibrary File)Q也是 .lib 文gQ在UNIX下,是Archive FileQ也是 .a 文g?
ȝ一下,源文仉先会生成中间目标文gQ再׃间目标文件生成执行文件?font color=#ff0000>在编译时Q编译器只检程序语法,和函数、变量是否被声明。如果函数未被声明,~译器会l出一个警告,但可以生成Object File。而在链接E序Ӟ链接器会在所有的Object File中找d数的实现Q如果找不到Q那到就会报链接错误码(Linker ErrorQ,在VC下,q种错误一般是QLink 2001错误Q意思说是说Q链接器未能扑ֈ函数的实现。你需要指定函数的Object File.
http://www.shnenglu.com/woaidongmao/archive/2008/12/17/69696.html
(一) SendMessage 的工作机?
首先我要先简要的说明一个和q个话题有关pȝ消息处理机制Q?
?span>Window操作pȝ当中Q窗口时属于所?span>Thread的也是?你这个窗口在那个Thread 当中Create 的那么你q个H口属于那?span>Thread。同时窗口的消息处理函数也都会在q个Thread 当中被执行的。(不要问ؓ什?span> Window 是q么设计?嘿嘿Q?
在讲死锁之前我们先把SendMessage的工作机制搞清楚Q?
SendMessage 发送出来的消息 到底q入不进入消息队列,有h说进入,有h说不q入Q其实都是错误的Q确切的说是有时q入Q有时不q入。那么什么时候进入,什么时候不q入呢? 我们举一例子来说Q假如在 Thread A 中有一?H口W1Q那??span> Thread A 中像 W1 SendMessage 一个消息,那么q个消息不会被攑օ消息队列Q而是直接调用?span>W1的消息处理函数来直接处理了这个消息。这是不被放入队列的情况Q假如现在又多了一?span>Thread B Q那么在 Thread B ??span> W1 SendMessage 发送消?q个时?span> W1 被攑օ?span> Thread A 的消息队列当中,q些Thread A 中的消息循环?span>GetMessage ?span>Get到这个消?q处理之?q就是进入消息队列情况;Ҏ在哪里我们来看看我的试l果Q?
试1Q?/span>我创Z一个无DOC/View 之支持的单文档工E:
我在CMainFramed如下代码Q?
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_WM_CREATE()
ON_WM_SETFOCUS()
ON_MESSAGE(WM_USER + 100,OnMy)
ON_MESSAGE(WM_USER + 200,OnMy2)
END_MESSAGE_MAP()
LRESULT CMainFrame::OnMy(WPARAM wParam,LPARAM lParam)
{
int i = 0;
return TRUE;
}
LRESULT CMainFrame::OnMy2(WPARAM wParam,LPARAM lParam)
{
int i = 2;
return TRUE;
}
然后我再 int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) 的最?/span> 加入了一行代码:
SendMessage(WM_USER + 100,0,0);
然后直接 F5 q行E序 {到 E序停止在断点上Q我们看?span>Call Stack 的调用顺序:
然后 我又?span> SendMessage ҎQ?
PostMessage(WM_USER + 100,0,0);
然后直接 F5 q行E序 {到 E序停止在断点上Q我们再看看Call Stack 的调用顺序:
通过q?span>2?span> Call Stack 大家可以很清楚的看到,执行SendMessage的时候,是直接调用了 AfxWndProcBase q个 消息处理函数(MFC 通过HOOK 所有窗口的处理函数都重定向到这?函数上了Q?span>AfxWndProcBase()不明白的自己ȝ?span>MFC深入出?span>)Q大家可以很清楚的看刎ͼ?span>SendMessage ?span> AfxWindProcBase 之间Ҏ没有调用CWinApp::Run() Q也是说从SendMessage 到执?span>OnMy()Ҏ没有通过E序的主消息循环?span>GetMessage Q?span>Run 内部好像用的PeekMessageC清楚了)取消息。那么有Z问,SendMessage的内部就不会先发消息攑օ队列再通过GetMesssage把消息取出来了吗Q答Ҏ没必要那样做Q那是脱裤子?span>P多此一举?
从这个测试例子的l果我判?span>SendMessage ?span> Thread A 中向 W1
SendMessage 的消息根本不q入消息队列?
试2Q?/span>那么什么时候进入队列呢我来看看q个例子
沿用上面那个例子的代码我?span> OnCreate 中的 SendMessage ?span> PostMessage 都删除掉。然后加入如下代码:
//Thread Proc
UINT ThreadProc(LPVOID lParam)
{
CMainFrame * v_pFrameWnd = (CMainFrame *)lParam;
if(v_pFrameWnd)
{
v_pFrameWnd->SendMessage(WM_USER + 100,0,0);
}
return 0;
}
q且 ?span> OnCreate U加入如下代码:
AfxBeginThread(ThreadProc,this);
然后F5 q行 {待E序停在断点处,?span>Call Stack 如下Q?/span>
我们发现q个 Call Stack 和刚才那个PostMessage ?span>Call Stack 是一L q个 WM_USER + 100 消息是通过 Run 内部?span> GetMessage 取出来的 。所以我断定Q?
?span> Thread B 中向W1 SendMessage 发送消?Q消息是攑օ?span> Thread A 的消息队列中。由?span>SendMessage的特性只有当消息被执行完毕才能够q回Q所?span>Thread B 中的SendMessage 要等 Thread A 当中消息执行完毕后才能够q回?
(一) SendMessage 产生?死锁问题
Thread 死锁肯定是发生在2?span>Thread 之间Q?span>A{?span>B Q?span>B{?span> AQ就产生了死锁。大家看了上面测试之后一定会发现Q?span>SendMessage 的死锁和上面的第二个例子有关p,也就??通过 Thread B ?span> W1 发送消息的时候又可能会生死锁?
那么死锁 何时产生?Q通过上面的例子我们知道了 如果Thread B ?span> W1 SendMessage一个消息,那么 Thread B 的这?span>SendMessage p{?span> Thread A 的队列中?消息执行完毕才能够返回,如果?span> Thread B SendMessage 的同?span> Thread A {待 Thread B 中的某一处理完毕才能够l处理消息的话,那么q个时候就发送了死锁?
我们l箋以测试来说明Q?
试3Q?
首先?span> CMainFrame中加入一?成员变量Q?/span>m_bThreadExit Q?span>PublicQ?/span>
我们?span> UINT ThreadProc(LPVOID lParam) 加入一样代码如下:
UINT ThreadProc(LPVOID lParam)
{
CMainFrame * v_pFrameWnd = (CMainFrame *)lParam;
if(v_pFrameWnd)
{
v_pFrameWnd->SendMessage(WM_USER + 100,0,0);
}
v_pFrameWnd->m_bThreadExit = TRUE;
return 0;
}
然后?span> OnCreate 当中d如下代码Q?
m_bThreadExit = FALSE;
AfxBeginThread(ThreadProc,this);
while(TRUE)
{
if(m_bThreadExit)
break;
Sleep(55);
}
OK ~译 F5 q行 发现E序 q入无响应状态,好这时我么让E序 暂停Q?
看看 2?span>Thread ?span> Call Stack 都停在那里了Q?
Main Thread如下Q?
在看?另一个线成:
q会 是不?很明了了
MainThread 停在 循环?{待 m_bThreadExit ?span> TrueQ?另一个线?则等?span> MainThread 处理完毕 WM_USER + 100 q个消息Q结果你{我Q我{你Q死了。。。?
(一) 处理办法
1Q?/span> 针对上面的例?我们 可以通过 ?strong>SendMessage Ҏ PostMessage 的方法来攑ּ{待?q样p决了
2Q?/span> 有些时?W?span>1U方法不W合要求比如下面q中情况
UINT ThreadProc(LPVOID lParam)
{
CMainFrame * v_pFrameWnd = (CMainFrame *)lParam;
if(v_pFrameWnd)
{
v_pFrameWnd->SetWindowText("lvyang");
}
v_pFrameWnd->m_bThreadExit = TRUE;
return 0;
}
q里面的CWndQ:SetWindowText里面实际上调用的是:Q?span>SetWindowText 而:Q?span>SetWindowText 里面有调?span> SendMessage 发送一个消息给CWnd 的窗?Q因为:Q?span>SetWindowText 内部的我们没有办法来修改Q那我只能去修改 MainThread 当中?span> While 循环了?
那如何修改呢Q?span> ThreadProc 当中 SetWindowText之所以被诸塞Q就是因?它向 MainThread SendMessage 的消息没有得到处理,那么我们让他处理的不?span>OK了吗Q好那我们就让他处理Q代码如下:
MSG msg;
while(TRUE)
{
if(m_bThreadExit)
break;
if(::PeekMessage(&msg,NULL,NULL,NULL,PM_NOREMOVE))
{
if(::GetMessage(&msg,NULL,NULL,NULL))
{
if(!PreTranslateMessage(&msg))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
}
Sleep(55);
}
l于搞完?
构徏pȝ数据模型Ӟ?共选择Q以Qgroup->account->son account举例
1、系l由多个groupl成Q?/p>
2、一个group有多个account;
3、一个account有多个son account.
?U数据模型构建方式选择Q?/p>
1、模式一的数据模型由3张表构成Qgroups表,accounts表,son accounts表。是层数据l构Q结构型Q,每张表的深度?。accounts表将有一个[group]字段兌到groups表里面的某条记录Qson accounts表将有一个[account]字段兌到accounts表里面的某条记录。可以说q是一U经典的数据l构Q结构型数据库就是由q样深度?的二l型数据表构成,多张表之间的关系通过增加兌字段来标明;
2、模式二的数据模型中Q把group视作一个整体,它是数据层的一个基本单元(unitQ,数据层由多个group对象l成Qgroup对象的深度是3Q是深层数据l构Q聚合型Q。现实的模型对应为对象型数据库;
现在的问题是Q模式一单还是模式二单?哪一U是更ؓ优越的选择Q我們于模式一Q因为:
1、结构型数据建模是经典的Q目前依然是L的,得到数据库的q泛支持Q即使不使用数据库,也容易序列化到存储,q且我相信群众,怿L意志的正性;
2、模式二的对象型数据建模Qgroup是数据元Q是数据操作的唯一入口Q所以需要提供accountQson account的操作接口,account又需要提供son account的操作接口,假设对象深度再多增加几层Q那q是一个庞大且累赘的冗余。另外一Ҏ树Ş的对象不Ҏ序列化,没有太多数据库支持;
3、模式二的层ơ太深,复杂度C升,q反了系l弱化成类模型的原则(多个c,每个cȝ复杂度都很低Q,而这里,group是一个很大的cR?/p>
4、第一感觉Q模式一的复杂度我能控制Q模式二没有把握,所以心里更认同模式一Q?/p>
5、虽然模式二直观的表明了数据的聚?l合关系Q与现实模型完全隐射Q在理论上应该是更好的选择。但是就人的理解能力的們来说Q我认ؓ理解q度的事务比较理解深度的事务而言更有优势Q?/p>
6、写到这里,我突然想说一句:化深度ؓq度Q符合h的认知规律,降低了复杂度?/font>