COM技术内幕第十章笔记EXE中的服务器

“不同EXE中的组件和客户将在不同的进程中运行,这是因为每一个EXE都有其自己的进程空间。这样一来,客户和组件之间的交互就会跨越进程边界了。”

每个EXE都有自己的不同进程,DLL将被映射到链接它们的EXE文件的进程空间中。
因此DLL被称作进程中服务器,EXE则被称作进程外服务器。
某些情况下,EXE也被称作是本地服务器以同另一种进程外服务器“远程服务器”相区别。
远程服务器也就是运行在另外一台机器上的进程外服务器。

组件调用,其实是将接口指针返回给客户,如果客户和组件位于不同的进程、使用的不是同一地址空间,那么接口指针(地址空间内的逻辑地址)的物理地址将是不同的。这样一来组件调用即成空谈。

SO,解决进程间的通信是多么重要啊!

本地过程调用LPC

进程间通信有几种方法:动态数据交换(DDE)命名管道以及共享内存等。COM所用的方法则为本地过程调用(LPC),它是基于远程过程调用RPC的用于单机上的进程通信的专利技术。RPC是在分布式计算环境(DCE)RPC规范中定义的,使得不同机器上的进程可以使用各种网络传输技术进程通信。

LPC实际上是由操作系统实现的。操作系统当然知道每个进程的逻辑地址空间和对应的物理地址空间,他当然可以调用任何想调用的东东。(操作系统为什么是男的?)(笔者你忘记了吗,这是你自己写的嘛)

得到了EXE中的函数也就是某个地址以后,第二步就是要在不同进程间传递数据,将函数调用的参数传入。俺们使用一种被称为“调整”的方法。如果倆进程在同一单机上,淡然只需要将参数复制到另一进程的地址空间就OK了;如果倆进程在不同的机器上,就要考虑到二者在数据表示方面的不同,例如字节顺序啊之类的,必须要将参数转换成标准的格式。(虾米标准?谁定的标准?不是类似W3C的标准吧?啊)

为对组件进行调整,可以实现一个IMarshal的接口。在COM创建组件的过程中,它将查询组件IMarshal接口,然后调用IMarshal的成员函数,以在客户调用函数的前后调整或反调整有关参数。COM库中实现了一个可以供大多数接口使用的IMarshal的标准版本。(又是一个标准)
关于IMarshal的问题,Krarig。。。所著《Inside OLE》有详细讨论

代理/残根DLL

一直都在说的一件事是通过一个IUnknown或其他什么接口就可以调用组件,但到底怎么做的也不知道,就知道是通过LPC做的。LPC,当然,实际上是操作系统在做。因此,此时得知几乎所有Win32函数都会用到LPC也就不奇怪了。调用Win32函数,也就是操作系统调用一个DLL中的函数,也就是会通过LPC调用Windows中的实际代码。

COM使用的结构与此类似(嗷~什么?类似?难道前面说的不就是COM所使用的结构吗?细细回味ing),客户将同一个模仿组件的DLL进行通信,这个DLL可以为客户完成参数的调整和LPC调用,在COM中,这个DLL,当然这个DLL也是一个组件,这个DLL这个组件被称作是一个代理

以COM的专业说法,一个代理就是同另外一个组件行为相同的组件。代理必须是DLL的!(为什么捏?)自然是因为代理就是要完成参数的调整,如果不是DLL不能正确获取地址空间内的数据,那就是白搭。通过代理DLL,组件能对输出的参数进行调整,对客户返回的数据调整是由另一个被称作残根的DLL来做的。

IDL/MIDL简介

不要被代理和残根吓到,借助一种名为IDL(接口定义语言),我们只需要描述一个接口,然后MIDL编译器就会自己生成代理和残根。当然,如果感兴趣、愿意和有能力去做,你也可自己去写这个代理和残根。(真想知道残根一词的英文原词是虾米

IDL语言,同UUID设计和RPC规范一样,都是从开放软件基金会(OSF)的分布式计算机环境(DCE)借用来的,“语法同C和C++是一样的”(那还何必叫劳什子IDL呢?),可以细致地描述接口和组件所共享的接口和数据。COM只用到了IDL的一个子集,Microsoft为了更好的支持COM对它做了一些扩展。

MIDL,微软的IDL编译器。

一个idl例子:

import "unknown.idl"
// Interface IX
[
       object,
       uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx),
       helpstring("IX Interface"),
       pointer _default(unique)
]
interface IX:IUnknown
{
       HRESULT FxStringIn([in,string] wchar _t *szIn);
       HRESULT FxStringOut([out,string] wchar _t ** szOut);
};

在C++中相应的函数应是:

virtual HRESULT __stdcall FxStringIn(wchar _t * szIn);
virtual HRESULT __stdcall FxStringOut(wchar _t ** szOut);

IDL用方括号[]来作为信息分隔符,在每个接口定义前,都有一个属性列表或称接口头
object表示定义的接口为一个COM接口,关键字object即是Microsft对IDL的扩展之一。
uuid,就是接口IID了。
helpstring,将一个帮助字符串放到类型库中。类型库的事情,咱下回再说
pointer _default,此处的重点。

pointer _default

用来告诉MIDL编译器在没有为指针指定其他属性时,如何处理该指针。

ref               将指针当成引用。此时表示指针总是指向一个合法地址,并可被反引用??
                   这种指针不能为空。在调用前后它们指向同一内存地址。
                   在函数内部,不能为它们指定别名。
unique         此类指针可以为空,并且函数内部可以修改它们的值,但不能为之指定别名。
ptr                此指针就是一个个C指针。可以有一个别名(强调一个?)可以NULL,值可被修改。

in、out和string

MIDL会根据in和out属性对代理及残根代码进行优化。in,表示残根DLL不需返回值,out表示代理DLL不需调整值,不需将值传送给组件。

string告诉MIDL该参数是一个字符串(末尾以一空字符结束)。COM中对字符串的标准约定是Unicode字符即wchar _t。

HRESULT

MIDL要求,用object修饰的接口必须返回HRESULT值。因为object修饰的接口是COM接口么,COM接口都是返回HRESULT?断然不是。“因为任何函数调用都会由于网络传输问题失败,因此应该有一种让所有函数都只是网络错误的方法,也就是让所有函数都返回HRESULT。”由此,COM接口都是返回HRESULT——这是果,而非因。

import

用于将其他idl文件中的定义包含到当前文件。类似……你知道的。但又和……你知道的……有所不同,import可以任意多次,不会引起重复定义问题。所有COM及OLE现在叫ActiveX的标准接口都定义在相应的IDL文件中,这些文件位于C++编译器的\include中。闲得疼时可浏览之研究之以图level up。

size _is

先看一个在客户和组件之间船体数组的接口的IDL描述。

// Interface IY
[
         object,
          uuid(XXXXXXXX-xxxx-xxxx-xxxx-xxxxxxxxxxxx),
         helpstring("IY Interface"),
         pointer _default(unique)
]
interface IY:IUnknown
{
         HRESULT FyCount( [out] long * sizeArray);
         HRESULT FyArrayIn( [In] long sizeIn,
                                           [in, size _is(sizeIn)] long arrayIn[] );
         HRESULT FyArrayOut( [Out, in ] long* psizeInOut,
                                               [out, size _is(* psizeInOut)] long arrayOut[] );
};

size _is 就是用来告诉MIDL,数组中的元素的个数。可以大胆的预料到,只是out参数,是不会用size_is修饰的。嗯。太傻了。

IDL的结构体

IDL说,struct可以有!于是IDL中struct就有了。

作者不愧是做OpenGL的,我刚想到可以在IDL定义Point、Vector啥的,就看到

// Structure for interface IZ
typedef struct
{
        double x;
        double y;
        double z;
} Point3d;

具体的实现。。。。。。略!-__-b

原文地址:https://www.cnblogs.com/mumuliang/p/1873480.html