锐英源软件
第一信赖

精通

英语

开源

擅长

开发

培训

胸怀四海 

第一信赖

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

1.1.8 DBCOM: COM Object in a DLL, Loaded Through COM

以COM方式加载的在DLL里的COM对象

The previous sample illustrated a portion of COM's infrastructure for creating instances of objects. In order to make our object a real COM object, only minor details are missing: 上节例子里演示了创建COM对象实例的COM架构。为了让我们对象成为实际的COM对象,还有些遗漏要补充:

  • Allow reference counting on our exposed interfaces.在我们导出的接口里支持引用计数
  • Allow multiple interfaces on an object.在一个对象里实现多接口
  • Use the standard IClassFactory interface on the class factory object.用标准的IClassFactory接口代替类工厂对象
  • Use the _stdcall calling convention.使用_stdcall调用约定
  • Allow for dynamic unloading of a DLL.允许动态卸载DLL。
  • Allow for self-registration of an object. 允许对象的自注册

None of these features requires a lot of implementation and their implementation is highly reusable. They take up some space to explain them, less space to implement them, and once you have implemented them for one object (as I did for this sample) you can reuse most of the implementation for all of your objects. The class factory, the loading and unloading code, and registration support simply require you to adjust the name of the class and the GUIDs involved.上面特性都不需要很多的实现代码,并且它们的实现是高度可重用的。他们占用些空间来解释自我,小部分空间来实现它们,且一旦你为一个对象实现了它们(如同我对本例里所做一样)你能重用这些代码到别的对象上。类工厂,加载和卸载代码,和注册支持只是简单地让你调整类名称和GUID。

1.1.8.1 Reference Counting引用计数

Suppose that our little database object will be used by several clients simultaneously. Currently we could return the same instance of CDB to all calls to IDBSrvFactory::CreateDB and all of the documents of our client would access the same object. But problems will arise if one of the clients calls Release on our object—the object would destroy itself, and all other clients would perform calls on a nonexistent object.假定我们的小数据库对象会被几个客户端同时使用。并发来看,我们能对所有到IDBSrvFactory::CreateDB的调用返回同一个实例,所有我们客户端的文档会访问同一个对象。但是如果一个客户端调用了Release来释放了对象,会导致产生问题—对象会销毁自己,且所有其它客户端的调用会使用一个不存在的对象了。

The solution to this problem is so simple that it is required for all COM objects: The object maintains a count of how many pointers to itself it gave away, and when Release is called, it decrements this reference count. If the count reaches zero, the object has no reason to exist anymore (at least, concerning external references), and it is free to destroy itself.解决的办法很简单,这个办法也适用于所有COM对象:对象维护了一个计数,表示了它衍生出来多少个指针,且当Release调用时,它减少这个引用计数。如果计数变为0,对象没有理由再存在了(至少,关注外部引用),接着可以自由地释放自己。

How does the object count the references when it gives away a pointer? One way would be to have the class factory object and the object work together: The class factory object increments the object's reference count whenever it gives away an external reference on a call to CreateInstance. But this approach would have a fairly limited application. Therefore, the object actually exposes another function to increment the reference count: Whoever has a pointer to the object can tell the object that she or he just gave the pointer to somebody else. This entity can be the class factory object or any other client. The function referred to above has a very simple name: AddRef.当衍生出指针时,对象怎样对引用进行计数呢?一个方法是让类工厂和对象一起作用:当类工厂对象基于CreateInstance调用衍生一个外部引用时,类工厂对象增加对象引用计数。但是这个方法限制太死。所以,对象实际上导出另外的函数来增加引用计数:对象指针的拥有者能够告诉对象它把指针给别人用了。这个实体能够是类工厂对象或其它客户端。上面提到的函数有一个简单的名称:AddRef。

Therefore, the two required member functions for managing reference counts on any COM object are:这样,管理引用计数的2个需要的成员函数为:

ULONG AddRef();
ULONG Release();

The functions are not really required. However, the approach makes so much sense for any object, even the tiniest, and the implementation of those functions is so inexpensive, that you should just implement them.函数并不是实际需要的。然而,这个方法对任何对象都是有道理的,甚至是最小的,这些函数的实现也不复杂,你只需要实现它们。

If you want your object to be accessible remotely (from another process and/or another machine), your object must provide these functions. The cost is very low for the benefit you receive, both in terms of a programming philosophy within your own code and in terms of migration to a distributed environment.如果你想你的对象能够远程访问(从其它进程或机器),你的对象必须提供这些函数。不管从你内部代码编程哲学角度看和从一个分布环境下的移植观点看,代价是非常小,收益却非常大。

These two functions do not return HRESULT, because they cannot really fail, unless the object does not exist anymore. And if so, who is going to give back an error code? Inasmuch as these functions do not need a return value, they simply return the current reference count of the object, but only if the object feels that it is necessary to do so. In other words, the object is not even required to return a reasonable value. This leaves the return value useful only for debugging purposes; you should not use it for anything in your program.这2个函数并不返回HRESULT,因为他们不可能错误。除非对象已经不存在了。且如果这样,谁会返回一个错误码呢?因为这些函数并不需要返回值,他们只是返回当前对象的引用计数,但是只当对象觉得它有必要这样做时。换个方式说,对象甚至不需要返回一个合理的值 。这让返回值只在调试模式下才有用;在你的程序里不要为任何其它事情使用它。

1.1.8.2 Multiple Interfaces多重接口

In the next section we will take a look in greater depth at providing multiple interfaces. For now, let's suppose that an object wants to return different interfaces for different clients. The clients would need some way to ask the object for a specific interface, and in fact we already introduced a method for doing so—the IID passed to DllGetClassObject. Remember that the client passed an interface ID to the class factory object, and in the object we validated whether it was our class factory interface.本节里我们会讨论下提供多重接口方面的难点。现在,我们假定对象要为不同的客户端返回不同的接口。客户端需要一些方法来要求对象处理指定的接口,且实际上我们也介绍了一个方法来做这样的事情—传递到DLLGetClassObject的IID参数。记住客户端传递一个接口ID到类工厂对象里,在对象内部我们审核它是不是我们类工厂的接口。

This is a good approach for a class factory: One client can obtain the class factory object, asking for one interface, perhaps, so that the client can instantiate an object without a user ID or password, and another client can create the object through an interface that passes in UserID/Password. (This is not a very useful example; it is just to explain why there could be multiple interfaces on an object, and why this mechanism is also provided for the class factory object.)这对类工厂来说是个好方法:一个客户端能获取类工厂对象,请求一个接口,可能,这样让某个客户端能够不用用户ID或密码来实例化一个对象,让其它客户端通过向接口传递用户ID/密码来实例化对象。(这不是非常有用的例子;它只是解释为什么会有多接口对象,为什么这个机制同样提供给类工厂对象)。

If an object wants to expose two different interfaces, we can have the CreateDB function of the class factory object—the function that actually instantiates an object—receive another parameter, an IID, and we can create the appropriate objects based on the requested interface.如果对象想要导出2个不同的接口,我们能够让类工厂对象的CreateDB函数 — 这个函数实际上实例化一个对象 — 接收其它参数,一个IID,且我们能够基于请求的接口创建适合的对象。

But what if a client needs two interfaces on a given object—perhaps one interface for creating tables and another for reading or writing? It would be great if the client could ask the object for another interface, once it has a pointer to the first interface.但是如果一个客户端在给定对象上需要2个接口—可能一个接口来创建表且另外一个来读写数据?如果客户端能够一旦有第一个接口,就能再要求对象提供另外一个接口,这就太棒了。

To provide this functionality, the object can expose an additional member function on its initial interface, a member function to which the client could pass the interface ID it wants and a pointer to where it wants to receive the new interface:为了提供这个功能,对象能够基于它的初始接口导出一个额外的成员函数,一个成员函数调用时让客户端能够传递要求的接口ID和保存新接口的指针:

HRESULT QueryInterface(RIID riid, void** ppObj);

On receiving a call to this function, the object could check the interface ID and return a pointer to a vtable that implements the requested functionality. The object can provide this pointer however it pleases, as long as the contract expressed by the interface ID is fulfilled. In other words, the order of the functions and their parameters must be correct. 在这个函数调用时,对象能够检查接口ID,且返回个指向虚函数表的指针,虚函数表实现了要求的功能。对象能够提供这个任何它满意的指针,只要接口ID认为是约定好的。也就是说,函数的顺序和他们的参数必须是正确的。

The object can create another C++ object, one that exposes a different interface but works as a client to the real object and returns this pointer. It is important that the returned interface work on the same logical object: If you delete a table through the second interface, the first interface must show the changes. (This is actually one way of implementing multiple interfaces on a COM object—composing a COM object of multiple interrelated C++ objects. This is the most tedious, but also the most flexible, way of doing it. Later we will see another approach using multiple inheritance from multiple abstract base classes, and another technique using C++ nested classes. You could even set up a table of pointers with the addresses of the functions of the interface, and return the address of a pointer to this table. You must do this if you want to use COM from plain C.)对象能够创建其它C++对象,这个新对象导出一个不同的接口,但是以客户端形式工作,且返回这个指针。返回的接口基于同样的逻辑工作是非常重要的:如果你通过第二个接口删除一个表,第一个接口必须显示修改结果。(这实际上是COM机制里实现多接口的一个方法—组成一个多个C++对象的COM对象。这非常乏味,但是非常灵活。随后我们会学习使用从多个抽象基类多继承的方法,和其它使用C++嵌套类技术。你甚至能够设置一个指针表,里面放置接口的函数地址,返回出来这个表的地址来用。如果你想从纯C方式来使用COM,必须这样做)。

The idea behind exposing multiple interfaces through QueryInterface is to allow for different ways of seeing the same object, not to access separate objects. Separate objects should be assigned individual CLSIDs. They can share the same interface: A database object in memory could allow the same methods as a database object working on a file. The two objects could be accessed through the same interface (IDB) but each have a unique CLSID for reaching the code that implements them. The file-based database would probably provide an additional interface for setting the filename and committing changes to the file.通过QueryInterface导出多个接口方法的内涵是让不同的方法看到一个对象,不是来访问独立的对象。独立的对象应该用独立的CLSID命名。他们能共享一个接口:在内存里的数据库对象能够允许同样的方法象处理数据库哪样处理文件。2个对象能够通过同样的接口IDB来存取,但是每个有独立的CLSID,通过独立的CLSID来访问实现代码。基于文件的数据库对象可能提供另外的接口来设置文件名和提交对文件的修改。

If an object is actually a sub-object of another object—for example, an object that represents a given table of the database—it can simply be returned by a member function of its parent object. That is, a Read function could return a pointer to a table object instead of returning a row of data.如果一个对象确实是另外对象的子对象—比如,一个对象表示了个给定的数据库表—它能简单地通过成员函数返回它的父对象。也就是说,一个Read函数能够返回一个表对象的指针,而不返回一行数据。

If this sounds a little confusing, wait until the next section, where we will see the real-world benefit of this feature. For now, I hope I have convinced you to implement this way of asking for other interfaces to an object. It is especially important if you think about a world of component objects communicating with each other and being able to inquire into each others' level of functionality, the version of that functionality, and so on.如果这有些迷惑,看过下节就明白了,下节里会着重讲这个技术的现实意义上的好处。暂时,我希望我已经劝诫你实现这个方法去请求对象的其它接口。如果你考虑一个互相通信的组件世界且能够询问别的对象的功能上的分支,你就会理解这个实现功能的方法的重要性等等。

1.1.8.3 Reference Counting and Support for Multiple Interfaces: Iunknown引用计数和为多重接口支持的:Iunknown

We have seen two great features to have on any object: Reference Counting (AddRef and Release) and multiple interfaces (QueryInterface). COM requires any object to implement these three functions, in the sense I mentioned above. It makes a lot of sense to have them on any object, and without them COM cannot handle remote processing for your object.我们已经看到了2个任何对象要支持的技术:引用计数(AddRef和Release)且多重接口(QueryInterface)。COM需要任何对象来实现这3个函数。这个要求非常有道理,没有它们,COM不能支持对象的远程处理。

In order to formalize this set of requirements, COM defines a standard interface called IUnknown (declared by including OLE2.H):为了把这个要求正式化,COM定义了个标准的接口,命名为IUnknown(在OLE2.H里声明)

class IUnknown {               
public:               
virtual HRESULT QueryInterface(RIID riid, void** ppObj) =0;               
virtual ULONG AddRef() =0;               
virtual ULONG Release() =0;             
};

An object can simply derive from IUnknown and implement the three functions. Another way to implement IUnknown is to add the three functions to the beginning of your custom interface. Let's use IDB as an example:一个对象能够简单地从IUnknown接口派生,且实现这三个函数。另外实现IUnknown的方法是添加这3个函数到你的定制接口的开始位置。让我们用IDB来做例子。

class IDB {              
// Interfaces              
public:                            
// Interfaces for COM and useful anywayCOM常用接口
virtual HRESULT QueryInterface(RIID riid, void** ppObj) =0;
virtual ULONG AddRef() =0;
virtual ULONG Release() =0;
// Interface for data access数据存取接口
virtual HRESULT Read(short nTable, short nRow, LPWSTR lpszData) =0;
(...)             
};

Another way of achieving the same goal is to simply derive IDB from IUnknown—that is, derive one abstract base class from another abstract base class, as follows:另外的方法是简单地从IUnknown接口派生出IDB类,从一个抽象基类派生另外一个抽象基类:

class IDB : public IUnknown {              
// Interfaces              
public:               
// Interface for data access
virtual HRESULT Read(short nTable, short nRow, LPWSTR lpszData) =0;
(...)             
};

This approach simply combines the two vtables into one: IDB includes all the functions of IUnknown; IDB has the same three functions at the beginning of its vtable. These functions are polymorphic, even on the binary level. 这个方法只是混合了2个虚函数表到一个里:IDB包含了IUnknown的所有函数;IDB在虚函数表的开始位置有3个同样的函数。这些函数是多态,在二进制形式上也是一样的。

With this in mind, we will re-implement our DBAlmostCOM to make it DBCOM. For QueryInterface, we already have two possible answers: The last sample we created can satisfy the requests for IDB. But now we can also satisfy the requests for IUnknown by simply returning IDB. Somebody who does not know IDB, but just IUnknown, will simply call the first three functions in our vtable, which happen to implement the same functions that IUnknown does.认真考虑下,我们将重新实现例子来让它为基于COM的DB。对于QueryInterface,我们已经有2个可能的答案:我们创建的最后的哪个例子能够满足IDB类的要求。但是现在,通过简单地返回IDB我们同样能够满足IUnknown的接口要求。一些朋友不知道IDB,但只知道IUnknown,会简单在调用虚函数表里前3个函数,这恰巧实现了IUnknown的3个函数。

Again, the real benefit of this approach will not be seen until the next sample, in which we implement really useful multiple interfaces.下节会实现实际的有用的多重接口,作用会更明显。

1.1.8.4 Standard Class Factory Interface: IClassFactory 标准类工厂接口:IClassFactory

Up to now, our CDBSrvFactory class factory object has been very specialized. Its only member function is到现在为止,我们的CDBSrvFactory类工厂对象已经非常特殊了,它唯一的成员函数是:

HRESULT CreateDB(IDB* ppObj)

which it exposes through a very specialized interface, IDBSrvFactory. Each object would need a specialized interface because the initial interface returned would probably be different for each object. It sounds like a good idea, if the caller of the CreateXXX function could be allowed to specify the initial interface on the actual object, just as COM does for the class factory:这个函数通过一个特殊的接口IDBSrvFactory暴露出来。因为返回的初始接口可能和每个对象是不一样的,每个对象需要一个特殊的接口。看起来是好主意,如果调用了CreateXXX函数能够允许指明在某个实际对象上的初始接口,这和COM为每个类工厂所作所为一样的:

HRESULT MyCreateInstance(RIID riid, void** ppObj);

The class factory object could check the IID and instantiate the appropriate object, implementing the required interface, or it could create an object and call QueryInterface on that object to obtain the requested IID.类工厂对象能够检查IID且初始化一个对应的对象,实现要求的接口,或者它能创建一个对象且使用对象调用QueryInterface来获取请求的IID。

It is important to distinguish between the class ID (CLSID) and the interface ID (IID): The class ID refers to a logical object that provides a given functionality through a given interface—possibly multiple interfaces. The interface ID refers to a specific layout of a vtable that is used to "talk" to the object.区分类ID(CLSID)和接口ID(IID)是非常重要的:类ID指向逻辑对象,对象通过给定的接口提供了一个给定的功能,它里面的接口可能是多重接口。接口ID指向一个具体的虚函数表的布局,虚函数表负责和对象“交流”。

This almost completes our standard class factory interface. Like any COM object, the class factory object also exports the IUnknown functions for reference counting and for supporting multiple interfaces:加上这个我们几乎完成了我们标准类工厂接口。象任何COM对象一样,类工厂对象同样导出了IUnknown函数来处理引用计数和支持多重接口:

class IClassFactory : public IUnknown {               
virtual HRESULT CreateInstance(IUnknown *pUnkOuter,               
REFIID riid, void** ppvObject) = 0;               
virtual HRESULT LockServer(BOOL fLock) = 0;             
};

Well, we almost have it: CreateInstance has an additional parameter, pUnkOuter, that allows for a sophisticated reuse mechanism called aggregation. We will not deal with aggregation in these samples, so we just pass in NULL and when we receive a call, we will check for NULL, and fail if pUnkOuter is not NULL.我们也拥有:CreateInstance有一个额外的参数pUnkOuter,这个参数允许了成熟的重用机制集合。这个例子里我们不会处理集合,所以我们只是传递NULL来作为参数,且当我们接收一个调用时,我们会检查是不是为NULL,如果不为空则会返回失败。

NoteAggregation allows you to effectively merge two objects and make them appear as one to a client. An “outer object” receives all the calls to IUnknown (reference count and queries for new interfaces) and can—usually—selectively return pointers to an “inner object” to the client. If the client calls a function from IUnknown on the inner object, the inner object must call back to the outer unknown (hence the parameter pUnkOuter at creation time of the inner object), in order for the two objects to be perceived as identical. See the Win32 SDK or the OLE Design Specification for details.注意:集合允许你有效地合并2个对象且让它们从客户端角度上看起来象一个。“外部对象”接收所有对IUnknown的调用(引用计数和新接口的查询)且能—通常上来说—可选择地返回“内部对象”的指针。如果客户端调用内部对象的IUnknow函数,内部对象必须回调给外部unknown(这也是在内部对象的创建时间点上的pUnkOuter参数),为了让2个对象被认为是不一样的。请参考WIN32的SDK或OLE设计规格书这类细节。

Another function we have passed over is LockServer. This function lets the client keep alive the module that implements the class factory object. Why do so, if reference counting through IUnknown's AddRef/Release methods lets the object know if someone still needs it? Some objects (such as local servers implemented in an EXE) do not count references to the class factory object as sufficient to keep the code for the object in memory; they exit when nothing but a class factory remains. Clients can keep these kind of objects alive by calling LockServer on the class factories that they want to keep after creating them.另外一个已经通过的函数是LockServer。此函数让客户端感知实现类工厂的模块。为什么这样做呢?如果引用计数方法让对象知道了某个客户端依然在用它?一些对象(比如在EXE文件里实现的本地服务)不能对类工厂对象进行引用计数,也达不到在内存中保持对象的功能。他们退出时不做处理,但是一个类工厂保留下来了。通过调用LockServer,客户端能够保留这些激活类的对象,在创建后想要保留就能保留了。

For now, we will implement LockServer by effecting a global reference count for the DLL that combines all outstanding references, whether it is to the database object(s) or class factory object(s).暂时为止,我们通过全局引用计数的实质化处理来为DLL实现了LockServer,DLL包含了所有有意义的计数,不管是数据库对象还是类工厂对象。

1.1.8.5 Dynamic Unloading of an Object's Code动态卸载对象的代码

In this sample, our client requires that the object be loaded while the client is running, unless the user closes all the documents. In that case, the DLL is no longer used by the client. If we had to optimize memory resources, we could unload the DLL in this situation.本例子里,我们的客户端需要让对象在客户端运行时加载上,除非用户关闭了所有文档,对象才能卸载掉。这种情况下,DLL不再由客户端使用。如果我们必须优化内存资源,我们能卸载DLL。

With implicit linking, there is no way to do this, and using the COM Libraries for loading our DLL leaves us without access to the LoadLibrary/FreeLibrary calls. COM takes the responsibility for unloading the code, which it does by querying the DLLs it has loaded to see if they are still in use. COM calls another DLL exported function, DllCanUnloadNow, and expects the DLL to answer S_OK or S_FALSE. 采用隐含链接且用COM库来加载DLL会让我们没办法对LoadLibrary/FreeeLibrary调用,则没有方法实现此功能,。COM负责卸载代码,这通过查询已经加载过的DLL是否还在使用。COM调用其它的DLL导出函数,DllCanUnloadNow,且期望DLL来回答S_OK或S_FALSE。

We will implement this function by maintaining a global reference count: Whenever a pointer is given away, whether to a class factory or to a database object, we call AddRef to increment both the object's reference count and a global variable containing all the reference counts. DllCanUnloadNow simply checks to see if the global reference count is 0 and returns S_OK. COM then knows that it can safely unload this DLL.我们通过维护一个全局的引用计数来实现这个功能:一旦一个指针给出让使用,不管是类工厂的还是数据库对象的,我们会调用AddRef来增加对象的引用计数和全局变量,这个全局变量包含了所有引用计数。DllCanUnloadNow简单地检查看全局引用计数是否为0,如果为0,返回S_OK。COM接着知道了能够安全地卸载DLL。

The COM libraries check the modules on behalf of the client: The client should call CoFreeUnusedLibraries periodically if it wants to ensure that COM unloads unused libraries.COM检查客户端代表的模块:客户端如果想确保卸载不再使用的库,应该间隔定时调用CoFreeUnsedLibraries。

1.1.8.6 Self-Registration自我注册

It is very practical for an object to provide for self-registration. This facilitates installation (and de-installation!) of an object. The idea is very simple: Two standard entry points, DllRegisterServer and DllUnregisterServer, provide this functionality and can be called by any program that needs to register a DLL. The regsrv32.exe utility provided with the OLE Controls CDK does nothing more than call one of these functions on a DLL.组件对象提供自注册能力非常实用。这实现了对象的注册和卸载。方法简单:2个标准的入口函数,DllRegisterServer和DllUnregisterServer,这2个函数能让想注册的程序来调用。regsrv32.exe工具里就是只做了这些函数的调用而已。

The implementation of these functions can be a little tedious due to the nature of the registry (or is it the Win32 API used for accessing it?), but is nonetheless straightforward.因于注册表的特点,这些功能可能有些复杂。

1.1.8.7 Changes: Step by Step逐步修改

  • Derive IDB from IUnknown. Remove the old declaration of Release from IDB. Add _stdcall to all members of IDB, since this is the standard calling convention for COM objects under Win32.从IUnknown里派生出IDB。从IDB里删除旧的Release声明。添加_stdcall修饰到所有IDB的成员函数里,因为这是Win32平台下COM对象机制里标准的调用约定。
  • Remove the declaration of IDBSrvFactory. We will now use the standard class factory interface, IClassFactory. Also remove the IID_IDBSrvFactory.删除IDBSrvFactory的声明。我们现在用标准的类工厂接口IClassFactory。同样删除IDBSrvFactory。
  • Create a new IID_IDB with GUIDGen (or use one of the GUIDs that you generated in advance). Do not reuse IIDB_IDBSrvFactory: GUIDs should never be reused, because they define a unique contract and they are not a limited resource. Add the declaration and implementation to interface\dbsrv.h, object\dbsrvfact.cpp, and client\dbdoc.cpp respectively. 用GUIDGen工具(或使用以前分配好的一个GUID)来创建新的IID_IDB。不再重用IIDB_IDBSrvFactory:GUID永远不应该被重用,因为他们有唯一的约定,并不是无限的资源。添加声明和实现到interface\dbsrv.h,object\dbsrvfact.cpp,和client\dbdoc.cpp里。

The new Interface\DBSRV.H looks like this:新的Interface\DBSRV.H看起来如下:

(...)             
// {30DF3432-0266-11cf-BAA6-00AA003E0EED}             
extern const GUID IID_IDB;             
//{ 0x30df3432, 0x266, 0x11cf, { 0xba, 0xa6, 0x0, 0xaa, 0x0, 0x3e, 0xe, 0xed } 
};             
class IDB : public IUnknown {             
// Interfaces             
public:             
// Interface for data access             
virtual HRESULT _stdcall Read(short nTable, short nRow, LPWSTR lpszData) =0;             
virtual HRESULT _stdcall Write(short nTable, short nRow, LPCWSTR lpszData)=0;             
// Interface for database management             
virtual HRESULT _stdcall Create(short &nTable, LPCWSTR lpszName) =0;             
virtual HRESULT _stdcall Delete(short nTable) =0;             
// Interfase para obtenber informacion sobre la base de datos             
virtual HRESULT _stdcall GetNumTables(short &nNumTables) =0;             
virtual HRESULT _stdcall GetTableName(short nTable, LPWSTR lpszName) =0;             
virtual HRESULT _stdcall GetNumRows(short nTable, short &nRows) =0;                          
//virtual ULONG Release() =0;                           
};
  • Derive CDBSrvFactory from IClassFactory instead of from IDBSrvFactory.从IClassFactory派生出来CDBSrvFactory,不再把IDBSrvFactory当作基类。
  • Change CDBSrvFactory::CreateDB to CDBSrvFactory::CreateInstance, and add a member function called CDBSrvFactory::LockServer.修改CDBSrvFactory::CreateDB为CDBSrvFactory::CreateInstance,且添加一个成员函数CDBSrvFactory::LockServer。
  • Add a ULONG m_dwRefCount member to both CDB and CDBSrvFactory for their respective reference counts. Also add a constructor to both classes and initialize m_dwRefCount to 0.添加一个数据成员ULONG m_dwRefCount到类CDB和CDBSrvFactory里当作引用计数。同样添加构造函数,构造函数里初始化m_dwRefCount为0.
  • Add a global variable, ULONG g_dwRefCount, to dbsrvimp.h and dbsrvimp.cpp.添加一个全局变量ULONG g_dwRefCount到dbsrvimp.h 和 dbsrvimp.cpp里。
  • Add QueryInterface, AddRef, and Release member functions to both CDB and CDBSrvFactory. (The order of the declaration in the implementation header file does not affect the order in the vtable. The vtable is defined by the order of declarations in IDB!) 添加QueryInterface, AddRef, 和 Release成员到类CDB和CDBSrvFactory里(在执行头文件里的声明顺序并不会影响虚函数表的顺序。虚函数表在IDB里定义了顺序)。

The new Object\DBSrvImp.h looks like this:新的Object\DBSrvImp.h看起来如下:

class CDB : public IDB {             
// Interfaces             
public:             
// Interface for data access
HRESULT _stdcall Read(short nTable, short nRow, LPWSTR lpszData);
(...)             
HRESULT _stdcall GetNumRows(short nTable, short &nRows);
HRESULT _stdcall QueryInterface(REFIID riid, void** ppObject);             
ULONG  _stdcall AddRef();
ULONG  _stdcall Release();
// Implementation             
private:             
CPtrArray m_arrTables; 
// Array of pointers to CStringArray
CStringArray m_arrNames; 
// Array of table names             
ULONG m_dwRefCount;             
public:             
CDB();             
~CDB();             
};             
extern ULONG g_dwRefCount;             
class CDBSrvFactory : public IClassFactory {             
// Interface             
public:             
HRESULT _stdcall QueryInterface(REFIID riid, void** ppObject);             
ULONG  _stdcall AddRef();
ULONG  _stdcall Release();
HRESULT _stdcall CreateInstance(IUnknown *pUnkOuter, REFIID riid,             
void** ppObject);             
HRESULT   _stdcall LockServer(BOOL fLock);             
// Implementation             
private:             
ULONG m_dwRefCount;             
public:             
CDBSrvFactory();             
};
  • Implement AddRef, Release, and QueryInterface for CDB and CDBSrvFactory.为CDB和CDBSrvFactory实现AddRef, Release, 和 QueryInterface。
  • Change CDBSrvFactory::CreateDB to CreateInstance, and validate the new parameters.修改CDBSrvFactory::CreateDB到CreateInstance,且审核新的参数。
  • Implement CDBSrvFactory::LockServer. 实现CDBSrvFactory::LockServer。
w

Here is the new implementation in DBSRV.CPP:下面是新的DBSRV.CPP

CDB::CDB() {             
m_dwRefCount=0;             
}             
HRESULT CDB::QueryInterface(REFIID riid, void** ppObject) {             
if (riid==IID_IUnknown || riid==IID_IDB) {             
*ppObject=(IDB*) this;             
}             
else {             
return E_NOINTERFACE;             
}             
AddRef();             
return NO_ERROR;             
}             
ULONG CDB::AddRef() {             
g_dwRefCount++;             
m_dwRefCount++;             
return m_dwRefCount;             
}             
ULONG CDB::Release() {             
g_dwRefCount--;             
m_dwRefCount--;             
if (m_dwRefCount==0) {             
delete this;             
return 0;             
}             
return m_dwRefCount;             
}

And the new implementation in DBSRVFact.cpp:添加新实现到DBSRVFact.cpp文件里。

ULONG g_dwRefCount=0;             
// {30DF3430-0266-11cf-BAA6-00AA003E0EED}
static const GUID CLSID_DBSAMPLE =
{ 0x30df3430, 0x266, 0x11cf, { 0xba, 0xa6, 0x0, 0xaa, 0x0, 0x3e, 0xe, 0xed } };             
// Create a new database object and return a pointer to it.             
HRESULT CDBSrvFactory::CreateInstance(IUnknown *pUnkOuter, REFIID riid,             
void** ppObject) {             
if (pUnkOuter!=NULL) {             
return CLASS_E_NOAGGREGATION;
}             
CDB* pDB=new CDB;             
if (FAILED(pDB->QueryInterface(riid, ppObject))) {             
delete pDB;             
*ppObject=NULL;             
return E_NOINTERFACE;             
}             
return NO_ERROR;             
}             
HRESULT   
CDBSrvFactory::LockServer(BOOL fLock) {
if (fLock) {             
g_dwRefCount++;             
}             
else {             
g_dwRefCount--;             
}             
return NO_ERROR;             
}             
CDBSrvFactory::CDBSrvFactory() {
m_dwRefCount=0;             
}             
HRESULT CDBSrvFactory::QueryInterface(REFIID riid, void** ppObject) {             
if (riid==IID_IUnknown || riid==IID_IClassFactory) {            
*ppObject=(IDB*) this;             
}             
else {             
return E_NOINTERFACE;             
}             
AddRef();             
return NO_ERROR;             
}             
ULONG CDBSrvFactory::AddRef() {             
g_dwRefCount++;             
m_dwRefCount++;             
return m_dwRefCount;             
}             
ULONG CDBSrvFactory::Release() {             
g_dwRefCount--;             
m_dwRefCount--;             
if (m_dwRefCount==0) {             
delete this;             
return 0;             
}             
return m_dwRefCount;             
}             
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppObject) {             
if (rclsid!=CLSID_DBSAMPLE) {             
return CLASS_E_CLASSNOTAVAILABLE;             
}             
CDBSrvFactory *pFactory= new CDBSrvFactory;             
if (FAILED(pFactory->QueryInterface(riid, ppObject))) {             
delete pFactory;             
*ppObject=NULL;             
return E_INVALIDARG;             
}             
return NO_ERROR;             
}

Note that my implementations of CreateInstance and DllGetClassObject do not verify the IID themselves but let the objects do the work by using QueryInterface on the newly created objects. This makes the implementation very reusable: You just change the name of the class and everything works fine. If you add more interfaces to the object, you only have to change QueryInterface in the object. 注意CreateInstance 和 DllGetClassObject的实现不亲自验证IID,而是基于新分配的对象让对象用QueryInterface来实现。这让实现非常可重用:你只需要修改类的名称,一切OK。如果你添加了多个接口到对象里,你只需要修改对象的QueryInterface

QueryInterface does an implicit AddRef, since it returns another pointer to the same object. Since we always use QueryInterface after creating an object, we initialize m_dwRefCount to 0. QueryInterface隐含调用了AddRef,因为它返回了同样对象的另外一个指针。在创建对象m_dwRefCount 为0后,我们总是用QueryInterface。

This is just one way of implementing IUnknown, but a very modular one.这只是实现IUnknown的一个方法,但是非常标准。

  • Add DllCanUnloadNow, DllRegisterServer, and DllUnregisterServer to bdsrvfact.cpp and export them in DB.DEF.添加DllCanUnloadNow, DllRegisterServer 和 DllUnregisterServer到bdsrvfact.cpp,且在DB.DEF中导出。
HRESULT _stdcall DllCanUnloadNow() {              
if (g_dwRefCount) {             
return S_FALSE;             
}             
else {             
return S_OK;             
}             
}             
STDAPI DllRegisterServer(void) {             
HKEY hKeyCLSID, hKeyInproc32;             
DWORD dwDisposition;             
if (RegCreateKeyEx(HKEY_CLASSES_ROOT,
"CLSID\\{30DF3430-0266-11cf-BAA6-00AA003E0EED}"),              
NULL, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL,              
&hKeyCLSID, &dwDisposition)!=ERROR_SUCCESS) {
return E_UNEXPECTED;             
}             
(. . .) // See dbsrvfact.cpp for details.             
return NOERROR;             
}             
STDAPI DllUnregisterServer(void) {
if (RegDeleteKey(HKEY_CLASSES_ROOT,
"CLSID\\{30DF3430-0266-11cf-BAA6-00AA003E0EED}             
\\InprocServer32"))!=ERROR_SUCCESS) {             
return E_UNEXPECTED;             
}             
if (RegDeleteKey(HKEY_CLASSES_ROOT,
"CLSID\\{30DF3430-0266-11cf-BAA6-00AA003E0EED}"))!=ERROR_SUCCESS) {             
return E_UNEXPECTED;             
}             
return NOERROR;             
}
  • Add uuid.lib in "Linker - Object/Library modules" to import the declaration of IID_IUnknown and IID_IClassFactory.添加uuid.lib到工程配置里的Linker - Object/Library modules部分,来导入IID_IUnknown 和 IID_IClassFactory的声明。
  • Client: Change DBDOC.CPP to create (through a class factory object) QueryInterface for IDB. (There is a Helper API in COM—CoCreateInstance—that combines all the calls in one. For showing the technical details, I chose to implement it here step by step.)客户端:修改DBDOC.CPP来创建(通过类工厂对象)面向IDB的QueryInterface调用。(在COM里有一个助手API—CoCreateInstance—它合并了所有的调用到一起。为了显示技术细节,我选择逐步来实现)。
  • Client: Add the definition of IID_IDB to DBDOC.CPP.客户端:添加IID_IDB的声明到DBDOC.CPP里。
  • Client: Add CDBApp::OnIdle (using ClassWizard). During idle processing, call CoFreeUnusedLibraries to make sure that any DLLs loaded by COM that do not have any reference to them get unloaded.客户端:添加CDBApp::OnIdle(用类向导)。在空闲处理里,调用CoFreeUnusedLibraries来确保任何由COM加载的在没有任何引用计数时DLL得到卸载。
BOOL CDBApp::OnIdle(LONG lCount)
{             
if (CWinApp::OnIdle(lCount)) {
return TRUE;             
}             
CoFreeUnusedLibraries();             
return FALSE;             
}
  • Add uuid.lib to the Linker - Object/Library Modules section, for IID_IClassFactory.
(...)             
IClassFactory *pDBFactory=NULL; 
HRESULT hRes;             
hRes=CoGetClassObject(CLSID_DBSAMPLE, CLSCTX_SERVER, NULL, IID_IClassFactory,
(void**) &pDBFactory);             
if (FAILED(hRes)) {  
CString csError;  
csError.Format(_T("Error %x obtaining class factory for DB Object!"), hRes);  
AfxMessageBox(csError);  
return FALSE;  
}             
hRes=pDBFactory->CreateInstance(NULL, IID_IDB, (void**) &m_pDB);             
if (FAILED(hRes)) {  
CString csError;  
csError.Format(_T("Error %x creating DB Object!"), hRes);  
AfxMessageBox(csError);  
return FALSE;  
}               
pDBFactory->Release(); 
// Do not need the factory anymore.             
(...)

Compile both the client and the object, register DB.DLL with regsvr32.exe, and run the client. Again, you can mix and match Unicode/ASCII and Release/Debug versions!把客户端和对象都编译好,用regsvr32.exe注册DB.DLL且运行客户端。再说一次,你能混合或匹配Unicode/ASCII和Release/Debug版本。

More than code, there was object philosophy involved, and your object design can benefit from that philosophy even if you do not use COM. Reference counting on objects and managing multiple interfaces on a single object are both useful concepts. The other part of the code sample illustrated standard "infrastructure," such as standard entry points and self-registration, that you can use in your own design, even if you are not planning to use COM.不但是代码,还有对象的逻辑也有关系,即使你不使用COM,你也能从这种哲学里吸取益处。基于对象的引用计数和基于单一对象的多重接口管理都是有用的概念。其它部分的代码例子演示了标准的“架构”,比如标准入口点和自注册,你也可以在你的设计里使用,甚至你不想使用COM。

友情链接
版权所有 Copyright(c)2004-2021 锐英源软件
公司注册号:410105000449586 豫ICP备08007559号 最佳分辨率 1024*768
地址:郑州市金水区郑州大学北校区院(文化路97号院)内