首页
论坛
专栏
课程

在ASM里建立一个COM objects

2005-7-12 21:27 8960

在ASM里建立一个COM objects

2005-7-12 21:27
8960
附件:com objects in asm.rar
我翻译得绝对是差, 错漏百出(看不懂多包涵).初学win32asm编程,我找有关在asm中运用流媒体资料时找到的,为了看懂,慢慢看,猜(没办法,英语差),可惜现在还是没有这方面的资料,而且这篇还没领悟透,发上来有兴趣的看一下.对了,谁有在asm中运用wmv和DirectShow的资料给份我.谢谢

在ASM里建立一个COM objects
[译者]:layper
[作者]:Ernest Murphy ernie

这篇文章的例子代码为是在: masm32\COM\examples\MyCom

用于的快速编辑程序的DLL在:masm32\COM/COM/BIN/BLDDLL.bat

摘要:

---------------------------------------------------------------------------------------------------------------------

COM(零部件目标模型)基于一个非具体的实施标准(平台和语言都没被指定), 但是现实的算机世界实际给这个标准添加限制条件。  

这里, 加工过程中服务器给WinTel 站台创造一简单而职能完全COM objects。 它用Visual Basic环境测试保证它符合标准。

---------------------------------------------------------------------------------------------------------------------在这里讨论的内容在MASM32包集合中。用VB6 写的Visual Basic客户机程序, 仍然设置它的形式版本为5 。 如果你用VB4 编辑VB5 版本, 它可能在早期的版本里工作,但这未测试过。 它也应该在任何VBA 里工作,但是这也没被测试过。

在这里我没有解释零部件目标模型的基础也非COM 的基本形式。 我假设您通晓这两方面和象 vtable, vtable 指针这样细节。如果您不是那么熟悉, 我建议您查找这里早先文章, 或是查找最佳的源解释Dale Rogerson 的"Inside COM"(参见参考书目) 。

因为COM 对象必须运行在真正的计算机而不是在某一课本中, 他们必须遵循仅限于那台计算机和操作系统的一定实施标准。加工过程中服务器的WinTel, 那里存在一个明确的标准装载一块代码和当不再需要卸载它: 低级的动态连接库。

从一个看法, 所有在COM 服务器程序包括是以一套5限定输出的a.DLL 。 这些是:

DllMain: 这是在任何dll里的第一个常项。当库装载时被呼叫。 它在加载过程应该检查客户(见app)服务, 如果不是检查失败。 (COM选择其它例子, 但是这里app不是。)

DllRegisterServer: 注册每个安装在系统COM对象的数据。 这个惯例自已在注册处注册组成部分。 这就是regsvr32.exe怎样能记录一个组成部分,regsvr32呼叫输出和显示返回值。

DllUnregisterServer: 当不再需要, 组成部分应该能unregister(卸载register)。  regsvr32.exe将呼叫它输出清除注册。

DllCanUnloadNow: 全局变量,在跟踪记录所有建立的对象和设置服务器为锁定(在这里解释为IClassFactory.LockServer) 。 客户app将周期性地呼叫输出检查服务器,如果服务器不再需要就卸载它。

DllGetClassObject: 最后不可思议的COM输出。 这输出带3个参数, 建立组成部分GUID, 建立GUID的接口, 并且建立指针对象。 如果这组成部分或接口不被支持, 这个惯例失败。

前4输出是简单的。DllGetClassObject 是新接触到的, 它需要更进一步的讨论。

现在, 您应该注意了的一件事是,如果这不是间接寻址COM什么也没有。间接寻址加强法则。实践中, 从DllGetClassObject 返回的对象不是我们寻找的对象: 这是"类工厂"对象。 类工厂对象是能举例(创造)其它类。这种间接方式最先指定允许对象建立的细节。如果它简单而直接返回对象的一个指针,然对象已经存在, 因此我们不能在它的建造者里确定并且控制任何参数。
DllGetClassObject 返回一个IClassFactory接口。 IClassFactory从IUnknown(当然,每个接口做)继承,这两个成员功能有:

HRESULT CreateInstance(

IUnknown * pUnkOuter, //什么时候指针分别传给A的外部对象

//REFIID riid总计,

//涉及接口标识符

oid ** ppvObject); 的输出变量//收到的输出变量的地址

//在riid申请接口指针

HRESULT LockServer(BOOL fLock);

//Increments 增值或者减量锁数

LockServer 牧举保持类工厂(如果你必须拷贝许多对象时有用) 。CreateInstance 这里是主要设备, 它习惯于建立对象的"工作"接口。CreateInstance不包括种类参考ID,它是仅仅知道怎样建立每种类的一种特别类工厂。当我们建立这种类接口时,种类ID被指定。 因而, 类工厂只会创造这成员类的接口。 目的是创建被要求的对象分类是唯一的。

如果种类需要一些特别的开始,这间接方式是有用的。 IClassFactory 是必须集中处理(总论我将没有增加什么, 在这里零部件设法对付不支持的难题) 。当然, 对于IClassFactory2,有一个定义检查组成部分申请。

为了实际上得到你的COM对象,客户程序通过产生CoCreateInstance API 功能呼叫DllGetClassObject。 这API为您处理类工厂, 并且返回被要求的接口指针 如果您只需要一个单纯功能对象, 这就是使用的功能。CoGetClassObject API 将返回给IClassFactory的指针是您的类需要进一步建立的参量。在内部, CoCreateInstance呼叫 CoGetClassObject 和通过IClassFactory举例类, 因此您总需要它定义类工厂对象创作接口。

类, 对象, 您和这些

---------------------------------------------------------------------------------------------------------------------我将偏离主题讲述一些关于怎样C++ 处理对象内部实施细节。 一位熟练C++ 程序员可能是从未充分地了解这些, 因为这是编译器为你完全处理的复杂性的水平。但是, COM 的设计师要充分充分利用概念,我们需要了解这些内容。

在C++里的++ 在语言中增加的新基本概念是类和对象。 并且对象是类的实例。 这意味我们在机器水平上探索什么。当我们在asm里写一个常规程序时, 我们依靠编译器为我们创造代码和数据段。 内存的一个区域是我们执行的代码, 另一拥有我们需要的数据。 什么++ 在C++ 为您做的是在运行时间动态地再分配数据记忆, 给类的每个实例, 代码的各小段是拥有数据段的。用一个非常正式的方式,这数据段是类的实例。种类的仅限于每个实例(或者复制)的数据储存在这个动态的数据区。 。

或许你已经听说C++ 通过额外用功能把参数隐藏拜访一个对象, 这价值。 命名这个概念的人不仅了解什么是必需, 而且有一种幽默感。当你为对象写着低级代码(在C++某编译器通常为您做的东西), 你第一问题将是 "对象是我代码?"

这是对象, 你总以这个对象运作。

这仅仅是类的实例数据存储区的一个指针。当一个对象分类功能被呼叫时,这被悄悄转移。 当私有数据对象被获取, 分类符号区域使用引用数据适合这个实例哪里。

为什么这很重要? 很简单地:一个COM接口的指针是与这指针是一样的。 我的语法规则现在疯狂提示我, 它不是正确语法如何运作。

在使用中, COM仅仅是一种接口的说明。没说明他们实际上在代码内实现怎样。实际上, 没定义写这样的细节因此实现这些接口的任何高级语言可能是一台COM服务器。

我把我的"类"数据区("对象")定义为如此:

; 宣布ClassFactory 对象结构

ClassFactoryObject STRUCT

lpVtbl DWORD 0 ; 作用平台指针

nRefCount DWORD 0; 对象和参考数

ClassFactoryObject ENDS

; 宣布MyCom 对象结构

MyComObject STRUCT

lpVtbl DWORD 0 ; 作用平台指针

nRefCount DWORD 0; 参考数

nValue DWORD 0; 接口专用数据

MyComObject ENDS

第一点是我在大范围定义这个结构。 对此COM是约定强制服从唯一的元素, 它包含DWORD指针对vtable 作用。 我也使用它为各个接口举行私有数据, 那是参考数和价值。nValue作为MyCom 接口运作与的价值, 其次将看见。 动态存储器为这些结构被以API作用CoTaskMemAlloc和释放CoTaskMemFree分配。 这些由ole32.dll输出, 这有很多其他有用的输出, 譬如检查GUID等同性, 并且从字符串中转变GUIDs。

MyCom, 一个简单的接口

对说明COM 接口工作, 我们将创造一个简单的接口称IMyCom (在COM全部接口应该适应接口有"I"前缀) 。这个连接, 象所有COM 接口, 从IUnknown 获得。它的第1个3个功能是QueryInterface,AddRef的这简单作用,并且释放。

为了增加我们的习惯接口成员, 我们增加下三名作用成员(用C设计原型):

HRESULT SetValue(long * pVal);

HRESULT GetValue(long newVal);

HRESULT RaiseValue(long newVal);

这些功能允许我们检查我们的接口功能。 SetValue和GetValue允许我们设置和读我们接口的数据成员。RaiseValue是给这数据添加价值的一个成员功能。 因而, 我们能保证自己从VB真正地访问一个完全功能的对象 。

用内存的这些结构看起来象这样:

分配结构,客户只拿这样的一个指针(ppv)。(一般ppv指针的名字来自C++ 定义"(无效)的指针的指针")" 当我们创造实例时,"对象"数据一块被分配和动态地被创建。 vtable 和服务器作用是静态的, 他们在编辑时间确定。

要注意到的一点vtable是认为给功能的指针, 他们自己是不起作用。 因而, 我们能通过改变完全"无视"继承的日常vtable指向功能。 我必须用引号说"无视",因为分类定义只是在这ASM里实施的一个精神概念,我不写类和不在其它类继承他们。 但概念是相同的。

在要遵循的例子里,有作用朝这个方向执行"忽略" 。IClassFactory 和IMyCom都从IUnknown 继承QueryInterface。 但是当他们支持不同的接口,他们需要不同的常规返回不同的结果。因而, 有二个QueryInterface 惯例(QueryInterfaceCF 和QueryInterfaceMC) 指向不同的vtables 。

简单说,至于AddRef和释放他们支持哪个接口也被定做。 这不是与AddRef一个问题, 释放不同功能MyCom必须知道怎样破坏MyCom对象。进一步提炼为未来将宣称所有对象因相似同样作用可能删除他们, 并且我们能为一切使用单个实施释放。

较少指出在从服务器内部告诉COM 连接: 完全没必要仔细审阅对象表得到一个成员指针作用: 在服务器里面这正是另一个功能,当代码确切知道在编译时间在哪里时,你能直接产生它。请认为它是一项先进的优化技术。这工作因为呼叫全部包含这些(对象指针) 作为参量, 并以此回答用"这个"对象回答"哪个"对象。

另外一个小问题, 每次客户访问我们的对象, 它通过内存从ppv 到pv 对vtable 得到产生作用的地址。如果你想要,你在目标生存期期间可以改变这个最后的指针使它展览不同的特性。有没什么在COM 合同禁止它, 虽然它是如同对自已修改代码。 我只提及它因为早先文字声称这不是真实的。

类型库

在 WinTel 平台, COM接口获得关于它存放在系统注册信息。这些接口可能被呼叫MIDL(微软接口定义语言)编译器编辑的"接口定义语言"(IDL)建立,命令行app 。 我不假设对您充分地理解IDL并能够在笔记里定义接口。 但是,因为MIDL工具只被MSVC装载,如果你有它,那么你就有VC,因此我使用Visual Studio 工具建立我原先的接口定义文件。

命名MyComApp,我开始一项ATL工程,并且插入一个新ATL对象并且选择简单的对象名称MyCom(我的app将使用相同的术语)。然后类向导(wizard)创造了空白 IMyCom 接口。 ATL 属性需要被设置单线, 习惯接口, 并且没有集成。 然后我由在类浏览器合适的点击IMyCom 接口建立了接口, 和使用增加属性插入SetValue, GetValue属性, 和 RaiseValue方式。 然后我保存并且关闭app把MyComApp.idl 文件复制到我的汇编程序文件夹。

这VC ATL接口定义文件的输出(.idl):

//MyCom.idl : MyCom.dll的IDL资源

//

//MIDL工具将处理这个文件

//生产类型信息库(MyCom.tlb)并且设置代码

import "oaidl.idl";

import "ocidl.idl";

[

object,

uuid(F8CE5E41-1135-11d4-A324-0040F6D487D9),

helpstring("IMyCom Interface"),

pointer_default(unique)

]

interface IMyCom : IUnknown

{

[propget, helpstring("property Value")]

HRESULT Value([out, retval] long *pVal);

[propput, helpstring("property Value")]

HRESULT Value([in] long newVal);

[helpstring("method Raise")]

HRESULT Raise(long Value);

};

[

uuid(F8CE5E42-1135-11d4-A324-0040F6D487D9),

version(1.0),

helpstring("MyComApp 1.0 Type Library")

]

library MyComLib

{

importlib("stdole32.tlb");

importlib("stdole2.tlb");

[

uuid(F8CE5E43-1135-11d4-A324-0040F6D487D9),

helpstring("MyCom Class")

]

coclass MyCom

{

[default] interface IMyCom;

};

};

这个文件可能为进一步的接口定义作为原型使用。 注意它包含3 GUIDs, 每一个接口、coclass, 和类型库。为新应用这些必须是能被改变和分明。

除了接口本身,这个定义文件应该看起来几乎不言而喻:

[propget, helpstring("property Value")] HRESULT Value([out, retval] long *pVal); [propput, helpstring("property Value")] HRESULT Value([in] long newVal); [helpstring("method Raise")] HRESULT Raise(long Value);

这接口同样被定义在MASM:

GetValue PROTO:DWORD,:DWORD

SetValue PROTO:DWORD,:DWORD

RaiseValue PROTO:DWORD,:DWORD

区别大... 但简单很原因。 为类型库写的接口可能一样全面, 并且针对象Visual Basic那样的客户, 并且VB尽量被设计握程序员的助手。为了保持对VB 用户接口简单操作,"特性"的概念被使用。可以"确定(set)"或者"得到(get)"特性值,因此这两个功能好像对VB 程序员(物体参考只移到等于操作者的另一边)是相同的。一种"方法"做一些改变或者执行与物体有关的一些行动。  

建立连接类型(type lib), 在命令行使用MIDL如下:

MIDL MyCom.idl

这主要产生您能忽略的几份输出文件,以及最重要MyCom.tlb, 我们的类型库。 这库应该被增加到资源提交给的dll

1 typelib MyCom.tlb

做它第一资源要素是重要的, 因为我们以后将使用 LoadTypeLib API功能提取这个库, 这个功能准备发现库在位置1 (除非告诉做其他) 。 如此简单, 我们保留它在位置1 。

记录组成部分

---------------------------------------------------------------------------------------------------------------------The DllRegisterServer 和DllUnregisterServer 为我们记录组成部分。dll(或ocx,真正好的加设计者扩展的dll) 。这些被做为登记入口:

HKEY_CLASSES_ROOT\CMyCom

(Default) "CMyCom simple client"

HKEY_CLASSES_ROOT\CMyCom\CLSID

(Default) "{A21A8C43-1266-11D4-A324-0040F6D487D9}"

HKEY_CLASSES_ROOT\CLSID\{A21A8C43-1266-11D4-A324-0040F6D487D9}

(Default) "CMyCom simple client"

HKEY_CLASSES_ROOT\CLSID\{A21A8C43-1266-11D4-A324-0040F6D487D9}\CMyCom

(Default) "CMyCom"

HKEY_CLASSES_ROOT\CLSID\{A21A8C43-1266-11D4-A324-0040F6D487D9}\InprocServer32

(Default) "C:\MASM32\MYCOM\MYCOM.DLL"

ThreadingModel "Single"

HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}

(Default) (value not set)

HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}\1.0

(Default) "MyCom 1.0 Type Library"

HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}\1.0\0

(Default) (value not set)

HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}\1.0\0\win32

(Default) " C:\masm32\COM\MyCom \MYCOM.DLL"

HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}\1.0\FLAGS

(Default) "O"

HKEY_CLASSES_ROOT\TypeLib\{A21A8C42-1266-11D4-A324-0040F6D487D9}\1.0\HELPDIR

(Default) "C:\masm32\COM\MyCom"

这里一关键价值是易变的, 那是服务器dll的路径和名字。 在我的系统我安置它在"C:\MASM32\COM\MYCOM\MYCOM.DLL "当我记录组成部分时,这被发现, 因为DllRegisterServer的另外一个功能是通过产生GetModuleFileName储存路径发现dll本身。

这是许多一个人台小服务器的信息。我们需要给实例知道我们服务器的全部身份验证{A21A8C43-1266 11D-4 A324-0040F-6D-487D-9 }以及CoCreateInstance的一个有效ID接口。我们不必知道它那些零部件在哪里,亦不设置它在特别目录里。 CoCreate API将通过登记设置追踪, 开始以CLSID 发现所有它需要知道建立的组成部分。一旦它有组成部分,如有必要它可能获悉装载类型库。

我们是幸运的,最后5 个登记入口是为我们通过RegisterTypeLib API做的。 在DllRegisterServer 我们呼叫一系列的登记功能设置前5个通道和价值, 然后产生RegisterTypeLib。DllUnregisterServer只间接通过这构造和删除它做的全部入口,然后产生UnRegisterTypeLib。当删除键时, 一定小心不要删除整个HKEY_CLASSES_ROOT\CLSID\tree, 否则你将在你系统完全搞乱你系统和uninstall部分隔activeX 零部件。

类型库被定义为"密集的一块(dense black blob)" 二进制数据。 微软公司显示的的内部结构唯一特性是前4个字节将是"MSFT"的ASCII 代码。为了获悉里面是什么,必须使用API方法。再次,这保持COM获得语言中立。

不为人知的执行

---------------------------------------------------------------------------------------------------------------------

MyCom 是非常简单的对象, 它impliments唯一有二个接口, IUnknown 和IMyCom 。因为这两个接口重叠, 返回的ppv指针不必派成两个接口中的任一个, 并且我们将满足非常简单的对象结构。如果您延伸到CoLib (部分库), 如果成倍增加您将看见一个非重复接口支持的,更复杂的对象结构被注册。

对象使用期由IUnknown 接口处理。 这些三个表面上简单的AddRef,Release, 和QueryInterface用法是相当强有力的, 而且被使用,因此每一个的功能性从未在另一个部分被复制。

这几次非重复功能或许促进为什么这样命名IUnknown。当DllGetClassObject产生时, 对象CLSID 和一个具体接口IID 是通过定义需要什么建立的。 想一下: 在我们建立它之前,我们实际上要求 DllGetClassObject在对象里执行QueryInterface. 那不是所发生的,我们不想要复制(即,QueryInterface两相同实施,一处是在QueryInterface它自己,一处是DllGetClassObject)的功能性。  如果不是别的,我们不想保持有相似代码的二个部分。

相反, 当DllGetClassObject产生时, 我们仅仅建立CLSID定义的对象。 实际上, 我们创造一个未知的对象。未知的是: 这个物体能支持我们需要的接口吗?

这个问题很容易回答。在未知的对象DllGetClassObject将产生QueryInterface。 如果它真地支持接口, 这参考将返回。 如果它不支持它, 对象被删除, 并且DllGetClassObject返回一条失败代码。

AddRef实现相当简单。因为我们有一个简单的对象结构, 并且"this"是这个结构基地址, 我们能直接地访问所有成员对象。

AddRef_MC proc this_:DWORD

mov eax, this_

inc (MyComObject ptr [eax]).nRefCount

mov eax, (MyComObject ptr [eax]).nRefCount

ret ; note we return the object count

AddRef_MC endp

AddRef 有点异常,因为它不返回HRESULT (失败代码), 返回对象数。在COM内返回值未定义,但是返回数字是常规的。

释放不只须decrement 对象数, 但当这计数为零时它必需删除对象, 和删除dll 的对象数(这样当对象计数为零dll也许被卸载) 。再次, 这种实施是琐细的:

Release_MC proc this_:DWORD

mov eax, this_

dec (MyComObject ptr [eax]).nRefCount

mov eax, (MyComObject ptr [eax]).nRefCount

.IF (eax == 0)

; 参考计数下降到零

; 对象没有被提到

; 因此我们删除它

invoke CoTaskMemFree, this_

dec MyCFObject.nRefCount

xor eax, eax ; 清除eax (count = 0)

.ENDIF

ret ; note we return the object count

Release_MC endp

MyCom也是要实施的一个琐细接口。 MyCom对象有一名额外成员'价值'特性被保持。

GetValue proc this_:DWORD, pval:DWORD

mov eax, this_

mov eax, (MyComObject ptr [eax]).nValue

mov edx, pval

mov [edx], eax

xor eax, eax ; 返回 S_OK

ret

GetValue endp

SetValue proc this_:DWORD, val:DWORD

mov eax, this_

mov edx, val

mov (MyComObject ptr [eax]).nValue, edx

xor eax, eax ; 返回S_OK

ret

SetValue endp

RaiseValue PROC this_:DWORD, val:DWORD

mov eax, this_

mov edx, val

add (MyComObject ptr [eax]).nValue, edx

xor eax, eax ; 返回S_OK

ret

RaiseValue ENDP

MyCom.dll, 服务器代码

建立COM 服务器时,使用快速编辑之下编写用的BLDDLL.BAT文件在"\masm32\COM\BIN" 中。 我建议您改变包括"Build DLL"选择在内编辑菜单设置。

这项工程要求建造它5个文件:

MyCom.asm 给项目主要汇编代码

MyCom.idl接口定义文件,必须被编辑到MyCom.tlb

MyCom.tlb 类型库,作为一种必要资源

rsrc.rc资源文件, 让类型库使用进入资源

MyCom.DEF标准DLL输出文件

一旦编写, 这个代码什么也没做, 直到您登记它。 最容易方式打开一个组件给dll的文件夹,并且运行:regsvr32 MyCom.dll. 或者,我提供bat文件.r.bat和 u.bat记录未登记, respectivly, MyCom零部件。

通过regsvr32 运转MyCom.dll将产生DllRegisterServer并且写登记我们的信息,因此我们能 ...

从Visual Basic 访问服务器

保证绘制了封闭区,因此邻点实际上没看见您拥有Visual.basic 。打开VB并且开始一项标准.Exe工程。在菜单里寻找工程 | 参考并点击它。 纸卷通过名单检查MyCom组件, 并且点击没问题。 这给VB 应用增加类ID, 并且VB 将为关于服务器的详细资料检查类型库。

以形式设计者,把textboxes Text1 和Text2 增加到Form1, 然后增加一个命令按钮Command1 。 改变提高命令标题。 现在在Form1 编码区增加以下内容:

Option Explicit

Private MC As New MyCom

Private Sub Command1_Click()

MC.Raise (Text2) Text1 = MC.Value

End Sub

Private Sub Form_Load()

Set MC = New MyCom

MC.Value = 100

Text1 = MC.Value

End Sub

现在你通过点击新加按钮能运转应用测试服务器。一定小心,有无失误检查看看是否你把有效的数目放进Text2。你看见的是运转一台汇编语言服务器的Visual Basic。

注意可供下载的抽样程序有些是更加复杂的,为测试建立两个服务器对象。这证明每个物体能取它自己的私人数据信息。这台服务器很多不全部是十分真实的,但这是从汇编到充分的COM功能起步。

[公告]安全服务和外包项目请将项目需求发到看雪企服平台:https://qifu.kanxue.com

最新回复 (2)
Lenus 3 2005-7-24 23:42
2
0
我来支持你
xzchina 1 2009-7-25 17:02
3
0
收下了.
游客
登录 | 注册 方可回帖
返回