锐英源软件
第一信赖

精通

英语

开源

擅长

开发

培训

胸怀四海 

第一信赖

当前位置:锐英源 / 视频介绍 / COM组件开发引导系列 / DBCOMREM: COM Object in a Separate Process
服务方向
人工智能数据处理
人工智能培训
kaldi数据准备
小语种语音识别
语音识别标注
语音识别系统
语音识别转文字
kaldi开发技术服务
软件开发
运动控制卡上位机
机械加工软件
软件开发培训
Java 安卓移动开发
VC++
C#软件
汇编和破解
驱动开发
联系方式
固话:0371-63888850
手机:138-0381-0136
Q Q:396806883
微信:ryysoft

1.1.10 DBCOMREM: COM Object in a Separate Process独立进程内的COM对象


1.1.10.1 Shipping and Handling of Function Calls运送和处理函数调用

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。

1.1.10.2 Exporting an Object from an Executable从可执行体里暴露对象

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。还有些其它标志,这里不再详细讨论。

1.1.10.3 Creating the Proxy/Stub Object创建代理/存根对象

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++头文件扩展而来,且它返回了一个整体的集成的文件:

  • Dlldata.c and xxx_p.c—Files compiled into a DLL.编译到DLL里的文件
  • Xxx.h—A C++ header file with the interface declaration. All IDL- extensions are either removed or commented out.带有接口声明的头文件。所有的IDL扩展都要删除或注释了
  • Xxx_i.c—A file with the definitions of the interface IDs. 接口ID的定义文件

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代码),就得到实现结果了。谁想看怎样实现,我同样实现了手工版本的注册函数。

1.1.10.4 IDL

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)]来指示给字符串参数。

1.1.10.5 Changes: Step by Step逐步修改

The Proxy/Stub代理/存根
  • Copy interface\bdsrv.h and save as interface\ibd.idl.拷贝interface\bdsrv.h并另存为interface\ibd.idl
  • Add [in], [out], and [size_is] flags, remove DECLARE_GUIDs, provide [object] headers for each interface.添加[in],[out]和[size_is]标志,删除DECLARE_GUID,给每个接口提供[object]头
  • Create refshort.idl to provide C declarations for the short parameters.创建refshort.idl来提供用于短参数的C声明。

【注】 cpp_quote:指导MIDL编译器将限定了的字符串转换成生成的头文件

  • Import unknwn.idl and refshort.idl in idb.idl.导入unknwn.idl 和 refshort.idl 到 idb.idl
  • Compile idb.idl with the following:用如下方式编译idb.idl:
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拷贝过来,
不再出现输入行太长的提示。
MIDL编译

running it with a working directory \ProxyStub. This instructs the compiler to write the interface files in our interface directory.指定工作目录\ProxyStub情况下运行上面命令。这指示编译器写接口文件到我们接口目录下。

  • Compile refshort.idl with midl ..\interface\refshort.idl /out ..\interface /ms_ext /c_ext. This will provide refshort.h, which is included in the generated idb.h.编译refshort.idl,命令为midl ..\interface\refshort.idl /out ..\interface /ms_ext /c_ext。这会提供出来refshort.h,这个文件会在生成的idb.h里包含。
  • Create a project for a plain DLL (idbps.mak) and include dlldata.cpp, idb_p.cpp, and interface\idb_i.c. Change idb_p.c to include ..\interface\idb.h instead of idb.h. (The MIDL compiler does not change the paths in the generated include files; you will have to apply this change anytime you recompile the IDL file.)创建一个纯DLL的工程(idbps.mak)且包含dlldata.cpp,idb_p.cpp,和interface\idb_i.c。修改idb_p.c来包含..\interface\idb.h,代替掉对idb.h的包含。(MIDL编译器并不修改生成的包含文件的路径,一旦要重新编译IDL文件你必须实现这个修改)。
  • Link with rpcrt4.lib to include the RPC run-time library used by the generated proxy/stub.把库rpcrt4.lib加入链接,包含上RPC运行时库,用来生成代理/存根。
  • Create a module definition file called idbps.def, add it to the project, and export DllGetClassObject, DllCanUnloadNow, DllRegisterServer, and DllUnregisterServer.创建模块定义文件idbps.def,添加到工程里,且导出DllGetClassObject, DllCanUnloadNow, DllRegisterServer, 和 DllUnregisterServer。
  • Self-registration. You can either define a REGISTER_PROXY_DLL symbol in Preprocessor options or, if you do not want to use the self-registration provided by MIDL for whatever reason, you must create a file called idbpsref.cpp and implement DllRegisterServer/DllUnregisterServer (see above for the exact keys we need to register). You could also register your proxy/stub manually (or in a special installer, or with a simple .REG file), but it is much more in keeping with object-oriented philosophy to provide self-registration within the same DLL.自注册。你能定义REGISTER_PROXY_DLL编译宏来实现,或者如果你不想使用MIDL提供自注册,你也能创建个名字为idbpsref.cpp的文件来实现DllRegisterServer/DllUnregisterServer(参考上面文章关键部分)。你还能手工注册你的代理/存根(或用一个特别的安装包,或一个简单的REG文件),但是在保留面向对象哲学来提供自注册是要下功夫的。
  • Compile the DLL and register it using regsvr32.exe. 编译DLL,用regsvr32.exe来注册。

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 Object

With the "expanded" COM, all we need to provide is an object implemented in an executable.在“扩展化”的COM环境下,我们全部需要提供的是在执行文件里实现对象。

  • Remove the IID definitions from dbsrv.h. They will be provided in IDB.H/IDB_C.C. Just leave the DEFINE_GUID for the CLSID of the object. Again, note that the proxy/stub is related to the interface, not to the database object itself. Any object that wants to use this custom interface will use the same proxy/stub. The proxy/stub is actually an extension of COM and not part of the object.从dbsrv.h里删除IID定义。他们会在IDB.H/IDB_C.C里提供。只是保留DEFINE_GUID。再说一次,注意代理/存根是接口相关的,不是数据库对象自身。任何对象想要使用这个定制的接口中,会用到同样的代理/存根。代理/存根实际是COM的扩展,不是对象的一部分。
  • Include idb.h.包含idb.h。
  • Create a new MFC Project called ObjectEXE\DBLocal.mak. Defaults: MDI, no support for anything OLE-related.创新新的MFC工程,命名为ObjectEXE\DBLocal.ma。默认:MDI,不支持OLE。
  • Add #define _AFX_NO_BSTR_SUPPORT and #include ole2.h to stdafx.h. Define a preprocessor symbol, LOCALSERVER, which we will use for conditional compilation of dbsrv.cpp. Add targets for Unicode. Add OLE32.LIB and UUID.LIB libraries.向stafx.h里添加#define _AFX_NO_BSTR_SUPPORT 和 #include ole2.h。定义预编译宏LOCALSERVER,这让我们会使用有条件的编译来处理dbsrv.cpp。添加UNICODE。添加OLE32.LIB和UUID.LIB库。
  • Add Object\DBSRV.CPP and Object\DBSrvFact.CPP to the project. We will use a common code base for in-process and local servers. Also add interface\idb_i.c and interface\guids.cpp, and disable precompiled headers for both.添加Object\DBSRV.CPP和Object\DBSrvFact.CPP到工程里。我们会使用通用代码基础来实现进程内和本地服务器。同样添加interface\idb_i.c和interface\guids.cpp这2个文件,同时对这2个文件禁用预编译头文件。
  • In CDBLocalApp::InitInstance call CoInitialize; create a CDBFactory object and register it with CoRegisterClassObject. Also check for command-line parameters (/REGSERVER and /UNREGSERVER) and calls (DllRegisterServer or DllUnregisterServer) if appropriate. You could also have the object register itself all the time (except when executed with /UNREGSERVER).在CDBLocalApp::InitInstance里调用CoInitialize:创建一个CDBFactory对象且用CoRegisterClassObject来注册它。同样检查命令行参数(/REGSERVER and /UNREGSERVER)且在情况合适时调用(DllRegisterServer 或 DllUnregisterServer)。你同样能让对象始终注册自己(除了执行/UNREGSERVER时)。
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.          
(...)          
}
  • Add CDBLocalApp::ExitInstance and revoke the class factory object (CoRevokeClassObject). Then call CoUninitialize().添加CDBLocalApp::ExitInstance且废除类工厂对象(CoRevokeClassObject)
int CDBLocalApp::ExitInstance() {          
if (m_dwDBFactory) {          
CoRevokeClassObject(m_dwDBFactory);
m_dwDBFactory=0;          
}             
return CWinApp::ExitInstance();
}
  • Declare CDBLocalApp::m_dwDBFactory and initialize it to 0 in the constructor. This data member saves the identifier that COM returns when registering the class factory object. Include ...\object\dbsrvimp.h in DBLocal.cpp. Remove the call to OnFileNew, because we will associate a document with each server object created.声明CDBLocalApp::m_dwDBFactory且在构造函数里初始化为0。这个数据成员保存注册类工厂对象时COM返回出来的标识。在DBLocal.cpp里包含...\object\dbsrvimp.h。删除对OnFileNew的调用,因为我们会和创建的服务器对象关联一个文档。
  • Change DllRegisterServer/UnregisterServer to write the name of the EXE in LocalServer32 instead of the name of the DLL in InprocServer32. (Use #ifdef LOCALSERVER to keep a common code base for local and in-process server!)修改DllRegisterServer/UnregisterServer来在LocalServer32里写EXE的名称,代替掉在InprocServer32里写DLL的名称.(使用#ifdef LOCALSERVER来保留通用的代码基础来处理本地和进程内服务器!)。
  • CDBFactory::AddRef and Release should not modify g_dwRefCount for a LOCALSERVER. 在LOCALSERVER情况下CDBFactory::AddRef 和 Release不再修改g_dwRefCount
  • For a LOCALSERVER: CDBRelease() should check g_dwRefCount and close the EXE if it is no longer used. 在LOCALSERVER情况下:CDBRelease()应该检查g_dwRefCount且在不再使用时关闭EXE。
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.下面的步骤是可选的。当数据库对象创建时,流程会创建个文档,且在数据库对象释放时关闭文档。注意这个实现不是线程安全的—你应该需要同步对象的创建过程。

  • Add a member CDBLocalApp::m_pLastDoc, set it to NULL in the constructor, and set its value to the this pointer in the constructor of CDBLocalDoc.添加一个成员CDBLocalApp::m_pLastDoc,在构造函数里初始化为空,且在CDBLocalDoc的构造函数里设置它的值为this指针。
  • In CDB::CDB create a new document and keep a pointer to it (#ifdef LOCALSERVER): Declare CDB as a friend of CDBLocalApp, call CDBLocalApp::OnFileNew, obtain m_pLastDoc and save it in a data member CDB::m_pDoc. Don't forget to include dblocal.h.在CDB::CDB里创建一个新的文档,保留它的指针(#ifdef LOCALSERVER):声明CDB为CDBLocalApp的友元,调用CDBLocalApp::OnFileNew,获取m_pLastDoc且保存它到数据成员里。
  • In CDB:~CDB close the document m_pDoc.在CDB:~CDB里关闭m_pDoc。
  • Declare CDB::m_pDoc (#ifdef!) and DllRegisterServer/DllUnregisterServer in dbsrv.h.在drsrv.h时声明CDB::m_pDoc (#ifdef!)和DllRegisterServer/DllUnregisterServer。
  • Optional: It is a good idea to make the object thread-safe. This is not required for remoting, because OLE's default uses only a single thread. It is easy to make your object thread-safe and be prepared for OLE's multithreading models (see the SDK for details). 可选择:让对象线程安全是个好主意。这对远程服务器来说是不需要的,因为OLE默认是使用单线程的。实现线程安全也容易,随后要准备服务于OLE的多线程模型。

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 Client客户端

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":我会做个小改动让用户选择是进程内还是本地服务器。在实际开发里,这是个设计上的决定,但是本节内目的是“用户接口”形式。

  • Before creating an object, show a message box and let the user choose. Use CLSCTX_LOCAL_SERVER to force a local server, if both server types are registered. Use CLSCTX_SERVER to use an in-process server if present, and default to a local server.在创建对象前,显示一个提示框,让用户选择。使用CLSCTX_LOCAL_SERVER强制为本地服务器。使用CLSCTX_SERVER为进程内服务器。默认是本地服务器。
(...)          
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);         
}          
(...)
  • Add idb_c.c to the project for the IIDs (we removed them from dbsrv.h!). 添加idb_c.c到工程里来支持IID(我们是从dbsrv.h里删除这些了)。

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提供了完整通明的模型来服务于进程内和进程外对象。同样的对象代码能用于高性能/低安全性进程内对象,也可用为网络速度/高安全性进程外对象。客户端在使用进程内或进程外之间看不到差异。

友情链接
版权所有 Copyright(c)2004-2024 锐英源软件
统一社会信用代码:91410105098562502G 豫ICP备08007559号 最佳分辨率 1440*900
地址:郑州市金水区文化路97号郑州大学北区院内南门附近