调整:将函数调用的参数从一个进程的地址空间传到另一个进程的地址空间。
代理:同另外一个组件行为相同的组件,必须是DLL形式的,因为需要访问客户进程的地址空间以便对接口数据进行调整。
残根:对客户传过来的数据进行反调整。
IDL(接口定义语言)
定义IX接口
//
// Server.idl - IDL source for Server.dll
//
// The MIDL compiler generates proxy/stub code and a type library
// from this file.
//
//
// Interface descriptions
//
//import将其他IDL文件中的定义包含到当前文件中
//unknwn.idl描述了IUnkown接口
import "unknwn.idl" ;
// Interface IX
//属性列表作为接口头
//用方括号作为信息分隔符
[
object, //COM接口
uuid(32bb8323-b41b-11cf-a6bb-0080c7b2d682), //相应的IID
helpstring("IX Interface"), //将一个帮助串放到一个类型库中
//如何处理指针
//ref:将指针当成引用,不能为空,不能指定别名
//unique:可以为空,可以修改值,不能指定别名
//ptr:C指针,可以为空,可以被修改,可以有别名
pointer_default(unique)
]
interface IX : IUnknown
{
//对于标记为in的参数,MIDL知道仅需将此参数从客户传递给组件
//残根代码不需要送回任何值
//对于标记为out的参数,MIDL指定仅需将词参数从组件传递给客户
//代理不需要对输出参数进行调整,输出参数必须为指针
HRESULT FxStringIn([in, string] wchar_t* szIn) ;
HRESULT FxStringOut([out, string] wchar_t** szOut) ;
} ;
使用IX接口
//COM对于字符串的标准约定是使用Unicode字符,即wchar_t
wchar_t* szOut = NULL ;
hr = pIX->FxStringIn(L"This is the test.") ;
assert(SUCCEEDED(hr)) ;
//大多数COM函数返回的均为HRESULT值
//若某个函数需要返回一个非HRESULT类型的值,在FxStringOut中定义了
//一个输出参数,以从组件中得到一个返回串
hr = pIX->FxStringOut(&szOut) ;
assert(SUCCEEDED(hr)) ;
// Display returned string.
ostrstream sout ;
sout << "FxStringOut returned a string: "
<< szOut
<< ends ;
trace(sout.str()) ;
// 释放内存
::CoTaskMemFree(szOut) ;
定义IY接口
// Interface IY
// 在客户和数组之间传递数组的接口的IDL描述
[
object,
uuid(32bb8324-b41b-11cf-a6bb-0080c7b2d682),
helpstring("IY Interface"),
pointer_default(unique)
]
interface IY : IUnknown
{
HRESULT FyCount([out] long* sizeArray) ;
//size_is告诉MIDL数组中元素个数将被保存在sizeIn中
HRESULT FyArrayIn([in] long sizeIn,
[in, size_is(sizeIn)] long arrayIn[]) ;
//函数将用内部数组填充arrayOut,将psizeInOut设为实际填充到数组中
//元素个数
HRESULT FyArrayOut([out, in] long* psizeInOut,
[out, size_is(*psizeInOut)] long arrayOut[]) ;
} ;
使用IY接口
// Send an array to the component.
long arrayIn[] = { 22, 44, 206, 76, 300, 500 } ;
long sizeIn = sizeof(arrayIn) / sizeof(arrayIn[0]) ;
hr = pIY->FyArrayIn(sizeIn, arrayIn) ;
assert(SUCCEEDED(hr)) ;
// Get the array back from the component.
// Get the size of the array.
long sizeOut = 0 ;
hr = pIY->FyCount(&sizeOut) ;
assert(SUCCEEDED(hr)) ;
// Allocate the array.
long* arrayOut = new long[sizeOut] ;
// Get the array.
hr = pIY->FyArrayOut(&sizeOut, arrayOut) ;
assert(SUCCEEDED(hr)) ;
// Display the array returned from the function.
ostrstream sout ;
sout << "FyArray returned "
<< sizeOut
<< " elements: " ;
for (int i = 0 ; i < sizeOut ; i++)
{
sout << " " << arrayOut[i] ;
}
sout << "." << ends ;
trace(sout.str()) ;
// Cleanup
trace("Release IY.") ;
delete [] arrayOut ;
定义接口IZ
// Structure for interface IZ
// IDL中也可定义C和C++风格的结构,并可用它们作为函数的参数
typedef struct
{
double x ;
double y ;
double z ;
} Point3d ;
// Interface IZ
[
object,
uuid(32bb8325-b41b-11cf-a6bb-0080c7b2d682),
helpstring("IZ Interface"),
pointer_default(unique)
]
interface IZ : IUnknown
{
HRESULT FzStructIn([in] Point3d pt) ;
HRESULT FzStructOut([out] Point3d* pt) ;
} ;
编写好IDL文件后,可用MIDL编译器进行编译,生成相应文件,比如可以将server.idl生成iface.h、guids.c、proxy.c、dlldata.c、server.tlb
通过makefile文件,应同时生成server.dll和server.exe
代理DLL
MIDL编译器
代理DLL的建立
编译和链接MIDL生成的头文件后,MAIDL将为组件生成相应的代理和残根的代码,还需写一个DEF文件生成DLL
LIBRARY Proxy.dll
DESCRIPTION 'Proxy/Stub DLL'
EXPORTS
DllGetClassObject @1 PRIVATE
DllCanUnloadNow @2 PRIVATE
GetProxyDllInfo @3 PRIVATE
DllRegisterServer @4 PRIVATE
DllUnregisterServer @5 PRIVATE
代理/残根的登记
生成代理DLL后将DLL登记,查看:
运行regedit.exe
本地服务器的实现
EXE提供组件不同于DLL,需对CUnkown和CFactory进行相应修改,但组件不需要变化。
示例程序运行
先点击server.exe,然后点击client.exe,选择2
client.cpp部分代码:
int main()
{
cout << "To which server do you want to connect?\r\n"
<< "1) In-proc Server\r\n"
<< "2) Local Server\r\n:" ;
int i = 0 ;
cin >> i ;
DWORD clsctx ;
if (i == 1)
{
clsctx = CLSCTX_INPROC_SERVER ; //连接到进程中服务器
trace("Attempt to create in-proc component.") ;
}
else
{
clsctx = CLSCTX_LOCAL_SERVER ; //连接到进程外服务器
trace("Attempt to create local component.") ;
}
... ...
}
可以看到server.exe输出:
去掉入口点函数
EXE无法输出函数,进程中服务器依赖如下输出函数:
DllGetClassObject
DllRegisterServer
DllUnregisterServer
DllCanUnloadNow
EXE可自己对生命期进行控制,不需要DllCanUnloadNow 。
EXE可通过命令含参数RegServer、UnRegServer完成自登记,不需要DllRegisterServer、DllUnregisterServer。
DllGetClassObject的替换会比较麻烦一些。
类厂的启动
CoCreateInstance调用CoGetClassObject,CoGetClassObject调用DllGetClassObject,DllGetClassObject返回一个IClassFactory指针。但exe不输出DllGetClassObject,需要另想办法获得IClassFactory指针。
COM解决的办法是维护一个被登记的类厂的内部表格,根据客户请求的CLISD得到相应的类厂。若找不到相应类厂,COM将在注册表中查找并启动相应的EXE,此EXE可调用COM函数CoRegisterClassObject完成类厂的登记,以便COM能找到它们。
可对CFactory增加一个新的 静态成员函数StartFactories,对每个组件调用CoRegisterClassObect。
CFactory.cpp
//
// Start factories
//
BOOL CFactory::StartFactories()
{
CFactoryData* pStart = &g_FactoryDataArray[0] ;
const CFactoryData* pEnd =
&g_FactoryDataArray[g_cFactoryDataEntries - 1] ;
for(CFactoryData* pData = pStart ; pData <= pEnd ; pData++)
{
// 初始化类厂指针和cookie.
//以下两个成员变量为CFactory新增加的
//保存与m_pCLSID相应的类ID的运行时类厂
pData->m_pIClassFactory = NULL ;
//保存此类厂的cookie
pData->m_dwRegister = NULL ;
// 创建类厂
IClassFactory* pIFactory = new CFactory(pData) ;
// 注册类厂
//对于第2、3个参数,若EXE的单个实例只能提供单个组件,应该为
//CLSCTX_LOCAL_SERVER,REGCLS_SINGLEUSER
//否则,可为CLSCTX_LOCAL_SERVER,REGCLS_MULTI_SEPARATE
//为了将一个EXE服务器作为它自己的进程中服务器登记,可将
//CLSCTX_LOCAL_SERVER 和 CLSTX_INPROC_SERVER组合使用
//如果使用REGCLS_MULTIPLEUSE,出现 CLSCTX_LOCAL_SERVER时自动
//设置CLSTX_INPROC_SERVER
DWORD dwRegister ;
HRESULT hr = ::CoRegisterClassObject(
*pData->m_pCLSID,
static_cast<IUnknown*>(pIFactory),
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE,
// REGCLS_MULTI_SEPARATE, //@Multi
&dwRegister) ; //传回一个cookie,注销时用到
if (FAILED(hr))
{
pIFactory->Release() ;
return FALSE ;
}
// 设置数据
pData->m_pIClassFactory = pIFactory ;
pData->m_dwRegister = dwRegister ;
}
return TRUE ;
}
类厂的释放
服务器被关闭时,必须删除相应类厂。CFactory成员函数StopFactories将为EXE所支持的所有类厂调用CoRevokeClassObeject。
CFactory.cpp
//
// Stop factories
// 当服务器被关闭时,为所有EXE所支持的所有类厂调用CoRevokeClassObject
//
void CFactory::StopFactories()
{
CFactoryData* pStart = &g_FactoryDataArray[0] ;
const CFactoryData* pEnd =
&g_FactoryDataArray[g_cFactoryDataEntries - 1] ;
for (CFactoryData* pData = pStart ; pData <= pEnd ; pData++)
{
// 得到 magic cookie 并停止类厂
// 将从CoRegisterClassObject得到的cookie传给CoRevokeClassObject
DWORD dwRegister = pData->m_dwRegister ;
if (dwRegister != 0)
{
::CoRevokeClassObject(dwRegister) ;
}
// 释放类厂
IClassFactory* pIFactory = pData->m_pIClassFactory ;
if (pIFactory != NULL)
{
pIFactory->Release() ;
}
}
}
对LockServet的修改
// LockServer
// 保证客户在创建组件时,需要的服务器在内存中
HRESULT __stdcall CFactory::LockServer(BOOL bLock)
{
if (bLock)
{
::InterlockedIncrement(&s_cServerLocks) ;
}
else
{
::InterlockedDecrement(&s_cServerLocks) ;
}
// If this is an out-of-proc server, check to see
// whether we should shut down.
// dll无法控制自己的生命期,因为装载和卸载是在另外一个exe中的,
// 但exe可以
CloseExe() ; //@local
return S_OK ;
}
//
// Destructor
//
CUnknown::~CUnknown()
{
::InterlockedDecrement(&s_cActiveComponents) ;
// If this is an EXE server, shut it down.
// 向程序的消息循环发散WM_QUIT消息
CFactory::CloseExe() ;
}
// 标示出同本地服务器相关的部分(当定义此宏时)
// 或同进程中服务器相关的部分(未定义此宏时)
#ifdef _OUTPROC_SERVER_
//
// Out-of-process server support
//
static BOOL StartFactories() ;
static void StopFactories() ;
static DWORD s_dwThreadID ;
// Shut down the application.
// 将给应用程序的消息循环发送一条WM_QUIT消息
static void CloseExe()
{
if (CanUnloadNow() == S_OK)
{
::PostThreadMessage(s_dwThreadID, WM_QUIT, 0, 0) ;
}
}
#else
// CloseExe doesn't do anything if we are in process.
static void CloseExe() { /*Empty*/ }
#endif
消息循环:
为不致使EXE退出,应加上一个消息循环,可以是Windows消息循环,在名为outproc.cpp中,只有在建立进程外服务器是需要被编译和连接。
远程访问能力
运行DCOMCNFG.EXE可使本地服务器变成一个远程服务器。
在程序中访问某个远程服务器
需要用CoCreateInstanceEX体会CoCreateInstance
MULTI_QI:
跨网络时,函数的调用的开销比较大,为减少QueryInterface调用的影响,DCOM(分布式COM)建立MULTI_QI的新结构,同时查询多个接口时,降低开销。
CoCreateInstanceEX退出时,若能查询到MULTI_QI中的所有接口,返回S_OK;部分接口,CO_S_NOTALLNTERFACES;无法查询任何接口,E_NO_INTERFACE。
MULTI_QI声明如下
IMultiQI的实现由组件的远程代理“免费”提供。
还需要确定DCOM是否可用,这里不再列出,详见书中内容。