追本朔源说COM (0分)

  • 主题发起人 主题发起人 晶晶
  • 开始时间 开始时间

晶晶

Unregistered / Unconfirmed
GUEST, unregistred user!
现在流行写心得(中国的程序员就是要多点这种行为,好的东西大家共享嘛)!我也写了一篇
现在节选其中一部分,如果大家发现什么不对的地方或者有更好的见解记得告诉我啊!
如果要看全文,大家可以访问:http://202.101.234.106/bbs/list.asp?boardid=1里面有这篇
文章的全部!
追本朔源说COM-3
简单的利用开发向导,写几行简单的代码就完成一个显示时间的com服务器,是不是很简单,如果
你真的这么认为那你就错了。对!虽然你会用delphi开发com了,但如果你想理解它或者以后开发
复杂的com和深入dcom机制不理解它的运行机制是不可能的!现在我们先就调用端来分析一下它是
怎么工作的!
我们调用是用这样的形式:
map:=createcomobject(clsid_tmyclass) as imyinterface;
其实这里完成的是两个操作:
完整的应该是这样的:
map1:=createcomobject(clsid_tmyclass);//map1是一个iunknown类型的接口
map1.queryinteface(iid_shelllinka,map);//通过map1查找iid_shelllinka接口
仅靠系统提供一个createcomobject我们就可以使用一个com服务器了,这个函数
肯定做了什么?
在delphi的源代码中createcomobject是这样定义的:
function createcomobject(const classid: tguid): iunknown;
begin
olecheck(cocreateinstance(classid, nil, clsctx_inproc_server or
clsctx_local_server, iunknown, result));
end;
呵呵,原来是函数的包装啊!
这里就要介绍一下cocreateinstance函数。
function cocreateinstance;
external ole32 name 'cocreateinstance';
它是系统(ole32.dll)提供的一个api,那么它是做什么的呢,具体做了什么工作?
这个函数真正要我们提供的参数就是一个classid,其他的可以使用上面的默认值。
它会从注册表中找该com类的classid(在hkey_classes_root/clsid键中),找到相应
的键后系统会读取其中inprocserver32的数据,找到服务器的地址后用loadlibrary函
数将服务器dll载入宿主进程,再利用dll中的dllgetclassobject函数(这是每一个dll
形式com服务器必须提供的函数),获取了iclassfactory接口指针后利用这个接口中
createinstance方法创建我们需要的com对象的实例(这个时候对象才出现了)!好了,
现在知道他们之间的调用过程吗。小节一下:
createcomobject-->cocreateinstance-->注册表clsid-->loadlibrary
-->dllgetclassobject(我们在后面分析中将从这里开始)
-->iclassfactory.createinstance
好了,前面的分析完成了,由于它大部分功能都是由系统完成的,没有多少研究的必要,
你只要会用,知道怎么用和它是做什么的就可以了。(也没有条件分析下去,我可没有
这个api的源码,你只有去问微软了,不知道它会不会给你),那么下面我们从
dllgetclassobject函数开始分析,它是编程语言提供的(系统要求的,工具提供的),我
们可以分析其中的代码,先看看dll工程的代码:
library project3;
uses
comserv,
unit3 in 'unit3.pas',
project3_tlb in 'project3_tlb.pas',
unit7 in 'unit7.pas' {form1};
exports
dllgetclassobject,
dllcanunloadnow,
dllregisterserver,
dllunregisterserver;
{$r *.tlb}
{$r *.res}
begin
end.
仅仅是导出comserv库中的四个函数,其中dllgetclassobject我们上面说过,也是最重要
的,先分析它:
function dllgetclassobject(const clsid, iid: tguid;
var obj): hresult;
var
factory: tcomobjectfactory;//注意:这里都是由类工厂提供的。
begin
factory := comclassmanager.getfactoryfromclassid(clsid);//1
if factory <> nil then
if factory.getinterface(iid, obj) then
//2
{由类工厂去查找相应的iid,成功就返回s_ok,并且将接口指针返回给obj}
result := s_ok
else
result := e_nointerface{没有找到就返回e_nointerface}
else
begin
//如果没有相应类工厂
pointer(obj) := nil;//将接口指针置nil
result := class_e_classnotavailable;
end;
end;
现在讲讲这个函数,这个函数通过clsid, iid参数来找他们在dll中的接口的位置,然后
通过参数obj来返回指针,而整个函数的返回值(hresult类型)只是用来告诉系统接口是
否存在。这里的obj是什么呢?就是我们前面定义的map啊!是不是对于这种返回形式还不
适应,不要紧,看多了就知道了,这种返回形式很有效的!不过要注意到它是var声明形
式的哟!!这个函数要往下分析的部分(或者要重点注意的部分)已经用数字标明了!那
么我们下一节就先分析1吧。
好了,先讲到这里,下节见。
晶晶
 
COM就是利用虚拟函数表的一种技术
 
下面是我节选的最后一部分:
原文全部在:http://202.101.234.106/bbs/list.asp?boardid=1中
追本朔源说COM!(Delphi6)-7
我们现在了解了com的生存和调用机制,大家是不是想试试身手啊,那么我提一个要求看大
家是否可以自己独立完成,如果不行的话再回来看我这个例子!
要求:
大家都知道delphi中的com都是通过相应的类工厂产生的一个二级封装对象,那么我们有没
有办法做一个一级的com对象呢,就是直接将自己的com对象暴露出来?
好了,现在来说说我的考虑:
前面讲过createcomobject来创建com对象后台其实是通过查找注册表载入相应的dll来完成
的,而dll中的dllgetclassobject在delphi中的实现中是通过查找相应的类工厂来找我们
的com对象类,那么如果我们重新更改所有的代码将是很困难的!!(除非你改写所有函数
并重新声明一个由tobject派生来的类,同时实现自己的和系统要求的iunknown接口,因为
在delphi中实现这个接口的类是tcomobject,但它没有提供一个好的构造机制,我们不好直
接取得它的实例地址!而所有的delphi中的com祖先类都是依靠一个函数:
createformfactory来构建对象!你却不能这样做,因为你不想要factory嘛,哪里来的
createformfactory方法呢?那么你就可以在你自己声明的com类的create中构造自己的类,
并使用tobject中的getinterfaceentry和getinterfacetable等等函数!基本上等于重新来,
原来我做过一个这样的程序,这里就不列出来了,因为实在太多代码了,源源本本自己制作
的com类,因为连dllgetclassobject等四个函数都要改写,将自己的com信息写入注册表。
麻烦波?!)那换个思维方式考虑,我们可以自己建立一个com对象,让它同时是自己的工厂
不就ok了,问题就迎刃而解!由于这个对象同时是自己的类工厂,那么就是一个一级的对象
了。好了,有了一个好的想法,我们现在就来实现它吧!
一个类工厂最大的特点就是具有iclassfactory接口,那我们就可以定义成一个这样的类:
type
tmyclasses=class of tcomobject;
imyclass = interface(iunknown)//我们自己的接口
['{20073cda-c810-4e6e-9b00-49b9349b3c99}']
function hehe: hresult;
stdcall;
end;
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;

tmyclass = class(tcomobjectfactory,iclassfactory,imyclass){看!我们这里导出
了两个接口,一个是类工厂需要的iclassfactory接口,一个是为我们服务的imyclass
接口!}
protected
function hehe: hresult;
stdcall;
function iclassfactory.createinstance=mycreate;{注意这里,由于我们是直接
继承于tcomobjectfactory类,它里面有默认的iclassfactory.createinstance实现,
我们要重新写一个实现方式,所以要将iclassfactory接口的createinstance方法指向
我们给它定义的一个相同参数的方法,偷梁换柱,呵呵}
public
function mycreate(const unkouter: iunknown;
const iid: tiid;
out obj): hresult;virtual;stdcall;{这就是我们给iclassfactory接口
createinstance方法的一个替换方法,注意应该声明为virtual方法,但不写这个关键
字好像delphi编译器也可以正确调用,看来编译器里面为我们做了很多事情啊!!虽然
可以不写,但我还是建议大家加上它!}
end;
好了,一个框架搞好了,现在先写我们的接口(imyclass)的唯一一个方法:
function tmyclass.hehe: hresult;
begin
messagebox(0,pchar('hehe'),pchar('success'),mb_ok);
result:=s_ok;
end;
仅仅是显示一个对话框并返回s_ok就可以了,我们仅仅是试验我们的想法和锻炼我们的能力
嘛。
现在的问题是我们该如何写那个我们改了的接口中的建立com对象的方法呢?
我们可以先看看系统提供的实现代码,它做了什么?前面的文章已经说得很清楚,这里重申
一下:它所做的就是建立我们的com类的对象并根据我们的对象查找相应的接口返回就可以
了。我们这里由于对象是本身,那就更好办了!因为它不要再去建立对象了,直接用自己就
可以了,下面是它的代码:
function tmyclass.mycreate(const unkouter: iunknown;
const iid: tiid;
out obj): hresult;
var mycom:tmyclass;//这里声明一个对象,就是我们对象的类型了
begin
if @obj = nil then
begin
result := e_pointer;
exit;
end;
pointer(obj) := nil;
if (unkouter <> nil) and not (isequaliid(iid, iunknown)) then
begin
result := class_e_noaggregation;
exit;
end;
try
mycom:=self;{这里是关键,将这个对象的指针变量就设置为自己。}
except
result := e_unexpected;
exit;
end;
result :=mycom.queryinterface(iid,obj);
if mycom.queryinterface(iid,obj)<>s_ok then
mycom.free;{根据我们的对象(自己
嘛)来调用queryinterface函数查找iid并且由obj返回之}
end;
代码简单吧!?如果你真的理解前面讲的内容,那么这些代码你一看就懂,否则的话我是怎
么改的你肯定不知道的!
再在单元中生成一个guid来表示类:
const class_myclass: tguid = '{021038df-233c-43f1-83b5-bf8cacc293a0}'
在初始化的地方写上:tmyclass.create({注意:这里使用的不是别的类工厂,而是我们定义
的对象本身tmyclass哟,由它自己创建}
comserver,
tmyclasses(tmyclass),{由于自己改写的函数中并没有用到这个参数,这里我们随便写一个
,只要记住类型就可以了,为了便于直接使用原来的函数,不然我们还得再写一个,麻烦!
这里我们复习一下原来讲的类引用的用法,所以故意写成这样的!}
class_myclass,
'mytestclass',
'this is my first comobject'
cimultiinstance,
tmapartment);
好了,一切都做好了!
注册,准备使用它,验证我们的想法是否正确!
然后再创建一个窗口来测试一下吧!
建立一个窗口并在上面放置一个按钮,按下按钮后调用com对象的hehe方法,看看会不会有
对话框弹出。下面是按钮的代码:
在类型声明中增加
const class_myclass: tguid = '{021038df-233c-43f1-83b5-bf8cacc293a0}';
imyclass = interface(iunknown)
['{20073cda-c810-4e6e-9b00-49b9349b3c99}']
function hehe: hresult;
stdcall;
end;
在窗口类中增加一个数据:p:imyclass;
按钮代码:
procedure tform1.button1click(sender: tobject);
var s:hresult;
begin
p:=createcomobject(class_myclass)as imyclass;
s:=p.hehe;//调用我们对象的方法;
end;
如果一切正常(我们的想法正确,我们的做法正确)的话,按下按钮后应该弹出一个对话框
的!我的程序截图如下:
此主题相关图片如下:
不知道你做得正确吗?如果不正确,那么肯定有什么地方没有理解或者疏忽了,再看看我的代
码吧!
好了,这个专题讲完了,本来还想讲一个com应用的例子,但现在这样的例子太多了,我就懒
得写那么多代码了,大家自己找找看吧,这些代码虽然简单,但里面的东西还是有很多东西要
自己理解,如果全部写下来,仔细分析所有的代码及其后面的机制,我看7篇文章是不够的,最
少得30篇,呵呵!
如果你有什么问题可以找我,我们共同讨论:tufeiping@vip.sina.com
如果有读者需要引用这篇文章,我希望读者能够保持文章的整体性(这里我分了7篇分别讲完)
,并保持作者的署名。
本专题的所有程序代码均为本人自己制作,试验并保证其有效性!
------全文完------
晶晶(奥运北京)
 
呵呵,不错不错,受益匪浅
 
今天上来把所有该结束的帖子结束!
 
后退
顶部