EXE中的服务器
每一个EXE文件都将在不同的进程中运行,而每一个进程都有其自己的进程空间,一个进程空间中的逻辑地址0x0000ABBA所对应的物理地址将不同于另外一个进程中同一逻辑地址所对应的物理地址。因此,若一个进程将地址0x0000ABBA传给另一个进程,后者用此地址所访问的内存单元将不是前一个进程所希望的。DLL却不同,DLL将被影射到链接他们的EXE文件的进程空间中。所以DLL称作是进程中服务器,EXE是进程外服务器。

组件同客户共享相同的地址空间,这样组件可以把一个接口传给客户。一个接口实际上是一个函数指针的数组。为调用接口中的函数,客户必须能够访问同接口相关联的内存。

对于跨越进城边界的接口,需要考虑如下一些条件:
一个进程需要能够调用另外一个进城中的函数;
一个进程需要能够将数据传递另外一个进城;
客户无需关心它所访问的服务器是进程内服务器还是进程外服务器。

对于进城间的通信,有几种不同的方法:如动态数据交换(DDE)、命名管道以及共享内存等。
COM所用的方法为本地调用(LPC).它是基于远程过程调用(RPC)的用于单机上进程痛心的技术。

调整:将函数调用的参数从一个进程的地址空间中传到另外一个进程的地址空间中。(memycpy)

代理/残根DLL:实现简单的调整技术。

IDL语言,用来描述接口和组件,然后可以用MIDL编译器生成代理和残根DLL的C代码
IDL接口描述举例:
Import”unknwn.idl”
[
    Object,
   Uuid(32bb8323-b41b-11cf-a6bb-0080c7b2d682),
   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是microsoft对于IDL的一个扩展。第二个关键字uuid为相应的IID。第三个关键字用于将一个帮助串放到一个类型库中。第四个关键字pointer _default

pointer _default:
使用IDL的目的是为了提供足够的信息,以使函数参数可以被调整。为此,IDL需要知道如何处理指针,pointer _default关键字的作用就是告诉MIDL编译器在没有为指针指定其他属性时如何处理此指针。pointer _default关键字三个不同的选项:
1、ref——将指针当成是引用对待。此时表示此指针将总是指向一个合法的地址,这种指针不能为空,在调用前后他们将指向同一内存地址。在函数内部不能为他们指定别名。
2、unique——此类指针可以为空,并且在函数内可以修改它们的值,但不能为之指定别名。
3、ptr——此选项指定相应的指针就是一个C指针,此类指针可以是有一个别名、可以为NULL,并且其值可以被修改。
MIDL将使用这些值对它生成的代理或残根代码进行优化。

IDL中的输入与输出参数:MIDL使用in和out参数属性对代理及残根代码进行优化,被标记in的参数,MIDL将此参数值从客户传递给组件,残根代码不需要送回任何值。Out关键字告诉MIDL相应的参数仅被用来从组件向客户传回有关的数据。可以同时使用这两个关键字来标记参数,如下所示:
HRESULT foo([in] int x,[in,out]int * y,[out]int *z);对于输出参数MIDL要求必须是一个指针。

IDL中的字符串:COM中对于字符串的标准约定是使用Unicode字符即wchar _t.

IDL中的import关键字:用于将其他IDL文件中的定义包含到当前文件中。Import同C++中的预处理指令#include 命令类似,但是import可以使用任意多次而不会引起重复定义的问题。

IDL中的size_is修饰符:用来确定数据的大小。提供size_is修饰符的参数只能是输入参数或输入_输出参数。

OLE自动化: 实现COM客户与组件之间的通信.自动化使得解释性语言和宏语言访问编写COM组件更为容易,它关注的是运行时的类型检查。
自动化是建立在COM基础上的,一个自动化服务器实际上是一个实现了IDispatch接口的COM组件。一个自动化控制器则是一个通过IDispatch 接口同自动化服务器进行通信的COM客户。自动化控制器通过IDispatch接口中的成员函数实现对服务器中函数的间接调用。

通过COM接口提供的任何服务都可以通过IDispatch接口来提供。
客户和组件之间的通信都是通过接口完成的,接口拥有一个由函数指针构成的数组,客户包含一个以抽象基类形式描述接口的头文件。编译器读取此头文件,然后为抽象基类中的每一个成员函数分配一个索引。此索引就是相应函数的指针在函数指针数组中的索引。

当在宏语言中调用COM组件中的一个函数时,可以使用三种类型的信息:实现被调用函数的组件的ProgID、函数的名称以及传给函数的参数。我们需要的是当宏运行时系统能够提供一种通过函数的名称来执行函数的简单方法,这种方法就是IDispatch接口提供的。

IDispatch接口:将接收一个函数的名称并执行它。
IDispatch中的GetIDsOfNames和Invoke函数:
HRESULT GetIDsOfNames(
[in] const IID& riid,
[in,size_is(cNames)]LPOLESTR* rgszNames,
[in]UINT cNames,
[in]LCID lcid,
[out,size_is(cNames)]DISPID* rgDispId);
GetIDsOfNames将取一个函数的名称并返回其调用ID,或称DISPID,DISPID是一个长整数,标示的是一个函数,对于IDispatch的某一个特定的实现,DISPID是唯一的IDispatch的每一个实现都有自己的IID(即调度接口的ID).

为执行某个函数,自动化控制程序将把DISPID传给Invoke成员函数,Invoke可以将DISPID作为函数指针数组的索引。

IDispatch::Invoke的一个实现所实现的函数集被称作是一个调度接口。COM接口是一个指向一个函数指针数组的指针,此数组的前三个元素分别是:QueryInterface\AddRef\Release。

双重接口:让实现IDispatch::Invoke的COM组件继承IDispatch而不是IUnknown。这是实现一种被称作是双重接口这种类型的接口的方法。双重接口使得C++程序员能够通过vtbl进行函数调用。当将VB变量定义为Object类型时,它将连接到调度接口上:
Dim doc As Object
Set doc = Application.ActiveDocument
Doc.Activate
但是如果给变量指定某种对象类型,那么VB将通过vtbl来完成相应的函数调用:
 Dim doc As Document
Set doc = Application.ActiveDocument
Doc.Activate

IDispatch的使用:
HRESULT hr = OleInitialize(NULL);
Wchar_t progid[] = L”InsideCOM.chap11”;
CLSID clsid;
::CLSIDFromProgID(progid,&clsid);
IDispatch * pIDispatch = NULL;
::CoCreateInstance(clsid,
NULL,CLSCTX_INPROC_SERVER,
IID_IDispatch,(void**)&pIDispatch);
CoCreateInstance返回一个IDispatch指针,即可得到调度函数的DISPID。函数IDispatch::GetIDsOfNames可以把一个代表函数名称的串转换成一个DISPID。
DISPID dispid;
OLECHAR* name = L”FX”;
pIDispatch->GetIDsOfName(
IID_NULL,
*name,
1,
GetUserDefaultLCID(),
&dispid);
对于客户而言,DISPID的作用是避免将字符串来回的传送,而对于服务器而言,DISPID所标识的将是客户待调用的函数的名称。
在得到Fx的DISPID后,可以通过IDispatch::Invoke来调用相应的函数。
pIDispatch->Invoke(dispid,
IID_NULL,
GetUserDefaultLCID(),
DISPATCH_METHOD,
&dispparamsNoArgs,
NULL,
NULL,
NULL);

Invoke的威力在于它可以被一致地使用,任何一个组件,只要它实现了Invoke,均可以用相同的代码来调用它。但是IDispatch::Invoke的工作之一是把参数传给要执行的函数,而Invoke可以传给调度接口中的函数的参数类型数目是有限的。

Invoke函数的参数:
第一个参数是控制程序待调用函数的CLSID;第二个参数是保留的,必须为IID_NULL,第三个参数保存的是位置信息。第四个参数的作用就是提供Invoke所要调用的函数的类型的信息。

方法和属性:
COM接口中的所有成员都是函数。IDL的propget和propput属性可以将COM函数说明为可以将其当成一个属性对待:
[
   Object,
   Uuid(32bb8323-b41b-11cf-a6bb-0080c7b2d682),
   Pointer _default(unique),
   dual
]
Interface IWindow:IDispatch
{
.
   [propput]
   HRESULT Visible([in] VARIANT _BOOL bVisible);
   [propget]
   HRESULT Visible([out,retval] VARIANT _BOOL *pbVisible);
.
}
以上代码为给接口定义了一个visible属性,被标记为propput的函数将接受一作为其值得参数。而被标记为propget的函数则通过一个输出参数返回一个值。此时属性的名称同函数的名称是相同的。

调度参数:IDispatch::Invoke的第五个参数包含的是传给被调用函数的参数。
第五个参数是一个DISPPARAMS结构:
Typedef struct tagDISPPARAMS{
   VARIANTARG* rgvarg; //Array of arguments;
   DISPID* rgdispidNamedArgs; //DISPIDS of named args
   Unsigned int cArgs; //Number of arguments;
   Unsigned int cNameArgs; //Number of named args;
} DISPPARAMS;
只有那些能够放到VARIANTARG结构中的类型才可以通过调度接口进行传递。

返回值的获取:
第六个参数pVarResult为指向一个VARIANT结构的指针,此结构将被用于保存Invoke所执行的函数或propget的结果。对于没有返回值的成员函数或propputs以及propputrefs,此参数值可以为NULL。
参数错误:IDispatch::Invoke的返回值为DISP_E_PARAMNOTFOUND或DISP_E_TYPEMIS_MATCH,此时Invoke将把与此错误相应的参数的索引保存在最后一个参数puArgErr中。