COM编程
进程内COM服务器(In-Process COM Server)
进程内的COM服务器(In-Process Com Server)在这前已经简单的介绍过,在此,只对一些需要明白、理解的知识点进行阐述,再进行编程可能会更好一点;进程内服务器是由于它们在DLL内实现而获得这个名称的。因此,服务器占擗了和使用它的应用程序一样的地址空间(进程)。所有的进程内COM服务器输出四个标准函数:DllRegisterServer、DllUnregisterServer、DllGetClassObject和DllCanUnloadNow。根据字面意思,我们也已经可以感觉的出这些函数的工作都会是什么。当然,Delphi已经为我们提供了这些函数的缺省实现。因此,读者不必自己写代码来实现这些函数,但应该理解他们具体是做什么的。
² DllRegisterServer。DllRegisterServer以两种方式自动调用。IDE的Register ActiveX Server菜单选项调用它,Windows的命令行应用程序RegSvr32.exe(或者Borland应用程序TRegSvr)也会调用它,很多情况下可以直接用Register ActiveX Server去调用,如果手动的去实现,可以写一个.Bat文件。无论通过那种方式调用它,DllRegisterServer用Windows注册表来注册COM对象。
² DllUnregisterServer。可以感觉的出来,这个函数和DllRegisterServer是作相反的工作,实际上它们就是一个相逆的过程,它移走了DllRegisterServer放在Windows注册表中的所有条目。可以用IDE里的 UnRegister ActiveX Server来调用这个工程。
² DllGetClassObject。DllGetClassObject负责提供给COM一个类厂,该类厂用于创建一个COM对象(在讨论类厂的时候,我们也做过明确的说明)。
² DllCanUnloadNow。COM负责调用DllCanUnLoadNow来看是否可以从内存中卸载COM服务器。
线程支持(Threading Support)
线程支持只适合于进程内服务器,并且不适用于进程外服务器。进程内服务器可以附着在几个的一个。进程内服务器的线程模型被存在Windows注册表中,具体如下:
² ……
² ……
² ……
² ……
…………
注册服务器(Registering the Server)
此处将不再重提如何注册服务器,只是简单的说一说为什么要进行注册。我们都知道,普通的Dll也需要进行注册才可以运行,而COM对象或是COM服务器要提供给服务于Client,那么客户端首先要知道要没有这个服务?如何进行这个服务的调用或是访问,因此它会找一些有用的键值,而客户所找的范畴就是注册表。由此而言,注册的确很有必要而肯是必不可少的。
构造函数
应该明确的是,做为一个提供服务的对象或接口或是一个组件,有一步工作应该提前做,那就是构造,而构造也是初始化,并且之前我们也说过,COM对象最好派生于TcomObject类,如此一来,我们就不得不去考虑在TcomObject中定义的构造函数都调用了虚方法函数Initialize。如果需要为自己的COM对象提供初化代码,只需要重载Initialize方法,定义如下:
Procedure Initialize ;Virtual;
将初始化代码放在Initialize中而不是构造函数中的原因是:Delphi中的COM对象的基类包含了一系列的非虚构造函数。根擗需要,类厂奖在不同的实例中调用不同的构造函数。而Initialize是虚方法,并且是唯一可以在调用时不考虑哪个构造函数是用于创建COM对象的方法。
创建一个进程内COM对象的实例
当用户需要在自己的客户程序代码中创建一个COM对象,一般会调用CreateConObject函数,该函数在ComObj.pas中定义,声明如下:
Function CreateComObject(const ClassID:TGUID):IunKnown;
CreateComObject将要创建的COM对象的GUID作为一个参数,并返回该COM对象的IunKnown指针。苦所请求的GUID在Windows注册表中找不到的话,将会抛出一个异常。那么我们一起来分析一下CreateComObject函数的实现如下:
function CreateComObject(const ClassID: TGUID): IUnknown;
begin
OleCheck(CoCreateInstance(ClassID, nil, CLSCTX_INPROC_SERVER or
CLSCTX_LOCAL_SERVER, IUnknown, Result));
end;
之前我们曾讨论过OleCheck,现在将重点的精力放在CoCreateInstance上边。如果根踪CoCreateInstance会发现如下信息:
function CoCreateInstance; external ole32 name 'CoCreateInstance';
因此可以这样认为,CreateComObject函数所作的只是调用了一下Windows的函数CoCreateInstance,并提供了一些默认的参数
CoCreateInstance主要有五个参数:
Clsid:Clsid是我们想要创建的COM服务器的GUID。这是我们专们传递给CreateComObject唯一的一个参数。
UnkOuter:只有在此COM对象是集合的一部分时才使用它。
DwClsContext:dwClsContext决定了读者想要创建的类型。CreateCOMObject自动要求创建一个进程内服务器(CLSCTX_INPROC_SERVER)或本地的进程外服务器(CLSCTX_LOCAL_SERVER)。有时与此函数一起使用的另外一个标记是CLSCTX_REMOTE_SERVER。此标志在DCOM中使用,以后将会讨论。
Iid:iid是我们想要获取一个对它的引用的接口。Delphi通常需要对IunKnown接口的应用,因为如果要其它别的引用的话,大多数类厂会失败。Microsoft用此参数主要是为了将来的扩展。
Pv:主要是得到IunKnown接口的指针。
{一个需要注册的是:CoCreateInstance内部创建负责创建COM对象类厂的实例,然后使用类厂来创建对象。创建完COM对象之后,类厂就被销毁。显然,如果要创建相同COM对象的多个实例,这不是非常有效的,在这种情况下就要自大闯将一个类厂的实例,并在删除它之前使用它的CreateInstance方法来创建COM对象。}
正如以前提到的,CreateComObject通常返回一个IunKnown指针。要获取需要的接口针,应使用as操作符,如:
MyIntf := CreateCOmObject(CLSID_MyServer) as IMyInterface
实例:一个简单的COM应用程序
在讨论了COM服务器的一些基本概念之后,我们用一个简单的小实例来说明如何调用以DLL形式提供服务的COM服务器,之后,我们会对此COM服务器进行扩展,并且会再给一个COM服务器的高级编程,但一切需要一步一步的来。
实例说明:
此实例是当正确登录到COM服务器时,就可以实现一个简单的算法,此处没有给出COM服务器与数据库服务器之间的连接,所以登录不是一个动态的从数据库用户信息表里进行判断,而是在程序中指定了一个固定的用户名,当然,在此处,可以连接一些文本数据库来进行动态的判断。此处的算法很简单,就是传统的小猴子吃桃子的问题,一个小猴子有一些桃子,每天吃一半,因为嘴馋,又多吃一个,这样每天就是吃一半多一个,当到了第十天时,吃完之后仅仅有一个了(此处的吃完仍然是吃了一半多一个),而算法就是要知道小猴子子本来要多少只桃子。
问题分析:因为需要用到COM服务器的一些方法,所以可以暂时作如下的决定:
用户的信息判断,即登录是否成功让服务器来为我们判断,客户端仅仅是作简单的错误性检查和数据的提交,正真的实现一个瘦客户端;
{说明:此处所说的瘦客户端希望读者朋友们不要仅仅根据字面意思来看客户端,并非是客户端的代码越少越好,客户端所要作的工作都让服务器去做才是瘦。其实,不应该如此去理解,而我们一般理解的服务器/客户端应该是服务器帮客户端处理一些逻辑上的、业务上的分析,而一些判断性的错误可以由客户端去完成,虽然是客户端,但它同样有一些自身的检查在里边,虽然是服务器端,它仍有一些任务由客户端去完成,再更多的情况,也许对读者朋友来说,感觉可能让客户做的更好}
算法实际上很简单,可以利用循环,也可以利用递归,在此仅仅做简单的分析。我们用循环来处理的话应该需要从后边入手,第十天刚刚吃的还有一个桃子,那么第十天,就应该是四个,第九天是十个,第八天是二十二个……,如此类推,我们可以很容易的得出一共有多少个桃子,可以进行如下的处理:
Var
Acount : Integer;
Asum : Integer;
Begin
Asum := 1;
For Acount := 10 DownTo 1 do
Asum := ( Asum + 1 ) * 2;
End;
理解起来很容易,到了第十天吃过之后还剩一个桃子,那么,第