精通
英语
和
开源
,
擅长
开发
与
培训
,
胸怀四海
第一信赖
If this were all there were to COM, it would still be interesting, but not really allow for a "component software revolution." (See note.) You could build tremendously complex software using objects implemented in DLLs and use them from a client. But what if you wanted to share an object between different processes? Or if you did not want to load another object in your same address space for security and reliability reasons? Or what if you wanted to share an object between several machines, like a SQL Server Database?如果COM就是这样了,虽然有趣,但是显然不够能力去做“组件软件革命。”你能够使用DLL里的对象且以客户方式使用DLL,这样也会构建很多复杂的软件。但是如果你想在不同的进程间共享组件时怎么办?或者你出于安全性和稳定性考虑,不想在相同的地址空间里加载其它对象怎么办?或者你想在几个机器之间共享对象,就象SQL数据库服务这样,这时怎么办?
Note Other technologies, such as OpenDoc or SOM, actually stop here! Their underlying technology does not even provide a way for objects in different processes to communicate with each other: Their programming model for in-process and out-of-process objects is fundamentally different (SOM vs. DSOM), and their standardization is based on the in-process model! 注意:其它的技术,比如OpenDoc或SOM,实际上,没有意义,略过。
COM provides a very easy way to make objects in other processes (on the same machine, and soon on remote machines) appear as standard COM objects as we know them now. The underlying idea is to simply extend the idea of directing a function call through a vtable—you provide a special object that implements your interface in a special way. A function in this special object (called a proxy object) receives all the parameters, writes them sequentially into a buffer in memory, and sends the buffer to the other process. In the other process, another special object (called a stub object) receives this buffer, unpacks the parameters again, and calls the function that the caller meant to call in the first place. Then the return value(s) are packed into another buffer, sent back to the calling process, and the proxy unpacks the return value(s) and returns them to the caller.COM提供一个非常容易的方法来让在其它进程(同一机器,远程机器)里的对象看起来象标准的COM对象。潜在的概念是简单地扩展,通过虚函数表的函数导向—你提供一个特殊的对象,这个对象以特殊方法实现接口。在这个特殊的对象(称为代理对象)接收所有的参数,按序列写入到内存里的缓冲上,发送这个缓冲到其它进程里。在其它进程里,另外特殊的对象(叫做存根对象)接收这个缓冲,解包出来参数,调用参数第一位置里指定的函数。接着把返回值打包到另外缓冲里,发回给调用进程,代理对象解包返回值,再返回给调用者。
This sounds complicated—and actually is even a bit more complicated—but to both the client and the server, this whole process of packing, sending, and unpacking is completely transparent, except for the difference in speed, due to the context switch or the network message sent.这看起来复杂了—且实际上确实有些复杂—但是对客户端和服务器端,这个总的过程:打包,发送,解包是全透明化的,除了速度上的差异,因为上下文切换和网络消息 发送是需要时间的。
All that an object has to supply is the code that actually packs and unpacks the parameters. COM takes care of shipping and handling—setting up the connections between proxy and stub, sending the packed parameters and return values. The marshalling code (as the proxy and stub are also called) is not provided on a per-object basis, but on a per-interface basis: The interface designer can provide the proxy and stub objects and everybody wanting to use this interface takes advantage of the remoting capability.对象必须提供的全部就是代码,代码实际上打包和解包了参数。COM处理中转和打理—设置代理和存根之间的连接,发送打包过的参数和返回值。编组代码(不管代理和存根都是同样称呼)不是基于每个对象基础上提供的,是基于每个接口基础上提供的:接口设计者能够提供代理和存根对象,且每个想使用这个接口的朋友利用了远程能力。
We will provide a DLL that implements a COM object and takes care of this packing and unpacking (also called marshalling and unmarshalling). We will register this COM object like any other COM object under the HKEY_CLASSES_ROOT\CLSID registry key.我们会提供DLL来实现一个COM对象且处理打包和解包(同样称呼为编组和反编组)。我们会注册这个对象到HKEY_CLASSES_ROOT\CLSID下,这和其它别的COM对象一样。
In addition, we will tell COM that this specific object is able to marshall a specific interface by registering its CLSID under another key called HKEY_CLASSES_ROOT\Interfaces\{iid}. COM knows how to marshall IUnknown, and when asked for a different interface, it looks under this key, finds the CLSID of the object that handles marshalling, and starts using it. I will not go into detail on this here, since it is explained in great detail in Kraig Brockschmidt's Inside OLE.另外,通过注册它的CLSID到另外一个键HKEY_CLASSES_ROOT\Interfaces\{iid}下,我们会告诉COM这个指定的对象是能够来编组一个指定的接口。COM知道怎样编组IUnknow,且当请求不同的接口时,它会检查这个键,找到处理编组的CLSID,接着开始使用它。这里我不会详细介绍它,因为它在Kraig Brockschmidt所著Inside OLE里有详细解释。
The object that does marshalling and unmarshalling is also called a proxy or stub. The part that pretends to be the object on the side of the client is called the proxy, and the part pretending to be the client on the side of the object is called the stub. The entry under HKEY_CLASSED_ROOT\Interfaces\{iid} is therefore called ProxyStubClsid32. There is another entry, NumMethods, that simply indicates the number of methods in this interface, including the three IUnknown methods.实现编组和反编组的对象通常称为代理或存根。在客户端假装对象的称作代理,在服务器端假装客户端叫做存根。在键HKEY_CLASSED_ROOT\Interfaces\{iid}下的称为ProxyStubClsid32。还另外一个项,NumMethods,它只是简单地指示在这个接口里方法个数,包含三个IUnknown的方法。
The generation of the proxy/stub object is actually trivial: The header file defining the interface needs to be enhanced to indicate more about the parameters than the C++ language provides, such as which parameters only go into the object, which parameters only go out (are actually return values), and which go in and out. Also, the length of buffers needs to be defined, and some other tedious details about pointers need to be added. For most simple parameters, the changes are actually straightforward, as we will see. This modified header file is not in C++ anymore—the language is called IDL, or Interface Definition Language.代理/存根对象的生成是很繁琐的:定义了接口的头文件需要扩充来指示更多关于参数的信息,C++语言提供的参数信息远远不够了,添加的比如哪个参数只是进入参数类型,哪个参数只是导出参数类型(实际上用来返回值),哪个是入和出都可以。同样,缓存的长度也需要定义,还有其它冗长的细节,比如要添加指针。对于大部分简单的参数,修改会很直观。这个修改过的头文件不再是C++形式的了—语言称为IDL,或接口定义语言(Interface Definition Language)。
This IDL file is then passed to a compiler for this language, which generates C source code necessary to build the DLL.IDL文件传递给编译器来处理,编译器生成C代码来进而编译出DLL。
There is now just one minor technical detail missing. How can COM call into an object that is implemented in an executable? EXEs do not provide callable entry points, so we cannot use the same approach as we did with a DLL using DllGetClassObject.还有一个遗漏的小技术细节点。COM怎样调用在可执行文件里实现的对象?EXE并没有提供入口点,DLL里提供的DllGetClassObject不能在这样情况下继续使用了。
The solution is relatively simple and spins around the class factory object: When initializing, the EXE simply calls an application programming interface (API) function in the COM libraries (CoRegisterClassObject) and passes it a pointer to IUnknown on a class factory object. COM saves this pointer in an internal table and uses it when clients want to create an object of this class. The only problem this creates in the object's code is that class factory objects are not supposed to keep an object running once a "real" object has been created and released. The object's code is not needed anymore, even if it still has its class factories registered with the COM libraries. Thus for global reference counting, references to the class factory object should not count. When the executable is finished, it revokes the class factory objects (CoRevokeClassObject), passing in an identifier that COM returned in CoRegisterClassObject.方法相对简单些,还是围绕类工厂对象来展开:当初始化时,EXE简单地调用COM的API函数(CoRegisterClassObject)且传递一一个指向IUnknown的指针,这个指针是基于类工厂对象的。COM保存这个指针到内部表里,且当客户端想要创建这个类的对象时使用。这个方法导致在对象代码里出现的唯一问题是类工厂对象不是假定来保留一个运行状态的对象,一旦“实际“对象创建又释放了,就会有运行状态对象产生 。对象的代码不再需要了,然而依然存在用COM库函数注册过的类工厂对象。这样让全局引用计数对类工厂对象的引用计数不再起作用了。当可执行文件结束它,它激活类工厂对象(CoRevokeClassObject),传递进一个标志符,COM在它里面返回结果出来。
A COM object in an executable (also called a local server) is registered very similarly to an object in a DLL (in-proc server). The entry is called LocalServer32 and contains the path to the executable. Both entries can be present, allowing clients to choose which implementations they prefer.在可执行文件里的COM对象(同样称作本地服务器)注册方法和DLL(进程内服务器)里几乎一样。入口叫做LocalServer32且包含可执行文件的路径。2个入口能表示出来,允许客户端来选择他们喜欢的实现。
For the client, all this is completely transparent. It just calls CoGetClassObject (or CoCreateInstance). If it asks for just a server (CLSCTX_SERVER), COM first checks if there is an in-proc server registered, and if not, it checks for a local server. It then loads the local server by running the executable and waits for the executable to call CoRegisterClassObject. It then wraps the pointer with a special proxy/stub for IUnknown (class factory objects are always registered as IUnknown first) and returns the client a pointer to the proxy. From there on, COM uses the mechanisms briefly described above: When the client calls QueryInterface, COM loads another stub object on the server, lets it wrap the object's interface, connects the stub to a proxy that it loads on the client's side, and returns a pointer to the proxy to the client.对于客户端来说,所有这些都是透明的。它只是调用CoGetClassObject (或 CoCreateInstance).如果它请求一个服务器(CLSCTX_SERVER),COM首先检查有没有一个进程内服务器注册上,如果没有,它检查本地服务器。接着加载本地服务器,这是通过运行来实现,马上等着可执行文件来调用CoRegisterClassObject。它接着封装带有特别的代理/存根的IUnknown指针(类工厂对象是永远先注册为IUnknown的),且返回给客户端一个指向代理的指针。自此,COM使用的机制如上所描述:当客户端调用QueryInterface,COM加载其它基于服务器的存根对象,让它封装上对象的接口,连接存根到由客户端加载出来的代理上,且返回代理的指针到客户端上。
The client never sees any of this; it just gets pointers to vtables, which it uses as before. The only time a client might be aware of an in-proc/remote difference is while creating the object: It can indicate to COM the context that it wants the object to run in—CLSCTX_LOCAL_SERVER, CLSCTX_INPROC_SERVER, or just CLSCTX_SERVER. There are some other flags, which I will not discuss here.客户端永远看不到这些;它只是获取到虚函数表指针,客户端以前以前怎样使用它,现在照样。客户端需要关注的唯一时间点是进程内/远程的区别,这在创建对象时影响不一样:这会提示出COM想要的对象来运行的上下文-- CLSCTX_LOCAL_SERVER, CLSCTX_INPROC_SERVER, 或 只是 CLSCTX_SERVER。还有些其它标志,这里不再详细讨论。
COM needs just a little bit of help to perform all these miracles: It needs an object that knows how to pack and unpack the specific parameters of an interface's functions.COM只需要一点儿帮助来实现所有这三个奇迹:它需要一个对象,这个对象要知道怎样打包和解包一个接口函数的指定参数。
The tool that generates these helper objects is the MIDL compiler (Microsoft IDL compiler). We feed it with a simple IDL file, which is basically an extended C++-header file, and it returns a whole bunch of files: 生成这些帮助对象的工具是MIDL编译器(微软IDL编译器)。我们会用一个简单的IDL文件来体验下,这个文件基本上是C++头文件扩展而来,且它返回了一个整体的集成的文件:
The header file and the file with the interface IDs will form part of the interface definition: They will be used by clients of the object and by the object itself.头文件和带有接口ID的文件会形成接口定义:他们会被对象客户端和对象自己使用。
We will put the two other files into a new directory, \ProxyStub, where we will also create a project for the DLL, a module definition file, and an additional file with support for self-registration.我们会放置2个其它文件到新目录下,\ ProxyStub,这个目录下我们同样会创建DLL的工程,一个模块定义文件和一个额外支持自注册的文件。
The DLL will export one COM object that provides proxy/stubs for all four interfaces. The object provides methods that let COM query for the correct proxy/stub implementation (see Inside OLE for details). Thus, under one CLSID, COM finds the proxy/stubs for four interfaces. By default, the MIDL-generated code uses the interface ID of the first interface declared in the IDL file as the CLSID for the proxy/stub object. Look in RPCPROXY.H for instructions on changing the default and some other options.DLL会导出COM对象,对象提供代码/存根需要的所有4个接口。对象提供方法让COM查询正确的代理/存根实现。这样,在一个CLSID下,COM查找到有四个接口的代理和存根。默认情况下,MIDL生成的代码使用IDL文件里声明接口的第一个接口ID,这个ID是代理/存根对象的CLSID。查找RPCPROXY.H来分辨出修改默认和其它选项的情况。
For the default proxy/stub, we will have to register the following keys:对于默认的代理/存根,必须注册如下键:
CLSID\{30DF3432-0266-11cf-BAA6-00AA003E0EED}="DB Sample ProxyStub" // This is the IID of IDB used as the CLSID of the proxy/stub for all // four interfaces. CLSID\{30DF3432-0266-11cf-BAA6-00AA003E0EED}\InprocServer32=<path>\db.dll Interface\{30DF3432-0266-11cf-BAA6-00AA003E0EED}="IDB" Interface\{30DF3432-0266-11cf-BAA6-00AA003E0EED}\ProxyStubClsid32=" {30DF3432-0266-11cf-BAA6-00AA003E0EED}" Interface\{30DF3432-0266-11cf-BAA6-00AA003E0EED}\NumMethods = "10" Interface\{30DF3433-0266-11cf-BAA6-00AA003E0EED}="IDBAccess" Interface\{30DF3433-0266-11cf-BAA6-00AA003E0EED}\ProxyStubClsid32=" {30DF3432-0266-11cf-BAA6-00AA003E0EED}" Interface\{30DF3433-0266-11cf-BAA6-00AA003E0EED}\NumMethods = "5"Interface\{30DF3434-0266-11cf-BAA6-00AA003E0EED}="IDBManage" Interface\{30DF3434-0266-11cf-BAA6-00AA003E0EED}\ProxyStubClsid32=" {30DF3432-0266-11cf-BAA6-00AA003E0EED}" Interface\{30DF3434-0266-11cf-BAA6-00AA003E0EED}\NumMethods = "5"Interface\{30DF3435-0266-11cf-BAA6-00AA003E0EED}="IDBInfo" Interface\{30DF3435-0266-11cf-BAA6-00AA003E0EED}\ProxyStubClsid32=" {30DF3432-0266-11cf-BAA6-00AA003E0EED}" Interface\{30DF3435-0266-11cf-BAA6-00AA003E0EED}\NumMethods = "6"
This looks like a lot of work, but the MIDL-generated code provides even implementations of DllRegisterServer and DLLUnregisterServer, if you compile (the C code) with the preprocessor symbol REGISTER_PROXY_DLL. For those of you who prefer to see how it is done, I also implemented a manual version of the registration functions.这看起来工作很多,但是MIDL生成代码甚至提供了DllRegisterServer 和 DLLUnregisterServer的实现,如果带有REGISTER_PROXY_DLL预编译宏来编译(C代码),就得到实现结果了。谁想看怎样实现,我同样实现了手工版本的注册函数。
Some comments regarding the IDL file: We will need to include unknwn.idl in order to derive our interfaces from IUnknown. If you need to use windows types such as DWORD and others, you can also include wtypes.idl.关于IDL文件的相关注释有:我们需要包含unknwn.idl来派生出我们的IUnknown的派生接口。如果你需要使用windows类型,比如DWORD或其它,你同样需要包含wtypes.idl。
Each interface is prefixed by a special header of the following structure:每个接口有一个特殊的头来开始,这个头类似如下:
[object, uuid(606C3DE0-FCF4-11ce-BAA3-00AA003E0EED), pointer_default(unique)]
This header instructs MIDL to generate a proxy/stub ("object"), tells it the IID ("uuid") and assumes pointers as unique (see the RPC reference for more information).这个头指示MIDL来生成代理/存根(”object”),告诉IID(”uuid”)且假定指针是唯一的(参考RPC相关文档)。
MIDL does not accept parameters by reference. Since on the binary level references are just pointers, we can "cheat" the MIDL with some typedefs (refshort.idl; I copied this idea from wtypes.idl): For C we provide a pointer to short, for C++ we provide a reference to short. MIDL不接受引用类型的参数。因为在二进制层面引用就是指针,我们不能用一些类型重定义来“欺骗“MIDL(refshort.idl;我从wtypes.idl里拷贝了这个主意):对于C代码,我们提供short类型指针,对于C++代码,提供short的引用)。
In order to receive string parameters ([out]), we will use a fixed buffer size, both to avoid having to free returned memory and to maintain "compatibility" with previous clients. This is indicated by an attribute [size_is(80)] to a string parameter. (See the RPC Reference for more information.)为了接收字符串参数([out]输出类型),我们会使用一引固定缓冲长度,既为了避免必须释放返回的内存,也为了维护和上版本客户端的“兼容性”。这由一个属性[size_is(80)]来指示给字符串参数。
【注】 cpp_quote:指导MIDL编译器将限定了的字符串转换成生成的头文件
midl ..\interface\idb.idl /header ..\interface\idb.h /iid ..\interface\idb_i.c /ms_ext /c_ext [注]midl的使用,bat执行先执行,导致不断输出,最后是输入行太长,把bat文件里的midl加上exe,形成midl.exe,并把VC执行目录下的midl.exe拷贝过来, 不再出现输入行太长的提示。
running it with a working directory \ProxyStub. This instructs the compiler to write the interface files in our interface directory.指定工作目录\ProxyStub情况下运行上面命令。这指示编译器写接口文件到我们接口目录下。
We just expanded COM to handle our four custom interfaces for remote access. Note that I named all the files IDB, to indicate that they deal with the "database"-interfaces, and not the object implementing these interfaces.我们只是扩展COM来处理我们四个定制接口来实现远程访问。注意,我命名所有文件带有IDB,来指示他们是“数据”相关的接口,且不是对象实现这四个接口。
[注]:编译工程时,提示没有idb.h,是目录没加上,在idb.h前面输入..\interface\就可以了。编译过后可以用regsvr32来进行注册
The ObjectWith the "expanded" COM, all we need to provide is an object implemented in an executable.在“扩展化”的COM环境下,我们全部需要提供的是在执行文件里实现对象。
BOOL CDBLocalApp::InitInstance() { if (m_lpCmdLine[0] != '\0') { if (lstrcmpi(m_lpCmdLine, "/REGSERVER")==0) { if (FAILED(DllRegisterServer())) { AfxMessageBox("Unable to register the server!"); } return FALSE; } else if (lstrcmpi(m_lpCmdLine, "/UNREGSERVER")==0) { if (FAILED(DllUnregisterServer())) { AfxMessageBox("Unable to unregister the server!"); } return FALSE; } } DllRegisterServer(); CoInitialize(NULL); CDBFactory *pFactory=new CDBFactory(); pFactory->AddRef(); if (FAILED(CoRegisterClassObject(CLSID_DBSAMPLE, (IUnknown*) pFactory, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &m_dwDBFactory))) { pFactory->Release(); return FALSE; } pFactory->Release(); // COM keeps a reference to the class factory. (...) }
int CDBLocalApp::ExitInstance() { if (m_dwDBFactory) { CoRevokeClassObject(m_dwDBFactory); m_dwDBFactory=0; } return CWinApp::ExitInstance(); }
ULONG CDB::Release() { g_dwRefCount--; m_dwRefCount--; if (m_dwRefCount==0) { #ifdef LOCALSERVER if (g_dwRefCount==0) { AfxGetMainWnd()->PostMessage(WM_CLOSE); } #endif delete this; return 0; } return m_dwRefCount; }
The following steps are optional. The procedure creates a document whenever a database object is created, and closes the document when a database object is released. Note that this implementation is not thread safe—you would need to synchronize object creation.下面的步骤是可选的。当数据库对象创建时,流程会创建个文档,且在数据库对象释放时关闭文档。注意这个实现不是线程安全的—你应该需要同步对象的创建过程。
If multiple functions are executed "simultaneously," we must guarantee that all our code is prepared for this by (a) making it reentrant, and (b) synchronizing access to global memory. Reentrancy is easy—most variables are local variables on the stack and are allocated exclusively for each thread executing a function. In our sample, all we have to worry about is the reference counters—both the global and the object counter—and the "database" structures. We will secure our AddRef and Release by using the Win32 API functions InterlockedIncrement and InterlockedDecrement. The structures use MFC classes that are thread-safe already for threads accessing different objects. We must protect each object instance against multiple use with a critical section per object.如果多个函数在“同时”执行,我们必须保证我们所有的代码是为重入做好准备,且同步访问全局内存。重入是容易的—大多数变量是栈上的本地变量且是线程专有地分配来执行函数。在我们的例子里,我们全部要关心的是引用计数—全局和对象计数—和“数据库”结构。我们会用InterlockedIncrement 和 InterlockedDecrement这2个API来确保AddRef 和 Release的安全性。数据库结构使用了MFC的类,这些类是线程安全设计的,能够让访问不同对象时不出问题。我们必须保护每个对象实例来确保多重使用不出问题,保证方法是用每对象关键节。
Here is the thread-safe implementation of IUnknown in dbsrv.cpp (analog in dbsrvfact.cpp):下面是dbsrv.cpp里IUnknown里的线程安全实现:
(...) ULONG CDB::AddRef() {InterlockedIncrement((long*) &g_dwRefCount); InterlockedIncrement((long*) &m_dwRefCount); return m_dwRefCount; } ULONG CDB::Release() { ULONG dwRefCount=m_dwRefCount-1;ULONG dwGlobalCount=InterlockedDecrement((long*) &g_dwRefCount); if (InterlockedDecrement((long*) &m_dwRefCount)==0) { #ifdef LOCALSERVER if (dwGlobalCount==0) { AfxGetMainWnd()->PostMessage(WM_CLOSE); } #endif delete this; return 0; } return dwRefCount; }
Making the object itself thread-safe: Declare a critical section in CDB:让对象自己线程安全:在CDB里声明下关键节:
class CDB : public IDB, public IDBAccess, public IDBManage, public IDBInfo { (...) // Implementation private: (...) ULONG m_dwRefCount; CRITICAL_SECTION m_secDB; (...) };
The implementation in dbsrv.cpp: Use the critical section.在dbsrv.cpp里使用关键节:
HRESULT CDB::Read(short nTable, short nRow, LPWSTR lpszData) { EnterCriticalSection(&m_secDB); (...) LeaveCriticalSection(&m_secDB); return NO_ERROR; } (...) CDB::~CDB() { EnterCriticalSection(&m_secDB); short nNumTables; for (GetNumTables(nNumTables);nNumTables>0; GetNumTables(nNumTables)) { Delete(nNumTables-1); } #ifdef LOCALSERVER m_pDoc->OnCloseDocument(); m_pDoc=NULL; #endif LeaveCriticalSection(&m_secDB); DeleteCriticalSection(&m_secDB); } CDB::CDB() { InitializeCriticalSection(&m_secDB); m_dwRefCount=0; #ifdef LOCALSERVER ((CDBLocalApp*) AfxGetApp())->OnFileNew(); m_pDoc=((CDBLocalApp*) AfxGetApp())->m_pLastDoc; #endif }
Compile the local server. If you want, also compile the in-process server, to validate your "common code base" (you'll need to add idb_c.c for the IIDs!).编译本地服务器。如果你想做,同样也编译进程内服务器,来验证你的“通用代码基础”(你需要添加idb_c.c来使用IID!)。
【注】:如果出现以下编译错误:
H:\专题讲座\COM\例子\msdn c++ into com sample\DBCOMREM\Object\DBSrvFact.CPP(110) : error C2664: 'RegCreateKeyExW' : cannot convert parameter 2 from 'char [15]' to 'const unsigned short *'
Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
哪是对Unicode环境支持没做好,字符串前没有用_T().编译过后,可以注册上组件。
【注】把本地服务器工程升级到VC6后会出编译错误:
msvcrt.lib(crtexew.obj) : error LNK2001: unresolved external symbol _WinMain@16
在工程设置里Link里的Entry-point symbol里加上。
The clients DBCOM and DBCOMMUL work without change! You just have to unregister the in-process server, register the local server, and COM does the rest.客户端DBCOM和DBCOMMUL不用修改!你只需要反注册进程内服务器,注册本地服务器,COM会做其余工作。
We will make a minor change to allow the user to choose between in-process and local server. In a real-world application this is a design decision, but for the purpose of this section we provide a "user-interface":我会做个小改动让用户选择是进程内还是本地服务器。在实际开发里,这是个设计上的决定,但是本节内目的是“用户接口”形式。
(...) if (AfxMessageBox(_T("Do you want a local server?"), MB_YESNO)==IDYES) { hRes=CoGetClassObject(CLSID_DBSAMPLE, CLSCTX_LOCAL_SERVER, NULL, IID_IClassFactory, (LPVOID*) &pDBFactory); } else { hRes=CoGetClassObject(CLSID_DBSAMPLE, CLSCTX_SERVER, NULL, IID_IClassFactory, (LPVOID*) &pDBFactory); } (...)
COM provides a completely transparent model for in-process and out-of-process objects. The same object code can be used as a high-performance/low-security in-process object or as a network-speed/high-security out-of-process object. The client does not see any difference in using an in-process or out-of-process object. Our client from the DBCOM sample works transparently with a server implemented in another process!COM提供了完整通明的模型来服务于进程内和进程外对象。同样的对象代码能用于高性能/低安全性进程内对象,也可用为网络速度/高安全性进程外对象。客户端在使用进程内或进程外之间看不到差异。