COM实现过程

来源:互联网 发布:windows资源监控 编辑:程序博客网 时间:2024/06/05 07:10

COM实现过程
原创:吴剑明(foxnt)

前言
COM已经成为一个必需的东西了。在我们周围,可以说处处充满了COM

– 如果你是在使用WINDOWS,并在其下面编写程序的话。然而,无论

你是用VC,还是使用DELPHI进行COM编程时,在大多数情况下,编程工

具的IDE已经向你隐藏了COM的大部分实现过程,使得程序员根本不需

要了解COM,只专心致志地写其所关心的逻辑代码。这就意味着,我们

很少有机会,能够揭开COM的神秘面纱,来看到它下面到底是什么东西

。这对于一个WINDOWS程序员来说,不能不是个遗憾。
因此,本文的宗旨,就是抛开现有的IDE提供的各种向导工具,引导大

家从最基本的地方入手,完整地从一个空白的程序里,建立起一个COM

程序,从而达到能够比较清晰地了解一个COM,到底是如何生成出来并

实现在程序中。
本文假设,您是一个有COM编程经验的DELPHI/VC程序员,并希望了解

COM的基本实现过程。限于篇幅和时间,我们只讨论进程内的COM(DLL

)的实现,并引导大家亲手建立起一个最简单的COM程序。

COM是什么?
COM有各种表现形式,可以是进程内,也可以是进程外;可以在本机调

用,也可以远程调用。记得国外有个研究COM的组织,他的主题就叫作

:COM就是爱! 这当然是外国人的幽默,他只是想说明,COM是个多么

重要的东西。那么COM到底是个什么东西呢?
很早以前,在我刚开始学习COM的时候,身边就有些程序员告诉我:

COM不是DLL,虽然它通常也是以DLL来作为扩展名的,可他完全与DLL

完全不同。那么,这种说法是否正确呢?我们来看看,要实现一个进

程内的COM,到底需要经过哪些步骤,那么,我们就能很清楚的知道答

案了。
完成一个进程内的COM,通常需要以下几步:
1. 建立一个DLL项目,并导出以下四个函数:
DllGetClassObject,
DllCanUnloadNow,
DllRegisterServer,
DllUnregisterServer;

2. 定义自定义的接口,同时必须实现Iunknown接口。
3. 建立GUID,以标识这个组件以及自定义的接口。
4. 在注册表中注册以标记这个DLL。
大家都看到了,在第一个步骤里,需要建立一个DLL项目,那么,是不

是意味着,COM就是一个DLL呢?在这里,我可以明确地告诉大家,从

技术上讲,一个进程内的COM完全可以被认为就是一个普通的DLL—动

态连接库!如果你抛弃常用的COM API,比如DELPHI中常用的:
CreateCOMObject()或者
CreateOLEObject()
那么您完全可以直接采用加载普通DLL的方式来调用这个COM组件,比

如说,您可以直接用LoadLibrary()函数来加载这个DLL,然后使用

GetProcAddress来调用从这个DLL里输出的接口,从而完成各项操作。

这是完全可行的。然而,我不得不告诉大家,把一个COM仅仅看成一个

DLL,那是非常肤浅的看法 – DLL仅仅是一种表现形式而已。更重要

的是,COM实现了一种规则。因此我们可以说:
l COM是一种包含了许多处理逻辑、符合了某种接口规范(如Iunknown

规范)的DLL组件。
(注:如果没有特别说明,我在本文里所指的COM,都是指进程内的

DLL形式的COM)
l COM实现了Iunknown接口。因此,任何只要符合Iunknown规范,实现

了Iunknown接口的DLL组件,我们都可以把他看成是一个COM。
那么,什么是Iunknown接口呢?如何实现一个Iunknown接口呢?我们

看看,在DELPHI中是如何定义一个Iunknown接口的:
IInterface = interface
['{00000000-0000-0000-C000-000000000046}']
function QueryInterface(const IID: TGUID; out Obj): HResult;

stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
IUnknown = IInterface;

简单一点看,我们直接这样理解就行了:
IUnknown = interface
['{00000000-0000-0000-C000-000000000046}']
function QueryInterface(const IID: TGUID; out Obj): HResult;

stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
在DELPHI里,interface是编译器所认识的一种类型。如果是在VC++中

,Iunknown将被定义为一种结构(struct)。如果要实现一个

Iunknown接口,我们必须用一个类来实现它,比如在DELPHI中,要实

现Iunknown接口可以写成为:
TMyCOMObject = class (Tobject, Iunknown)
……
end;
有心的读者可能会立即问:这个Iunknown接口由Tobject来实现,那么

,可不可以是由其他类来实现呢?比如说用Tcomponent类来实现?答

案是: 完全可以!!
例如,我们要实现一个自定义的接口IMyCOM,可以写成这样:
IMyCOMTest = interface(Iunknown);
TMyCOMTest = class(Tcomponent, IMyCOMTest)
…….
End;
这样是完全可以的!因为COM关注的只是如何实现一个接口,至于程序

员使用什么类来实现,COM是不管的。
后面我们要实现一个COM的例子,而且我打算就用这个IMyCOMTest接口

来做。所以我们把这个接口声明成为例1,以便后面使用。

COM的产生
假如我们已经完成了一个COM,并且已经在系统中注册了。那么,一个

客户端需要来调用这个COM,这时,系统中发生了哪些事呢?
一般来说,以DELPHI为例,客户程序使用CreateCOMObject或者

CreateOLEObject调用COM组件时,会发生以下几个步骤:
1. CreateCOMObject或者CreateOLEObject的动作。
我们看看这两个函数都干了些什么:

function CreateComObject(const ClassID: TGUID): IUnknown;
begin
OleCheck(CoCreateInstance(ClassID, nil, CLSCTX_INPROC_SERVER

or
CLSCTX_LOCAL_SERVER, IUnknown, Result));
end;

CreateOLEObject稍微复杂些:

function CreateOleObject(const ClassName: string): IDispatch;
var
ClassID: TCLSID;
begin
ClassID := ProgIDToClassID(ClassName);
OleCheck(CoCreateInstance(ClassID, nil, CLSCTX_INPROC_SERVER

or
CLSCTX_LOCAL_SERVER, IDispatch, Result));
end;
看到了吗?CreateOLEObject多了一个ProgIDToClassID函数。这意味

着,如果我们要用CreateOLEObject来调用我们的COM组件,我们将要

多一些步骤来编写我们的COM。这个我将会在后面说明。现在,我们要

关注的是CoCreateInstance API函数。
2. CoCreateInstance API函数将调用CoGetClassObject API,这个调

用过程我们是看不到相关的代码的,因为微软已经把他封装好了。而

CoGetClassObject函数的作用是什么呢?它将调用LoadLibrary来寻找

我们指定的COM组件(DLL),然后使用GetProcAddress 来寻找组件的

入口函数 – 还记得我们上面说过的那四个被导出的函数吗?对,其

中的DllGetClassObject 函数就在这里将被调用。该函数的原形在

DELPHI中是:
function DllGetClassObject(const CLSID, IID: TGUID; var Obj):

HResult;
其中第三个参数:Obj ,将向我们返回COM中的定义的接口。但是,要

注意,这个接口并不是我们自定义的接口,而是向我们返回了一个被

成为是“类工厂”接口的IclassFactory的接口。当我们获得类工厂接

口后,就可以获得我们所需要的、那个我们自定义的接口了。看看

IclassFactory 的接口声明:
IClassFactory = interface(IUnknown)
['{00000001-0000-0000-C000-000000000046}']
function CreateInstance(const unkOuter: IUnknown; const iid:

TIID;
out obj): HResult; stdcall;
function LockServer(fLock: BOOL): HResult; stdcall;
end;
看到那个CreateInstance 的方法了吗?对了,它的第三个参数 obj

将向我们返回那个我们定义的接口,比如是我们的IMyCOMTest接口(

例1)。这样,我们就可以调用我们自定义的接口方法了。
以上的众多步骤看起来有点让人迷惑。那么我们就用一个简单的流程

来描绘我们刚才所发生的步骤。不要被那些步骤吓倒,其实他们是非

常简单的。
l CreateCOMObject --à CoCreateInstance。 CoCreateInstance 在

注册表中查找COM的注册信息。
l CoCreateInstance -à CoGetClassObject 。注册信息被交给

CoGetClassObject。这时候CoGetClassObject将知道COM组件在磁盘上

的位置。
l CoGetClassObject -à LoadLibrary 。LoadLibrary 将寻找COM DLL

的入口,然后GetProcAddress调用其输出函数DllGetClassObject
l DllGetClassObject 的输出参数将向我们返回“类工厂”接口

IClassFactory。
l IclassFactory --à CreateInstance 。CreateInstance方法建立其

我们实现接口的类。该类将调用自身的QueryInterface 方法,查看用

户指定的接口是否被自己实现,如果实现了,则向返回自定义的接口


l 调用结束后,COM客户将调用COM的DLL输出函数DllCanUnloadNow 。

如果该函数返回S_OK,则释放该组件。

实际的COM例子
下面我们来做一个实际的例子。包括如何建立一个COM Server和一个

COM Client。
对于COM Server,我们将实现以下功能:
l 单线程,单客户支持。
l 实现自定义的接口
l 能够使用Regsvr32 在系统中注册和反注册。
l 能够被DELPHI或者VC++程序调用。
我们只关注实现最基本的功能。当大家清楚整个流程后,自然就能写

出更高级的功能,比如多线程支持等。
下面,让我们开始COM实现之旅。


COM Server程序
l 在DELPHI中,新建一个DLL工程。注意是DLL,而不是 Activex

Library。并把工程名保存为MyCOM。然后依次建立两个单元文件:
MyCOMServer 单元: 此单元描述了COM的逻辑实现
COMDef 单元: 此单元描述了COM的输出函数定义。
l 在MyCOM单元里,我们定义DLL的输出函数,整个代码:

library MyCOM;

uses
SysUtils,
Classes,
COMDef,
MyCOMServer in 'MyCOMServer.pas';

//在这里导出四个函数。
exports
DllGetClassObject,
DllCanUnloadNow,
DllRegisterServer,
DllUnregisterServer;

{$R *.res}

begin
end.

先做好定义,不要考虑他们是如何实现的。这个在后面我会做详细解

说。在这里我先说明这四个函数的作用:
DllGetClassObject : 返回类工厂接口。
DllCanUnloadNow : 告诉客户

l 类工厂的实现
正如我前面所说的,一个类工厂必须去建立我们自定义的接口。在上

面,我们定义了自定义的接口,并由类TMyCOMServer 去实现。那么,

现在我们还要做的是,实现类工厂,然后由类工厂建立一个

TMyCOMServer 的接口实例。类工厂接口定义如下:
IClassFactory = interface(IUnknown)
['{00000001-0000-0000-C000-000000000046}']
function CreateInstance(const UnkOuter: IUnknown; const IID:

TGUID;
out Obj): HResult; stdcall;
function LockServer(fLock: Boolean): HResult; stdcall;
end;
注意,IclassFactory是系统预先定义了的,在ACTIVEX单元有,所以

不需要自己再去定义一次。我们只要去实现它就是:
TClassFactory = class(TObject, IClassFactory)
protected
FLock: integer;
function QueryInterface(const IID: TGUID; out Obj): HResult;

stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
Constructor Create;
function CreateInstance(const UnkOuter: IUnknown; const IID:

TGUID;
out Obj): HResult; stdcall;
function LockServer(fLock: Boolean): HResult; stdcall;
end;
我们只关注CreateInstance 方法。LockServer 用于在多客户调用COM

时,锁定COM,以免一个客户退出时销毁了COM,那么其他客户的调用

将发生错误。但是我们在这里只实现单客户,所以不考虑这个函数,

把他置空就是。
function TClassFactory.CreateInstance(const UnkOuter:

IInterface;
const IID: TGUID; out Obj): HResult;
begin

//我们的自定义接口,就是在这里被创建的。
MC := TMyCOMServer.Create;
Pointer(Obj) := Pointer(MC);
end;

function TClassFactory.LockServer(fLock: Boolean): HResult;
begin

end;

同样的,TclassFactory也必须实现引用计数,因为它也实现了

Iunknown接口。
function TClassFactory._AddRef: Integer;
begin
Inc(FLock);

end;

function TClassFactory._Release: Integer;
begin
Dec(FLock);
if FLock = 0 then
Free;
end;

function TClassFactory.QueryInterface(const IID: TGUID; out

Obj): HResult;
begin

end;
其中,QueryInterface 我把它置空,因为在这个例子中,不需要向它

查询什么接口。如果以后读者需要向它查询接口时,自己实现相关代

码。
同样,在它的构造器中,也预先对计数加1
constructor TClassFactory.Create;
begin
Inc(FLock);
end;

到目前为止,我们已经基本实现了一个COM需要的大部分功能。现在,

我们需要把它注册到系统中,以便被其他程序调用。
l COM的注册和反注册
我们回过头来,看看如何去实现那四个DLL的输出函数。这四个函数的

原形如下:
function DllGetClassObject(const CLSID, IID: TGUID; var Obj):

HResult;stdcall;
function DllCanUnloadNow: HResult;stdcall;
function DllRegisterServer: HResult;stdcall;
function DllUnregisterServer: HResult;stdcall;

我们上面所说的类工厂的实例,就是在DllGetClassObject 中创建的

。代码如下:
function DllGetClassObject(const CLSID, IID: TGUID; var Obj):

HResult;
begin
CF := TClassFactory.Create;
Pointer(obj) := Pointer(CF);
Result := S_OK;
end;
同样的,我们只有一个类工厂,所以可以不理会前面那两个参数。否

则,就要根据不同GUID,来创建不同的类工厂对象。在这里,我们直

接把类工厂对象给返回了。
函数DllCanUnloadNow 用来注销一个COM。在正常使用中,要根据引用

计数,来判断是否允许用户注销。在这里我们直接返回S_OK,让用户

直接注销。
function DllCanUnloadNow: HResult;
begin
Result := S_OK;
end;
函数DllRegisterServer 用来向注册表注册一个COM组件信息。要注册

一个COM,用户必须知道COM在注册表中的信息是如何组织的。结构如

下:
HKEY_CLASSES_ROOT
---- CLSID
---- GUID
----- InprocServer32 标明 COM所在磁盘的路径以及线程模型
----- ProgID 标明COM所实现的接口
----- TypeLib 标明 COM 的类型库的GUID
----- Version 标明 COM的版本号。
当发生CreateCOMObject()调用时,输入参数为COM的CLASS类型的GUID

,系统将在注册表中搜索到相关信息,然后就可以找到该COM的位置,

就可以开始调用了。
注意,如果您希望COM组件支持客户端的CreateOLEObject()函数的调

用,您必须还要注册一个信息:
HKEY_CLASSES_ROOT
----- 接口声明
----- CLSID 标明 COM 接口和CLASS类型的GUID的对应关系。
那么,当发生 CreateOLEObject 调用时,系统将会根据输入参数(一

个COM接口声明,如a.b),去查找和接口对应的CLASS GUID,然后就

可以读到COM的相关信息了。
全部代码如下:
function DllRegisterServer: HResult;
var
lp: pchar;
ns: Dword;
begin
Result := S_FALSE;
Reg := TRegistry.Create;
GetMem(lp, 255);
try
Reg.RootKey := HKEY_CLASSES_ROOT;
if Reg.OpenKey('/MyCOM.MyCOMTest',true) then
begin
Reg.CreateKey('CLSID');
if Reg.OpenKey('CLSID',true) then
Reg.WriteString('',GUIDToString(Class_MyCOM));
end;
if Reg.OpenKey('/CLSID/' + GUIDToString(Class_MyCOM), true)

then
begin
if Reg.CreateKey('InprocServer32') = false or
Reg.CreateKey('ProgID') = false or
Reg.CreateKey('TypeLib') = false or
Reg.CreateKey('Version') = false then
Exit;
Reg.WriteString('','MyCOM');
if Reg.OpenKey('/CLSID/' + GUIDToString(Class_MyCOM) +
'/InprocServer32', false) then
begin
Windows.GetModuleFileName(HInstance,lp, 255);
Reg.WriteString('', lp);
Reg.WriteString('ThreadingModel', 'Single');
end;
if Reg.OpenKey('/CLSID/' + GUIDToString(Class_MyCOM) +

'/ProgID', false) then
Reg.WriteString('','MyCOM.MyCOMTest');
if Reg.OpenKey('/CLSID/' + GUIDToString(Class_MyCOM) +

'/Version', false) then
Reg.WriteString('','1.0');
if Reg.OpenKey('/CLSID/' + GUIDToString(Class_MyCOM) +

'/TypeLib', false) then
Reg.WriteString('',GUIDToString(LIBID_MyCOM));

Reg.CloseKey;
Result := S_OK;
end;
finally
begin
FreeMem(lp);
Reg.Free;
end;
end;
end;

函数DllUnregisterServer 则向系统注销一个COM的注册信息。它比较

简单,直接把COM的相关注册键给删除就是:
function DllUnRegisterServer: Hresult;
begin
Result := S_False;
Reg := TRegistry.Create;
try
Reg.RootKey := HKEY_CLASSES_ROOT;
Reg.DeleteKey('/CLSID/' + GUIDToString(Class_MyCOM));
Reg.CloseKey;
Finally
Reg.Free;
end;
end;


l 最后工作。
现在,我们编译程序,然后生成一个DLL文件,在命令行下,使用:
regsvr32 MyCOM.dll
向系统注册COM。


COM Client程序
在DELPHI中调用
新建一个项目,然后在单元中,定义接口信息:
IMyCOMTest = interface(IUnknown)
['{D1C4A022-7F6F-42F0-A9B0-4A91703EB124}']
function msg: integer;stdcall;
end;
定义变量:
class_wjm: TGUID = '{CE38847E-A386-4753-89F1-34BE80042107}';
a: IMyCOMTest;
然后在窗口的OnCreate 事件里,添加如下代码:
procedure TForm1.FormCreate(Sender: TObject);
begin
//随便用哪个都可以
a := createcomobject(class_wjm) as IMyCOMTest;
//或者使用 a := createoleobject('MyCOM.MyCOMTest') as

IMyCOMTest;
end;
然后,放一个按钮,并在其事件里添加代码:
procedure TForm1.Button1Click(Sender: TObject);
begin
showmessage(inttostr(a.msg));
end;

在窗口的OnCLose事件里加上:
procedure TForm1.FormClose(Sender: TObject; var Action:

TCloseAction);
begin
a := nil;
end;
注意一定要释放接口,否则可能是个难看的 AV 错误哦。
运行我们的程序,点下按钮,你将看到输出信息“1978”。
如果你看不到正确的信息,请仔细查看你的代码是否和文中一致。你

也可以直接向我索要源代码。

在VC6中调用
稍微复杂点。先把GUID翻译过来:
//{CE38847E-A386-4753-89F1-34BE80042107};
static const CLSID CLSID_MyCOM = {0xCE38847E,0xA386,0x4753,
{0x89,0xF1,0x34,0xBE,0x80,0x04,0x21,0x07}};
//{D1C4A022-7F6F-42F0-A9B0-4A91703EB124}
static const IID IID_MyCOM = {0xD1C4A022,0x7F6F,0x42F0,
{0xA9,0xB0,0x4A,0x91,0x70,0x3E,0xB1,0x24}};
然后在声明一次接口的定义:
struct IMyCOMTest : public IUnknown
{
virtual LONG __stdcall msg();
};
IMyCOMTest* pLink;
然后放个按钮上去,并在相关事件里写代码:
void CMyvcView::OnButton6()
{
pLink = NULL;
int a =0;
CoInitialize(NULL);
a = CoCreateInstance(CLSID_MyCOM, NULL,
CLSCTX_INPROC_SERVER,IID_MyCOM, (void**)&pLink);
if (a==S_OK){
LONG a= pLink->msg();
};
}
注意,一定要记住调用 CoInitialize(NULL); 这个函数,否则COM无

法使用的。
编译运行,你应该能看到 a 是等于1978 的。
总结
到目前为止,我们成功的编写了一个最简单的COM组件,并且在DELPHI

和VC中成功调用。这都说明我们的工作是成功的。同时我们也看到,

实现一个COM,并不难。
关于进程外的COM以及DCOM,前者是基于LPC 本地过程调用,后者是基

于RPC远程过程调用。除了协议不同外,其他的都一样。大家有兴趣,

可以以后继续讨论。
关于COM的线程模式,我曾经以为,是COM向导中自动会产生对应的线

程代码,来实现多线程的。但是我后来又认为,根本没有这回事,COM

只是做了个标记,告诉操作系统他的线程模型,至于如何产生线程,

则是操作系统做的。有关这方面的讨论,还需要进一步研究。
一个小尾巴
我们知道,在DELPHI里,有一个Import Type Library 的功能。可以

把一个COM组件导到DELPHI中直接使用。但是,如果我们试图把我们刚

才写的那个组件,也ADD进去的时候,DELPHI会提示:
加载类型库/DLL时出错。
这是怎么回事呢? 原来,这是MS/BORLAND的一个小花招。我们看看

VCL的代码就知道了,在DELPHI的向导为你创建一个COM时,它偷偷地

加了一个IprovideClassInfo 的接口进去,该接口使用ItypeInfo 接

口,主要用于向外提供COM的类型信息的。大家仔细跟下

TtypedComObject 这个类,就会发现这个奥秘了。在前例中,我们没

有实现这个接口,那么当然无法被DELPHI加载了。关于如何实现这个

接口,已经超出了本文的范围,所以不于讨论。
有兴趣的朋友,可以继续关注 “COM实现过程(2)”,主要讲述如何

实现类型库的。


2002/6/27
版权所有
转载时请包括作者姓名

 

 

 

不好意思,前面写错了,如果对方给你的是com,那得看是那种类型的,如果是进程内的(dll/ocx)就用菜单中导入的功能,如果是进程外的,一

运行它自己在系统中就注册了。再有
对于进程内的
uses  //你的com的接口导入库Delphi自动可以生成
var
  TMyTest:coYourInterface
begin
  TMyTest:=coYourInterface.Create;
  TMyTest.//提供的方法
end;

进程外的
var
  TMyTest:YourInterface
begin
  TMyTest:=CreateComObject(com类的唯一标识符)
  TMyTest.//提供的方法
end

 

转载(我不知道出处):
第1章 在Delphi中使用接口:
1.1 定义接口:
目的:什么是接口,以及和抽象类的关联以及不同点。
抽象类(备注理解接口最简单的方法)
永远不能创建一个抽象类的实例;
本身不能实现功能,依靠派生类实现;
接口
被申明为interface类型。接口名从字母I开始。类类型名从T开始。
所有的接口从IUnknown继承;
不能创建接口实例;
不能在接口中指定范围指示。所有的方法都是公有型(public),不能在接口中申明包括范围指示;
不能申明变量;接口只能决定提供什么样的功能,对于如何完成功能没有限制。
接口中申明的所有函数和过程,概念上讲都是虚(virtual)抽象函数和过程。申明时不能带virtual;
接口是不变的;
1.2申明一个接口
目的:如何声明一个接口

GUID(Globally Unique Identifier)全球唯一标示符:CoCreateGuid产生(API)
1.3 实现接口
目的:如何实现接口

实现IUnknown: QueryInterface、 _AddRef、 _Release
使用TInterfaceObject来自动实现Iunknown,否则的话自己要实现上面的方法。
创建、使用及销毁接口: create;指向接口的指针不访问任何信息;自动释放、强迫销毁一个接口将变量置为nil
注:delphi自动创建和销毁接口。
获取单个接口的指针:
直接分配:类与他们实现的接口类型兼容的
GetInterface(const IID: TGUID; out obj):判断对象是否支持一个接口
as操作符: 对象支持特定的接口(对象不支持接口就错的话,可以拦截错误);
as自动调用计数功能;
 
1.4 高级多级接口问题
目的:在一个类中实现多个接口

在一个类中实现多个接口
TXY = class(TInterfacedObject, IXX, IYY): 类TXY 实现了IXX和IYY接口的所有方法。
多个接口不是多重继承:TXY有且只有一个基类TInterfacedObject;
方法分辨字句:当接口方法在类中实现时,方法分辨子句可使用改变他的名称
TXY = class(TInterfacedObject, IXX, IYY)
procedure IXX.pxy = pxy1
procedure IYY.pxy = pxy2
 
接口授权:一个接口的实现授权给另一个类:一个类包含针对另一个类的指针。
内部类: 实现一个或多个接口的功能性;
外部类: 简单的将这些方法传递给内部类,而不是重新实现接口;
 
接口属性:可以定义只读、只写、或者读写属性;
但是所有访问都必须通过访问函数,因为接口不能定义存储。 
1.5 小结
目的:如何在delphi应用程序中内部使用接口,了解delphi语言要素的接口。

申明一个接口; 
在类中实现接口; 
实现IUnknown所需要的功能; 
自动对象析构的处理; 
在类中实现多个接口; 
将一个接口的实现授权给一个内部对象; 
定义并实现接口属性

第二章 接口与COM
2.1 GUIDs 和 COM
目的:
CLSID: Class ID是GUID一个具体的类型的名称,注册表 HKEY_CLASSES_ROOT/CLSID
每个接口CLSID或GUID都代表一个COM接口的实现 
COM对象: TCOMObject继承(TInterfacedObject不提供实现COM对象的必要功能)
Hresult: 特殊类型的返回值,意味着函数调用成功还是失败。
OleCheck: 检查函数调用可能产生的错误;当调用返回HResult的COM函数时应使用该函数;
 
类厂(Class Factory):
COM对象不是由程序直接例示的;
COM使用类厂来创建对象;
类厂是一个对象,目的就是创建其他对象;
每一个COM都有一个相关的类厂,负责创建在服务器中实现的COM对象;
类厂把COM从实际构造一个对象的过程中分离出来,COM没有对象构造过程
类厂支持IClassFactory接口: IClassFactory只定义2个函数CreateInstance和LockServer
CreateInstance函数: 负责创建类厂涉及的COM对象的实例的函数;
LockServer: 保持服务器在内存中,一般不要调用他;
类厂中的双重类:
2.2 进程内的COM服务器(In-Process COM Server)
目的:理解进程内的COM服务器

共性:有一个InprocServer32的子键、所有进程内服务器都输出以下四个标准函数;
DllRegisterServer: 2种方式自动调用
IDE的Register ActiveX Server菜单
Windows的命令行应用程序RegSvr32.exe(或Boland应用程序TRegSvr)
DllUnregisterServer: 是DllRegisterServer的逆进程,移走放在注册表中的条目;
DllGetClassObject:
负责提供给COM一个类厂,该类厂用语创建一个COM对象;
每个COM服务器将实现它输出的每个COM对象的类厂;
DllCanUnloadNow: 返回S_True,CO在内存中移走COM服务器;如果返回S_False
 
线程支持(Threading Support): 只适用于进程内服务器;被保存在注册表中;线程模型如下
Single-Thread Apartment(STA): 实际上根本没有线程支持,所有对COM服务器的访问由Windows顺序执行,不必考虑同步存取的问题。
Mutli-Threaded Apartment(MTA): 允许同时有多个线程存取COM对象,必须控制不同线程同步存取的程序代码;
Both Apartment: 可以执行在MTS或STA中;
自由的
 
注册服务器(registering the Server): 所有的COM服务器都需要Windows注册表来工作。 
定制构造函数(Custom constructors): delphi中COM对象的基类包括一系列的非虚构造函数。
只需重载Initialize方法;
不要试图重载一个COM对象的构造函数;
 
创建一个进程内COM对象
function CreateComObject(const ClassID: TGUID): IUnknown;
CoCreateInstance内部创建负责创建COM对象类厂的实例,然后使用类厂来创建对象,创建完后COM对象后,类厂被销毁;
 
虚方法表(Virtual Method Tables):
接口实现为独立的方法表,该表实现在内存中紧靠VMT的地方;
不同的接口占据不同的内存部分,不能简单的把一个接口值赋给另外一个接口;
通常使用as操作符从一个接口转换为另外一个接口;
2.3 进程外COM服务器(Out-Of-Process COM Server)
目的:理解进程外COM服务器

进程外服务器是在exe中实现; 
实例化(Instancing): 创建多少个客户需要的实例;可以支持三个实例化方法中的一个;
Single Instace(单实例):
只容许一个COM对象的实例
每个需要COM对象实例的应用程序将产生COM服务器的单独拷贝;
Multiple Instance(多实例):
指COM Server可以创建一个COM对象的多个拷贝;
客户请求COM对象的一个实例时,由当前运行的服务器创建COM对象的一个实例;
Internal Only(内部实例):用于不被客户应用程序使用的COM对象;
 
调度数据(Marshaling Data):
一个可执行的程序不能直接访问另一个可执行程序的地址空间;
Windows通过调度(Marshaling)进程在调用应用程序和进程外COM服务器之间移动数据;
自动化兼容 SmallInt、Integer、Single、Double、currency、TDateTime、WideString、Idispatch、Scode、WordBool、Olevariant、

IUnknown、ShortInt、Byte;
记录和数组不能自动调度;
2.4 Variant 数组
目的:如何使用Variant 数组;

Variant:
一种可以拥有各种数据类型;
也可以告诉目前存储的数据是什么类型(通过使用VarType函数);
可以给相同的Variant分配不同的数据类型,只要Variant包含数字值就可以执行算法;
 
variant数组只不过是variant型的数组,不必包含同类型的 数据; 
variant数组的创建方法:
function VarArrayCreate(const Bounds: array of Integer; VarType: integer): variant;
Bounds: 告诉数组的上下界;
VarType: 决定了数组的中存储什么类型的数据。

例如:创建数组的数组, 可以模仿任何类型的数据结构类型:
VarArrayX := VarArrayCreate([1,10], varVariant);
数组的单个元素可以装载一个数组: VarArrayX[1] := VarArrayCreate([1,5], varVariant);
function VarArrayOf(const Values: array of Variant): Variant;
运行时用于创建一维数组;
可以创建全异的数值数组;
例如: MyArray := VarArrayOf(['李维', 30, '60', 60.369, 'China']);
 
使用Variant数组: 与使用标准Delphi数组类似;
VarArrayLowBound、VarArrayHighBound(与数组的low、high类似)计算边界;
VarArrayDimCount:计算数组的维数;
2.5 小结
目的:接口GUID及进程COM服务器的基本知识了解;

Guid是什么;以及为什么Guid对COM是如此重要;如何创建Guid以供自己使用; 
进程内COM服务器,创建及如何使用; 
Variant数组; 是什么、及如何使用; 
进程外COM服务器的介绍、调度及Windows可自动调度的类型; 
只有能理解虚方法表(VMT)的面向对象的语言才能访问只有虚方法表的COM服务器

第三章 类型库

3.1 定义类型库
目的: 类型库是什么,和它的作用是什么。

使用类型库的好处:
编写自动化控制时早期连接(Early Binding);
许多编译器可以从一种类型库中自动生成针对特定编程语言的代码;
实用程序可以用来读取并显示有关包含类型库的COM服务器的信息;
在COM客户和服务器之间自动参数调度;
 
类型库对某些COM服务器是必须的,例如:自动化服务器和ActiveX控件; 
TTypedComObject: Delphi由该类及其派生类提供了对类型库的支持;
Delphi自动创建XXXX_TLB.pas文件;
COM由TTypedComObject派生,而不是TComObjectFactory.Create;
初始时,delphi调用了TTypedComObjectFactory.create、而不是TComObjectFactory.Create;
3.2 使用delphi来创建类型库
目的: 如何使用Delphi来创建类型库的基本知识;

可以使用IDL(Interface Definition LAnguage 接口定义语言)编码类型库; 
类型库编辑器:
工具条:可以添加接口、方法、以及属性到COM服务器中;
注:工具条上可以通过点击鼠标右键弹出的菜单中选择Text Labels命令打开工具条的标题;
Interface(接口): 自动为每一个新建的接口产生一个GUId;
Dispinterface(派遣接口): 与接口类似,但是使用不同的派遣机制调用服务器中的方法;
Coclass(): 是一个术语,被指定给实现接口的COM对象;
Enumer

 

原创粉丝点击